2020-10-06 07:44:27 +00:00
|
|
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2021-08-12 10:14:50 +00:00
|
|
|
import { AbstractType, Component, CUSTOM_ELEMENTS_SCHEMA, Type, ViewChild } from '@angular/core';
|
|
|
|
import { BrowserModule } from '@angular/platform-browser';
|
2020-10-06 07:44:27 +00:00
|
|
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
2021-08-12 10:14:50 +00:00
|
|
|
import { Network } from '@ionic-native/network/ngx';
|
|
|
|
import { Observable, Subject } from 'rxjs';
|
|
|
|
import { Platform } from '@ionic/angular';
|
2020-10-26 12:20:00 +00:00
|
|
|
|
2021-08-12 10:14:50 +00:00
|
|
|
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
2021-03-02 09:51:23 +00:00
|
|
|
import { CoreSingletonProxy } from '@singletons';
|
2021-08-11 12:31:49 +00:00
|
|
|
import { CoreTextUtilsProvider } from '@services/utils/text';
|
2020-10-06 07:44:27 +00:00
|
|
|
|
2021-08-12 10:14:50 +00:00
|
|
|
import { TranslatePipeStub } from './stubs/pipes/translate';
|
|
|
|
import { CoreExternalContentDirectiveStub } from './stubs/directives/core-external-content';
|
|
|
|
|
2020-10-26 12:20:00 +00:00
|
|
|
abstract class WrapperComponent<U> {
|
|
|
|
|
|
|
|
child!: U;
|
|
|
|
|
2020-10-07 09:13:54 +00:00
|
|
|
};
|
|
|
|
|
2021-08-12 10:14:50 +00:00
|
|
|
type ServiceInjectionToken = AbstractType<unknown> | Type<unknown> | string;
|
|
|
|
|
|
|
|
let testBedInitialized = false;
|
|
|
|
const textUtils = new CoreTextUtilsProvider();
|
|
|
|
|
|
|
|
async function renderAngularComponent<T>(component: Type<T>, config: RenderConfig): Promise<ComponentFixture<T>> {
|
|
|
|
config.declarations.push(component);
|
|
|
|
|
|
|
|
TestBed.configureTestingModule({
|
|
|
|
declarations: [
|
|
|
|
...getDefaultDeclarations(),
|
|
|
|
...config.declarations,
|
|
|
|
],
|
|
|
|
providers: [
|
|
|
|
...getDefaultProviders(),
|
|
|
|
...config.providers,
|
|
|
|
],
|
|
|
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
|
|
imports: [BrowserModule],
|
|
|
|
});
|
|
|
|
|
|
|
|
testBedInitialized = true;
|
|
|
|
|
|
|
|
await TestBed.compileComponents();
|
|
|
|
|
|
|
|
const fixture = TestBed.createComponent(component);
|
|
|
|
|
|
|
|
fixture.autoDetectChanges(true);
|
|
|
|
|
|
|
|
await fixture.whenRenderingDone();
|
|
|
|
await fixture.whenStable();
|
|
|
|
|
|
|
|
return fixture;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createWrapperComponent<U>(template: string, componentClass: Type<U>): Type<WrapperComponent<U>> {
|
|
|
|
@Component({ template })
|
|
|
|
class HostComponent extends WrapperComponent<U> {
|
|
|
|
|
|
|
|
@ViewChild(componentClass) child!: U;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return HostComponent;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDefaultDeclarations(): unknown[] {
|
|
|
|
return [
|
|
|
|
TranslatePipeStub,
|
|
|
|
CoreExternalContentDirectiveStub,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDefaultProviders(): unknown[] {
|
|
|
|
const platformMock = mock<Platform>({ is: () => false, ready: () => Promise.resolve(), resume: new Subject<void>() });
|
|
|
|
const networkMock = mock<Network>({ onChange: () => new Observable() });
|
|
|
|
|
|
|
|
return [
|
|
|
|
{ provide: Platform, useValue: platformMock },
|
|
|
|
{ provide: Network, useValue: networkMock },
|
|
|
|
{ provide: CORE_SITE_SCHEMAS, multiple: true, useValue: [] },
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
function resolveServiceInstanceFromTestBed(injectionToken: Exclude<ServiceInjectionToken, string>): Record<string, unknown> | null {
|
|
|
|
if (!testBedInitialized) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TestBed.inject(injectionToken) as Record<string, unknown> | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createNewServiceInstance(injectionToken: Exclude<ServiceInjectionToken, string>): Record<string, unknown> | null {
|
|
|
|
try {
|
|
|
|
const constructor = injectionToken as { new (): Record<string, unknown> };
|
|
|
|
|
|
|
|
return new constructor();
|
|
|
|
} catch (e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-26 12:20:00 +00:00
|
|
|
export interface RenderConfig {
|
|
|
|
declarations: unknown[];
|
|
|
|
providers: unknown[];
|
2020-10-07 09:13:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-26 12:20:00 +00:00
|
|
|
export type WrapperComponentFixture<T> = ComponentFixture<WrapperComponent<T>>;
|
|
|
|
|
|
|
|
export function mock<T>(instance?: Record<string, unknown>): T;
|
|
|
|
export function mock<T>(methods: string[], instance?: Record<string, unknown>): T;
|
|
|
|
export function mock<T>(
|
|
|
|
methodsOrInstance: string[] | Record<string, unknown> = [],
|
|
|
|
instance: Record<string, unknown> = {},
|
|
|
|
): T {
|
|
|
|
instance = Array.isArray(methodsOrInstance) ? instance : methodsOrInstance;
|
|
|
|
|
|
|
|
const methods = Array.isArray(methodsOrInstance) ? methodsOrInstance : [];
|
2020-10-07 09:13:54 +00:00
|
|
|
|
2021-03-15 13:31:00 +00:00
|
|
|
for (const property of Object.getOwnPropertyNames(instance)) {
|
|
|
|
const value = instance[property];
|
|
|
|
|
|
|
|
if (typeof value !== 'function') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
instance[property] = jest.fn((...args) => value.call(instance, ...args));
|
|
|
|
}
|
|
|
|
|
2020-10-07 09:13:54 +00:00
|
|
|
for (const method of methods) {
|
2020-10-26 12:20:00 +00:00
|
|
|
instance[method] = jest.fn();
|
2020-10-07 09:13:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-26 12:20:00 +00:00
|
|
|
return instance as T;
|
2020-10-07 09:13:54 +00:00
|
|
|
}
|
|
|
|
|
2021-03-02 09:51:23 +00:00
|
|
|
export function mockSingleton<T>(singletonClass: CoreSingletonProxy<T>, instance: T): T;
|
|
|
|
export function mockSingleton<T>(singletonClass: CoreSingletonProxy<unknown>, instance?: Record<string, unknown>): T;
|
2021-01-14 11:58:51 +00:00
|
|
|
export function mockSingleton<T>(
|
2021-03-02 09:51:23 +00:00
|
|
|
singletonClass: CoreSingletonProxy<unknown>,
|
2020-10-26 12:20:00 +00:00
|
|
|
methods: string[],
|
|
|
|
instance?: Record<string, unknown>,
|
2021-01-14 11:58:51 +00:00
|
|
|
): T;
|
|
|
|
export function mockSingleton<T>(
|
2021-03-02 09:51:23 +00:00
|
|
|
singleton: CoreSingletonProxy<T>,
|
2021-08-12 10:14:50 +00:00
|
|
|
methodsOrProperties: string[] | Record<string, unknown> = [],
|
|
|
|
properties: Record<string, unknown> = {},
|
2021-01-14 11:58:51 +00:00
|
|
|
): T {
|
2021-08-12 10:14:50 +00:00
|
|
|
properties = Array.isArray(methodsOrProperties) ? properties : methodsOrProperties;
|
2020-10-26 12:20:00 +00:00
|
|
|
|
2021-08-12 10:14:50 +00:00
|
|
|
const methods = Array.isArray(methodsOrProperties) ? methodsOrProperties : [];
|
|
|
|
const instance = getServiceInstance(singleton.injectionToken);
|
2021-01-14 11:58:51 +00:00
|
|
|
const mockInstance = mock<T>(methods, instance);
|
|
|
|
|
2021-08-12 10:14:50 +00:00
|
|
|
Object.assign(mockInstance, properties);
|
|
|
|
|
2021-03-02 09:51:23 +00:00
|
|
|
singleton.setInstance(mockInstance);
|
|
|
|
|
2021-01-14 11:58:51 +00:00
|
|
|
return mockInstance;
|
2020-10-26 12:20:00 +00:00
|
|
|
}
|
|
|
|
|
2021-08-12 10:14:50 +00:00
|
|
|
export function resetTestingEnvironment(): void {
|
|
|
|
testBedInitialized = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getServiceInstance(injectionToken: ServiceInjectionToken): Record<string, unknown> {
|
|
|
|
if (typeof injectionToken === 'string') {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return resolveServiceInstanceFromTestBed(injectionToken)
|
|
|
|
?? createNewServiceInstance(injectionToken)
|
|
|
|
?? {};
|
|
|
|
}
|
|
|
|
|
2020-10-26 12:20:00 +00:00
|
|
|
export async function renderComponent<T>(component: Type<T>, config: Partial<RenderConfig> = {}): Promise<ComponentFixture<T>> {
|
|
|
|
return renderAngularComponent(component, {
|
|
|
|
declarations: [],
|
|
|
|
providers: [],
|
|
|
|
...config,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-26 15:52:26 +00:00
|
|
|
export async function renderTemplate<T>(
|
2020-10-26 12:20:00 +00:00
|
|
|
component: Type<T>,
|
2020-10-26 15:52:26 +00:00
|
|
|
template: string,
|
2020-10-26 12:20:00 +00:00
|
|
|
config: Partial<RenderConfig> = {},
|
|
|
|
): Promise<WrapperComponentFixture<T>> {
|
|
|
|
config.declarations = config.declarations ?? [];
|
|
|
|
config.declarations.push(component);
|
|
|
|
|
|
|
|
return renderAngularComponent(
|
2020-10-26 15:52:26 +00:00
|
|
|
createWrapperComponent(template, component),
|
2020-10-26 12:20:00 +00:00
|
|
|
{
|
|
|
|
declarations: [],
|
|
|
|
providers: [],
|
|
|
|
...config,
|
|
|
|
},
|
|
|
|
);
|
2020-10-07 09:13:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-26 15:52:26 +00:00
|
|
|
export async function renderWrapperComponent<T>(
|
|
|
|
component: Type<T>,
|
|
|
|
tag: string,
|
2021-08-11 12:31:49 +00:00
|
|
|
inputs: Record<string, unknown> = {},
|
2020-10-26 15:52:26 +00:00
|
|
|
config: Partial<RenderConfig> = {},
|
|
|
|
): Promise<WrapperComponentFixture<T>> {
|
|
|
|
const inputAttributes = Object
|
|
|
|
.entries(inputs)
|
2021-08-11 12:31:49 +00:00
|
|
|
.map(([name, value]) => `[${name}]="${textUtils.escapeHTML(JSON.stringify(value)).replace(/\//g, '\\/')}"`)
|
2020-10-26 15:52:26 +00:00
|
|
|
.join(' ');
|
|
|
|
|
|
|
|
return renderTemplate(component, `<${tag} ${inputAttributes}></${tag}>`, config);
|
|
|
|
}
|