Browse Source

Revert "Expand and improve core service and utility test coverage"

This reverts commit dab1d319c0.
pull/23460/head
Fahri Gedik 12 months ago
parent
commit
dfded29257
  1. 6
      npm/ng-packs/packages/core/src/lib/services/localization.service.ts
  2. 61
      npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts
  3. 41
      npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts
  4. 482
      npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts
  5. 98
      npm/ng-packs/packages/core/src/lib/tests/generator-utils.spec.ts
  6. 71
      npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts
  7. 76
      npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts
  8. 460
      npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts
  9. 97
      npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts
  10. 4
      npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts
  11. 98
      npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts
  12. 22
      npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts
  13. 5
      npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts
  14. 350
      npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts
  15. 5
      npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts
  16. 32
      npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts
  17. 4
      npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts
  18. 52
      npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts
  19. 284
      npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts
  20. 4
      npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts

6
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;
}

61
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: '<div class="foo">bar</div>', standalone: true })
@Component({ template: '<div class="foo">bar</div>' })
class TestComponent {}
@Component({
template: '<div class="context">{{ contextData }}</div>',
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);
});
});
});

41
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');
});
});
});

482
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: '<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();
});
});

98
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);
});
});
});

71
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');
});
});
});

76
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);
});
});
});

460
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<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.');
});
});
});

97
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<TestComponent, { val: any; override: boolean; testValueFn: any; testValueLimitFn: any }>;
let spectator: SpectatorHost<TestComponent, { val: any; override: boolean }>;
const createHost = createHostFactory({
component: TestComponent,
declarations: [AbstractNgModelComponent],
imports: [FormsModule],
});
beforeEach(() => {
spectator = createHost('<abp-test [(ngModel)]="val" [override]="override" [testValueFn]="testValueFn" [testValueLimitFn]="testValueLimitFn"></abp-test>', {
spectator = createHost('<abp-test [(ngModel)]="val" [override]="override"></abp-test>', {
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);
});
});

4
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<PermissionDirective>;
@ -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();

98
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<PermissionGuard>;
let guard: PermissionGuard;
let routes: SpyObject<RoutesService>;
@ -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 () => {

22
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: '<div class="foo">{{ bar || baz }}</div>',
standalone: true,
})
class TestComponent {
bar: string;
@ -29,7 +28,6 @@ describe('ComponentProjectionStrategy', () => {
@Component({
template: '<ng-container #container></ng-container>',
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: '<div class="foo">{{ bar || baz }}</div>',
standalone: true,
})
class TestComponent {
bar: string;
baz = 'baz';
}
@Component({ template: '', standalone: true })
@Component({ template: '' })
class HostComponent {}
let spectator: Spectator<HostComponent>;
@ -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', () => {
</ng-template>
<ng-container #container></ng-container>
`,
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();
});

5
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: '<p>external</p>',
standalone: true,
})
export class ExternalComponent {}
@Component({
selector: 'abp-default-component',
template: '<p>default</p>',
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],
});

350
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: ' <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();
});
});
});

5
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: '',

32
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<RouterEvents>;
let service: RouterEvents;
const events = new Subject<RouterEvent>();
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();

4
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<RouterOutletComponent>;
let spectator: Spectator<RouterOutletComponent>;
const createHost = createHostFactory({
component: RouterOutletComponent,
imports: [RouterTestingModule],

52
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();
});

284
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<void>();
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);
});
});

4
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.'}

Loading…
Cancel
Save