Merge pull request #2577 from NoelDeMartin/MOBILE-3320
MOBILE-3320: Add tests & Fix linting
This commit is contained in:
		
						commit
						4b8e7898c9
					
				| @ -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', | ||||
|  | ||||
							
								
								
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -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,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<RenderConfig>; | ||||
| 
 | ||||
|     beforeEach(() => { | ||||
|         langProvider = createMock<CoreLangProvider>(['clearCustomStrings']); | ||||
| 
 | ||||
|         prepareComponentTest(AppComponent, [ | ||||
|             { provide: CoreLangProvider, useValue: langProvider }, | ||||
|         ]); | ||||
|         langProvider = mock<CoreLangProvider>(['clearCustomStrings']); | ||||
|         navController = mock<NavController>(['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'); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
|  | ||||
							
								
								
									
										35
									
								
								src/app/components/tests/icon.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/app/components/tests/icon.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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'); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
							
								
								
									
										19
									
								
								src/app/components/tests/iframe.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/app/components/tests/iframe.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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'); | ||||
| 
 | ||||
| }); | ||||
							
								
								
									
										19
									
								
								src/app/components/tests/user-avatar.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/app/components/tests/user-avatar.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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'); | ||||
| 
 | ||||
| }); | ||||
| @ -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. | ||||
|  | ||||
| @ -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<RenderConfig>; | ||||
| 
 | ||||
|     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<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'); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
|  | ||||
| @ -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'; | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										112
									
								
								src/app/directives/tests/format-text.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/app/directives/tests/format-text.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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'); | ||||
| 
 | ||||
| }); | ||||
							
								
								
									
										38
									
								
								src/app/directives/tests/link.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/app/directives/tests/link.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||
|             '<a href="https://moodle.org/" core-link [capture]="true">Link</a>', | ||||
|         ); | ||||
| 
 | ||||
|         // 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'); | ||||
| 
 | ||||
| }); | ||||
							
								
								
									
										81
									
								
								src/app/services/tests/utils/text.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/app/services/tests/utils/text.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<DomSanitizer>([], { 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(); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
| @ -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<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 renderTemplate<T>( | ||||
|     component: Type<T>, | ||||
|     template: string, | ||||
|     config: Partial<RenderConfig> = {}, | ||||
| ): Promise<WrapperComponentFixture<T>> { | ||||
|     config.declarations = config.declarations ?? []; | ||||
|     config.declarations.push(component); | ||||
| 
 | ||||
|     return renderAngularComponent( | ||||
|         createWrapperComponent(template, 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(' '); | ||||
| 
 | ||||
|     return renderTemplate(component, `<${tag} ${inputAttributes}></${tag}>`, 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user