diff --git a/.eslintrc.js b/.eslintrc.js index 19d6f58e5..e5a4a4906 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -27,6 +27,7 @@ const appConfig = { reportUnusedDisableDirectives: true, rules: { '@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }], + '@angular-eslint/no-output-on-prefix': 'off', '@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/ban-types': [ 'error', @@ -112,6 +113,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 +199,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', diff --git a/package-lock.json b/package-lock.json index 738a386c8..5834af075 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 94694b8ac..a77f4a743 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/app.component.test.ts b/src/app/app.component.test.ts index 619c8faf9..72baacfd2 100644 --- a/src/app/app.component.test.ts +++ b/src/app/app.component.test.ts @@ -12,38 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { createComponent, createMock, prepareComponentTest } from '@/tests/utils'; +import { NavController } from '@ionic/angular'; import { AppComponent } from '@app/app.component'; -import { CoreLangProvider } from '@services/lang'; import { CoreEvents } from '@singletons/events'; +import { CoreLangProvider } from '@services/lang'; -describe('App component', () => { +import { mock, renderComponent, RenderConfig } from '@/tests/utils'; + +describe('AppComponent', () => { let langProvider: CoreLangProvider; + let navController: NavController; + let config: Partial; beforeEach(() => { - langProvider = createMock(['clearCustomStrings']); - - prepareComponentTest(AppComponent, [ - { provide: CoreLangProvider, useValue: langProvider }, - ]); + langProvider = mock(['clearCustomStrings']); + navController = mock(['navigateRoot']); + config = { + providers: [ + { provide: CoreLangProvider, useValue: langProvider }, + { provide: NavController, useValue: navController }, + ], + }; }); - 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); + it('cleans up on logout', async () => { + const fixture = await renderComponent(AppComponent, config); fixture.componentInstance.ngOnInit(); CoreEvents.trigger(CoreEvents.LOGOUT); expect(langProvider.clearCustomStrings).toHaveBeenCalled(); + expect(navController.navigateRoot).toHaveBeenCalledWith('/login/sites'); }); }); diff --git a/src/app/components/tests/icon.test.ts b/src/app/components/tests/icon.test.ts new file mode 100644 index 000000000..e7157feda --- /dev/null +++ b/src/app/components/tests/icon.test.ts @@ -0,0 +1,35 @@ +// (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 { CoreIconComponent } from '@components/icon/icon'; + +import { renderWrapperComponent } from '@/tests/utils'; + +describe('CoreIconComponent', () => { + + it('should render', async () => { + // Act + const fixture = await renderWrapperComponent(CoreIconComponent, 'core-icon', { name: 'fa-thumbs-up' }); + + // Assert + expect(fixture.nativeElement.innerHTML.trim()).not.toHaveLength(0); + + const icon = fixture.nativeElement.querySelector('ion-icon'); + expect(icon).not.toBeNull(); + expect(icon.classList.contains('fa')).toBe(true); + expect(icon.classList.contains('fa-thumbs-up')).toBe(true); + expect(icon.getAttribute('role')).toEqual('presentation'); + }); + +}); diff --git a/src/app/components/tests/iframe.test.ts b/src/app/components/tests/iframe.test.ts new file mode 100644 index 000000000..2b80c90c3 --- /dev/null +++ b/src/app/components/tests/iframe.test.ts @@ -0,0 +1,19 @@ +// (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. + +describe('CoreIframeComponent', () => { + + it.todo('should render'); + +}); diff --git a/src/app/components/tests/user-avatar.test.ts b/src/app/components/tests/user-avatar.test.ts new file mode 100644 index 000000000..a8d933837 --- /dev/null +++ b/src/app/components/tests/user-avatar.test.ts @@ -0,0 +1,19 @@ +// (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. + +describe('CoreUserAvatarComponent', () => { + + it.todo('should render'); + +}); diff --git a/src/app/core/login/pages/init/init.page.ts b/src/app/core/login/pages/init/init.page.ts index 889a7178f..4b702488a 100644 --- a/src/app/core/login/pages/init/init.page.ts +++ b/src/app/core/login/pages/init/init.page.ts @@ -19,9 +19,8 @@ import { CoreApp, CoreRedirectData } from '@services/app'; import { CoreInit } from '@services/init'; import { SplashScreen } from '@singletons/core.singletons'; import { CoreConstants } from '@core/constants'; -import { CoreSite } from '@/app/classes/site'; import { CoreSites } from '@/app/services/sites'; -import { CoreLoginHelper, CoreLoginHelperProvider } from '../../services/helper'; +import { CoreLoginHelper } from '@/app/core/login/services/helper'; /** * Page that displays a "splash screen" while the app is being initialized. diff --git a/src/app/core/login/tests/init.page.test.ts b/src/app/core/login/tests/init.page.test.ts index f5ddfce3c..3271530f0 100644 --- a/src/app/core/login/tests/init.page.test.ts +++ b/src/app/core/login/tests/init.page.test.ts @@ -12,39 +12,49 @@ // 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 { CoreSites } from '@/app/services/sites'; +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', () => { +describe('CoreLoginInitPage', () => { - let mocks: PageTestMocks; + let navController: NavController; + let config: Partial; - beforeEach(async () => { - const initPromise = Promise.resolve(); + beforeEach(() => { + mockSingleton(CoreApp, { getRedirect: () => ({}) }); + mockSingleton(CoreInit, { ready: () => Promise.resolve() }); + mockSingleton(CoreSites, { isLoggedIn: () => false }); + mockSingleton(SplashScreen, ['hide']); - mockSingleton(CoreInit, [], { ready: () => initPromise }); - mockSingleton(CoreApp, [], { getRedirect: () => ({}) }); - - mocks = await preparePageTest(CoreLoginInitPage); + navController = mock(['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'); }); }); diff --git a/src/app/core/mainmenu/pages/menu/menu.page.ts b/src/app/core/mainmenu/pages/menu/menu.page.ts index 47dddd926..c42062d38 100644 --- a/src/app/core/mainmenu/pages/menu/menu.page.ts +++ b/src/app/core/mainmenu/pages/menu/menu.page.ts @@ -22,7 +22,6 @@ import { CoreSites } from '@services/sites'; import { CoreEvents, CoreEventObserver, CoreEventLoadPageMainMenuData } from '@singletons/events'; import { CoreMainMenu } from '../../services/mainmenu'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/delegate'; -import { CoreUtils } from '@/app/services/utils/utils'; import { CoreDomUtils } from '@/app/services/utils/dom'; import { Translate } from '@/app/singletons/core.singletons'; diff --git a/src/app/directives/external-content.ts b/src/app/directives/external-content.ts index 0b228312f..dac1bbfd3 100644 --- a/src/app/directives/external-content.ts +++ b/src/app/directives/external-content.ts @@ -47,7 +47,6 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { @Input() href?: string; @Input('target-src') targetSrc?: string; // eslint-disable-line @angular-eslint/no-input-rename @Input() poster?: string; - // eslint-disable-next-line @angular-eslint/no-output-on-prefix @Output() onLoad = new EventEmitter(); // Emitted when content is loaded. Only for images. loaded = false; diff --git a/src/app/directives/supress-events.ts b/src/app/directives/supress-events.ts index 401578d8b..6a4ea4d35 100644 --- a/src/app/directives/supress-events.ts +++ b/src/app/directives/supress-events.ts @@ -39,7 +39,7 @@ import { Directive, ElementRef, OnInit, Input, Output, EventEmitter } from '@ang export class CoreSupressEventsDirective implements OnInit { @Input('core-suppress-events') suppressEvents?: string | string[]; - @Output() onClick = new EventEmitter(); // eslint-disable-line @angular-eslint/no-output-on-prefix + @Output() onClick = new EventEmitter(); protected element: HTMLElement; diff --git a/src/app/directives/tests/format-text.test.ts b/src/app/directives/tests/format-text.test.ts new file mode 100644 index 000000000..3f7d858e9 --- /dev/null +++ b/src/app/directives/tests/format-text.test.ts @@ -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; + + beforeEach(() => { + mockSingleton(Platform, { ready: () => Promise.resolve() }); + mockSingleton(CoreConfig, { get: (_, defaultValue) => defaultValue }); + + CoreDomUtils.setInstance(new CoreDomUtilsProvider(mock())); + CoreUrlUtils.setInstance(new CoreUrlUtilsProvider()); + CoreUtils.setInstance(new CoreUtilsProvider(mock())); + + 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({ + 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 = () => 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: '', 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'); + +}); diff --git a/src/app/directives/tests/link.test.ts b/src/app/directives/tests/link.test.ts new file mode 100644 index 000000000..30a99db90 --- /dev/null +++ b/src/app/directives/tests/link.test.ts @@ -0,0 +1,38 @@ +// (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 { CoreLinkDirective } from '@directives/link'; + +import { renderTemplate } from '@/tests/utils'; + +describe('CoreLinkDirective', () => { + + it('should render', async () => { + // Act + const fixture = await renderTemplate( + CoreLinkDirective, + 'Link', + ); + + // Assert + expect(fixture.nativeElement.innerHTML.trim()).not.toHaveLength(0); + + const anchor = fixture.nativeElement.querySelector('a'); + expect(anchor).not.toBeNull(); + expect(anchor.href).toEqual('https://moodle.org/'); + }); + + it.todo('should capture clicks'); + +}); diff --git a/src/app/services/tests/utils/text.test.ts b/src/app/services/tests/utils/text.test.ts new file mode 100644 index 000000000..82bbc2bfd --- /dev/null +++ b/src/app/services/tests/utils/text.test.ts @@ -0,0 +1,81 @@ +// (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 { CoreApp } from '@services/app'; +import { CoreTextUtilsProvider } from '@services/utils/text'; + +import { mock, mockSingleton } from '@/tests/utils'; + +describe('CoreTextUtilsProvider', () => { + + const config = { platform: 'android' }; + let sanitizer: DomSanitizer; + let textUtils: CoreTextUtilsProvider; + + beforeEach(() => { + mockSingleton(CoreApp, [], { isAndroid: jest.fn(() => config.platform === 'android') }); + + sanitizer = mock([], { bypassSecurityTrustUrl: jest.fn(url => url) }); + textUtils = new CoreTextUtilsProvider(sanitizer); + }); + + it('adds ending slashes', () => { + const originalUrl = 'https://moodle.org'; + const url = textUtils.addEndingSlash(originalUrl); + + expect(url).toEqual('https://moodle.org/'); + }); + + it('doesn\'t add duplicated ending slashes', () => { + const originalUrl = 'https://moodle.org/'; + const url = textUtils.addEndingSlash(originalUrl); + + expect(url).toEqual('https://moodle.org/'); + }); + + it('builds address URL for Android platforms', () => { + // Arrange + const address = 'Moodle Spain HQ'; + + config.platform = 'android'; + + // Act + const url = textUtils.buildAddressURL(address); + + // Assert + expect(url).toEqual('geo:0,0?q=Moodle%20Spain%20HQ'); + + expect(sanitizer.bypassSecurityTrustUrl).toHaveBeenCalled(); + expect(CoreApp.instance.isAndroid).toHaveBeenCalled(); + }); + + it('builds address URL for non-Android platforms', () => { + // Arrange + const address = 'Moodle Spain HQ'; + + config.platform = 'ios'; + + // Act + const url = textUtils.buildAddressURL(address); + + // Assert + expect(url).toEqual('http://maps.google.com?q=Moodle%20Spain%20HQ'); + + expect(sanitizer.bypassSecurityTrustUrl).toHaveBeenCalled(); + expect(CoreApp.instance.isAndroid).toHaveBeenCalled(); + }); + +}); diff --git a/src/tests/utils.ts b/src/tests/utils.ts index 9adc3bdd6..f15be53ae 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -12,65 +12,127 @@ // 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 { + + child!: U; + }; -export interface PageTestMocks extends ComponentTestMocks { - navController: NavController; +export interface RenderConfig { + declarations: unknown[]; + providers: unknown[]; } -export function createMock(methods: string[] = [], properties: Record = {}): T { - const mockObject = properties; +export type WrapperComponentFixture = ComponentFixture>; + +export function mock(instance?: Record): T; +export function mock(methods: string[], instance?: Record): T; +export function mock( + methodsOrInstance: string[] | Record = [], + instance: Record = {}, +): 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, instance?: Record): void; export function mockSingleton( singletonClass: CoreSingletonClass, - methods: string[] = [], - properties: Record = {}, + methods: string[], + instance?: Record, +): void; +export function mockSingleton( + singletonClass: CoreSingletonClass, + methodsOrInstance: string[] | Record = [], + instance: Record = {}, ): 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(component: Type, providers: unknown[] = []): Promise { +export async function renderComponent(component: Type, config: Partial = {}): Promise> { + return renderAngularComponent(component, { + declarations: [], + providers: [], + ...config, + }); +} + +export async function renderTemplate( + component: Type, + template: string, + config: Partial = {}, +): Promise> { + config.declarations = config.declarations ?? []; + config.declarations.push(component); + + return renderAngularComponent( + createWrapperComponent(template, component), + { + declarations: [], + providers: [], + ...config, + }, + ); +} + +export async function renderWrapperComponent( + component: Type, + tag: string, + inputs: Record = {}, + config: Partial = {}, +): Promise> { + const inputAttributes = Object + .entries(inputs) + .map(([name, value]) => `${name}="${value.toString().replace(/"/g, '"')}"`) + .join(' '); + + return renderTemplate(component, `<${tag} ${inputAttributes}>`, config); +} + +async function renderAngularComponent(component: Type, config: RenderConfig): Promise> { + 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(component: Type, providers: unknown[] = []): Promise { - const mocks = { - navController: createMock(['navigateRoot']), - }; +function createWrapperComponent(template: string, componentClass: Type): Type> { + @Component({ template }) + class HostComponent extends WrapperComponent { - const componentTestMocks = await prepareComponentTest(component, [ - ...providers, - { provide: NavController, useValue: mocks.navController }, - ]); + @ViewChild(componentClass) child!: U; - return { - ...componentTestMocks, - ...mocks, - }; -} - -export function createComponent(component: Type): ComponentFixture { - return TestBed.createComponent(component); + } + + return HostComponent; } diff --git a/tsconfig.json b/tsconfig.json index 5c880d8f6..b2401e638 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,7 @@ "cordova-plugin-inappbrowser", "cordova", "dom-mediacapture-record", + "faker", "jest", "node" ], diff --git a/tsconfig.test.json b/tsconfig.test.json index 25bf4d29a..06e4ff33d 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -10,6 +10,7 @@ "cordova-plugin-inappbrowser", "cordova", "dom-mediacapture-record", + "faker", "jest", "node" ],