From e95d0d3c1d9dcbc22d6b964eca3c9c6529039124 Mon Sep 17 00:00:00 2001 From: liupengfei Date: Fri, 17 Feb 2023 18:47:48 +0800 Subject: [PATCH] feat(core): remove the lifecycle `onBootstrap`, `onDestroy` --- packages/core/src/lib/app.ts | 78 +---- packages/core/src/lib/bus.ts | 188 ++++-------- packages/core/src/lib/event-emitter.ts | 11 +- packages/core/src/lib/socket.ts | 14 +- packages/core/src/lib/utils.ts | 21 +- packages/core/src/types.ts | 23 +- packages/core/test/app.test.ts | 305 +++++++------------- packages/core/test/bus.test.ts | 78 +++-- packages/core/test/test-apps/css.ts | 8 - packages/core/test/test-apps/invalid-app.ts | 14 - packages/core/test/test-apps/react.ts | 11 - packages/core/test/test-apps/valid-app.ts | 14 - packages/core/test/utils.test.ts | 9 - 13 files changed, 220 insertions(+), 554 deletions(-) delete mode 100644 packages/core/test/test-apps/css.ts delete mode 100644 packages/core/test/test-apps/invalid-app.ts delete mode 100644 packages/core/test/test-apps/react.ts delete mode 100644 packages/core/test/test-apps/valid-app.ts diff --git a/packages/core/src/lib/app.ts b/packages/core/src/lib/app.ts index 39def6d..8ffcb27 100644 --- a/packages/core/src/lib/app.ts +++ b/packages/core/src/lib/app.ts @@ -1,14 +1,8 @@ -import type { LifecyleCallbackType, DependencyType, RelateType } from '../types' // eslint-disable-line -import { deduplicate } from './utils' - export class App { - public dependenciesReady: boolean = false - public bootstrapping: Promise = null - public dependencies: Array<{ name: string; ctx?: Record; data?: any }> = [] - public relatedApps: Array<{ name: string; ctx?: Record }> = [] - public doBootstrap?: LifecyleCallbackType - public doActivate?: LifecyleCallbackType - public doDestroy?: LifecyleCallbackType + public activated: Promise = null + public dependencies: Array = [] + public relatedApps: Array = [] + public doActivate?: () => void | Promise public isRallieCoreApp: boolean constructor(public name: string) { @@ -21,75 +15,27 @@ export class App { * @param relatedApps * @returns */ - public relateTo(relatedApps: RelateType[]) { - const getName = (relateApp: RelateType) => - typeof relateApp === 'string' ? relateApp : relateApp.name - const deduplicatedRelatedApps = deduplicate(relatedApps) - const currentRelatedAppNames: string[] = this.relatedApps.map((item) => item.name) - deduplicatedRelatedApps.forEach((relatedApp) => { - if (!currentRelatedAppNames.includes(getName(relatedApp))) { - this.relatedApps.push({ - name: getName(relatedApp), - ctx: typeof relatedApp !== 'string' ? relatedApp.ctx : undefined, - }) - } - }) + public relateTo(relatedApps: string[]) { + this.relatedApps = Array.from(new Set([...this.relatedApps, ...relatedApps])) return this } /** - * indicate the apps to be activate before your app is bootstrapped + * indicate the apps to be activate before your app is activated * @param dependencies */ - public relyOn(dependencies: DependencyType[]) { - const getName = (dependencyApp: DependencyType) => - typeof dependencyApp === 'string' ? dependencyApp : dependencyApp.name - const deduplicatedDependencies: DependencyType[] = deduplicate(dependencies) - const currentDependenciesNames = this.dependencies.map((item) => item.name) - const currentRelatedAppsNames = this.relatedApps.map((item) => item.name) - deduplicatedDependencies.forEach((dependency) => { - const name = getName(dependency) - if (!currentDependenciesNames.includes(name)) { - this.dependencies.push({ - name, - ctx: typeof dependency !== 'string' ? dependency.ctx : undefined, - data: typeof dependency !== 'string' ? dependency.data : undefined, - }) - } - if (!currentRelatedAppsNames.includes(name)) { - this.relatedApps.push({ - name, - ctx: typeof dependency !== 'string' ? dependency.ctx : undefined, - }) - } - }) - return this - } - - /** - * indicate the callback your app will run when it's activated the first time - * @param {function} callback - */ - public onBootstrap(callback: LifecyleCallbackType) { - this.doBootstrap = callback + public relyOn(dependencies: string[]) { + this.relateTo(dependencies) + this.dependencies = Array.from(new Set([...this.dependencies, ...dependencies])) return this } /** - * indicate the callback your app will run when it's activated after the first time + * indicate the callback your app will run when it's activated * @param callback */ - public onActivate(callback: LifecyleCallbackType) { + public onActivate(callback: () => void | Promise) { this.doActivate = callback return this } - - /** - * indicate the callback when your app is destroyed - * @param callback - */ - public onDestroy(callback: LifecyleCallbackType) { - this.doDestroy = callback - return this - } } diff --git a/packages/core/src/lib/bus.ts b/packages/core/src/lib/bus.ts index 814455f..3bdaf28 100644 --- a/packages/core/src/lib/bus.ts +++ b/packages/core/src/lib/bus.ts @@ -30,54 +30,15 @@ export class Bus { return this.apps[appName] && typeof this.apps[appName] !== 'boolean' } - /** - * config the bus - * @param conf the new configuration object - */ - public config(conf: Partial) { - this.conf = { - ...this.conf, - ...conf, - assets: { - ...this.conf.assets, - ...(conf?.assets || {}), - }, - } - return this - } - - /** - * register the middleware - * @param middleware - */ - public use(middleware: MiddlewareFnType) { - if (typeof middleware !== 'function') { - throw new Error(Errors.wrongMiddlewareType()) - } - this.middlewares.push(middleware) - this.composedMiddlewareFn = compose(this.middlewares) - return this - } - - /** - * create the context to pass to the middleware - * @param ctx - * @returns - */ - private createContext(name: string, ctx: Record = {}) { + private createContext(name: string) { const context: ContextType = { name, loadScript: loader.loadScript, loadLink: loader.loadLink, - ...ctx, } return context } - /** - * the core middleware - * @param ctx the context - */ private async loadResourcesFromAssetsConfig(ctx: ContextType) { const { name, loadScript = loader.loadScript, loadLink = loader.loadLink } = ctx const { assets } = this.conf @@ -98,26 +59,49 @@ export class Bus { } } - /** - * create a socket - * @return the socket instance - */ + private async innerActivateApp(name: string, visitPath: string[]) { + await this.loadApp(name) + if (this.isRallieCoreApp(name)) { + const app = this.apps[name] as App + await this.loadRelatedApps(app) + if (visitPath.includes(name)) { + const startIndex = visitPath.indexOf(name) + const circularPath = [...visitPath.slice(startIndex), name] + throw new Error(Errors.circularDependencies(name, circularPath)) + } + visitPath.push(name) + if (!app.activated) { + const activating = async () => { + await this.activateDependencies(app, visitPath) + app.doActivate && (await Promise.resolve(app.doActivate())) + } + app.activated = activating() + } + await app.activated + visitPath.pop() + } + } + + private async activateDependencies(app: App, visitPath: string[]) { + if (app.dependencies.length !== 0) { + for (const appName of app.dependencies) { + await this.innerActivateApp(appName, visitPath) + } + } + } + + private async loadRelatedApps(app: App) { + await Promise.all(app.relatedApps.map((appName) => this.loadApp(appName))) + } + public createSocket() { return new Socket(this.eventEmitter, this.stores) } - /** - * return true if an app is created - */ public existApp(name: string) { return !!this.apps[name] } - /** - * create an app - * @param name the name of the app - * @return the app instance - */ public createApp(name: string) { if (this.existApp(name)) { throw new Error(Errors.createExistingApp(name)) @@ -127,15 +111,11 @@ export class Bus { return app } - /** - * load the resources of an app - * @param ctx - */ - public async loadApp(name: string, ctx: Record = {}) { + public async loadApp(name: string) { if (!this.apps[name]) { if (!this.loadingApps[name]) { this.loadingApps[name] = new Promise((resolve, reject) => { - const context = this.createContext(name, ctx) + const context = this.createContext(name) // apply the middlewares this.composedMiddlewareFn(context, this.loadResourcesFromAssetsConfig.bind(this)) .then(() => { @@ -157,81 +137,35 @@ export class Bus { } } - private async activateDependencies(app: App, visitPath: string[]) { - if (!app.dependenciesReady && app.dependencies.length !== 0) { - for (const dependence of app.dependencies) { - const { name, data, ctx } = dependence - await this.activateApp(name, data, ctx, visitPath) - } - app.dependenciesReady = true - } + public async activateApp(name: string) { + await this.innerActivateApp(name, []) } - private async loadRelatedApps(app: App) { - await Promise.all(app.relatedApps.map(({ name, ctx }) => this.loadApp(name, ctx))) - } - - /** - * activate an app - * @param name - * @param data - */ - public async activateApp( - name: string, - data?: T, - ctx: Record = {}, - visitPath: string[] = [], - ) { - await this.loadApp(name, ctx) - if (this.isRallieCoreApp(name)) { - const app = this.apps[name] as App - await this.loadRelatedApps(app) - if (visitPath.includes(name)) { - const startIndex = visitPath.indexOf(name) - const circularPath = [...visitPath.slice(startIndex), name] - throw new Error(Errors.circularDependencies(name, circularPath)) - } - visitPath.push(name) - if (!app.bootstrapping) { - const bootstrapping = async () => { - await this.activateDependencies(app, visitPath) - if (app.doBootstrap) { - await Promise.resolve(app.doBootstrap(data)) - } else if (app.doActivate) { - await Promise.resolve(app.doActivate(data)) - } - } - app.bootstrapping = bootstrapping() - await app.bootstrapping - } else { - await app.bootstrapping - app.doActivate && (await Promise.resolve(app.doActivate(data))) - } - visitPath.pop() + public config(conf: Partial) { + this.conf = { + ...this.conf, + ...conf, + assets: { + ...this.conf.assets, + ...(conf?.assets || {}), + }, } + return this } - /** - * destroy an app - * @param name - * @param data - */ - public async destroyApp(name: string, data?: T) { - if (this.isRallieCoreApp(name)) { - const app = this.apps[name] as App - app.doDestroy && (await Promise.resolve(app.doDestroy(data))) - app.bootstrapping = null - app.dependenciesReady = false + public use(middleware: MiddlewareFnType) { + if (typeof middleware !== 'function') { + throw new Error(Errors.wrongMiddlewareType()) } + this.middlewares.push(middleware) + this.composedMiddlewareFn = compose(this.middlewares) + return this } } const busProxy = {} export const DEFAULT_BUS_NAME = 'DEFAULT_BUS' -/** - * create a bus and record it on window.RALLIE_BUS_STORE - * @param name the name of the bus - */ + export const createBus = (name: string = DEFAULT_BUS_NAME) => { if (window.RALLIE_BUS_STORE === undefined) { Reflect.defineProperty(window, 'RALLIE_BUS_STORE', { @@ -252,20 +186,10 @@ export const createBus = (name: string = DEFAULT_BUS_NAME) => { } } -/** - * get the bus from window.RALLIE_BUS_STORE - * @param name the name of the bus - * @returns - */ export const getBus = (name: string = DEFAULT_BUS_NAME) => { return window.RALLIE_BUS_STORE && window.RALLIE_BUS_STORE[name] } -/** - * get the bus from window.RALLIE_BUS_STORE, if the bus is not created, then create it - * @param name the name of the bus - * @returns - */ export const touchBus = (name: string = DEFAULT_BUS_NAME): [Bus, boolean] => { let bus: Bus = null let isHost: boolean = false diff --git a/packages/core/src/lib/event-emitter.ts b/packages/core/src/lib/event-emitter.ts index 93f8b8c..0cbc654 100644 --- a/packages/core/src/lib/event-emitter.ts +++ b/packages/core/src/lib/event-emitter.ts @@ -1,22 +1,21 @@ -import type { CallbackType } from '../types' // eslint-disable-line import { Errors } from './utils' type BroadcastEventsType = Record< string, { - listeners: Set + listeners: Set emitedArgs: Array } > -type unicastEventsType = Record +type unicastEventsType = Record export class EventEmitter { private broadcastEvents: BroadcastEventsType = {} private unicastEvents: unicastEventsType = {} - public addBroadcastEventListener(event: string, callback: CallbackType) { + public addBroadcastEventListener(event: string, callback: Function) { this.broadcastEvents[event] = this.broadcastEvents[event] || { listeners: new Set(), emitedArgs: [], @@ -31,14 +30,14 @@ export class EventEmitter { } } - public addUnicastEventListener(event: string, callback: CallbackType) { + public addUnicastEventListener(event: string, callback: Function) { if (this.unicastEvents[event]) { throw new Error(Errors.registedExistedUnicast(event)) } this.unicastEvents[event] = callback } - public removeBroadcastEventListener(event: string, callback: CallbackType) { + public removeBroadcastEventListener(event: string, callback: Function) { const registedcallbacks = this.broadcastEvents[event]?.listeners if (registedcallbacks) { if (registedcallbacks.has(callback)) { diff --git a/packages/core/src/lib/socket.ts b/packages/core/src/lib/socket.ts index 4a22a72..94eb1ba 100644 --- a/packages/core/src/lib/socket.ts +++ b/packages/core/src/lib/socket.ts @@ -1,7 +1,7 @@ // eslint-disable-next-line vue/prefer-import-from-vue import { effect, reactive, readonly, toRaw } from '@vue/reactivity' import type { EventEmitter } from './event-emitter' -import type { CallbackType, StoresType } from '../types' +import type { StoresType } from '../types' import { Errors, isPrimitive, Warnings } from './utils' import { Watcher } from './watcher' @@ -12,7 +12,7 @@ export class Socket { this.stores = stores } - private offEvents(events: Record, isUnicast: boolean, eventName?: string) { + private offEvents(events: Record, isUnicast: boolean, eventName?: string) { let cancelListening = isUnicast ? this.eventEmitter.removeUnicastEventListener : this.eventEmitter.removeBroadcastEventListener @@ -35,7 +35,7 @@ export class Socket { * add broadcast event listeners * @param events */ - public onBroadcast>(events: T) { + public onBroadcast>(events: T) { Object.entries(events).forEach(([eventName, handler]) => { this.eventEmitter.addBroadcastEventListener(eventName, handler) }) @@ -48,7 +48,7 @@ export class Socket { * add unicast event listeners * @param events */ - public onUnicast>(events: T) { + public onUnicast>(events: T) { Object.entries(events).forEach(([eventName, handler]) => { try { this.eventEmitter.addUnicastEventListener(eventName, handler) @@ -65,7 +65,7 @@ export class Socket { * create a proxy to emit a broadcast event * @param logger */ - public createBroadcaster>( + public createBroadcaster>( logger?: (eventName: string) => void, ) { return new Proxy({} as any, { @@ -85,9 +85,7 @@ export class Socket { * create a proxy to emit unicast event * @param logger */ - public createUnicaster>( - logger?: (eventName: string) => void, - ) { + public createUnicaster>(logger?: (eventName: string) => void) { return new Proxy({} as any, { get: (target, eventName) => { return (...args: any[]) => { diff --git a/packages/core/src/lib/utils.ts b/packages/core/src/lib/utils.ts index 9a795e8..f663f17 100644 --- a/packages/core/src/lib/utils.ts +++ b/packages/core/src/lib/utils.ts @@ -1,10 +1,4 @@ -import type { - MiddlewareFnType, - NextFnType, - ContextType, - DependencyType, - RelateType, -} from '../types' // eslint-disable-line +import type { MiddlewareFnType, NextFnType, ContextType } from '../types' export const Errors = { // ================= EventEmitter.broadcast ================= @@ -80,19 +74,6 @@ export function isPrimitive(object: unknown): boolean { return ['string', 'number', 'boolean', 'undefined'].includes(typeof object) } -export function deduplicate(items: T[]) { - const flags: Record = {} - const result: Array = [] - items.forEach((item) => { - const name = typeof item === 'string' ? item : item.name - if (!flags[name]) { - result.push(item) - flags[name] = true - } - }) - return result -} - /** * the compose function copied from koa-compose * @param middlewares diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c207752..fb6d4f2 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,7 +1,5 @@ -import { Socket } from './lib/socket' // eslint-disable-line -import { Watcher } from './lib/watcher' // eslint-disable-line - -export type CallbackType = (...args: any[]) => any +import type { Socket } from './lib/socket' +import type { Watcher } from './lib/watcher' export type ScriptType = Partial | string | HTMLScriptElement @@ -31,23 +29,6 @@ export type NextFnType = (ctx?: ContextType) => void | Promise export type MiddlewareFnType = (ctx: ContextType, next: NextFnType) => void | Promise -export type LifecyleCallbackType = (data?: any) => Promise | void - -export type DependencyType = - | { - name: string - data?: any - ctx?: Record - } - | string - -export type RelateType = - | { - name: string - ctx: Record - } - | string - export type StoreType = { state: T owner: Socket | null diff --git a/packages/core/test/app.test.ts b/packages/core/test/app.test.ts index 142ed6c..b0feaec 100644 --- a/packages/core/test/app.test.ts +++ b/packages/core/test/app.test.ts @@ -1,209 +1,132 @@ -import { createBus, touchBus, App } from '../src' +import { createBus, App } from '../src' import { Errors } from '../src/lib/utils' import nock from 'nock' +type CharOf = T extends `${infer First}${infer Rest}` + ? First | CharOf + : never + describe('Test lifecycles of App', () => { const bus = createBus('testBus') - test('# case 1: test the lifecycle of the app which indicate both bootstrap and activate callback', (done) => { - /** - * app 'a' indicates both bootstrap and activate callback, - * when it is activated at the first time, the bootstrap callback should be called, - * when it is activated after the first time , the activate callback should be called - */ + test('# case 1: test the lifecycle of the app which indicates a synchronous activate callback', async () => { let activateCount = 0 - bus - .createApp('case1') - .onBootstrap(() => { - activateCount = 1 - }) - .onActivate(() => { - activateCount++ - }) - bus - .activateApp('case1') - .then(() => { - expect(activateCount).toEqual(1) - return bus.activateApp('case1') - }) - .then(() => { - expect(activateCount).toEqual(2) - done() - }) + bus.createApp('case1').onActivate(() => { + activateCount++ + }) + await bus.activateApp('case1') + expect(activateCount).toEqual(1) + bus.activateApp('case1') + bus.activateApp('case1') + bus.activateApp('case1') + expect(activateCount).toEqual(1) }) - test('# case 2: test the lifecycle of the app which only indicate the bootstrap callback', (done) => { - /** - * app 'b' indicate only the bootstrap callback - * when it is activated at the first time, the bootstrap callback should be called, - * when it is activated after the first time, nothing will happen - */ + test('# case 2: test the lifecycle of the app which indicates a asynchronous callback', async () => { let activateCount = 0 - bus.createApp('case2').onBootstrap(async () => { + bus.createApp('case2').onActivate(async () => { activateCount++ }) - bus - .activateApp('case2') - .then(() => { - expect(activateCount).toEqual(1) - return bus.activateApp('case2') - }) - .then(() => { - expect(activateCount).toEqual(1) - done() - }) + const activated = bus.activateApp('case2') + expect(activateCount).toEqual(0) + await activated + expect(activateCount).toEqual(1) + await bus.activateApp('case2') + await bus.activateApp('case2') + await bus.activateApp('case2') + expect(activateCount).toEqual(1) }) - test('# case 3: test the lifecycle of the app which only indicate the activate callback', (done) => { - /** - * app 'c' indicate only the activate callback - * the activate callback will be called everytime it's activated - */ + test('#case 3: test activate an app multi times at a time', async () => { let activateCount = 0 bus.createApp('case3').onActivate(async () => { activateCount++ }) - bus - .activateApp('case3') - .then(() => { - expect(activateCount).toEqual(1) - return bus.activateApp('case3') - }) - .then(() => { - expect(activateCount).toEqual(2) - done() - }) + await Promise.all([ + bus.activateApp('case3'), + bus.activateApp('case3'), + bus.activateApp('case3'), + ]) + expect(activateCount).toEqual(1) }) +}) - test('# case 4: test the lifecycle of the app indicate the destroy callback', (done) => { - /** - * app 'd' indicate the destroy callback - * the destroy callback should be called before it is destroyed - * after it is destroyed, when the bus reactivate the app, it will recall the bootstrap lifecycle callback - */ - let bootstraped = false - let activated = false +describe('Test relations', () => { + nock('https://cdn.rallie.com') + .get('/assets/related-app.js') + .reply( + 200, + ` + (function() { + const bus = window.RALLIE_BUS_STORE.DEFAULT_BUS; + console.log('relatedApp is loaded') + bus.createApp('relatedApp') + .onActivate(() => { + console.log('relatedApp is activated'); + }) + })() + `, + ) + const bus = createBus() + bus.config({ + assets: { + relatedApp: { + js: ['https://cdn.rallie.com/assets/related-app.js'], + }, + }, + }) + test('# case 1: activate an app, its related app will be loaded but not activated', async () => { + console.log = jest.fn() + const app = bus.createApp('case1') + app.relateTo(['relatedApp']).onActivate(() => { + console.log('case1 is activated') + }) + await bus.activateApp('case1') + expect(console.log).toBeCalledTimes(2) + expect(console.log).toBeCalledWith('relatedApp is loaded') + expect(console.log).toBeCalledWith('case1 is activated') + await bus.activateApp('relatedApp') + expect(console.log).toBeCalledWith('relatedApp is activated') + }) + + test('# case 2: circular relations is allowed', async () => { + console.log = jest.fn() bus - .createApp('case4') - .onBootstrap(async () => { - bootstraped = true - }) - .onActivate(async () => { - activated = true - }) - .onDestroy(async () => { - bootstraped = false - activated = false + .createApp('case2-1') + .relateTo(['case2-2']) + .onActivate(() => { + console.log('case2-1 is activated') }) bus - .activateApp('case4') - .then(() => { - expect(bootstraped).toBeTruthy() - expect(activated).toBeFalsy() - return bus.activateApp('case4') - }) - .then(() => { - expect(bootstraped).toBeTruthy() - expect(activated).toBeTruthy() - return bus.destroyApp('case4') - }) - .then(() => { - expect(bootstraped).toBeFalsy() - expect(activated).toBeFalsy() - return bus.activateApp('case4') - }) - .then(() => { - expect(bootstraped).toBeTruthy() - expect(activated).toBeFalsy() - done() + .createApp('case2-2') + .relateTo(['case2-1']) + .onActivate(() => { + console.log('case2-2 is activated') }) - }) - - test('#case 5: test activate an app multi times at a time', async () => { - nock('https://test.case5.com') - .get('/index.js') - .reply( - 200, - ` - const bus = window.RALLIE_BUS_STORE.DEFAULT_BUS - bus.createApp('case5') - .onBootstrap((counter) => { - counter.bootstrap++ - }) - .onActivate((counter) => { - counter.activate++ - }) - `, - ) - const [bus] = touchBus() - bus.config({ - assets: { - case5: { - js: ['https://test.case5.com/index.js'], - }, - }, - }) - const counter = { - bootstrap: 0, - activate: 0, - } - await Promise.all([ - bus.activateApp('case5', counter), - bus.activateApp('case5', counter), - bus.activateApp('case5', counter), - ]) - expect(counter.bootstrap).toEqual(1) - expect(counter.activate).toEqual(2) + await Promise.all([bus.activateApp('case2-1'), bus.activateApp('case2-2')]) + expect(console.log).toBeCalledTimes(2) + expect(console.log).toBeCalledWith('case2-1 is activated') + expect(console.log).toBeCalledWith('case2-2 is activated') }) }) -describe('Test dependencies of App', () => { +describe('Test dependencies', () => { const bus = createBus('testBus2') - const appNames = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - ] - const apps: Record = {} - let bootstrapedApps: string[] = [] - let reactivateApps: string[] = [] + const appNameStrs = 'abcdefghijklmnopqr' as const + const appNames = appNameStrs.split('') as Array> + const apps = {} as Record, App> + let activatedApps: string[] = [] appNames.forEach((appName) => { - const app = bus.createApp(appName) + const app = bus.createApp(appName).onActivate(async () => { + activatedApps.push(appName) + }) apps[appName] = app - app - .onBootstrap(async () => { - bootstrapedApps.push(appName) - }) - .onActivate(async () => { - reactivateApps.push(appName) - }) - .onDestroy(async () => { - bootstrapedApps.splice(bootstrapedApps.indexOf(appName), 1) - }) }) afterEach(() => { - appNames.forEach((appName) => { - reactivateApps = [] - bus.destroyApp(appName) - }) + activatedApps = [] }) - test('# case 1: test normal dependencies', (done) => { + test('# case 1: test normal dependencies', async () => { /** the dependencies relationship * |-- a * |--b @@ -213,25 +136,11 @@ describe('Test dependencies of App', () => { * |--f * |--g */ - apps.a - .relyOn(['b', 'd', 'g']) - .relateTo(['b', 'c', 'd', 'e', { name: 'f', ctx: { version: '0.1.0' } }, 'g', 'b']) - apps.b.relyOn(['c']).relateTo(['c']).relateTo(['c']) // circular relations doesn't matter - apps.c.relyOn([]).relateTo(['b']) // circular relations doesn't matter - apps.d.relyOn(['e']).relyOn(['f']) // test duplicated dependencies - bus - .activateApp('a') - .then(() => { - expect(bootstrapedApps.join(',')).toEqual('c,b,e,f,d,g,a') - bootstrapedApps = [] - return bus.activateApp('a') - }) - .then(() => { - // reactivate app 'a', its dependencies shouldn't be reactivated - expect(bootstrapedApps.join(',')).toEqual('') - expect(reactivateApps.join(',')).toEqual('a') - done() - }) + apps.a.relyOn(['b', 'd', 'g']) + apps.b.relyOn(['c']) + apps.d.relyOn(['e']).relyOn(['f']) + await bus.activateApp('a') + expect(activatedApps.join(',')).toEqual('c,b,e,f,d,g,a') }) test('# case 2: test circular dependency', async () => { @@ -240,7 +149,7 @@ describe('Test dependencies of App', () => { * | | * |----i * */ - apps.h.relyOn([{ name: 'i', ctx: { version: '*' } }]) + apps.h.relyOn(['i']) apps.i.relyOn(['h']) try { await bus.activateApp('i') @@ -267,16 +176,4 @@ describe('Test dependencies of App', () => { expect(err.message).toEqual(Errors.circularDependencies('j', ['j', 'm', 'p', 'j'])) } }) - - test('# case 3: test params of lifecycles', (done) => { - apps.q.relyOn([{ name: 'r', data: 'app named q activate me' }]) - apps.r.relyOn([]).onBootstrap(async (data) => { - console.log(data) - }) - console.log = jest.fn() - bus.activateApp('q').then(() => { - expect(console.log).toBeCalledWith('app named q activate me') - done() - }) - }) }) diff --git a/packages/core/test/bus.test.ts b/packages/core/test/bus.test.ts index 88d0639..c4a536e 100644 --- a/packages/core/test/bus.test.ts +++ b/packages/core/test/bus.test.ts @@ -1,40 +1,51 @@ import { createBus, getBus, touchBus } from '../src/index' import { Bus } from '../src/lib/bus' import nock from 'nock' -import cssCode from './test-apps/css' -import validAppCode from './test-apps/valid-app' -import invalidAppCode from './test-apps/invalid-app' -import reactCode from './test-apps/react' import { Errors } from '../src/lib/utils' -/* eslint-disable no-unused-vars */ - declare global { + // eslint-disable-next-line no-unused-vars interface Window { - appsLoadedFromLocalhost: any - lastLoadingApp: any - React: any + lastLoadingApp: string + React: string RALLIE_BUS_STORE: Record } } -nock('https://cdn.obvious.com') +nock('https://cdn.rallie.com') .get('/assets/valid-app.js') - .reply(200, validAppCode) + .reply( + 200, + ` + (function() { + const bus = window.RALLIE_BUS_STORE.DEFAULT_BUS; + bus.createApp('valid-app') + .relyOn([ + 'lib:react' + ]) + .onActivate(() => { + console.log('valid-app is created'); + }) + })() + `, + ) + .get('/assets/invalid-app.js') + .reply(200, "console.log('invalid-app loaded');") .get('/assets/css-code.css') - .reply(200, cssCode) + .reply(200, 'css-code') .get('/assets/react.js') - .reply(200, reactCode) - -nock('https://localhost').get('/assets/invalid-app.js').reply(200, invalidAppCode) + .reply(200, "window.React = 'reactSourceCode'") describe('Test the capability to load the resources of an app or lib', () => { const staticAssetsConfig = { 'lib:react': { - js: ['https://cdn.obvious.com/assets/react.js'], + js: ['https://cdn.rallie.com/assets/react.js'], }, 'valid-app': { - js: ['https://cdn.obvious.com/assets/valid-app.js'], - css: ['https://cdn.obvious.com/assets/css-code.css'], + js: ['https://cdn.rallie.com/assets/valid-app.js'], + css: ['https://cdn.rallie.com/assets/css-code.css'], + }, + 'invalid-app': { + js: ['https://cdn.rallie.com/assets/invalid-app.js'], }, 'invalid-resource-app': { js: ['validFile.png'], @@ -42,8 +53,6 @@ describe('Test the capability to load the resources of an app or lib', () => { }, } - window.appsLoadedFromLocalhost = [] - const globalBus = createBus() globalBus .config({ @@ -53,14 +62,6 @@ describe('Test the capability to load the resources of an app or lib', () => { window.lastLoadingApp = ctx.name await next() }) - .use(async (ctx, next) => { - if (ctx.loadedFromLocalhost) { - window.appsLoadedFromLocalhost.push(ctx.name) - await ctx.loadScript(`https://localhost/assets/${ctx.name}.js`) - } else { - await next() - } - }) test('# case 1: create a bus, it should be mounted on window.RALLIE_BUS_STORE ', () => { expect(getBus()).toBe(globalBus) @@ -69,22 +70,20 @@ describe('Test the capability to load the resources of an app or lib', () => { }).toThrowError() }) - test('# case 2: activate valid-app, it should activate its dependencies and the bootstrap callback should be called', async () => { + test('# case 2: activate valid-app, it should activate its dependencies and the onActivate callback should be called', async () => { console.log = jest.fn() await globalBus.activateApp('valid-app') - expect(window.React.value).toEqual('reactSourceCode') + expect(window.React).toEqual('reactSourceCode') expect(window.lastLoadingApp).toEqual('lib:react') expect(console.log).toBeCalledWith('valid-app is created') - expect(window.appsLoadedFromLocalhost.length).toEqual(0) }) - test('# case 3: activate invalid-app, the middleware should be excuted', async () => { + test('# case 3: activate invalid-app, an error should be throwed', async () => { console.log = jest.fn() try { - await globalBus.activateApp('invalid-app', null, { loadedFromLocalhost: true }) + await globalBus.activateApp('invalid-app') } catch (error) { expect(error.message).toEqual(Errors.appNotCreated('invalid-app')) - expect(window.appsLoadedFromLocalhost[0]).toEqual('invalid-app') expect(window.lastLoadingApp).toEqual('invalid-app') expect(console.log).toBeCalledWith('invalid-app loaded') } @@ -117,8 +116,8 @@ describe('Test the capability to load the resources of an app or lib', () => { expect(bus2).toEqual(bus1) }) - test('# case 7: test errors', async () => { - const bus = new Bus('case7-bus') + test('# case 7: test errors of bus', async () => { + const bus = createBus('case7-bus') bus.createApp('case7') expect(() => { bus.createApp('case7') @@ -142,12 +141,9 @@ describe('Test the capability to load the resources of an app or lib', () => { // @ts-ignore bus.use('') }).toThrowError(Errors.wrongMiddlewareType()) - }) - test("# case 8: bus's name should be unique", () => { - createBus('case8-bus') expect(() => { - createBus('case8-bus') - }).toThrowError(Errors.duplicatedBus('case8-bus')) + createBus('case7-bus') + }).toThrowError(Errors.duplicatedBus('case7-bus')) }) }) diff --git a/packages/core/test/test-apps/css.ts b/packages/core/test/test-apps/css.ts deleted file mode 100644 index 8fee9f6..0000000 --- a/packages/core/test/test-apps/css.ts +++ /dev/null @@ -1,8 +0,0 @@ -const code = ` -/*this is an useless file because there is no way to test style in ut*/ -.app-invalid { - color: red -} -` - -export default code diff --git a/packages/core/test/test-apps/invalid-app.ts b/packages/core/test/test-apps/invalid-app.ts deleted file mode 100644 index 8b19e44..0000000 --- a/packages/core/test/test-apps/invalid-app.ts +++ /dev/null @@ -1,14 +0,0 @@ -const code = `\ -/** - * this is a file which does not use 'bus.createApp' - * to create an app, when it's activated, there should - * be an error to be throwed - **/ - -const doSomething = () => { - console.log('invalid-app loaded'); -}; -doSomething(); -` - -export default code diff --git a/packages/core/test/test-apps/react.ts b/packages/core/test/test-apps/react.ts deleted file mode 100644 index 5238f12..0000000 --- a/packages/core/test/test-apps/react.ts +++ /dev/null @@ -1,11 +0,0 @@ -const code = `\ -/** - * this is a file to simulate the source code of react.js - **/ - -window.React = { - value: 'reactSourceCode' -}; -` - -export default code diff --git a/packages/core/test/test-apps/valid-app.ts b/packages/core/test/test-apps/valid-app.ts deleted file mode 100644 index 466429f..0000000 --- a/packages/core/test/test-apps/valid-app.ts +++ /dev/null @@ -1,14 +0,0 @@ -const code = `\ -(function() { - const bus = window.RALLIE_BUS_STORE.DEFAULT_BUS; - bus.createApp('valid-app') - .relyOn([ - 'lib:react' - ]) - .onBootstrap(async () => { - console.log('valid-app is created'); - }) -})() -` - -export default code diff --git a/packages/core/test/utils.test.ts b/packages/core/test/utils.test.ts index 97127a0..88c87ca 100644 --- a/packages/core/test/utils.test.ts +++ b/packages/core/test/utils.test.ts @@ -8,13 +8,4 @@ describe('Test utils', () => { expect(utils.isPrimitive(1)).toBeTruthy() expect(utils.isPrimitive(false)).toBeTruthy() }) - - test('#case2: test deduplicate', () => { - const arr1 = utils.deduplicate(['a', 'a', { name: 'a' }]) - expect(arr1).toHaveLength(1) - expect(arr1[0]).toEqual('a') - const arr2 = utils.deduplicate([{ name: 'a' }, { name: 'b' }, 'c', 'a'] as any[]) - expect(arr2).toHaveLength(3) - expect(arr2[0].name).toEqual('a') - }) })