UNPKG

@web-atoms/core

Version:
276 lines (238 loc) • 8.74 kB
import { App } from "../App"; import { Atom } from "../Atom"; import { AtomUri } from "../core/AtomUri"; import { IDisposable, INameValuePairs, INameValues } from "../core/types"; import { Inject } from "../di/Inject"; import { RegisterSingleton } from "../di/RegisterSingleton"; import { ServiceCollection } from "../di/ServiceCollection"; import { AtomViewModel } from "../view-model/AtomViewModel"; import { AtomWindowViewModel } from "../view-model/AtomWindowViewModel"; import { NavigationService } from "./NavigationService"; export interface IWindowRegistration { windowType: string; action: (vm: AtomViewModel) => any; } export class MockConfirmViewModel extends AtomWindowViewModel { public message: string; public title: string; } /** * Mock of WindowService for unit tests * @export * @class MockWindowService * @extends {WindowService} */ @RegisterSingleton export class MockNavigationService extends NavigationService { public title: string; private windowStack: IWindowRegistration[] = []; private history: AtomUri[] = []; private mLocation: AtomUri = new AtomUri(""); /** * Gets current location of browser, this does not return * actual location but it returns values of browser location. * This is done to provide mocking behavior for unit testing. * * @readonly * @type {AtomLocation} * @memberof BrowserService */ public get location(): AtomUri { return this.mLocation; } public set location(v: AtomUri) { if (JSON.stringify(this.location) === JSON.stringify(v)) { return; } this.history.push(this.mLocation); this.mLocation = v; } constructor(app: App) { super(app); } public refresh(): void { // nothing } /** * Navigate current browser to given url. * @param {string} url * @memberof BrowserService */ public navigate(url: string): void { this.location = new AtomUri(url); } public back(): void { if (this.history.length) { const top = this.history.pop(); this.location = top; this.history.pop(); } } /** * Internal usage * @param {string} msg * @param {string} [title] * @returns {Promise<any>} * @memberof MockWindowService */ public alert(msg: string, title?: string): Promise<any> { return this.openTestWindow(`__AlertWindow_${msg}`, { message: msg, title }); } /** * Internal usage * @param {string} msg * @param {string} [title] * @returns {Promise<boolean>} * @memberof MockWindowService */ public confirm(msg: string, title?: string): Promise<boolean> { return this.openTestWindow(`__ConfirmWindow_${msg}`, { message: msg, title }); } public openPage<T>(pageName: string, p?: INameValuePairs): Promise<T> { return this.openTestWindow(pageName, p); } public async notify(message: string, title?: string): Promise<void> { const url = `__AlertNotification_${message}`; const w: any = this.windowStack.find((x) => x.windowType === message); if (!w) { throw new Error(`No notification registered for "${message}"`); } w.action({}); } /** * Internal usage * @template T * @param {string} c * @param {AtomViewModel} vm * @returns {Promise<T>} * @memberof MockWindowService */ public openTestWindow<T>(c: string | AtomUri, p?: INameValues): Promise<T> { const url = c instanceof AtomUri ? c : new AtomUri(c); if (p) { for (const key in p) { if (p.hasOwnProperty(key)) { const element = p[key]; url.query[key] = element; } } } return new Promise((resolve, reject) => { const w: any = this.windowStack.find((x) => x.windowType === url.path); if (!w) { const ex: Error = new Error( `No window registered for "${c} with ${(p ? JSON.stringify(p, undefined, 2) : "")}"`); reject(ex); return; } setTimeout(() => { try { const vm = new AtomWindowViewModel(this.app) as any; for (const key in url.query) { if (url.query.hasOwnProperty(key)) { const element = url.query[key]; vm[key] = element; } } resolve(w.action(vm)); } catch (e) { reject(e); } }, 5); }); } /** * Call this method before any method that should expect an alert. * You can add many alerts, but each expected alert will only be called * once. * @param {string} msg * @returns {IDisposable} * @memberof MockWindowService */ public expectAlert(msg: string): IDisposable { return this.expectWindow(`__AlertWindow_${msg}`, (vm) => true); } /** * Call this method before any method that should expect a notification. * You can add many alerts, but each expected alert will only be called * once. * @param {string} msg * @returns {IDisposable} * @memberof MockWindowService */ public expectNotification(msg: string): IDisposable { return this.expectWindow(`__AlertNotification_${msg}`, (vm) => true); } /** * Call this method before any method that should expect a confirm. * You can add many confirms, but each expected confirm will only be called * once. * @param {string} msg * @param {(vm:MockConfirmViewModel) => boolean} action * @returns {IDisposable} * @memberof MockWindowService */ public expectConfirm(msg: string, action: (vm: MockConfirmViewModel) => boolean): IDisposable { return this.expectWindow(`__ConfirmWindow_${msg}`, action); } /** * Call this method before any method that should expect a window of given type. * Each window will only be used once and return value in windowAction will be * resolved in promise created by openWindow call. * @template TViewModel * @param {string} windowType * @param {(vm:TViewModel) => any} windowAction * @param {number} [maxDelay=10000] * @returns {IDisposable} * @memberof MockWindowService */ public expectPopup<TViewModel extends AtomViewModel>( popupType: string, windowAction: (vm: TViewModel) => any): IDisposable { return this.expectWindow(popupType, windowAction); } /** * Call this method before any method that should expect a window of given type. * Each window will only be used once and return value in windowAction will be * resolved in promise created by openWindow call. * @template TViewModel * @param {string} windowType * @param {(vm:TViewModel) => any} windowAction * @param {number} [maxDelay=10000] * @returns {IDisposable} * @memberof MockWindowService */ public expectWindow<TViewModel extends AtomViewModel>( windowType: string, windowAction: (vm: TViewModel) => any): IDisposable { const registration: any = { windowType, action: windowAction }; registration.action = (vm: TViewModel) => { this.removeRegistration(registration); return windowAction(vm); }; this.windowStack.push(registration); return { dispose: () => { this.removeRegistration(registration); } }; } public removeRegistration(r: IWindowRegistration): void { this.windowStack = this.windowStack.filter((x) => x !== r); } public assert(): void { if (!this.windowStack.length) { return; } throw new Error(`Expected windows did not open ${this.windowStack.map((x) => `"${x.windowType}"`).join(",")}`); } protected registerForPopup(): void { // nothing } protected forceRemove(view: any): void { throw new Error("Method not implemented."); } protected openWindow<T>(url: AtomUri): Promise<T> { return this.openTestWindow(url); } }