Skip to content

Commit

Permalink
MOBILE-4266 app: Sort some app initializers
Browse files Browse the repository at this point in the history
  • Loading branch information
crazyserver committed Jan 31, 2024
1 parent 492fa10 commit 2c9091c
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 328 deletions.
90 changes: 1 addition & 89 deletions src/app/app.component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,104 +13,16 @@
// limitations under the License.

import { AppComponent } from '@/app/app.component';
import { CoreEvents } from '@singletons/events';
import { CoreLang, CoreLangProvider } from '@services/lang';

import { mock, mockSingleton, renderComponent, wait } from '@/testing/utils';
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { Http } from '@singletons';
import { of } from 'rxjs';
import { CoreSite } from '@classes/sites/site';
import { CoreUtils } from '@services/utils/utils';
import { renderComponent } from '@/testing/utils';

describe('AppComponent', () => {

let langProvider: CoreLangProvider;
beforeEach(() => {
langProvider = mockSingleton(CoreLang, mock({ getCurrentLanguage: async () => 'en' , clearCustomStrings: () => null }));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mockSingleton(Http, { get: () => of(null as any) });
});

it('should render', async () => {
const fixture = await renderComponent(AppComponent);

expect(fixture.debugElement.componentInstance).toBeTruthy();
expect(fixture.nativeElement.querySelector('ion-router-outlet')).toBeTruthy();
});

it('cleans up on logout', async () => {
const fixture = await renderComponent(AppComponent);
const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);

fixture.componentInstance.ngOnInit();
CoreEvents.trigger(CoreEvents.LOGOUT);

expect(langProvider.clearCustomStrings).toHaveBeenCalled();
expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
});

it('adds ionic platform and theme classes', async () => {
const fixture = await renderComponent(AppComponent);
const siteUrl = 'https://campus.example.edu';
const themeName = 'mytheme';
const themeName2 = 'anothertheme';

fixture.componentInstance.ngOnInit();

expect(document.documentElement.classList.contains('ionic7')).toBe(true);

const site = mock(new CoreSite('42', siteUrl, 'token', { info: {
sitename: 'Example Campus',
username: 'admin',
firstname: 'Admin',
lastname: 'User',
fullname: 'Admin User',
lang: 'en',
userid: 1,
siteurl: siteUrl,
userpictureurl: '',
theme: themeName,
functions: [],
} }));

mockSingleton(CoreSites, {
getSite: () => Promise.resolve(site),
getCurrentSiteId: () => '42',
});

CoreEvents.trigger(CoreEvents.LOGIN, {}, '42');
// Wait the event to be processed.
await wait(50);

expect(document.documentElement.classList.contains('theme-site-'+themeName)).toBe(true);
expect(document.documentElement.classList.contains('theme-site-'+themeName2)).toBe(false);

if (site.infos) {
site.infos.theme = themeName2;
}

CoreEvents.trigger(CoreEvents.SITE_UPDATED, site.infos , '42');

// Wait the event to be processed.
await CoreUtils.nextTick();

expect(document.documentElement.classList.contains('theme-site-'+themeName2)).toBe(true);
expect(document.documentElement.classList.contains('theme-site-'+themeName)).toBe(false);

CoreEvents.trigger(CoreEvents.LOGOUT);

expect(document.documentElement.classList.contains('theme-site-'+themeName)).toBe(false);
expect(document.documentElement.classList.contains('theme-site-'+themeName2)).toBe(false);

CoreEvents.trigger(CoreEvents.SITE_ADDED, site.infos , '42');

// Wait the event to be processed.
await CoreUtils.nextTick();

expect(document.documentElement.classList.contains('theme-site-'+themeName2)).toBe(true);
expect(document.documentElement.classList.contains('theme-site-'+themeName)).toBe(false);
});

});
235 changes: 2 additions & 233 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,19 @@

import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
import { IonRouterOutlet } from '@ionic/angular';
import { BackButtonEvent, ScrollDetail } from '@ionic/core';
import { BackButtonEvent } from '@ionic/core';

import { CoreLang } from '@services/lang';
import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreEvents } from '@singletons/events';
import { NgZone, SplashScreen } from '@singletons';
import { CoreNetwork } from '@services/network';
import { SplashScreen } from '@singletons';
import { CoreApp } from '@services/app';
import { CoreSites } from '@services/sites';
import { CoreNavigator } from '@services/navigator';
import { CoreSubscriptions } from '@singletons/subscriptions';
import { CoreWindow } from '@singletons/window';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreDom } from '@singletons/dom';
import { CorePlatform } from '@services/platform';
import { CoreUrl } from '@singletons/url';
import { CoreLogger } from '@singletons/logger';
import { CorePromisedValue } from '@classes/promised-value';
import { register } from 'swiper/element/bundle';
import { CoreSiteInfo, CoreSiteInfoResponse } from '@classes/sites/unauthenticated-site';

const MOODLE_SITE_URL_PREFIX = 'url-';
const MOODLE_VERSION_PREFIX = 'version-';
const MOODLEAPP_VERSION_PREFIX = 'moodleapp-';
const MOODLE_SITE_THEME_PREFIX = 'theme-site-';

register();

Expand All @@ -61,43 +46,6 @@ export class AppComponent implements OnInit, AfterViewInit {
ngOnInit(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = <any> window;
CoreDomUtils.toggleModeClass('ionic7', true, { includeLegacy: true });
CoreDomUtils.toggleModeClass('development', CoreConstants.BUILD.isDevelopment);
this.addVersionClass(MOODLEAPP_VERSION_PREFIX, CoreConstants.CONFIG.versionname.replace('-dev', ''));

CoreEvents.on(CoreEvents.LOGOUT, async () => {
// Unload lang custom strings.
CoreLang.clearCustomStrings();

// Remove version classes from body.
this.removeSiteClasses();

// Go to sites page when user is logged out.
await CoreNavigator.navigate('/login/sites', { reset: true });

if (CoreSitePlugins.hasSitePluginsLoaded) {
// Temporary fix. Reload the page to unload all plugins.
window.location.reload();
}
});

// Listen to scroll to add style when scroll is not 0.
win.addEventListener('ionScroll', async ({ detail, target }: CustomEvent<ScrollDetail>) => {
if ((target as HTMLElement).tagName != 'ION-CONTENT') {
return;
}
const content = (target as HTMLIonContentElement);

const page = content.closest('.ion-page');
if (!page) {
return;
}

page.querySelector<HTMLIonHeaderElement>('ion-header')?.classList.toggle('core-header-shadow', detail.scrollTop > 0);

const scrollElement = await content.getScrollElement();
content.classList.toggle('core-footer-shadow', !CoreDom.scrollIsBottom(scrollElement));
});

CorePlatform.resume.subscribe(() => {
// Wait a second before setting it to false since in iOS there could be some frozen WS calls.
Expand All @@ -119,41 +67,6 @@ export class AppComponent implements OnInit, AfterViewInit {
CoreWindow.open(url);
};

CoreEvents.on(CoreEvents.LOGIN, async (data) => {
if (data.siteId) {
const site = await CoreSites.getSite(data.siteId);
const info = site.getInfo();
if (info) {
this.addSiteClasses(info);
}
}

this.loadCustomStrings();
});

// Site config is checked in login.
CoreEvents.on(CoreEvents.LOGIN_SITE_CHECKED, (data) => {
this.addSiteUrlClass(data.config.httpswwwroot);
});

CoreEvents.on(CoreEvents.SITE_UPDATED, async (data) => {
if (data.siteId === CoreSites.getCurrentSiteId()) {
this.loadCustomStrings();

this.addSiteClasses(data);
}
});

CoreEvents.on(CoreEvents.SITE_ADDED, (data) => {
if (data.siteId === CoreSites.getCurrentSiteId()) {
this.loadCustomStrings();

this.addSiteClasses(data);
}
});

this.onPlatformReady();

// Quit app with back button.
document.addEventListener('ionBackButton', (event: BackButtonEvent) => {
// This callback should have the lowest priority in the app.
Expand Down Expand Up @@ -234,148 +147,4 @@ export class AppComponent implements OnInit, AfterViewInit {
return promise;
}

/**
* Async init function on platform ready.
*/
protected async onPlatformReady(): Promise<void> {
await CorePlatform.ready();

this.logger.debug('Platform is ready');

// Refresh online status when changes.
CoreNetwork.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.run(() => {
const isOnline = CoreNetwork.isOnline();
const hadOfflineMessage = CoreDomUtils.hasModeClass('core-offline');

CoreDomUtils.toggleModeClass('core-offline', !isOnline, { includeLegacy: true });

if (isOnline && hadOfflineMessage) {
CoreDomUtils.toggleModeClass('core-online', true, { includeLegacy: true });

setTimeout(() => {
CoreDomUtils.toggleModeClass('core-online', false, { includeLegacy: true });
}, 3000);
} else if (!isOnline) {
CoreDomUtils.toggleModeClass('core-online', false, { includeLegacy: true });
}
});
});

const isOnline = CoreNetwork.isOnline();
CoreDomUtils.toggleModeClass('core-offline', !isOnline, { includeLegacy: true });
}

/**
* Load custom lang strings. This cannot be done inside the lang provider because it causes circular dependencies.
*/
protected loadCustomStrings(): void {
const currentSite = CoreSites.getCurrentSite();

if (currentSite) {
CoreLang.loadCustomStringsFromSite(currentSite);
}
}

/**
* Convenience function to add version to html classes.
*
* @param prefix Prefix to add to the class.
* @param release Current release number of the site.
*/
protected addVersionClass(prefix: string, release: string): void {
const parts = release.split('.', 3);

parts[1] = parts[1] || '0';
parts[2] = parts[2] || '0';

CoreDomUtils.toggleModeClass(prefix + parts[0], true, { includeLegacy: true });
CoreDomUtils.toggleModeClass(prefix + parts[0] + '-' + parts[1], true, { includeLegacy: true });
CoreDomUtils.toggleModeClass(prefix + parts[0] + '-' + parts[1] + '-' + parts[2], true, { includeLegacy: true });
}

/**
* Convenience function to remove all mode classes form body.
*
* @param prefixes Prefixes of the class mode to be removed.
*/
protected removeModeClasses(prefixes: string[]): void {
for (const modeClass of CoreDomUtils.getModeClasses()) {
if (!prefixes.some((prefix) => modeClass.startsWith(prefix))) {
continue;
}

CoreDomUtils.toggleModeClass(modeClass, false, { includeLegacy: true });
}
}

/**
* Convenience function to add site classes to html.
*
* @param siteInfo Site Info.
*/
protected addSiteClasses(siteInfo: CoreSiteInfo | CoreSiteInfoResponse): void {
// Add version classes to html tag.
this.removeSiteClasses();

this.addVersionClass(MOODLE_VERSION_PREFIX, CoreSites.getReleaseNumber(siteInfo.release || ''));
this.addSiteUrlClass(siteInfo.siteurl);

if (siteInfo.theme) {
CoreDomUtils.toggleModeClass(MOODLE_SITE_THEME_PREFIX + siteInfo.theme, true);
}
}

/**
* Convenience function to remove all site mode classes form html.
*/
protected removeSiteClasses(): void {
// Remove version classes from html tag.
this.removeModeClasses(
[MOODLE_VERSION_PREFIX, MOODLE_SITE_URL_PREFIX, MOODLE_SITE_THEME_PREFIX],
);
}

/**
* Converts the provided URL into a CSS class that be used within the page.
* This is primarily used to add the siteurl to the body tag as a CSS class.
* Extracted from LMS url_to_class_name function.
*
* @param url Url.
* @returns Class name
*/
protected urlToClassName(url: string): string {
const parsedUrl = CoreUrl.parse(url);

if (!parsedUrl) {
return '';
}

let className = parsedUrl.domain?.replace(/\./g, '-') || '';

if (parsedUrl.port) {
className += `--${parsedUrl.port}`;
}
if (parsedUrl.path) {
const leading = new RegExp('^/+');
const trailing = new RegExp('/+$');
const path = parsedUrl.path.replace(leading, '').replace(trailing, '');
if (path) {
className += '--' + path.replace(/\//g, '-') || '';
}
}

return className;
}

/**
* Convenience function to add site url to html classes.
*/
protected addSiteUrlClass(siteUrl: string): void {
const className = this.urlToClassName(siteUrl);

CoreDomUtils.toggleModeClass(MOODLE_SITE_URL_PREFIX + className, true);
}

}
Loading

0 comments on commit 2c9091c

Please sign in to comment.