diff --git a/src/BlueBase.tsx b/src/BlueBase.tsx index bdc078bc..cdf60942 100644 --- a/src/BlueBase.tsx +++ b/src/BlueBase.tsx @@ -17,9 +17,27 @@ import { } from './registries'; import { MaybeRenderPropChildren } from './utils'; -import React from 'react'; import systemFilters from './filters'; +export interface BlueBaseProgress { + /** + * Has the app booted yet + */ + readonly booted: boolean; + + /** + * Are we loading the app + */ + readonly loading: boolean; + + /** + * Any errors occured while booting the app + */ + readonly error: any; +} + +export type SetStateFn = (state: BlueBaseProgress) => Promise; + export interface BootOptions { /** Collection of assets to add in BlueBase's Asset Registry. */ assets: AssetCollection; @@ -47,6 +65,21 @@ export interface BootOptions { children?: MaybeRenderPropChildren<{ BB: BlueBase }>; } +export interface BootOptionsInternal extends BootOptions { + onProgress?: SetStateFn; + reset?: boolean; +} + +const emptyBootOptions: BootOptionsInternal = { + assets: {}, + components: {}, + configs: {}, + filters: {}, + fonts: {}, + plugins: [], + themes: [], +}; + export class BlueBase { // APIs public Analytics = new Analytics(this); @@ -67,20 +100,57 @@ export class BlueBase { // Flags public booted = false; - private bootOptions: BootOptions = { - assets: {}, - components: {}, - configs: {}, - filters: {}, - fonts: {}, - plugins: [], - themes: [], - }; - - public async boot(options?: Partial & { children?: React.ReactNode }) { + private bootOptions: BootOptions = emptyBootOptions; + + public async boot({ onProgress, ...options }: Partial = {}) { + // Store onProgress for later use, even after boot finishes (i.e. in reboot, etc) + if (onProgress) { + this.onProgress = onProgress; + } + + await this.onProgress({ + booted: false, + error: null, + loading: true, + }); + + try { + await this.bootInternal(options); + await this.onProgress({ + booted: this.booted, + error: null, + loading: false, + }); + } catch (error) { + await this.onProgress({ + booted: false, + error, + loading: false, + }); + } + + return this; + } + + /** + * Performs a reset and boot. + */ + public async reboot(options?: BootOptionsInternal) { + return this.boot({ ...options, reset: true }); + } + + /** + * Main boot business logic + * @param param + */ + protected async bootInternal({ reset, ...options }: Partial = {}) { // Update boot options this.bootOptions = { ...this.bootOptions, ...options }; + if (reset === true) { + await this.Filters.run('bluebase.system.reset', this.bootOptions); + } + // Register basic filters here, so they can be used in boot await this.Filters.registerNestedCollection(systemFilters); @@ -92,4 +162,8 @@ export class BlueBase { return this; } + + private onProgress: SetStateFn = async () => { + return; + } } diff --git a/src/OfflineComponents/BlueBaseApp/BlueBaseApp.tsx b/src/OfflineComponents/BlueBaseApp/BlueBaseApp.tsx index 7dda4249..a80ad3e2 100644 --- a/src/OfflineComponents/BlueBaseApp/BlueBaseApp.tsx +++ b/src/OfflineComponents/BlueBaseApp/BlueBaseApp.tsx @@ -1,4 +1,4 @@ -import { BlueBase, BootOptions } from '../../BlueBase'; +import { BlueBase, BlueBaseProgress, BootOptions } from '../../BlueBase'; import { BlueBaseAppError, BlueBaseAppErrorProps, @@ -80,20 +80,11 @@ export class BlueBaseApp extends React.Component { + await this.setState(params); } componentDidCatch(error: Error | null) { diff --git a/src/OfflineComponents/BlueBaseApp/__tests__/BlueBaseApp.test.tsx b/src/OfflineComponents/BlueBaseApp/__tests__/BlueBaseApp.test.tsx index 8b4f519a..3be5149f 100644 --- a/src/OfflineComponents/BlueBaseApp/__tests__/BlueBaseApp.test.tsx +++ b/src/OfflineComponents/BlueBaseApp/__tests__/BlueBaseApp.test.tsx @@ -1,9 +1,11 @@ +import { waitForElement, waitForState } from 'enzyme-async-helpers'; + import { BlueBase } from '../../../BlueBase'; import { BlueBaseApp } from '../BlueBaseApp'; +import { BlueBaseAppError } from '../../BlueBaseAppError'; import React from 'react'; import { Text } from 'react-native'; import { mount } from 'enzyme'; -import { waitForState } from 'enzyme-async-helpers'; declare const global: any; @@ -73,12 +75,14 @@ describe('BlueBaseApp', () => { test(`should render error state when boot throws an error`, async () => { const BB = new BlueBase(); BB.Configs.setValue('development', true); - BB.boot = () => { + (BB as any).bootInternal = () => { throw Error('Boot Error!'); }; const wrapper = mount(); + await waitForElement(wrapper, BlueBaseAppError); + // expect(wrapper).toMatchSnapshot(); expect( wrapper @@ -149,6 +153,8 @@ describe('BlueBaseApp', () => { await waitForState(wrapper as any, (state: any) => state.loading === false); wrapper.update(); + await waitForElement(wrapper, BlueBaseAppError); + // expect(wrapper).toMatchSnapshot(); expect( wrapper @@ -184,6 +190,8 @@ describe('BlueBaseApp', () => { await waitForState(wrapper as any, (state: any) => state.loading === false); wrapper.update(); + await waitForElement(wrapper, BlueBaseAppError); + // expect(wrapper).toMatchSnapshot(); expect( wrapper @@ -202,12 +210,14 @@ describe('BlueBaseApp', () => { // tslint:disable-next-line: max-line-length test(`should render error state with actual error message when development config is undefined, && isProduction is false`, async () => { const BB = new BlueBase(); - BB.boot = () => { + (BB as any).bootInternal = () => { throw Error('Boot Error!'); }; const wrapper = mount(); + await waitForElement(wrapper, BlueBaseAppError); + // expect(wrapper).toMatchSnapshot(); expect( wrapper @@ -226,7 +236,7 @@ describe('BlueBaseApp', () => { // tslint:disable-next-line: max-line-length test(`should render error state with custom error message when development config is undefined, && isProduction is true`, async () => { const BB = new BlueBase(); - BB.boot = () => { + (BB as any).bootInternal = () => { throw Error('Boot Error!'); }; @@ -234,6 +244,8 @@ describe('BlueBaseApp', () => { const wrapper = mount(); + await waitForElement(wrapper, BlueBaseAppError); + // expect(wrapper).toMatchSnapshot(); expect( wrapper diff --git a/src/filters/boot.ts b/src/filters/boot.ts index a735063a..29b93fe1 100644 --- a/src/filters/boot.ts +++ b/src/filters/boot.ts @@ -6,7 +6,7 @@ export const boot: FilterNestedCollection = { 'bluebase.boot': [ { key: 'bluebase-boot-default', - priority: 5, + priority: 3, // tslint:disable-next-line:object-literal-sort-keys value: async (bootOptions: BootOptions, _ctx: {}, BB: BlueBase) => { @@ -48,4 +48,24 @@ export const boot: FilterNestedCollection = { }, }, ], + + 'bluebase.system.reset': [ + { + key: 'bluebase-reset-default', + priority: 3, + + // tslint:disable-next-line:object-literal-sort-keys + value: async (bootOptions: BootOptions, _ctx: {}, BB: BlueBase) => { + BB.Assets.clear(); + BB.Components.clear(); + BB.Configs.clear(); + BB.Filters.clear(); + BB.Fonts.clear(); + BB.Plugins.clear(); + BB.Themes.clear(); + + return bootOptions; + }, + }, + ], };