MOBILE-3320 tests: Add format-text directive tests
parent
ca95f53134
commit
504bfe4c08
|
@ -112,6 +112,7 @@ const appConfig = {
|
|||
},
|
||||
],
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'@typescript-eslint/no-this-alias': 'error',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/quotes': [
|
||||
|
@ -197,7 +198,6 @@ const appConfig = {
|
|||
'no-irregular-whitespace': 'error',
|
||||
'no-multiple-empty-lines': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
'no-redeclare': 'error',
|
||||
'no-sequences': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-underscore-dangle': 'error',
|
||||
|
|
|
@ -3065,6 +3065,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/dom-mediacapture-record/-/dom-mediacapture-record-1.0.7.tgz",
|
||||
"integrity": "sha512-ddDIRTO1ajtbxaNo2o7fPJggpN54PZf1ZUJKOjto2ENMJE/9GKUvaw3ZRuQzlS/p0E+PnIcssxfoqYJ4yiXSBw=="
|
||||
},
|
||||
"@types/faker": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/faker/-/faker-5.1.3.tgz",
|
||||
"integrity": "sha512-7YTyCRoujZWYaCpDLslQJ8QzaFWFLZZ3mZ7Vfr/jJHascRmSd05pYteyt2FK4btF2vXyGq0obuoyLpcF99OvaA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
|
||||
|
|
|
@ -123,6 +123,7 @@
|
|||
"@angular/compiler-cli": "~10.0.0",
|
||||
"@angular/language-service": "~10.0.0",
|
||||
"@ionic/angular-toolkit": "^2.3.0",
|
||||
"@types/faker": "^5.1.3",
|
||||
"@types/node": "^12.12.64",
|
||||
"@typescript-eslint/eslint-plugin": "4.3.0",
|
||||
"@typescript-eslint/parser": "4.3.0",
|
||||
|
|
|
@ -12,33 +12,35 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { createComponent, createMock, prepareComponentTest } from '@/tests/utils';
|
||||
|
||||
import { AppComponent } from '@app/app.component';
|
||||
import { CoreLangProvider } from '@services/lang';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreLangProvider } from '@services/lang';
|
||||
|
||||
import { mock, renderComponent, RenderConfig } from '@/tests/utils';
|
||||
|
||||
describe('App component', () => {
|
||||
|
||||
let langProvider: CoreLangProvider;
|
||||
let config: Partial<RenderConfig>;
|
||||
|
||||
beforeEach(() => {
|
||||
langProvider = createMock<CoreLangProvider>(['clearCustomStrings']);
|
||||
|
||||
prepareComponentTest(AppComponent, [
|
||||
langProvider = mock<CoreLangProvider>(['clearCustomStrings']);
|
||||
config = {
|
||||
providers: [
|
||||
{ provide: CoreLangProvider, useValue: langProvider },
|
||||
]);
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
const fixture = createComponent(AppComponent);
|
||||
it('should render', async () => {
|
||||
const fixture = await renderComponent(AppComponent, config);
|
||||
|
||||
expect(fixture.debugElement.componentInstance).toBeTruthy();
|
||||
expect(fixture.nativeElement.querySelector('ion-router-outlet')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('clears custom strings on logout', async () => {
|
||||
const fixture = createComponent(AppComponent);
|
||||
const fixture = await renderComponent(AppComponent, config);
|
||||
|
||||
fixture.componentInstance.ngOnInit();
|
||||
CoreEvents.trigger(CoreEvents.LOGOUT);
|
||||
|
|
|
@ -12,39 +12,47 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NavController } from '@ionic/angular';
|
||||
|
||||
import { CoreApp } from '@/app/services/app';
|
||||
import { CoreInit } from '@services/init';
|
||||
import { CoreLoginInitPage } from '@core/login/pages/init/init.page';
|
||||
import { CoreApp } from '@/app/services/app';
|
||||
import { SplashScreen } from '@/app/singletons/core.singletons';
|
||||
|
||||
import { createComponent, preparePageTest, PageTestMocks, mockSingleton } from '@/tests/utils';
|
||||
import { mock, mockSingleton, renderComponent, RenderConfig } from '@/tests/utils';
|
||||
|
||||
describe('CoreLogin Init Page', () => {
|
||||
|
||||
let mocks: PageTestMocks;
|
||||
let navController: NavController;
|
||||
let config: Partial<RenderConfig>;
|
||||
|
||||
beforeEach(async () => {
|
||||
const initPromise = Promise.resolve();
|
||||
beforeEach(() => {
|
||||
mockSingleton(SplashScreen, ['hide']);
|
||||
mockSingleton(CoreInit, { ready: () => Promise.resolve() });
|
||||
mockSingleton(CoreApp, { getRedirect: () => ({}) });
|
||||
|
||||
mockSingleton(CoreInit, [], { ready: () => initPromise });
|
||||
mockSingleton(CoreApp, [], { getRedirect: () => ({}) });
|
||||
|
||||
mocks = await preparePageTest(CoreLoginInitPage);
|
||||
navController = mock<NavController>(['navigateRoot']);
|
||||
config = {
|
||||
providers: [
|
||||
{ provide: NavController, useValue: navController },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
const fixture = createComponent(CoreLoginInitPage);
|
||||
it('should render', async () => {
|
||||
const fixture = await renderComponent(CoreLoginInitPage, config);
|
||||
|
||||
expect(fixture.debugElement.componentInstance).toBeTruthy();
|
||||
expect(fixture.nativeElement.querySelector('ion-spinner')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('navigates to sites page after loading', async () => {
|
||||
const fixture = createComponent(CoreLoginInitPage);
|
||||
const fixture = await renderComponent(CoreLoginInitPage, config);
|
||||
|
||||
fixture.componentInstance.ngOnInit();
|
||||
await CoreInit.instance.ready();
|
||||
|
||||
expect(mocks.navController.navigateRoot).toHaveBeenCalledWith('/login/sites');
|
||||
expect(navController.navigateRoot).toHaveBeenCalledWith('/login/sites');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
// (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.
|
||||
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { IonContent, NavController } from '@ionic/angular';
|
||||
import { NgZone } from '@angular/core';
|
||||
import Faker from 'faker';
|
||||
|
||||
import { CoreConfig } from '@services/config';
|
||||
import { CoreDomUtils, CoreDomUtilsProvider } from '@services/utils/dom';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreFormatTextDirective } from '@directives/format-text';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUrlUtils, CoreUrlUtilsProvider } from '@services/utils/url';
|
||||
import { CoreUtils, CoreUtilsProvider } from '@services/utils/utils';
|
||||
import { Platform } from '@singletons/core.singletons';
|
||||
|
||||
import { mock, mockSingleton, RenderConfig, renderWrapperComponent } from '@/tests/utils';
|
||||
|
||||
describe('CoreFormatTextDirective', () => {
|
||||
|
||||
let config: Partial<RenderConfig>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSingleton(Platform, { ready: () => Promise.resolve() });
|
||||
mockSingleton(CoreConfig, { get: (_, defaultValue) => defaultValue });
|
||||
|
||||
CoreDomUtils.setInstance(new CoreDomUtilsProvider(mock<DomSanitizer>()));
|
||||
CoreUrlUtils.setInstance(new CoreUrlUtilsProvider());
|
||||
CoreUtils.setInstance(new CoreUtilsProvider(mock<NgZone>()));
|
||||
|
||||
config = {
|
||||
providers: [
|
||||
{ provide: NavController, useValue: null },
|
||||
{ provide: IonContent, useValue: null },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
it('should render', async () => {
|
||||
// Arrange
|
||||
const sentence = Faker.lorem.sentence();
|
||||
|
||||
mockSingleton(CoreSites, { getSite: () => Promise.reject() });
|
||||
|
||||
// Act
|
||||
const fixture = await renderWrapperComponent(
|
||||
CoreFormatTextDirective,
|
||||
'core-format-text',
|
||||
{ text: sentence },
|
||||
config,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const text = fixture.nativeElement.querySelector('core-format-text');
|
||||
expect(text).not.toBeNull();
|
||||
expect(text.innerHTML).toEqual(sentence);
|
||||
});
|
||||
|
||||
it('should use external-content directive on images', async () => {
|
||||
// Arrange
|
||||
const site = mock<CoreSite>({
|
||||
getId: () => '42',
|
||||
canDownloadFiles: () => true,
|
||||
isVersionGreaterEqualThan: () => true,
|
||||
});
|
||||
|
||||
// @todo this is done because we cannot mock image being loaded, we should find an alternative...
|
||||
CoreUtils.instance.timeoutPromise = <T>() => Promise.resolve(null as unknown as T);
|
||||
|
||||
mockSingleton(CoreFilepool, { getSrcByUrl: jest.fn(() => Promise.resolve('file://local-path')) });
|
||||
mockSingleton(CoreSites, {
|
||||
getSite: jest.fn(() => Promise.resolve(site)),
|
||||
getCurrentSite: () => Promise.resolve(site),
|
||||
});
|
||||
|
||||
// Act
|
||||
const fixture = await renderWrapperComponent(
|
||||
CoreFormatTextDirective,
|
||||
'core-format-text',
|
||||
{ text: '<img src="https://image-url">', siteId: site.getId() },
|
||||
config,
|
||||
);
|
||||
|
||||
// Assert
|
||||
const image = fixture.nativeElement.querySelector('img');
|
||||
expect(image).not.toBeNull();
|
||||
expect(image.src).toEqual('file://local-path/');
|
||||
|
||||
expect(CoreSites.instance.getSite).toHaveBeenCalledWith(site.getId());
|
||||
expect(CoreFilepool.instance.getSrcByUrl).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.todo('should format text');
|
||||
|
||||
it.todo('should get filters from server and format text');
|
||||
|
||||
it.todo('should use link directive on anchors');
|
||||
|
||||
});
|
|
@ -12,65 +12,119 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, Type } from '@angular/core';
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA, Type, ViewChild } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NavController } from '@ionic/angular';
|
||||
|
||||
import { CoreSingletonClass } from '@app/classes/singletons-factory';
|
||||
|
||||
export interface ComponentTestMocks {
|
||||
//
|
||||
abstract class WrapperComponent<U> {
|
||||
|
||||
child!: U;
|
||||
|
||||
};
|
||||
|
||||
export interface PageTestMocks extends ComponentTestMocks {
|
||||
navController: NavController;
|
||||
export interface RenderConfig {
|
||||
declarations: unknown[];
|
||||
providers: unknown[];
|
||||
}
|
||||
|
||||
export function createMock<T>(methods: string[] = [], properties: Record<string, unknown> = {}): T {
|
||||
const mockObject = properties;
|
||||
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 : [];
|
||||
|
||||
for (const method of methods) {
|
||||
mockObject[method] = jest.fn();
|
||||
instance[method] = jest.fn();
|
||||
}
|
||||
|
||||
return mockObject as T;
|
||||
return instance as T;
|
||||
}
|
||||
|
||||
export function mockSingleton(singletonClass: CoreSingletonClass<unknown>, instance?: Record<string, unknown>): void;
|
||||
export function mockSingleton(
|
||||
singletonClass: CoreSingletonClass<unknown>,
|
||||
methods: string[] = [],
|
||||
properties: Record<string, unknown> = {},
|
||||
methods: string[],
|
||||
instance?: Record<string, unknown>,
|
||||
): void;
|
||||
export function mockSingleton(
|
||||
singletonClass: CoreSingletonClass<unknown>,
|
||||
methodsOrInstance: string[] | Record<string, unknown> = [],
|
||||
instance: Record<string, unknown> = {},
|
||||
): void {
|
||||
singletonClass.setInstance(createMock(methods, properties));
|
||||
instance = Array.isArray(methodsOrInstance) ? instance : methodsOrInstance;
|
||||
|
||||
const methods = Array.isArray(methodsOrInstance) ? methodsOrInstance : [];
|
||||
|
||||
singletonClass.setInstance(mock(methods, instance));
|
||||
}
|
||||
|
||||
export async function prepareComponentTest<T>(component: Type<T>, providers: unknown[] = []): Promise<ComponentTestMocks> {
|
||||
export async function renderComponent<T>(component: Type<T>, config: Partial<RenderConfig> = {}): Promise<ComponentFixture<T>> {
|
||||
return renderAngularComponent(component, {
|
||||
declarations: [],
|
||||
providers: [],
|
||||
...config,
|
||||
});
|
||||
}
|
||||
|
||||
export async function renderWrapperComponent<T>(
|
||||
component: Type<T>,
|
||||
tag: string,
|
||||
inputs: Record<string, { toString() }> = {},
|
||||
config: Partial<RenderConfig> = {},
|
||||
): Promise<WrapperComponentFixture<T>> {
|
||||
const inputAttributes = Object
|
||||
.entries(inputs)
|
||||
.map(([name, value]) => `${name}="${value.toString().replace(/"/g, '"')}"`)
|
||||
.join(' ');
|
||||
|
||||
config.declarations = config.declarations ?? [];
|
||||
config.declarations.push(component);
|
||||
|
||||
return renderAngularComponent(
|
||||
createWrapperComponent(`<${tag} ${inputAttributes}></${tag}>`, component),
|
||||
{
|
||||
declarations: [],
|
||||
providers: [],
|
||||
...config,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function renderAngularComponent<T>(component: Type<T>, config: RenderConfig): Promise<ComponentFixture<T>> {
|
||||
config.declarations.push(component);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [component],
|
||||
declarations: config.declarations,
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers,
|
||||
providers: config.providers,
|
||||
});
|
||||
|
||||
await TestBed.compileComponents();
|
||||
|
||||
return {};
|
||||
const fixture = TestBed.createComponent(component);
|
||||
|
||||
fixture.autoDetectChanges(true);
|
||||
|
||||
await fixture.whenRenderingDone();
|
||||
await fixture.whenStable();
|
||||
|
||||
return fixture;
|
||||
}
|
||||
|
||||
export async function preparePageTest<T>(component: Type<T>, providers: unknown[] = []): Promise<PageTestMocks> {
|
||||
const mocks = {
|
||||
navController: createMock<NavController>(['navigateRoot']),
|
||||
};
|
||||
function createWrapperComponent<U>(template: string, componentClass: Type<U>): Type<WrapperComponent<U>> {
|
||||
@Component({ template })
|
||||
class HostComponent extends WrapperComponent<U> {
|
||||
|
||||
const componentTestMocks = await prepareComponentTest(component, [
|
||||
...providers,
|
||||
{ provide: NavController, useValue: mocks.navController },
|
||||
]);
|
||||
@ViewChild(componentClass) child!: U;
|
||||
|
||||
return {
|
||||
...componentTestMocks,
|
||||
...mocks,
|
||||
};
|
||||
}
|
||||
|
||||
export function createComponent<T>(component: Type<T>): ComponentFixture<T> {
|
||||
return TestBed.createComponent(component);
|
||||
}
|
||||
|
||||
return HostComponent;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"cordova-plugin-inappbrowser",
|
||||
"cordova",
|
||||
"dom-mediacapture-record",
|
||||
"faker",
|
||||
"jest",
|
||||
"node"
|
||||
],
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"cordova-plugin-inappbrowser",
|
||||
"cordova",
|
||||
"dom-mediacapture-record",
|
||||
"faker",
|
||||
"jest",
|
||||
"node"
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue