From 942ab19110d898d4c4732c87f98c6dd0f32ce88b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 12 Aug 2021 06:54:35 +0200 Subject: [PATCH 01/44] manual release of @agile-ts/react and @agile-ts/core --- packages/core/package.json | 2 +- packages/react/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index a0b5c23e..5e8cb90b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@agile-ts/core", - "version": "0.2.0-alpha.3", + "version": "0.2.0-alpha.4", "author": "BennoDev", "license": "MIT", "homepage": "https://agile-ts.org/", diff --git a/packages/react/package.json b/packages/react/package.json index 20eb51d1..2e03a774 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@agile-ts/react", - "version": "0.1.2", + "version": "0.2.0-alpha.1", "author": "BennoDev", "license": "MIT", "homepage": "https://agile-ts.org/", From 4c4d1b5453ea9fd1698fb606ca1e16d426a69d88 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 13 Aug 2021 06:52:17 +0200 Subject: [PATCH 02/44] started to outsource persist logic for better treeshaking --- packages/core/src/agile.ts | 46 ------------------------ packages/core/src/state/state.ts | 2 -- packages/core/src/storages/index.ts | 23 +++++++++++- packages/core/src/storages/persistent.ts | 19 ++++++++-- packages/core/src/storages/storages.ts | 10 +++--- 5 files changed, 44 insertions(+), 56 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 23a02432..797107df 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -1,12 +1,9 @@ import { Runtime, Integration, - Storage, Integrations, SubController, globalBind, - Storages, - RegisterConfigInterface, LogCodeManager, IntegrationsConfigInterface, defineConfig, @@ -22,8 +19,6 @@ export class Agile { public runtime: Runtime; // Manages and simplifies the subscription to UI-Components public subController: SubController; - // Handles the permanent persistence of Agile Classes - public storages: Storages; // Integrations (UI-Frameworks) that are integrated into the Agile Instance public integrations: Integrations; @@ -75,9 +70,6 @@ export class Agile { }); this.runtime = new Runtime(this); this.subController = new SubController(this); - this.storages = new Storages(this, { - localStorage: config.localStorage, - }); LogCodeManager.log('10:00:00', [], this); @@ -107,27 +99,6 @@ export class Agile { return this; } - /** - * Registers the specified Storage with AgileTs. - * - * After a successful registration, - * [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) such as States - * can be persisted in the external Storage. - * - * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#registerstorage) - * - * @public - * @param storage - Storage to be registered. - * @param config - Configuration object - */ - public registerStorage( - storage: Storage, - config: RegisterConfigInterface = {} - ): this { - this.storages.register(storage, config); - return this; - } - /** * Returns a boolean indicating whether any Integration * has been registered with AgileTs or not. @@ -139,18 +110,6 @@ export class Agile { public hasIntegration(): boolean { return this.integrations.hasIntegration(); } - - /** - * Returns a boolean indicating whether any Storage - * has been registered with AgileTs or not. - * - * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#hasstorage) - * - * @public - */ - public hasStorage(): boolean { - return this.storages.hasStorage(); - } } export type AgileKey = string | number; @@ -163,11 +122,6 @@ export interface CreateAgileConfigInterface * @default true */ waitForMount?: boolean; - /** - * Whether the Local Storage should be registered as a Agile Storage by default. - * @default false - */ - localStorage?: boolean; /** * Whether the Agile Instance should be globally bound (globalThis) * and thus be globally available. diff --git a/packages/core/src/state/state.ts b/packages/core/src/state/state.ts index 592a4e36..d9d97164 100644 --- a/packages/core/src/state/state.ts +++ b/packages/core/src/state/state.ts @@ -54,8 +54,6 @@ export class State { // Whether the State is persisted in an external Storage public isPersisted = false; - // Manages the permanent persistent in external Storages - public persistent: StatePersistent | undefined; // When an interval is active, the 'intervalId' to clear the interval is temporary stored here public currentInterval?: NodeJS.Timer | number; diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index a8938be1..0d105fc8 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -1,4 +1,5 @@ -import { CreateStorageConfigInterface, Storage } from '../internal'; +import { CreateStorageConfigInterface, generateId, Storage } from '../internal'; +import type { Storages, Persistent } from '../internal'; export * from './storages'; // export * from './storage'; @@ -22,3 +23,23 @@ export * from './storages'; export function createStorage(config: CreateStorageConfigInterface): Storage { return new Storage(config); } + +// Handles the permanent persistence of Agile Classes +export const storageManagers: { + [key: string]: Storages; +} = {}; +export let defaultStorageManagerKey: string | null = null; + +export const registerStorageManager = ( + instance: Storages, + key: string = generateId() +) => { + if (Object.prototype.hasOwnProperty.call(storageManagers, key)) { + // TODO + return; + } + + storageManagers[key] = instance; + + if (defaultStorageManagerKey == null) defaultStorageManagerKey = key; +}; diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index e4134fca..ceb8aa04 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -1,9 +1,11 @@ import { Agile, copy, + defaultStorageManagerKey, defineConfig, LogCodeManager, StorageKey, + storageManagers, } from '../internal'; export class Persistent { @@ -27,6 +29,8 @@ export class Persistent { // Key/Name identifier of the Storages the Persistent value is stored in public storageKeys: StorageKey[] = []; + public storageManagerKey: string; + /** * A Persistent manages the permanent persistence * of an Agile Class such as the `State Class` in external Storages. @@ -48,8 +52,9 @@ export class Persistent { instantiate: true, storageKeys: [], defaultStorageKey: null as any, + storageManagerKey: defaultStorageManagerKey, }); - this.agileInstance().storages.persistentInstances.add(this); + this.storageManagerKey = config.storageManagerKey as any; this.config = { defaultStorageKey: config.defaultStorageKey as any }; // Instantiate Persistent @@ -127,6 +132,9 @@ export class Persistent { this._key = this.formatKey(config.key) ?? Persistent.placeHolderKey; this.assignStorageKeys(config.storageKeys, config.defaultStorageKey); this.validatePersistent(); + storageManagers[this.storageManagerKey].persistentInstances[ + this._key + ] = this; } /** @@ -140,6 +148,7 @@ export class Persistent { */ public validatePersistent(): boolean { let isValid = true; + const storages = storageManagers[this.storageManagerKey]; // Validate Persistent key/name identifier if (this._key === Persistent.placeHolderKey) { @@ -155,7 +164,7 @@ export class Persistent { // Check if the Storages exist at the specified Storage keys this.storageKeys.map((key) => { - if (!this.agileInstance().storages.storages[key]) { + if (!storages.storages[key]) { LogCodeManager.log('12:03:02', [this._key, key]); isValid = false; } @@ -180,7 +189,7 @@ export class Persistent { storageKeys: StorageKey[] = [], defaultStorageKey?: StorageKey ): void { - const storages = this.agileInstance().storages; + const storages = storageManagers[this.storageManagerKey]; const _storageKeys = copy(storageKeys); // Assign specified default Storage key to the 'storageKeys' array @@ -318,6 +327,10 @@ export interface CreatePersistentConfigInterface { * @default true */ instantiate?: boolean; + /** + * TODO + */ + storageManagerKey?: string; } export interface PersistentConfigInterface { diff --git a/packages/core/src/storages/storages.ts b/packages/core/src/storages/storages.ts index 498f9bb5..dd0f9ea1 100644 --- a/packages/core/src/storages/storages.ts +++ b/packages/core/src/storages/storages.ts @@ -18,7 +18,7 @@ export class Storages { // Registered Storages public storages: { [key: string]: Storage } = {}; // Persistent from Instances (for example States) that were persisted - public persistentInstances: Set = new Set(); + public persistentInstances: { [key: string]: Persistent } = {}; /** * The Storages Class manages all external Storages for an Agile Instance @@ -97,12 +97,14 @@ export class Storages { this.storages[storage.key] = storage; if (config.default) this.config.defaultStorageKey = storage.key; - this.persistentInstances.forEach((persistent) => { + for (const persistentKey in this.persistentInstances) { + const persistent = this.persistentInstances[persistentKey]; + // Revalidate Persistent, which contains key/name identifier of the newly registered Storage if (persistent.storageKeys.includes(storage.key)) { const isValid = persistent.validatePersistent(); if (isValid) persistent.initialLoading(); - return; + continue; } // If Persistent has no default Storage key, @@ -113,7 +115,7 @@ export class Storages { const isValid = persistent.validatePersistent(); if (isValid) persistent.initialLoading(); } - }); + } LogCodeManager.log('13:00:00', [storage.key], storage); From 0cd44aae2619fa04db4bd7c34d996ec97c67d156 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 14 Aug 2021 14:28:38 +0200 Subject: [PATCH 03/44] fixed typo --- packages/core/src/state/state.ts | 30 ++++++++++++++++++++--------- packages/core/src/storages/index.ts | 2 +- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/core/src/state/state.ts b/packages/core/src/state/state.ts index d9d97164..ccc50bb4 100644 --- a/packages/core/src/state/state.ts +++ b/packages/core/src/state/state.ts @@ -17,6 +17,8 @@ import { removeProperties, LogCodeManager, defineConfig, + storageManagers, + defaultStorageManagerKey, } from '../internal'; export class State { @@ -432,15 +434,25 @@ export class State { }); // Check if State is already persisted - if (this.persistent != null && this.isPersisted) return this; - - // Create Persistent (-> persist value) - this.persistent = new StatePersistent(this, { - instantiate: _config.loadValue, - storageKeys: _config.storageKeys, - key: key, - defaultStorageKey: _config.defaultStorageKey, - }); + if (this.isPersisted) return this; + + const storageManager = storageManagers[defaultStorageManagerKey || '']; + + // Check if a Storage Manager exists + if (storageManager != null && key != null) { + // TODO tree shake persistent when Storage not registered + storageManager.persistentInstances[key] = new StatePersistent( + this, + { + instantiate: _config.loadValue, + storageKeys: _config.storageKeys, + key: key, + defaultStorageKey: _config.defaultStorageKey, + } + ); + } else { + // TODO add error + } return this; } diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 0d105fc8..0e7c44ce 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -1,5 +1,5 @@ import { CreateStorageConfigInterface, generateId, Storage } from '../internal'; -import type { Storages, Persistent } from '../internal'; +import type { Storages } from '../internal'; export * from './storages'; // export * from './storage'; From 88df4659038f70c131794c20ce64b75231306c16 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 15 Aug 2021 18:44:16 +0200 Subject: [PATCH 04/44] added persistableState --- packages/core/src/collection/group/index.ts | 4 +- packages/core/src/collection/item.ts | 8 +- packages/core/src/collection/selector.ts | 4 +- packages/core/src/internal.ts | 1 + packages/core/src/state/persistableState.ts | 123 ++++++++++++++++++++ packages/core/src/state/state.ts | 116 ------------------ 6 files changed, 132 insertions(+), 124 deletions(-) create mode 100644 packages/core/src/state/persistableState.ts diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index 44aef2c5..7c9a3df7 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -1,5 +1,5 @@ import { - State, + PersistableState, Collection, DefaultItem, ItemKey, @@ -23,7 +23,7 @@ import { export class Group< DataType extends Object = DefaultItem, ValueType = Array // To extract the Group Type Value in Integration methods like 'useAgile()' -> extends State> { +> extends PersistableState> { // Collection the Group belongs to collection: () => Collection; diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 1be4cebf..6a170896 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -1,5 +1,5 @@ import { - State, + PersistableState, Collection, StateKey, StateRuntimeJobConfigInterface, @@ -12,9 +12,9 @@ import { defineConfig, } from '../internal'; -export class Item extends State< - DataType -> { +export class Item< + DataType extends Object = DefaultItem +> extends PersistableState { // Collection the Group belongs to public collection: () => Collection; diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 937c5458..279c316e 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -4,13 +4,13 @@ import { defineConfig, Item, ItemKey, - State, + PersistableState, StateRuntimeJobConfigInterface, } from '../internal'; export class Selector< DataType extends Object = DefaultItem -> extends State { +> extends PersistableState { // Collection the Selector belongs to public collection: () => Collection; diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index a1c8852c..4ddcde87 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -38,6 +38,7 @@ export * from './state'; export * from './state/state.observer'; export * from './state/state.persistent'; export * from './state/state.runtime.job'; +export * from './state/persistableState'; // Computed export * from './computed'; diff --git a/packages/core/src/state/persistableState.ts b/packages/core/src/state/persistableState.ts new file mode 100644 index 00000000..8c83f8f7 --- /dev/null +++ b/packages/core/src/state/persistableState.ts @@ -0,0 +1,123 @@ +import { + defineConfig, + isFunction, + isValidObject, + LogCodeManager, + PersistentKey, + State, + StateKey, + StatePersistent, + StatePersistentConfigInterface, +} from '../internal'; + +export class PersistableState extends State { + // Whether the State is persisted in an external Storage + public isPersisted = false; + // Manages the permanent persistent in external Storages + public persistent: StatePersistent | undefined; + + public setKey(value: StateKey | undefined): this { + const oldKey = this._key; + + super.setKey(value); + + // Update key in Persistent (only if oldKey is equal to persistentKey + // because otherwise the persistentKey is detached from the State key + // -> not managed by State anymore) + if (value != null && this.persistent?._key === oldKey) + this.persistent?.setKey(value); + + return this; + } + + /** + * Preserves the State `value` in the corresponding external Storage. + * + * The State key/name is used as the unique identifier for the Persistent. + * If that is not desired or the State has no unique identifier, + * please specify a separate unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * + * @public + * @param config - Configuration object + */ + public persist(config?: StatePersistentConfigInterface): this; + /** + * Preserves the State `value` in the corresponding external Storage. + * + * The specified key is used as the unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * + * @public + * @param key - Key/Name identifier of Persistent. + * @param config - Configuration object + */ + public persist( + key?: PersistentKey, + config?: StatePersistentConfigInterface + ): this; + public persist( + keyOrConfig: PersistentKey | StatePersistentConfigInterface = {}, + config: StatePersistentConfigInterface = {} + ): this { + let _config: StatePersistentConfigInterface; + let key: PersistentKey | undefined; + + if (isValidObject(keyOrConfig)) { + _config = keyOrConfig as StatePersistentConfigInterface; + key = this._key; + } else { + _config = config || {}; + key = keyOrConfig as PersistentKey; + } + + _config = defineConfig(_config, { + loadValue: true, + storageKeys: [], + defaultStorageKey: null as any, + }); + + // Check if State is already persisted + if (this.persistent != null && this.isPersisted) return this; + + // Create Persistent (-> persist value) + this.persistent = new StatePersistent(this, { + instantiate: _config.loadValue, + storageKeys: _config.storageKeys, + key: key, + defaultStorageKey: _config.defaultStorageKey, + }); + + return this; + } + + /** + * Fires immediately after the persisted `value` + * is loaded into the State from a corresponding external Storage. + * + * Registering such callback function makes only sense + * when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#onload) + * + * @public + * @param callback - A function to be executed after the externally persisted `value` was loaded into the State. + */ + public onLoad(callback: (success: boolean) => void): this { + if (!this.persistent) return this; + if (!isFunction(callback)) { + LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); + return this; + } + + // Register specified callback + this.persistent.onLoad = callback; + + // If State is already persisted ('isPersisted') fire specified callback immediately + if (this.isPersisted) callback(true); + + return this; + } +} diff --git a/packages/core/src/state/state.ts b/packages/core/src/state/state.ts index ccc50bb4..bca39f1e 100644 --- a/packages/core/src/state/state.ts +++ b/packages/core/src/state/state.ts @@ -5,20 +5,16 @@ import { flatMerge, isValidObject, StateObserver, - StatePersistent, Observer, equal, isFunction, notEqual, generateId, - PersistentKey, ComputedTracker, StateIngestConfigInterface, removeProperties, LogCodeManager, defineConfig, - storageManagers, - defaultStorageManagerKey, } from '../internal'; export class State { @@ -54,9 +50,6 @@ export class State { // Method for dynamically computing the existence of the State public computeExistsMethod: ComputeExistsMethod; - // Whether the State is persisted in an external Storage - public isPersisted = false; - // When an interval is active, the 'intervalId' to clear the interval is temporary stored here public currentInterval?: NodeJS.Timer | number; @@ -159,8 +152,6 @@ export class State { * @param value - New key/name identifier. */ public setKey(value: StateKey | undefined): this { - const oldKey = this._key; - // Update State key this._key = value; @@ -168,12 +159,6 @@ export class State { for (const observerKey in this.observers) this.observers[observerKey]._key = value; - // Update key in Persistent (only if oldKey is equal to persistentKey - // because otherwise the persistentKey is detached from the State key - // -> not managed by State anymore) - if (value != null && this.persistent?._key === oldKey) - this.persistent?.setKey(value); - return this; } @@ -384,107 +369,6 @@ export class State { return this; } - /** - * Preserves the State `value` in the corresponding external Storage. - * - * The State key/name is used as the unique identifier for the Persistent. - * If that is not desired or the State has no unique identifier, - * please specify a separate unique identifier for the Persistent. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) - * - * @public - * @param config - Configuration object - */ - public persist(config?: StatePersistentConfigInterface): this; - /** - * Preserves the State `value` in the corresponding external Storage. - * - * The specified key is used as the unique identifier for the Persistent. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) - * - * @public - * @param key - Key/Name identifier of Persistent. - * @param config - Configuration object - */ - public persist( - key?: PersistentKey, - config?: StatePersistentConfigInterface - ): this; - public persist( - keyOrConfig: PersistentKey | StatePersistentConfigInterface = {}, - config: StatePersistentConfigInterface = {} - ): this { - let _config: StatePersistentConfigInterface; - let key: PersistentKey | undefined; - - if (isValidObject(keyOrConfig)) { - _config = keyOrConfig as StatePersistentConfigInterface; - key = this._key; - } else { - _config = config || {}; - key = keyOrConfig as PersistentKey; - } - - _config = defineConfig(_config, { - loadValue: true, - storageKeys: [], - defaultStorageKey: null as any, - }); - - // Check if State is already persisted - if (this.isPersisted) return this; - - const storageManager = storageManagers[defaultStorageManagerKey || '']; - - // Check if a Storage Manager exists - if (storageManager != null && key != null) { - // TODO tree shake persistent when Storage not registered - storageManager.persistentInstances[key] = new StatePersistent( - this, - { - instantiate: _config.loadValue, - storageKeys: _config.storageKeys, - key: key, - defaultStorageKey: _config.defaultStorageKey, - } - ); - } else { - // TODO add error - } - - return this; - } - - /** - * Fires immediately after the persisted `value` - * is loaded into the State from a corresponding external Storage. - * - * Registering such callback function makes only sense - * when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#onload) - * - * @public - * @param callback - A function to be executed after the externally persisted `value` was loaded into the State. - */ - public onLoad(callback: (success: boolean) => void): this { - if (!this.persistent) return this; - if (!isFunction(callback)) { - LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); - return this; - } - - // Register specified callback - this.persistent.onLoad = callback; - - // If State is already persisted ('isPersisted') fire specified callback immediately - if (this.isPersisted) callback(true); - - return this; - } - /** * Repeatedly calls the specified callback function, * with a fixed time delay between each call. From c4ac13baa44564f6b7cbf6633b49d2d6c37741ff Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 15 Aug 2021 18:53:23 +0200 Subject: [PATCH 05/44] fixed typos --- .../src/collection/collection.persistent.ts | 7 +++--- packages/core/src/shared.ts | 3 +-- packages/core/src/state/state.persistent.ts | 15 +++++++------ packages/core/src/storages/index.ts | 20 +++++------------ packages/core/src/storages/persistent.ts | 22 ++++--------------- 5 files changed, 22 insertions(+), 45 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 2f2b7136..c789fab4 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -11,6 +11,7 @@ import { Persistent, PersistentKey, StorageKey, + storageManager, } from '../internal'; export class CollectionPersistent< @@ -84,7 +85,7 @@ export class CollectionPersistent< // Check if Collection is already persisted // (indicated by the persistence of 'true' at '_storageItemKey') - const isPersisted = await this.agileInstance().storages.get( + const isPersisted = await storageManager?.get( _storageItemKey, this.config.defaultStorageKey as any ); @@ -207,7 +208,7 @@ export class CollectionPersistent< ); // Set flag in Storage to indicate that the Collection is persisted - this.agileInstance().storages.set(_storageItemKey, true, this.storageKeys); + storageManager?.set(_storageItemKey, true, this.storageKeys); // Persist default Group defaultGroup.persist(defaultGroupStorageKey, { @@ -282,7 +283,7 @@ export class CollectionPersistent< ); // Remove Collection is persisted indicator flag from Storage - this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); + storageManager?.remove(_storageItemKey, this.storageKeys); // Remove default Group from the Storage defaultGroup.persistent?.removePersistedValue(defaultGroupStorageKey); diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index 604d1c89..a3ec1f87 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -1,11 +1,10 @@ -import { Agile, runsOnServer } from './internal'; +import { Agile } from './internal'; /** * Shared Agile Instance that is used when no Agile Instance was specified. */ let sharedAgileInstance = new Agile({ key: 'shared', - localStorage: !runsOnServer(), }); export { sharedAgileInstance as shared }; diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index d978196c..31322839 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -1,14 +1,15 @@ import { CreatePersistentConfigInterface, defineConfig, + PersistableState, Persistent, PersistentKey, - State, + storageManager, } from '../internal'; export class StatePersistent extends Persistent { // State the Persistent belongs to - public state: () => State; + public state: () => PersistableState; static storeValueSideEffectKey = 'rebuildStateStorageValue'; @@ -20,7 +21,7 @@ export class StatePersistent extends Persistent { * @param config - Configuration object */ constructor( - state: State, + state: PersistableState, config: CreatePersistentConfigInterface = {} ) { super(state.agileInstance(), { @@ -72,7 +73,7 @@ export class StatePersistent extends Persistent { const _storageItemKey = storageItemKey ?? this._key; // Load State value from the default Storage - const loadedValue = await this.agileInstance().storages.get( + const loadedValue = await storageManager?.get( _storageItemKey, this.config.defaultStorageKey as any ); @@ -150,7 +151,7 @@ export class StatePersistent extends Persistent { if (!this.ready) return false; const _storageItemKey = storageItemKey || this._key; this.state().removeSideEffect(StatePersistent.storeValueSideEffectKey); - this.agileInstance().storages.remove(_storageItemKey, this.storageKeys); + storageManager?.remove(_storageItemKey, this.storageKeys); this.isPersisted = false; return true; } @@ -184,12 +185,12 @@ export class StatePersistent extends Persistent { * @param config - Configuration object */ public rebuildStorageSideEffect( - state: State, + state: PersistableState, storageItemKey: PersistentKey, config: { [key: string]: any } = {} ) { if (config['storage'] == null || config.storage) { - this.agileInstance().storages.set( + storageManager?.set( storageItemKey, this.state().getPersistableValue(), this.storageKeys diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 0e7c44ce..a0ef25f2 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -25,21 +25,11 @@ export function createStorage(config: CreateStorageConfigInterface): Storage { } // Handles the permanent persistence of Agile Classes -export const storageManagers: { - [key: string]: Storages; -} = {}; -export let defaultStorageManagerKey: string | null = null; +export let storageManager: Storages | null = null; -export const registerStorageManager = ( - instance: Storages, - key: string = generateId() -) => { - if (Object.prototype.hasOwnProperty.call(storageManagers, key)) { - // TODO - return; +export const registerStorageManager = (instance: Storages) => { + if (storageManager != null) { + // TODO print warning } - - storageManagers[key] = instance; - - if (defaultStorageManagerKey == null) defaultStorageManagerKey = key; + storageManager = instance; }; diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index ceb8aa04..ace74de9 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -1,11 +1,10 @@ import { Agile, copy, - defaultStorageManagerKey, defineConfig, LogCodeManager, StorageKey, - storageManagers, + storageManager, } from '../internal'; export class Persistent { @@ -29,8 +28,6 @@ export class Persistent { // Key/Name identifier of the Storages the Persistent value is stored in public storageKeys: StorageKey[] = []; - public storageManagerKey: string; - /** * A Persistent manages the permanent persistence * of an Agile Class such as the `State Class` in external Storages. @@ -52,9 +49,7 @@ export class Persistent { instantiate: true, storageKeys: [], defaultStorageKey: null as any, - storageManagerKey: defaultStorageManagerKey, }); - this.storageManagerKey = config.storageManagerKey as any; this.config = { defaultStorageKey: config.defaultStorageKey as any }; // Instantiate Persistent @@ -132,9 +127,6 @@ export class Persistent { this._key = this.formatKey(config.key) ?? Persistent.placeHolderKey; this.assignStorageKeys(config.storageKeys, config.defaultStorageKey); this.validatePersistent(); - storageManagers[this.storageManagerKey].persistentInstances[ - this._key - ] = this; } /** @@ -148,7 +140,6 @@ export class Persistent { */ public validatePersistent(): boolean { let isValid = true; - const storages = storageManagers[this.storageManagerKey]; // Validate Persistent key/name identifier if (this._key === Persistent.placeHolderKey) { @@ -164,7 +155,7 @@ export class Persistent { // Check if the Storages exist at the specified Storage keys this.storageKeys.map((key) => { - if (!storages.storages[key]) { + if (!storageManager?.storages[key]) { LogCodeManager.log('12:03:02', [this._key, key]); isValid = false; } @@ -189,7 +180,6 @@ export class Persistent { storageKeys: StorageKey[] = [], defaultStorageKey?: StorageKey ): void { - const storages = storageManagers[this.storageManagerKey]; const _storageKeys = copy(storageKeys); // Assign specified default Storage key to the 'storageKeys' array @@ -200,10 +190,10 @@ export class Persistent { // and specify it as the Persistent's default Storage key // if no valid Storage key was provided if (_storageKeys.length <= 0) { - const defaultStorageKey = storages.config.defaultStorageKey; + const defaultStorageKey = storageManager?.config.defaultStorageKey; if (defaultStorageKey != null) { this.config.defaultStorageKey = defaultStorageKey; - _storageKeys.push(storages.config.defaultStorageKey as any); + _storageKeys.push(storageManager?.config.defaultStorageKey as any); } } else { this.config.defaultStorageKey = defaultStorageKey ?? _storageKeys[0]; @@ -327,10 +317,6 @@ export interface CreatePersistentConfigInterface { * @default true */ instantiate?: boolean; - /** - * TODO - */ - storageManagerKey?: string; } export interface PersistentConfigInterface { From 7f335e9bbbfb8e5b9712d5d37f813c077d57ebad Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 15 Aug 2021 19:42:34 +0200 Subject: [PATCH 06/44] fixed typos --- examples/react/release/boxes/package.json | 2 +- packages/core/src/logCodeManager.ts | 13 +++++++------ packages/core/src/state/index.ts | 15 +++++++++++++++ packages/core/src/state/persistableState.ts | 9 +++++++++ packages/core/src/state/state.ts | 9 --------- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/examples/react/release/boxes/package.json b/examples/react/release/boxes/package.json index 479c96d1..ebb40840 100644 --- a/examples/react/release/boxes/package.json +++ b/examples/react/release/boxes/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@agile-ts/core": "^0.1.3", + "@agile-ts/core": "file:.yalc/@agile-ts/core", "@agile-ts/logger": "^0.0.7", "@agile-ts/proxytree": "^0.0.5", "@agile-ts/react": "^0.1.2", diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 819563fa..bb5c9ac6 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -270,6 +270,13 @@ let tempLogCodeManager: { logIfTags: typeof logIfTags; }; if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { + let loggerPackage: any = null; + try { + loggerPackage = require('@agile-ts/logger'); + } catch (e) { + // empty catch block + } + tempLogCodeManager = { getLog, log, @@ -278,12 +285,6 @@ if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { // Not doing 'logger: loggerPackage?.sharedAgileLogger' // because only by calling a function (now 'getLogger()') the 'sharedLogger' is refetched getLogger: () => { - let loggerPackage: any = null; - try { - loggerPackage = require('@agile-ts/logger'); - } catch (e) { - // empty catch block - } return loggerPackage?.sharedAgileLogger ?? null; }, logIfTags, diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index ba8e7e27..37192cd8 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -5,6 +5,7 @@ import { removeProperties, CreateAgileSubInstanceInterface, shared, + PersistableState, } from '../internal'; export * from './state'; @@ -44,3 +45,17 @@ export function createState( removeProperties(config, ['agileInstance']) ); } + +export function createPersistableState( + initialValue: ValueType, + config: CreateStateConfigInterfaceWithAgile = {} +): PersistableState { + config = defineConfig(config, { + agileInstance: shared, + }); + return new PersistableState( + config.agileInstance as any, + initialValue, + removeProperties(config, ['agileInstance']) + ); +} diff --git a/packages/core/src/state/persistableState.ts b/packages/core/src/state/persistableState.ts index 8c83f8f7..b9eb51c3 100644 --- a/packages/core/src/state/persistableState.ts +++ b/packages/core/src/state/persistableState.ts @@ -120,4 +120,13 @@ export class PersistableState extends State { return this; } + + /** + * Returns the persistable value of the State. + * + * @internal + */ + public getPersistableValue(): any { + return this._value; + } } diff --git a/packages/core/src/state/state.ts b/packages/core/src/state/state.ts index bca39f1e..2c23bb5c 100644 --- a/packages/core/src/state/state.ts +++ b/packages/core/src/state/state.ts @@ -590,15 +590,6 @@ export class State { public hasSideEffect(key: string): boolean { return !!this.sideEffects[key]; } - - /** - * Returns the persistable value of the State. - * - * @internal - */ - public getPersistableValue(): any { - return this._value; - } } export type StateKey = string | number; From e9c67caaab05d0223eebb520ef17bf548c6df3e9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 16 Aug 2021 06:19:16 +0200 Subject: [PATCH 07/44] added enhanced state with all the enhanced features like undo, .. --- packages/core/src/collection/group/index.ts | 4 +- packages/core/src/collection/item.ts | 8 +- packages/core/src/collection/selector.ts | 4 +- packages/core/src/internal.ts | 2 +- packages/core/src/state/index.ts | 25 +- packages/core/src/state/persistableState.ts | 132 ----- packages/core/src/state/state.enhanced.ts | 526 ++++++++++++++++++++ packages/core/src/state/state.persistent.ts | 8 +- packages/core/src/state/state.ts | 377 -------------- 9 files changed, 559 insertions(+), 527 deletions(-) delete mode 100644 packages/core/src/state/persistableState.ts create mode 100644 packages/core/src/state/state.enhanced.ts diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index 7c9a3df7..dc339159 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -1,5 +1,5 @@ import { - PersistableState, + EnhancedState, Collection, DefaultItem, ItemKey, @@ -23,7 +23,7 @@ import { export class Group< DataType extends Object = DefaultItem, ValueType = Array // To extract the Group Type Value in Integration methods like 'useAgile()' -> extends PersistableState> { +> extends EnhancedState> { // Collection the Group belongs to collection: () => Collection; diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 6a170896..db98104f 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -1,5 +1,5 @@ import { - PersistableState, + EnhancedState, Collection, StateKey, StateRuntimeJobConfigInterface, @@ -12,9 +12,9 @@ import { defineConfig, } from '../internal'; -export class Item< - DataType extends Object = DefaultItem -> extends PersistableState { +export class Item extends EnhancedState< + DataType +> { // Collection the Group belongs to public collection: () => Collection; diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 279c316e..76795f35 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -4,13 +4,13 @@ import { defineConfig, Item, ItemKey, - PersistableState, + EnhancedState, StateRuntimeJobConfigInterface, } from '../internal'; export class Selector< DataType extends Object = DefaultItem -> extends PersistableState { +> extends EnhancedState { // Collection the Selector belongs to public collection: () => Collection; diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 4ddcde87..26581a63 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -38,7 +38,7 @@ export * from './state'; export * from './state/state.observer'; export * from './state/state.persistent'; export * from './state/state.runtime.job'; -export * from './state/persistableState'; +export * from './state/state.enhanced'; // Computed export * from './computed'; diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 37192cd8..b99ec509 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -5,7 +5,7 @@ import { removeProperties, CreateAgileSubInstanceInterface, shared, - PersistableState, + EnhancedState, } from '../internal'; export * from './state'; @@ -32,7 +32,7 @@ export interface CreateStateConfigInterfaceWithAgile * @param initialValue - Initial value of the State. * @param config - Configuration object */ -export function createState( +export function createLightState( initialValue: ValueType, config: CreateStateConfigInterfaceWithAgile = {} ): State { @@ -46,14 +46,29 @@ export function createState( ); } -export function createPersistableState( +/** + * Returns a newly created State. + * + * A State manages a piece of Information + * that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this piece of Information. + * + * You can create as many global States as you need. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) + * + * @public + * @param initialValue - Initial value of the State. + * @param config - Configuration object + */ +export function createState( initialValue: ValueType, config: CreateStateConfigInterfaceWithAgile = {} -): PersistableState { +): EnhancedState { config = defineConfig(config, { agileInstance: shared, }); - return new PersistableState( + return new EnhancedState( config.agileInstance as any, initialValue, removeProperties(config, ['agileInstance']) diff --git a/packages/core/src/state/persistableState.ts b/packages/core/src/state/persistableState.ts deleted file mode 100644 index b9eb51c3..00000000 --- a/packages/core/src/state/persistableState.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { - defineConfig, - isFunction, - isValidObject, - LogCodeManager, - PersistentKey, - State, - StateKey, - StatePersistent, - StatePersistentConfigInterface, -} from '../internal'; - -export class PersistableState extends State { - // Whether the State is persisted in an external Storage - public isPersisted = false; - // Manages the permanent persistent in external Storages - public persistent: StatePersistent | undefined; - - public setKey(value: StateKey | undefined): this { - const oldKey = this._key; - - super.setKey(value); - - // Update key in Persistent (only if oldKey is equal to persistentKey - // because otherwise the persistentKey is detached from the State key - // -> not managed by State anymore) - if (value != null && this.persistent?._key === oldKey) - this.persistent?.setKey(value); - - return this; - } - - /** - * Preserves the State `value` in the corresponding external Storage. - * - * The State key/name is used as the unique identifier for the Persistent. - * If that is not desired or the State has no unique identifier, - * please specify a separate unique identifier for the Persistent. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) - * - * @public - * @param config - Configuration object - */ - public persist(config?: StatePersistentConfigInterface): this; - /** - * Preserves the State `value` in the corresponding external Storage. - * - * The specified key is used as the unique identifier for the Persistent. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) - * - * @public - * @param key - Key/Name identifier of Persistent. - * @param config - Configuration object - */ - public persist( - key?: PersistentKey, - config?: StatePersistentConfigInterface - ): this; - public persist( - keyOrConfig: PersistentKey | StatePersistentConfigInterface = {}, - config: StatePersistentConfigInterface = {} - ): this { - let _config: StatePersistentConfigInterface; - let key: PersistentKey | undefined; - - if (isValidObject(keyOrConfig)) { - _config = keyOrConfig as StatePersistentConfigInterface; - key = this._key; - } else { - _config = config || {}; - key = keyOrConfig as PersistentKey; - } - - _config = defineConfig(_config, { - loadValue: true, - storageKeys: [], - defaultStorageKey: null as any, - }); - - // Check if State is already persisted - if (this.persistent != null && this.isPersisted) return this; - - // Create Persistent (-> persist value) - this.persistent = new StatePersistent(this, { - instantiate: _config.loadValue, - storageKeys: _config.storageKeys, - key: key, - defaultStorageKey: _config.defaultStorageKey, - }); - - return this; - } - - /** - * Fires immediately after the persisted `value` - * is loaded into the State from a corresponding external Storage. - * - * Registering such callback function makes only sense - * when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#onload) - * - * @public - * @param callback - A function to be executed after the externally persisted `value` was loaded into the State. - */ - public onLoad(callback: (success: boolean) => void): this { - if (!this.persistent) return this; - if (!isFunction(callback)) { - LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); - return this; - } - - // Register specified callback - this.persistent.onLoad = callback; - - // If State is already persisted ('isPersisted') fire specified callback immediately - if (this.isPersisted) callback(true); - - return this; - } - - /** - * Returns the persistable value of the State. - * - * @internal - */ - public getPersistableValue(): any { - return this._value; - } -} diff --git a/packages/core/src/state/state.enhanced.ts b/packages/core/src/state/state.enhanced.ts new file mode 100644 index 00000000..5dcde925 --- /dev/null +++ b/packages/core/src/state/state.enhanced.ts @@ -0,0 +1,526 @@ +import { + Agile, + defineConfig, + equal, + flatMerge, + generateId, + isFunction, + isValidObject, + LogCodeManager, + notEqual, + PersistentKey, + removeProperties, + State, + StateConfigInterface, + StateIngestConfigInterface, + StateKey, + StatePersistent, + StorageKey, +} from '../internal'; + +export class EnhancedState extends State { + // Whether the State is persisted in an external Storage + public isPersisted = false; + // Manages the permanent persistent in external Storages + public persistent: StatePersistent | undefined; + + // Method for dynamically computing the State value + public computeValueMethod?: ComputeValueMethod; + // Method for dynamically computing the existence of the State + public computeExistsMethod: ComputeExistsMethod; + + // When an interval is active, the 'intervalId' to clear the interval is temporary stored here + public currentInterval?: NodeJS.Timer | number; + + constructor( + agileInstance: Agile, + initialValue: ValueType, + config: StateConfigInterface = {} + ) { + super(agileInstance, initialValue, config); + this.computeExistsMethod = (v) => { + return v != null; + }; + } + + public setKey(value: StateKey | undefined): this { + const oldKey = this._key; + + // Update State key + super.setKey(value); + + // Update key in Persistent (only if oldKey is equal to persistentKey + // because otherwise the persistentKey is detached from the State key + // -> not managed by State anymore) + if (value != null && this.persistent?._key === oldKey) + this.persistent?.setKey(value); + + return this; + } + + /** + * Undoes the latest State value change. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#undo) + * + * @public + * @param config - Configuration object + */ + public undo(config: StateIngestConfigInterface = {}): this { + this.set(this.previousStateValue, config); + return this; + } + + /** + * Resets the State value to its initial value. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#reset) + * + * @public + * @param config - Configuration object + */ + public reset(config: StateIngestConfigInterface = {}): this { + this.set(this.initialStateValue, config); + return this; + } + + /** + * Merges the specified `targetWithChanges` object into the current State value. + * This merge can differ for different value combinations: + * - If the current State value is an `object`, it does a partial update for the object. + * - If the current State value is an `array` and the specified argument is an array too, + * it concatenates the current State value with the value of the argument. + * - If the current State value is neither an `object` nor an `array`, the patch can't be performed. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#patch) + * + * @public + * @param targetWithChanges - Object to be merged into the current State value. + * @param config - Configuration object + */ + public patch( + targetWithChanges: Object, + config: PatchConfigInterface = {} + ): this { + config = defineConfig(config, { + addNewProperties: true, + }); + + // Check if the given conditions are suitable for a patch action + if (!isValidObject(this.nextStateValue, true)) { + LogCodeManager.log('14:03:02'); + return this; + } + if (!isValidObject(targetWithChanges, true)) { + LogCodeManager.log('00:03:01', ['TargetWithChanges', 'object']); + return this; + } + + // Merge targetWithChanges object into the nextStateValue + if ( + Array.isArray(targetWithChanges) && + Array.isArray(this.nextStateValue) + ) { + this.nextStateValue = [ + ...this.nextStateValue, + ...targetWithChanges, + ] as any; + } else { + this.nextStateValue = flatMerge( + this.nextStateValue, + targetWithChanges, + { addNewProperties: config.addNewProperties } + ); + } + + // Ingest updated 'nextStateValue' into runtime + this.ingest(removeProperties(config, ['addNewProperties'])); + + return this; + } + + /** + * Fires on each State value change. + * + * Returns the key/name identifier of the created watcher callback. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) + * + * @public + * @param callback - A function to be executed on each State value change. + */ + public watch(callback: StateWatcherCallback): string; + /** + * Fires on each State value change. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) + * + * @public + * @param key - Key/Name identifier of the watcher callback. + * @param callback - A function to be executed on each State value change. + */ + public watch(key: string, callback: StateWatcherCallback): this; + public watch( + keyOrCallback: string | StateWatcherCallback, + callback?: StateWatcherCallback + ): this | string { + const generateKey = isFunction(keyOrCallback); + let _callback: StateWatcherCallback; + let key: string; + + if (generateKey) { + key = generateId(); + _callback = keyOrCallback as StateWatcherCallback; + } else { + key = keyOrCallback as string; + _callback = callback as StateWatcherCallback; + } + + if (!isFunction(_callback)) { + LogCodeManager.log('00:03:01', ['Watcher Callback', 'function']); + return this; + } + + this.addSideEffect( + key, + (instance) => { + _callback(instance.value, key); + }, + { weight: 0 } + ); + return generateKey ? key : this; + } + + /** + * Removes a watcher callback with the specified key/name identifier from the State. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#removewatcher) + * + * @public + * @param key - Key/Name identifier of the watcher callback to be removed. + */ + public removeWatcher(key: string): this { + this.removeSideEffect(key); + return this; + } + + /** + * Fires on the initial State value assignment and then destroys itself. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#oninaugurated) + * + * @public + * @param callback - A function to be executed after the first State value assignment. + */ + public onInaugurated(callback: StateWatcherCallback): this { + const watcherKey = 'InauguratedWatcherKey'; + this.watch(watcherKey, (value, key) => { + callback(value, key); + this.removeSideEffect(watcherKey); + }); + return this; + } + + /** + * Repeatedly calls the specified callback function, + * with a fixed time delay between each call. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#interval) + * + * @public + * @param handler - A function to be executed every delay milliseconds. + * @param delay - The time, in milliseconds (thousandths of a second), + * the timer should delay in between executions of the specified function. + */ + public interval( + handler: (value: ValueType) => ValueType, + delay?: number + ): this { + if (!isFunction(handler)) { + LogCodeManager.log('00:03:01', ['Interval Callback', 'function']); + return this; + } + if (this.currentInterval) { + LogCodeManager.log('14:03:03', [], this.currentInterval); + return this; + } + this.currentInterval = setInterval(() => { + this.set(handler(this._value)); + }, delay ?? 1000); + return this; + } + + /** + * Cancels a active timed, repeating action + * which was previously established by a call to `interval()`. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#clearinterval) + * + * @public + */ + public clearInterval(): void { + if (this.currentInterval) { + clearInterval(this.currentInterval as number); + delete this.currentInterval; + } + } + + /** + * Returns a boolean indicating whether the State exists. + * + * It calculates the value based on the `computeExistsMethod()` + * and whether the State is a placeholder. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#exists) + * + * @public + */ + public get exists(): boolean { + return !this.isPlaceholder && this.computeExistsMethod(this.value); + } + + /** + * Defines the method used to compute the existence of the State. + * + * It is retrieved on each `exists()` method call + * to determine whether the State exists or not. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computeexists) + * + * @public + * @param method - Method to compute the existence of the State. + */ + public computeExists(method: ComputeExistsMethod): this { + if (!isFunction(method)) { + LogCodeManager.log('00:03:01', ['Compute Exists Method', 'function']); + return this; + } + this.computeExistsMethod = method; + return this; + } + + /** + * Defines the method used to compute the value of the State. + * + * It is retrieved on each State value change, + * in order to compute the new State value + * based on the specified compute method. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computevalue) + * + * @public + * @param method - Method to compute the value of the State. + */ + public computeValue(method: ComputeValueMethod): this { + if (!isFunction(method)) { + LogCodeManager.log('00:03:01', ['Compute Value Method', 'function']); + return this; + } + this.computeValueMethod = method; + + // Initial compute + // (not directly computing it here since it is computed once in the runtime!) + this.set(this.nextStateValue); + + return this; + } + + /** + * Returns a boolean indicating whether the specified value is equal to the current State value. + * + * Equivalent to `===` with the difference that it looks at the value + * and not on the reference in the case of objects. + * + * @public + * @param value - Value to be compared with the current State value. + */ + public is(value: ValueType): boolean { + return equal(value, this.value); + } + + /** + * Returns a boolean indicating whether the specified value is not equal to the current State value. + * + * Equivalent to `!==` with the difference that it looks at the value + * and not on the reference in the case of objects. + * + * @public + * @param value - Value to be compared with the current State value. + */ + public isNot(value: ValueType): boolean { + return notEqual(value, this.value); + } + + /** + * Inverts the current State value. + * + * Some examples are: + * - `'jeff'` -> `'ffej'` + * - `true` -> `false` + * - `[1, 2, 3]` -> `[3, 2, 1]` + * - `10` -> `-10` + * + * @public + */ + public invert(): this { + switch (typeof this.nextStateValue) { + case 'boolean': + this.set(!this.nextStateValue as any); + break; + case 'object': + if (Array.isArray(this.nextStateValue)) + this.set(this.nextStateValue.reverse() as any); + break; + case 'string': + this.set(this.nextStateValue.split('').reverse().join('') as any); + break; + case 'number': + this.set((this.nextStateValue * -1) as any); + break; + default: + LogCodeManager.log('14:03:04', [typeof this.nextStateValue]); + } + return this; + } + + /** + * Preserves the State `value` in the corresponding external Storage. + * + * The State key/name is used as the unique identifier for the Persistent. + * If that is not desired or the State has no unique identifier, + * please specify a separate unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * + * @public + * @param config - Configuration object + */ + public persist(config?: StatePersistentConfigInterface): this; + /** + * Preserves the State `value` in the corresponding external Storage. + * + * The specified key is used as the unique identifier for the Persistent. + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#persist) + * + * @public + * @param key - Key/Name identifier of Persistent. + * @param config - Configuration object + */ + public persist( + key?: PersistentKey, + config?: StatePersistentConfigInterface + ): this; + public persist( + keyOrConfig: PersistentKey | StatePersistentConfigInterface = {}, + config: StatePersistentConfigInterface = {} + ): this { + let _config: StatePersistentConfigInterface; + let key: PersistentKey | undefined; + + if (isValidObject(keyOrConfig)) { + _config = keyOrConfig as StatePersistentConfigInterface; + key = this._key; + } else { + _config = config || {}; + key = keyOrConfig as PersistentKey; + } + + _config = defineConfig(_config, { + loadValue: true, + storageKeys: [], + defaultStorageKey: null as any, + }); + + // Check if State is already persisted + if (this.persistent != null && this.isPersisted) return this; + + // Create Persistent (-> persist value) + this.persistent = new StatePersistent(this, { + instantiate: _config.loadValue, + storageKeys: _config.storageKeys, + key: key, + defaultStorageKey: _config.defaultStorageKey, + }); + + return this; + } + + /** + * Fires immediately after the persisted `value` + * is loaded into the State from a corresponding external Storage. + * + * Registering such callback function makes only sense + * when the State is [persisted](https://agile-ts.org/docs/core/state/methods/#persist). + * + * [Learn more..](https://agile-ts.org/docs/core/state/methods/#onload) + * + * @public + * @param callback - A function to be executed after the externally persisted `value` was loaded into the State. + */ + public onLoad(callback: (success: boolean) => void): this { + if (!this.persistent) return this; + if (!isFunction(callback)) { + LogCodeManager.log('00:03:01', ['OnLoad Callback', 'function']); + return this; + } + + // Register specified callback + this.persistent.onLoad = callback; + + // If State is already persisted ('isPersisted') fire specified callback immediately + if (this.isPersisted) callback(true); + + return this; + } + + /** + * Returns the persistable value of the State. + * + * @internal + */ + public getPersistableValue(): any { + return this._value; + } +} + +export interface PatchConfigInterface + extends StateIngestConfigInterface, + PatchOptionConfigInterface {} + +export interface PatchOptionConfigInterface { + /** + * Whether to add new properties to the object during the merge. + * @default true + */ + addNewProperties?: boolean; +} + +export interface StatePersistentConfigInterface { + /** + * Whether the Persistent should automatically load + * the persisted value into the State after its instantiation. + * @default true + */ + loadValue?: boolean; + /** + * Key/Name identifier of Storages + * in which the State value should be or is persisted. + * @default [`defaultStorageKey`] + */ + storageKeys?: StorageKey[]; + /** + * Key/Name identifier of the default Storage of the specified Storage keys. + * + * The State value is loaded from the default Storage by default + * and is only loaded from the remaining Storages (`storageKeys`) + * if the loading from the default Storage failed. + * + * @default first index of the specified Storage keys or the AgileTs default Storage key + */ + defaultStorageKey?: StorageKey; +} + +export type StateWatcherCallback = (value: T, key: string) => void; +export type ComputeValueMethod = (value: T) => T; +export type ComputeExistsMethod = (value: T) => boolean; diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 31322839..45bc8d46 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -1,7 +1,7 @@ import { CreatePersistentConfigInterface, defineConfig, - PersistableState, + EnhancedState, Persistent, PersistentKey, storageManager, @@ -9,7 +9,7 @@ import { export class StatePersistent extends Persistent { // State the Persistent belongs to - public state: () => PersistableState; + public state: () => EnhancedState; static storeValueSideEffectKey = 'rebuildStateStorageValue'; @@ -21,7 +21,7 @@ export class StatePersistent extends Persistent { * @param config - Configuration object */ constructor( - state: PersistableState, + state: EnhancedState, config: CreatePersistentConfigInterface = {} ) { super(state.agileInstance(), { @@ -185,7 +185,7 @@ export class StatePersistent extends Persistent { * @param config - Configuration object */ public rebuildStorageSideEffect( - state: PersistableState, + state: EnhancedState, storageItemKey: PersistentKey, config: { [key: string]: any } = {} ) { diff --git a/packages/core/src/state/state.ts b/packages/core/src/state/state.ts index 2c23bb5c..a14cfd85 100644 --- a/packages/core/src/state/state.ts +++ b/packages/core/src/state/state.ts @@ -45,14 +45,6 @@ export class State { [key: string]: SideEffectInterface>; } = {}; - // Method for dynamically computing the State value - public computeValueMethod?: ComputeValueMethod; - // Method for dynamically computing the existence of the State - public computeExistsMethod: ComputeExistsMethod; - - // When an interval is active, the 'intervalId' to clear the interval is temporary stored here - public currentInterval?: NodeJS.Timer | number; - /** * A State manages a piece of Information * that we need to remember globally at a later point in time. @@ -87,9 +79,6 @@ export class State { this.previousStateValue = copy(initialValue); this.nextStateValue = copy(initialValue); this.isPlaceholder = true; - this.computeExistsMethod = (v) => { - return v != null; - }; // Set State value to specified initial value if (!config.isPlaceholder) this.set(initialValue, { overwrite: true }); @@ -206,331 +195,6 @@ export class State { return this; } - /** - * Undoes the latest State value change. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#undo) - * - * @public - * @param config - Configuration object - */ - public undo(config: StateIngestConfigInterface = {}): this { - this.set(this.previousStateValue, config); - return this; - } - - /** - * Resets the State value to its initial value. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#reset) - * - * @public - * @param config - Configuration object - */ - public reset(config: StateIngestConfigInterface = {}): this { - this.set(this.initialStateValue, config); - return this; - } - - /** - * Merges the specified `targetWithChanges` object into the current State value. - * This merge can differ for different value combinations: - * - If the current State value is an `object`, it does a partial update for the object. - * - If the current State value is an `array` and the specified argument is an array too, - * it concatenates the current State value with the value of the argument. - * - If the current State value is neither an `object` nor an `array`, the patch can't be performed. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#patch) - * - * @public - * @param targetWithChanges - Object to be merged into the current State value. - * @param config - Configuration object - */ - public patch( - targetWithChanges: Object, - config: PatchConfigInterface = {} - ): this { - config = defineConfig(config, { - addNewProperties: true, - }); - - // Check if the given conditions are suitable for a patch action - if (!isValidObject(this.nextStateValue, true)) { - LogCodeManager.log('14:03:02'); - return this; - } - if (!isValidObject(targetWithChanges, true)) { - LogCodeManager.log('00:03:01', ['TargetWithChanges', 'object']); - return this; - } - - // Merge targetWithChanges object into the nextStateValue - if ( - Array.isArray(targetWithChanges) && - Array.isArray(this.nextStateValue) - ) { - this.nextStateValue = [ - ...this.nextStateValue, - ...targetWithChanges, - ] as any; - } else { - this.nextStateValue = flatMerge( - this.nextStateValue, - targetWithChanges, - { addNewProperties: config.addNewProperties } - ); - } - - // Ingest updated 'nextStateValue' into runtime - this.ingest(removeProperties(config, ['addNewProperties'])); - - return this; - } - - /** - * Fires on each State value change. - * - * Returns the key/name identifier of the created watcher callback. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) - * - * @public - * @param callback - A function to be executed on each State value change. - */ - public watch(callback: StateWatcherCallback): string; - /** - * Fires on each State value change. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#watch) - * - * @public - * @param key - Key/Name identifier of the watcher callback. - * @param callback - A function to be executed on each State value change. - */ - public watch(key: string, callback: StateWatcherCallback): this; - public watch( - keyOrCallback: string | StateWatcherCallback, - callback?: StateWatcherCallback - ): this | string { - const generateKey = isFunction(keyOrCallback); - let _callback: StateWatcherCallback; - let key: string; - - if (generateKey) { - key = generateId(); - _callback = keyOrCallback as StateWatcherCallback; - } else { - key = keyOrCallback as string; - _callback = callback as StateWatcherCallback; - } - - if (!isFunction(_callback)) { - LogCodeManager.log('00:03:01', ['Watcher Callback', 'function']); - return this; - } - - this.addSideEffect( - key, - (instance) => { - _callback(instance.value, key); - }, - { weight: 0 } - ); - return generateKey ? key : this; - } - - /** - * Removes a watcher callback with the specified key/name identifier from the State. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#removewatcher) - * - * @public - * @param key - Key/Name identifier of the watcher callback to be removed. - */ - public removeWatcher(key: string): this { - this.removeSideEffect(key); - return this; - } - - /** - * Fires on the initial State value assignment and then destroys itself. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#oninaugurated) - * - * @public - * @param callback - A function to be executed after the first State value assignment. - */ - public onInaugurated(callback: StateWatcherCallback): this { - const watcherKey = 'InauguratedWatcherKey'; - this.watch(watcherKey, (value, key) => { - callback(value, key); - this.removeSideEffect(watcherKey); - }); - return this; - } - - /** - * Repeatedly calls the specified callback function, - * with a fixed time delay between each call. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#interval) - * - * @public - * @param handler - A function to be executed every delay milliseconds. - * @param delay - The time, in milliseconds (thousandths of a second), - * the timer should delay in between executions of the specified function. - */ - public interval( - handler: (value: ValueType) => ValueType, - delay?: number - ): this { - if (!isFunction(handler)) { - LogCodeManager.log('00:03:01', ['Interval Callback', 'function']); - return this; - } - if (this.currentInterval) { - LogCodeManager.log('14:03:03', [], this.currentInterval); - return this; - } - this.currentInterval = setInterval(() => { - this.set(handler(this._value)); - }, delay ?? 1000); - return this; - } - - /** - * Cancels a active timed, repeating action - * which was previously established by a call to `interval()`. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#clearinterval) - * - * @public - */ - public clearInterval(): void { - if (this.currentInterval) { - clearInterval(this.currentInterval as number); - delete this.currentInterval; - } - } - - /** - * Returns a boolean indicating whether the State exists. - * - * It calculates the value based on the `computeExistsMethod()` - * and whether the State is a placeholder. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#exists) - * - * @public - */ - public get exists(): boolean { - return !this.isPlaceholder && this.computeExistsMethod(this.value); - } - - /** - * Defines the method used to compute the existence of the State. - * - * It is retrieved on each `exists()` method call - * to determine whether the State exists or not. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computeexists) - * - * @public - * @param method - Method to compute the existence of the State. - */ - public computeExists(method: ComputeExistsMethod): this { - if (!isFunction(method)) { - LogCodeManager.log('00:03:01', ['Compute Exists Method', 'function']); - return this; - } - this.computeExistsMethod = method; - return this; - } - - /** - * Defines the method used to compute the value of the State. - * - * It is retrieved on each State value change, - * in order to compute the new State value - * based on the specified compute method. - * - * [Learn more..](https://agile-ts.org/docs/core/state/methods/#computevalue) - * - * @public - * @param method - Method to compute the value of the State. - */ - public computeValue(method: ComputeValueMethod): this { - if (!isFunction(method)) { - LogCodeManager.log('00:03:01', ['Compute Value Method', 'function']); - return this; - } - this.computeValueMethod = method; - - // Initial compute - // (not directly computing it here since it is computed once in the runtime!) - this.set(this.nextStateValue); - - return this; - } - - /** - * Returns a boolean indicating whether the specified value is equal to the current State value. - * - * Equivalent to `===` with the difference that it looks at the value - * and not on the reference in the case of objects. - * - * @public - * @param value - Value to be compared with the current State value. - */ - public is(value: ValueType): boolean { - return equal(value, this.value); - } - - /** - * Returns a boolean indicating whether the specified value is not equal to the current State value. - * - * Equivalent to `!==` with the difference that it looks at the value - * and not on the reference in the case of objects. - * - * @public - * @param value - Value to be compared with the current State value. - */ - public isNot(value: ValueType): boolean { - return notEqual(value, this.value); - } - - /** - * Inverts the current State value. - * - * Some examples are: - * - `'jeff'` -> `'ffej'` - * - `true` -> `false` - * - `[1, 2, 3]` -> `[3, 2, 1]` - * - `10` -> `-10` - * - * @public - */ - public invert(): this { - switch (typeof this.nextStateValue) { - case 'boolean': - this.set(!this.nextStateValue as any); - break; - case 'object': - if (Array.isArray(this.nextStateValue)) - this.set(this.nextStateValue.reverse() as any); - break; - case 'string': - this.set(this.nextStateValue.split('').reverse().join('') as any); - break; - case 'number': - this.set((this.nextStateValue * -1) as any); - break; - default: - LogCodeManager.log('14:03:04', [typeof this.nextStateValue]); - } - return this; - } - /** * * Registers a `callback` function that is executed in the `runtime` @@ -620,47 +284,6 @@ export interface StateConfigInterface { isPlaceholder?: boolean; } -export interface PatchConfigInterface - extends StateIngestConfigInterface, - PatchOptionConfigInterface {} - -export interface PatchOptionConfigInterface { - /** - * Whether to add new properties to the object during the merge. - * @default true - */ - addNewProperties?: boolean; -} - -export interface StatePersistentConfigInterface { - /** - * Whether the Persistent should automatically load - * the persisted value into the State after its instantiation. - * @default true - */ - loadValue?: boolean; - /** - * Key/Name identifier of Storages - * in which the State value should be or is persisted. - * @default [`defaultStorageKey`] - */ - storageKeys?: StorageKey[]; - /** - * Key/Name identifier of the default Storage of the specified Storage keys. - * - * The State value is loaded from the default Storage by default - * and is only loaded from the remaining Storages (`storageKeys`) - * if the loading from the default Storage failed. - * - * @default first index of the specified Storage keys or the AgileTs default Storage key - */ - defaultStorageKey?: StorageKey; -} - -export type StateWatcherCallback = (value: T, key: string) => void; -export type ComputeValueMethod = (value: T) => T; -export type ComputeExistsMethod = (value: T) => boolean; - export type SideEffectFunctionType = ( instance: Instance, properties?: { From feefba5fa396cf4acb61cbe2b77146b1dab2ff8b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 16 Aug 2021 06:47:07 +0200 Subject: [PATCH 08/44] fixed typo --- .../develop/simple-counter/src/state-manager/Agile.js | 8 ++++---- packages/core/src/state/state.observer.ts | 7 ++++--- packages/core/src/state/state.ts | 7 ------- packages/core/src/storages/index.ts | 2 +- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/examples/react/develop/simple-counter/src/state-manager/Agile.js b/examples/react/develop/simple-counter/src/state-manager/Agile.js index e86b199e..d67b75de 100644 --- a/examples/react/develop/simple-counter/src/state-manager/Agile.js +++ b/examples/react/develop/simple-counter/src/state-manager/Agile.js @@ -1,10 +1,10 @@ import React from 'react'; -import { createState } from '@agile-ts/core'; +import { createLightState } from '@agile-ts/core'; import { useAgile, useValue } from '@agile-ts/react'; -const COUNTER_A = createState(1); -const COUNTER_B = createState(2); -const COUNTER_C = createState(3); +const COUNTER_A = createLightState(1); +const COUNTER_B = createLightState(2); +const COUNTER_C = createLightState(3); const CounterA = () => { const count = useAgile(COUNTER_A); diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index ab02dc7c..b7cd7327 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -15,6 +15,7 @@ import { ObserverKey, defineConfig, } from '../internal'; +import type { EnhancedState } from '../internal'; export class StateObserver extends Observer { // State the Observer belongs to @@ -105,9 +106,9 @@ export class StateObserver extends Observer { config.overwrite = true; } - // Assign next State value to Observer and compute it if necessary - this.nextStateValue = state.computeValueMethod - ? copy(state.computeValueMethod(newStateValue)) + // Assign next State value to Observer and compute it if necessary (enhanced State) + this.nextStateValue = (state as EnhancedState).computeValueMethod + ? copy((state as any).computeValueMethod(newStateValue)) : copy(newStateValue); // Check if current State value and to assign State value are equal diff --git a/packages/core/src/state/state.ts b/packages/core/src/state/state.ts index a14cfd85..c4b5b0ab 100644 --- a/packages/core/src/state/state.ts +++ b/packages/core/src/state/state.ts @@ -1,18 +1,11 @@ import { Agile, - StorageKey, copy, - flatMerge, - isValidObject, StateObserver, Observer, - equal, isFunction, - notEqual, - generateId, ComputedTracker, StateIngestConfigInterface, - removeProperties, LogCodeManager, defineConfig, } from '../internal'; diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index a0ef25f2..28993733 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -1,4 +1,4 @@ -import { CreateStorageConfigInterface, generateId, Storage } from '../internal'; +import { CreateStorageConfigInterface, Storage } from '../internal'; import type { Storages } from '../internal'; export * from './storages'; From eb8852262c2ef5f0f7c5015f4a878576f1a98bd4 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 16 Aug 2021 07:56:40 +0200 Subject: [PATCH 09/44] updated tree-shaking test --- examples/plainjs/develop/tree-shaking/package.json | 8 +++----- examples/plainjs/develop/tree-shaking/src/index.js | 7 ++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/examples/plainjs/develop/tree-shaking/package.json b/examples/plainjs/develop/tree-shaking/package.json index 53515c71..9f6bb167 100644 --- a/examples/plainjs/develop/tree-shaking/package.json +++ b/examples/plainjs/develop/tree-shaking/package.json @@ -6,8 +6,8 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", - "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react & yarn install", - "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react & yarn install" + "install:dev:agile": "yalc add @agile-ts/core & yarn install", + "install:prod:agile": "yarn add @agile-ts/core & yarn install" }, "author": "", "license": "ISC", @@ -16,8 +16,6 @@ "webpack-cli": "^4.7.2" }, "dependencies": { - "@agile-ts/core": "file:.yalc/@agile-ts/core", - "@agile-ts/react": "file:.yalc/@agile-ts/react", - "react": "^17.0.2" + "@agile-ts/core": "file:.yalc/@agile-ts/core" } } diff --git a/examples/plainjs/develop/tree-shaking/src/index.js b/examples/plainjs/develop/tree-shaking/src/index.js index 9d713ce5..ed9cc3c7 100644 --- a/examples/plainjs/develop/tree-shaking/src/index.js +++ b/examples/plainjs/develop/tree-shaking/src/index.js @@ -1,8 +1,5 @@ -import { createState } from '@agile-ts/core'; -import { useAgile } from '@agile-ts/react'; +import { createLightState } from '@agile-ts/core'; -const MY_STATE = createState('hi'); +const MY_STATE = createLightState('hi'); console.log(MY_STATE.value); - -useAgile(MY_STATE); From b7ec4a2dc0efad9d2e241c455de13fea1e309519 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 16 Aug 2021 18:28:32 +0200 Subject: [PATCH 10/44] fixed typos --- .../src/collection/collection.persistent.ts | 8 ++--- packages/core/src/computed/index.ts | 8 ++--- packages/core/src/state/index.ts | 8 ++--- packages/core/src/state/state.persistent.ts | 8 ++--- packages/core/src/storages/index.ts | 36 ++++++++++++++++--- packages/core/src/storages/persistent.ts | 8 ++--- 6 files changed, 52 insertions(+), 24 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index c789fab4..2b2232f1 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -11,7 +11,7 @@ import { Persistent, PersistentKey, StorageKey, - storageManager, + getStorageManager, } from '../internal'; export class CollectionPersistent< @@ -85,7 +85,7 @@ export class CollectionPersistent< // Check if Collection is already persisted // (indicated by the persistence of 'true' at '_storageItemKey') - const isPersisted = await storageManager?.get( + const isPersisted = await getStorageManager()?.get( _storageItemKey, this.config.defaultStorageKey as any ); @@ -208,7 +208,7 @@ export class CollectionPersistent< ); // Set flag in Storage to indicate that the Collection is persisted - storageManager?.set(_storageItemKey, true, this.storageKeys); + getStorageManager()?.set(_storageItemKey, true, this.storageKeys); // Persist default Group defaultGroup.persist(defaultGroupStorageKey, { @@ -283,7 +283,7 @@ export class CollectionPersistent< ); // Remove Collection is persisted indicator flag from Storage - storageManager?.remove(_storageItemKey, this.storageKeys); + getStorageManager()?.remove(_storageItemKey, this.storageKeys); // Remove default Group from the Storage defaultGroup.persistent?.removePersistedValue(defaultGroupStorageKey); diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 22217093..35219792 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -12,10 +12,6 @@ import { export * from './computed'; // export * from './computed.tracker'; -export interface CreateComputedConfigInterfaceWithAgile - extends CreateAgileSubInstanceInterface, - CreateComputedConfigInterface {} - /** * Returns a newly created Computed. * @@ -84,3 +80,7 @@ export function createComputed( removeProperties(_config, ['agileInstance']) ); } + +export interface CreateComputedConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + CreateComputedConfigInterface {} diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index b99ec509..2c8f1104 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -13,10 +13,6 @@ export * from './state'; // export * from './state.persistent'; // export * from './state.runtime.job'; -export interface CreateStateConfigInterfaceWithAgile - extends CreateAgileSubInstanceInterface, - StateConfigInterface {} - /** * Returns a newly created State. * @@ -74,3 +70,7 @@ export function createState( removeProperties(config, ['agileInstance']) ); } + +export interface CreateStateConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + StateConfigInterface {} diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 45bc8d46..5a1d7f58 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -2,9 +2,9 @@ import { CreatePersistentConfigInterface, defineConfig, EnhancedState, + getStorageManager, Persistent, PersistentKey, - storageManager, } from '../internal'; export class StatePersistent extends Persistent { @@ -73,7 +73,7 @@ export class StatePersistent extends Persistent { const _storageItemKey = storageItemKey ?? this._key; // Load State value from the default Storage - const loadedValue = await storageManager?.get( + const loadedValue = await getStorageManager()?.get( _storageItemKey, this.config.defaultStorageKey as any ); @@ -151,7 +151,7 @@ export class StatePersistent extends Persistent { if (!this.ready) return false; const _storageItemKey = storageItemKey || this._key; this.state().removeSideEffect(StatePersistent.storeValueSideEffectKey); - storageManager?.remove(_storageItemKey, this.storageKeys); + getStorageManager()?.remove(_storageItemKey, this.storageKeys); this.isPersisted = false; return true; } @@ -190,7 +190,7 @@ export class StatePersistent extends Persistent { config: { [key: string]: any } = {} ) { if (config['storage'] == null || config.storage) { - storageManager?.set( + getStorageManager()?.set( storageItemKey, this.state().getPersistableValue(), this.storageKeys diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 28993733..c5d9f797 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -1,10 +1,21 @@ -import { CreateStorageConfigInterface, Storage } from '../internal'; -import type { Storages } from '../internal'; +import { + CreateStorageConfigInterface, + Storage, + Storages, + shared, + CreateStoragesConfigInterface, + CreateAgileSubInstanceInterface, + defineConfig, + removeProperties, +} from '../internal'; export * from './storages'; // export * from './storage'; // export * from './persistent'; +// Handles the permanent persistence of Agile Classes +let storageManager: Storages | null = null; + /** * Returns a newly created Storage. * @@ -24,8 +35,21 @@ export function createStorage(config: CreateStorageConfigInterface): Storage { return new Storage(config); } -// Handles the permanent persistence of Agile Classes -export let storageManager: Storages | null = null; +export function createStorageManager( + config: CreateStorageManagerConfigInterfaceWithAgile = {} +): Storages { + config = defineConfig(config, { + agileInstance: shared, + }); + return new Storages( + config.agileInstance as any, + removeProperties(config, ['agileInstance']) + ); +} + +export function getStorageManager(): Storages | null { + return storageManager; +} export const registerStorageManager = (instance: Storages) => { if (storageManager != null) { @@ -33,3 +57,7 @@ export const registerStorageManager = (instance: Storages) => { } storageManager = instance; }; + +export interface CreateStorageManagerConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + CreateStoragesConfigInterface {} diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index ace74de9..8d869790 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -2,9 +2,9 @@ import { Agile, copy, defineConfig, + getStorageManager, LogCodeManager, StorageKey, - storageManager, } from '../internal'; export class Persistent { @@ -155,7 +155,7 @@ export class Persistent { // Check if the Storages exist at the specified Storage keys this.storageKeys.map((key) => { - if (!storageManager?.storages[key]) { + if (!getStorageManager()?.storages[key]) { LogCodeManager.log('12:03:02', [this._key, key]); isValid = false; } @@ -190,10 +190,10 @@ export class Persistent { // and specify it as the Persistent's default Storage key // if no valid Storage key was provided if (_storageKeys.length <= 0) { - const defaultStorageKey = storageManager?.config.defaultStorageKey; + const defaultStorageKey = getStorageManager()?.config.defaultStorageKey; if (defaultStorageKey != null) { this.config.defaultStorageKey = defaultStorageKey; - _storageKeys.push(storageManager?.config.defaultStorageKey as any); + _storageKeys.push(getStorageManager()?.config.defaultStorageKey as any); } } else { this.config.defaultStorageKey = defaultStorageKey ?? _storageKeys[0]; From 136309752dde1222b95cfed8b5ed3412c1fc80a0 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 17 Aug 2021 06:31:19 +0200 Subject: [PATCH 11/44] fixed typos --- .../react/develop/simple-counter/src/state-manager/Agile.js | 6 ++++-- packages/core/src/internal.ts | 2 +- packages/core/src/state/index.ts | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/react/develop/simple-counter/src/state-manager/Agile.js b/examples/react/develop/simple-counter/src/state-manager/Agile.js index d67b75de..8ec4ad49 100644 --- a/examples/react/develop/simple-counter/src/state-manager/Agile.js +++ b/examples/react/develop/simple-counter/src/state-manager/Agile.js @@ -1,7 +1,9 @@ import React from 'react'; import { createLightState } from '@agile-ts/core'; -import { useAgile, useValue } from '@agile-ts/react'; +import { useAgile } from '@agile-ts/react'; +// registerStorageManager(createStorageManager({ localStorage: true })); +// const COUNTER_A = createState(1).persist('persistKey'); const COUNTER_A = createLightState(1); const COUNTER_B = createLightState(2); const COUNTER_C = createLightState(3); @@ -16,7 +18,7 @@ const CounterA = () => { }; const CounterB = () => { - const count = useValue(COUNTER_B); + const count = useAgile(COUNTER_B); return (
B: {count} diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 26581a63..f504535e 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -36,9 +36,9 @@ export * from './storages/persistent'; // State export * from './state'; export * from './state/state.observer'; +export * from './state/state.enhanced'; export * from './state/state.persistent'; export * from './state/state.runtime.job'; -export * from './state/state.enhanced'; // Computed export * from './computed'; diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 2c8f1104..babcbee5 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -10,6 +10,7 @@ import { export * from './state'; // export * from './state.observer'; +// export * from './state.enhanced'; // export * from './state.persistent'; // export * from './state.runtime.job'; From 23ee44e755a641f01bc249cf7a7b42ba11ecc0e1 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 17 Aug 2021 06:54:34 +0200 Subject: [PATCH 12/44] added webpack analyzer to example --- .../react/develop/simple-counter/package.json | 6 +- .../develop/simple-counter/scripts/analyze.js | 20 ++++++ .../react/develop/simple-counter/yarn.lock | 63 ++++++++++++++++++- 3 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 examples/react/develop/simple-counter/scripts/analyze.js diff --git a/examples/react/develop/simple-counter/package.json b/examples/react/develop/simple-counter/package.json index 0b329bb3..058eac66 100644 --- a/examples/react/develop/simple-counter/package.json +++ b/examples/react/develop/simple-counter/package.json @@ -4,8 +4,8 @@ "private": true, "dependencies": { "@agile-ts/core": "file:.yalc/@agile-ts/core", - "@agile-ts/react": "file:.yalc/@agile-ts/react", "@agile-ts/logger": "file:.yalc/@agile-ts/logger", + "@agile-ts/react": "file:.yalc/@agile-ts/react", "@reduxjs/toolkit": "^1.6.1", "jotai": "^1.2.2", "nanostores": "^0.4.1", @@ -16,7 +16,8 @@ "recoil": "^0.4.0" }, "devDependencies": { - "source-map-explorer": "^2.5.2" + "source-map-explorer": "^2.5.2", + "webpack-bundle-analyzer": "^4.4.2" }, "scripts": { "start": "react-scripts start", @@ -24,6 +25,7 @@ "test": "react-scripts test", "eject": "react-scripts eject", "analyze": "yarn run build && source-map-explorer 'build/static/js/*.js'", + "analyze:webpack": "node scripts/analyze.js", "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/logger & yarn install", "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/logger & yarn install" }, diff --git a/examples/react/develop/simple-counter/scripts/analyze.js b/examples/react/develop/simple-counter/scripts/analyze.js new file mode 100644 index 00000000..3fbf0a66 --- /dev/null +++ b/examples/react/develop/simple-counter/scripts/analyze.js @@ -0,0 +1,20 @@ +// https://medium.com/@hamidihamza/optimize-react-web-apps-with-webpack-bundle-analyzer-6ecb9f162c76 + +process.env.NODE_ENV = 'production'; + +const webpack = require('webpack'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') + .BundleAnalyzerPlugin; +const webpackConfigProd = require('react-scripts/config/webpack.config')( + 'production' +); + +// Add Bundle Analyzer Plugin to React webpack config +webpackConfigProd.plugins.push(new BundleAnalyzerPlugin()); + +// Build project with webpack +webpack(webpackConfigProd, (err, stats) => { + if (err || stats.hasErrors()) { + console.error(err); + } +}); diff --git a/examples/react/develop/simple-counter/yarn.lock b/examples/react/develop/simple-counter/yarn.lock index ebaf211d..23f11909 100644 --- a/examples/react/develop/simple-counter/yarn.lock +++ b/examples/react/develop/simple-counter/yarn.lock @@ -3,7 +3,7 @@ "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.2.0-alpha.3" + version "0.2.0-alpha.4" dependencies: "@agile-ts/utils" "^0.0.7" @@ -2011,6 +2011,11 @@ schema-utils "^2.6.5" source-map "^0.7.3" +"@polka/url@^1.0.0-next.17": + version "1.0.0-next.17" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.17.tgz#25fdbdfd282c2f86ddf3fcefbd98be99cd2627e2" + integrity sha512-0p1rCgM3LLbAdwBnc7gqgnvjHg9KpbhcSphergHShlkWz8EdPawoMJ3/VbezI0mGC5eKCDzMaPgF9Yca6cKvrg== + "@reduxjs/toolkit@^1.6.1": version "1.6.1" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.1.tgz#7bc83b47352a663bf28db01e79d17ba54b98ade9" @@ -2701,6 +2706,11 @@ acorn-walk@^7.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.1.1.tgz#3ddab7f84e4a7e2313f6c414c5b7dac85f4e3ebc" + integrity sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w== + acorn@^6.4.1: version "6.4.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" @@ -2711,6 +2721,11 @@ acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.0.4: + version "8.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" + integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== + address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -3956,6 +3971,11 @@ commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -7788,7 +7808,7 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.4.4: +mime@^2.3.1, mime@^2.4.4: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== @@ -8297,6 +8317,11 @@ open@^7.0.2, open@^7.3.1: is-docker "^2.0.0" is-wsl "^2.1.1" +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -10501,6 +10526,15 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +sirv@^1.0.7: + version "1.0.14" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.14.tgz#b826343f573e12653c5b3c3080a3a2a6a06595cd" + integrity sha512-czTFDFjK9lXj0u9mJ3OmJoXFztoilYS+NdRPcJoT182w44wSEkHSiO7A2517GLJ8wKM4GjCm2OXE66Dhngbzjg== + dependencies: + "@polka/url" "^1.0.0-next.17" + mime "^2.3.1" + totalist "^1.0.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -11220,6 +11254,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +totalist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" + integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== + tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -11665,6 +11704,21 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webpack-bundle-analyzer@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.2.tgz#39898cf6200178240910d629705f0f3493f7d666" + integrity sha512-PIagMYhlEzFfhMYOzs5gFT55DkUdkyrJi/SxJp8EF3YMWhS+T9vvs2EoTetpk5qb6VsCq02eXTlRDOydRhDFAQ== + dependencies: + acorn "^8.0.4" + acorn-walk "^8.0.0" + chalk "^4.1.0" + commander "^6.2.0" + gzip-size "^6.0.0" + lodash "^4.17.20" + opener "^1.5.2" + sirv "^1.0.7" + ws "^7.3.1" + webpack-dev-middleware@^3.7.2: version "3.7.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" @@ -12062,6 +12116,11 @@ ws@^7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.3.tgz#1f9643de34a543b8edb124bdcbc457ae55a6e5cd" integrity sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA== +ws@^7.3.1: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" From 414c033fc1911b68fc153b28d920dd4c1afed7b9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 17 Aug 2021 21:06:39 +0200 Subject: [PATCH 13/44] fixed typos --- .../develop/simple-counter/scripts/analyze.js | 17 +++++++++++++++-- packages/core/src/agile.ts | 1 - packages/core/src/computed/index.ts | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/react/develop/simple-counter/scripts/analyze.js b/examples/react/develop/simple-counter/scripts/analyze.js index 3fbf0a66..9300d1a2 100644 --- a/examples/react/develop/simple-counter/scripts/analyze.js +++ b/examples/react/develop/simple-counter/scripts/analyze.js @@ -1,6 +1,15 @@ // https://medium.com/@hamidihamza/optimize-react-web-apps-with-webpack-bundle-analyzer-6ecb9f162c76 +// Note: Webpack Bundle Analyzer doesn't show accurately which bundles were tree shaken +// (See: https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/161) -process.env.NODE_ENV = 'production'; +// https://nodejs.org/docs/latest/api/process.html#process_process_argv +const isDev = process.argv.includes('--dev'); + +console.log( + `Start bundling a '${isDev ? 'development' : 'production'}' build!` +); + +process.env.NODE_ENV = isDev ? 'development' : 'production'; const webpack = require('webpack'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') @@ -8,12 +17,16 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') const webpackConfigProd = require('react-scripts/config/webpack.config')( 'production' ); +const webpackConfigDev = require('react-scripts/config/webpack.config')( + 'development' +); // Add Bundle Analyzer Plugin to React webpack config webpackConfigProd.plugins.push(new BundleAnalyzerPlugin()); +webpackConfigDev.plugins.push(new BundleAnalyzerPlugin()); // Build project with webpack -webpack(webpackConfigProd, (err, stats) => { +webpack(isDev ? webpackConfigDev : webpackConfigProd, (err, stats) => { if (err || stats.hasErrors()) { console.error(err); } diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 797107df..82fe3bab 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -42,7 +42,6 @@ export class Agile { * changes in the Runtime to prevent race conditions * - update/rerender subscribed UI-Components through the provided Integrations * such as the [React Integration](https://agile-ts.org/docs/react) - * - integrate with the persistent [Storage](https://agile-ts.org/docs/core/storage) * - provide configuration object * * Each Agile Sub Instance requires an Agile Instance to be instantiated and function properly. diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index 35219792..185dec62 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -24,7 +24,7 @@ export * from './computed'; * Direct dependencies can be States and Collections. * So when, for example, a dependent State value changes, the computed value is recomputed. * - * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcomputed) * * @public * @param computeFunction - Function to compute the computed value. From d62cd8912d4948ee535fc8e2bb1501b48c0340fe Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 18 Aug 2021 21:21:51 +0200 Subject: [PATCH 14/44] added env config --- examples/react/develop/simple-counter/scripts/analyze.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/react/develop/simple-counter/scripts/analyze.js b/examples/react/develop/simple-counter/scripts/analyze.js index 9300d1a2..ea2c0df2 100644 --- a/examples/react/develop/simple-counter/scripts/analyze.js +++ b/examples/react/develop/simple-counter/scripts/analyze.js @@ -2,8 +2,13 @@ // Note: Webpack Bundle Analyzer doesn't show accurately which bundles were tree shaken // (See: https://github.com/webpack-contrib/webpack-bundle-analyzer/issues/161) +import dotenv from 'dotenv'; + +// Loads environment variables from the '.env' file +dotenv.config(); + // https://nodejs.org/docs/latest/api/process.html#process_process_argv -const isDev = process.argv.includes('--dev'); +const isDev = process.argv.includes('--dev') || process.env.DEV === 'true'; console.log( `Start bundling a '${isDev ? 'development' : 'production'}' build!` From 52d995e8034739b44ff529b70dc391c82ff4ebe8 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 19 Aug 2021 21:08:51 +0200 Subject: [PATCH 15/44] added warning when adding an existing storage manager --- packages/core/src/logCodeManager.ts | 2 ++ packages/core/src/storages/index.ts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index bb5c9ac6..2e10408e 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -40,6 +40,8 @@ const niceLogCodeMessages = { "Couldn't find Storage '${0}'. " + "The Storage with the key/name '${0}' doesn't exists!", '11:03:02': "Storage with the key/name '${0}' isn't ready yet!", + '11:02:06': + 'By registering a new Storage Manager the old one will be overwritten!', '11:03:03': 'No Storage found to get a value from! Please specify at least one Storage.', '11:03:04': diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index c5d9f797..b306d8f9 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -7,6 +7,7 @@ import { CreateAgileSubInstanceInterface, defineConfig, removeProperties, + LogCodeManager, } from '../internal'; export * from './storages'; @@ -53,7 +54,7 @@ export function getStorageManager(): Storages | null { export const registerStorageManager = (instance: Storages) => { if (storageManager != null) { - // TODO print warning + LogCodeManager.log('11:02:06', [], storageManager); } storageManager = instance; }; From 4e614ea43e80d9eac2ac2cd286faa3d99b0992a6 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 20 Aug 2021 19:47:07 +0200 Subject: [PATCH 16/44] fixed typo --- packages/core/src/storages/index.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index b306d8f9..3b4586cc 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -36,6 +36,13 @@ export function createStorage(config: CreateStorageConfigInterface): Storage { return new Storage(config); } +/** + * Returns a newly created Storage Manager. + * + * A Storage Manager manages the Storages. + * + * @param config - Configuration object + */ export function createStorageManager( config: CreateStorageManagerConfigInterfaceWithAgile = {} ): Storages { @@ -48,10 +55,18 @@ export function createStorageManager( ); } +/** + * Returns the current Storage Manager. + */ export function getStorageManager(): Storages | null { return storageManager; } +/** + * Registers a Storage Manager. + * + * @param instance - Storage Manager to be registered as the default Storage Manager. + */ export const registerStorageManager = (instance: Storages) => { if (storageManager != null) { LogCodeManager.log('11:02:06', [], storageManager); From 0226cab8d5eacb16e0fa8b7a6e3631d48d0cd94d Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 21 Aug 2021 07:47:50 +0200 Subject: [PATCH 17/44] updated registerStorageManager name --- .../develop/functional-component-ts/src/core/index.ts | 11 +++++++---- packages/core/src/storages/index.ts | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index f2f4efe8..89ce58f3 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -4,7 +4,9 @@ import Agile, { createComputed, createState, createStorage, + createStorageManager, Item, + registerSharedStorageManager, } from '@agile-ts/core'; import Event from '@agile-ts/event'; import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger'; @@ -13,13 +15,14 @@ import { clone } from '@agile-ts/utils'; export const myStorage: any = {}; assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); -export const App = new Agile({ - localStorage: true, -}); +export const App = new Agile(); assignSharedAgileInstance(App); +const storageManager = createStorageManager({ localStorage: true }); +registerSharedStorageManager(storageManager); + // Register custom second Storage -App.registerStorage( +storageManager.register( createStorage({ key: 'myStorage', methods: { diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 3b4586cc..86ea5528 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -67,7 +67,7 @@ export function getStorageManager(): Storages | null { * * @param instance - Storage Manager to be registered as the default Storage Manager. */ -export const registerStorageManager = (instance: Storages) => { +export const registerSharedStorageManager = (instance: Storages) => { if (storageManager != null) { LogCodeManager.log('11:02:06', [], storageManager); } From e6d1adc1c2ac592d4cb768a6c250b3ad3a173285 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sun, 22 Aug 2021 08:02:32 +0200 Subject: [PATCH 18/44] fixed typos --- .../develop/functional-component-ts/yarn.lock | 28 +++++++++---------- packages/core/src/storages/index.ts | 9 ++++-- yarn.lock | 14 +++++++++- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/examples/react/develop/functional-component-ts/yarn.lock b/examples/react/develop/functional-component-ts/yarn.lock index b44f5283..d760f47c 100644 --- a/examples/react/develop/functional-component-ts/yarn.lock +++ b/examples/react/develop/functional-component-ts/yarn.lock @@ -3,36 +3,36 @@ "@agile-ts/api@file:.yalc/@agile-ts/api": - version "0.0.19" + version "0.0.21" dependencies: - "@agile-ts/utils" "^0.0.5" + "@agile-ts/utils" "^0.0.7" "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.1.0" + version "0.2.0-alpha.4" dependencies: - "@agile-ts/utils" "^0.0.5" + "@agile-ts/utils" "^0.0.7" "@agile-ts/event@file:.yalc/@agile-ts/event": - version "0.0.8" + version "0.0.10" "@agile-ts/logger@file:.yalc/@agile-ts/logger": - version "0.0.5" + version "0.0.7" dependencies: - "@agile-ts/utils" "^0.0.5" + "@agile-ts/utils" "^0.0.7" "@agile-ts/multieditor@file:.yalc/@agile-ts/multieditor": - version "0.0.18" + version "0.0.20" "@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree": - version "0.0.4" + version "0.0.5" "@agile-ts/react@file:.yalc/@agile-ts/react": - version "0.1.0" + version "0.2.0-alpha.1" -"@agile-ts/utils@^0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6" - integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA== +"@agile-ts/utils@^0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.7.tgz#3dd1add6b9f63d0a5bf35e71f54ac46448ae047f" + integrity sha512-OviTDC+ZbfyiUx8Gy8veS6YymC/tT6UeP23nT8V0EQV4F2MmuWqZ2yiKk+AYxZx8h74Ey8BVEUX6/ntpxhSNPw== "@babel/code-frame@7.8.3": version "7.8.3" diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 3b4586cc..2da27474 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -39,7 +39,9 @@ export function createStorage(config: CreateStorageConfigInterface): Storage { /** * Returns a newly created Storage Manager. * - * A Storage Manager manages the Storages. + * A Storage Manager manages all external Storages for an AgileTs + * and provides an interface to easily store, + * load and remove values from multiple Storages at once. * * @param config - Configuration object */ @@ -56,14 +58,15 @@ export function createStorageManager( } /** - * Returns the current Storage Manager. + * Returns the current registered Storage Manager. */ export function getStorageManager(): Storages | null { return storageManager; } /** - * Registers a Storage Manager. + * Registers the specified Storage Manager + * as default Storage Manager for all Agile Instances. * * @param instance - Storage Manager to be registered as the default Storage Manager. */ diff --git a/yarn.lock b/yarn.lock index f275cf02..5219edd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,8 +2,15 @@ # yarn lockfile v1 +"@agile-ts/core@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@agile-ts/core/-/core-0.1.3.tgz#d96dd4a20d65adce9aaba1c494b31e4e0dd1bb60" + integrity sha512-sHw9PMbqww0dwqLEZih9hIpZjMAmZB4yea7bkbqblNc1CRDKfCGeYGnNcg8GOqXfNfq5SywMGWo5KhhFFyx+ag== + dependencies: + "@agile-ts/utils" "^0.0.7" + "@agile-ts/core@file:packages/core": - version "0.1.2" + version "0.2.0-alpha.4" dependencies: "@agile-ts/utils" "^0.0.7" @@ -15,6 +22,11 @@ "@agile-ts/proxytree@file:packages/proxytree": version "0.0.5" +"@agile-ts/react@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@agile-ts/react/-/react-0.1.2.tgz#d07f6b935d9322cd60d2e9e3871da554b04460af" + integrity sha512-W4u2+X6KCeXPdkjit/NsMJG5nBsa7dNFaEzyfTsp5Cqbs99zLqY6dO8LUIYyhRt/+HBvEW9o64i/6Kqd59WM1Q== + "@akryum/winattr@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@akryum/winattr/-/winattr-3.0.0.tgz#c345d49f8415583897e345729c12b3503927dd11" From 5d2d04ce76af084bf945cbda22e7f096d358a9da Mon Sep 17 00:00:00 2001 From: BennoDev Date: Mon, 23 Aug 2021 08:02:31 +0200 Subject: [PATCH 19/44] fixed typos --- packages/core/src/state/index.ts | 10 +- packages/core/tests/unit/agile.test.ts | 54 -- .../tests/unit/state/state.enhanced.test.ts | 811 ++++++++++++++++++ packages/core/tests/unit/state/state.test.ts | 699 +-------------- 4 files changed, 823 insertions(+), 751 deletions(-) create mode 100644 packages/core/tests/unit/state/state.enhanced.test.ts diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index babcbee5..84b1ece9 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -44,13 +44,17 @@ export function createLightState( } /** - * Returns a newly created State. + * Returns a newly created enhanced State. * - * A State manages a piece of Information + * A enhanced State manages, like a normal State, a piece of Information * that we need to remember globally at a later point in time. * While providing a toolkit to use and mutate this piece of Information. * - * You can create as many global States as you need. + * The main difference to a normal State is however + * that an enhanced State provides a wider variety of inbuilt utilities (like a persist, undo, watch functionality) + * but requires a larger bundle size in return. + * + * You can create as many global enhanced States as you need. * * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) * diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index ee68d7ee..9d18f45b 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -93,10 +93,6 @@ describe('Agile Tests', () => { // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock (mockImplementation) expect(SubControllerMock).toHaveBeenCalledWith(agile); expect(agile.subController).toBeInstanceOf(SubController); - expect(StoragesMock).toHaveBeenCalledWith(agile, { - localStorage: false, - }); - expect(agile.storages).toBeInstanceOf(Storages); // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBeUndefined(); @@ -106,7 +102,6 @@ describe('Agile Tests', () => { const agile = new Agile({ waitForMount: false, bucket: false, - localStorage: true, bindGlobal: true, key: 'jeff', autoIntegrate: false, @@ -125,10 +120,6 @@ describe('Agile Tests', () => { // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock (mockImplementation) expect(SubControllerMock).toHaveBeenCalledWith(agile); expect(agile.subController).toBeInstanceOf(SubController); - expect(StoragesMock).toHaveBeenCalledWith(agile, { - localStorage: true, - }); - expect(agile.storages).toBeInstanceOf(Storages); // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBe(agile); @@ -174,39 +165,6 @@ describe('Agile Tests', () => { }); }); - describe('registerStorage function tests', () => { - beforeEach(() => { - agile.storages.register = jest.fn(); - }); - - it('should register provided Storage', () => { - const dummyStorage = new Storage({ - prefix: 'test', - methods: { - get: () => { - /* empty function */ - }, - set: () => { - /* empty function */ - }, - remove: () => { - /* empty function */ - }, - }, - key: 'myTestStorage', - }); - - const returnedAgile = agile.registerStorage(dummyStorage, { - default: false, - }); - - expect(returnedAgile).toBe(agile); - expect(agile.storages.register).toHaveBeenCalledWith(dummyStorage, { - default: false, - }); - }); - }); - describe('hasIntegration function tests', () => { it('should check if Agile has any registered Integration', () => { agile.hasIntegration(); @@ -214,17 +172,5 @@ describe('Agile Tests', () => { expect(agile.integrations.hasIntegration).toHaveBeenCalled(); }); }); - - describe('hasStorage function tests', () => { - beforeEach(() => { - agile.storages.hasStorage = jest.fn(); - }); - - it('should check if Agile has any registered Storage', () => { - agile.hasStorage(); - - expect(agile.storages.hasStorage).toHaveBeenCalled(); - }); - }); }); }); diff --git a/packages/core/tests/unit/state/state.enhanced.test.ts b/packages/core/tests/unit/state/state.enhanced.test.ts new file mode 100644 index 00000000..7c80127c --- /dev/null +++ b/packages/core/tests/unit/state/state.enhanced.test.ts @@ -0,0 +1,811 @@ +import { + State, + Agile, + StateObserver, + Observer, + StatePersistent, + EnhancedState, +} from '../../../src'; +import * as Utils from '@agile-ts/utils'; +import { LogMock } from '../../helper/logMock'; + +jest.mock('../../../src/state/state.persistent'); + +describe('Enhanced State Tests', () => { + let dummyAgile: Agile; + + beforeEach(() => { + LogMock.mockLogs(); + + dummyAgile = new Agile(); + + jest.spyOn(State.prototype, 'set'); + + jest.clearAllMocks(); + }); + + it('should create Enhanced State and should call initial set (default config)', () => { + // Overwrite select once to not call it + jest + .spyOn(EnhancedState.prototype, 'set') + .mockReturnValueOnce(undefined as any); + + const state = new EnhancedState(dummyAgile, 'coolValue'); + + expect(state.isPersisted).toBeFalsy(); + expect(state.persistent).toBeUndefined(); + expect(state.computeValueMethod).toBeUndefined(); + expect(state.computeExistsMethod).toBeInstanceOf(Function); + expect(state.currentInterval).toBeUndefined(); + + // Check if State was called with correct parameters + expect(state._key).toBeUndefined(); + expect(state.isSet).toBeFalsy(); + expect(state.isPlaceholder).toBeTruthy(); + expect(state.initialStateValue).toBe('coolValue'); + expect(state._value).toBe('coolValue'); + expect(state.previousStateValue).toBe('coolValue'); + expect(state.nextStateValue).toBe('coolValue'); + expect(state.observers['value']).toBeInstanceOf(StateObserver); + expect(Array.from(state.observers['value'].dependents)).toStrictEqual([]); + expect(state.observers['value']._key).toBeUndefined(); + expect(state.sideEffects).toStrictEqual({}); + }); + + it('should create Enhanced State and should call initial set (specific config)', () => { + // Overwrite select once to not call it + jest + .spyOn(EnhancedState.prototype, 'set') + .mockReturnValueOnce(undefined as any); + + const dummyObserver = new Observer(dummyAgile); + + const state = new EnhancedState(dummyAgile, 'coolValue', { + key: 'coolState', + dependents: [dummyObserver], + }); + + expect(state.isPersisted).toBeFalsy(); + expect(state.persistent).toBeUndefined(); + expect(state.computeValueMethod).toBeUndefined(); + expect(state.computeExistsMethod).toBeInstanceOf(Function); + expect(state.currentInterval).toBeUndefined(); + + // Check if State was called with correct parameters + expect(state._key).toBe('coolState'); + expect(state.isSet).toBeFalsy(); + expect(state.isPlaceholder).toBeTruthy(); + expect(state.initialStateValue).toBe('coolValue'); + expect(state._value).toBe('coolValue'); + expect(state.previousStateValue).toBe('coolValue'); + expect(state.nextStateValue).toBe('coolValue'); + expect(state.observers['value']).toBeInstanceOf(StateObserver); + expect(Array.from(state.observers['value'].dependents)).toStrictEqual([ + dummyObserver, + ]); + expect(state.observers['value']._key).toBe('coolState'); + expect(state.sideEffects).toStrictEqual({}); + }); + + it("should create Enhanced State and shouldn't call initial set (config.isPlaceholder = true)", () => { + // Overwrite select once to not call it + jest + .spyOn(EnhancedState.prototype, 'set') + .mockReturnValueOnce(undefined as any); + + const state = new EnhancedState(dummyAgile, 'coolValue', { + isPlaceholder: true, + }); + + expect(state.isPersisted).toBeFalsy(); + expect(state.persistent).toBeUndefined(); + expect(state.computeValueMethod).toBeUndefined(); + expect(state.computeExistsMethod).toBeInstanceOf(Function); + expect(state.currentInterval).toBeUndefined(); + + // Check if State was called with correct parameters + expect(state._key).toBeUndefined(); + expect(state.isSet).toBeFalsy(); + expect(state.isPlaceholder).toBeTruthy(); + expect(state.initialStateValue).toBe('coolValue'); + expect(state._value).toBe('coolValue'); + expect(state.previousStateValue).toBe('coolValue'); + expect(state.nextStateValue).toBe('coolValue'); + expect(state.observers['value']).toBeInstanceOf(StateObserver); + expect(Array.from(state.observers['value'].dependents)).toStrictEqual([]); + expect(state.observers['value']._key).toBeUndefined(); + expect(state.sideEffects).toStrictEqual({}); + }); + + describe('State Function Tests', () => { + let numberState: EnhancedState; + let objectState: EnhancedState<{ name: string; age: number }>; + let arrayState: EnhancedState; + let booleanState: EnhancedState; + + beforeEach(() => { + numberState = new EnhancedState(dummyAgile, 10, { + key: 'numberStateKey', + }); + objectState = new EnhancedState<{ name: string; age: number }>( + dummyAgile, + { name: 'jeff', age: 10 }, + { + key: 'objectStateKey', + } + ); + arrayState = new EnhancedState(dummyAgile, ['jeff'], { + key: 'arrayStateKey', + }); + booleanState = new EnhancedState(dummyAgile, false, { + key: 'booleanStateKey', + }); + }); + + describe('setKey function tests', () => { + // TODO + }); + + describe('undo function tests', () => { + beforeEach(() => { + numberState.set = jest.fn(); + }); + + it('should assign previousStateValue to currentValue (default config)', () => { + numberState.previousStateValue = 99; + + numberState.undo(); + + expect(numberState.set).toHaveBeenCalledWith( + numberState.previousStateValue, + {} + ); + }); + + it('should assign previousStateValue to currentValue (specific config)', () => { + numberState.previousStateValue = 99; + + numberState.undo({ + force: true, + storage: false, + }); + + expect(numberState.set).toHaveBeenCalledWith( + numberState.previousStateValue, + { + force: true, + storage: false, + } + ); + }); + }); + + describe('reset function tests', () => { + beforeEach(() => { + numberState.set = jest.fn(); + }); + + it('should assign initialStateValue to currentValue (default config)', () => { + numberState.initialStateValue = 99; + + numberState.reset(); + + expect(numberState.set).toHaveBeenCalledWith( + numberState.initialStateValue, + {} + ); + }); + + it('should assign initialStateValue to currentValue (specific config)', () => { + numberState.initialStateValue = 99; + + numberState.reset({ + force: true, + storage: false, + }); + + expect(numberState.set).toHaveBeenCalledWith( + numberState.initialStateValue, + { + force: true, + storage: false, + } + ); + }); + }); + + describe('patch function tests', () => { + beforeEach(() => { + objectState.ingest = jest.fn(); + numberState.ingest = jest.fn(); + arrayState.ingest = jest.fn(); + jest.spyOn(Utils, 'flatMerge'); + }); + + it("shouldn't patch specified object value into a not object based State (default config)", () => { + numberState.patch({ changed: 'object' }); + + LogMock.hasLoggedCode('14:03:02'); + expect(objectState.ingest).not.toHaveBeenCalled(); + }); + + it("shouldn't patch specified non object value into a object based State (default config)", () => { + objectState.patch('number' as any); + + LogMock.hasLoggedCode('00:03:01', ['TargetWithChanges', 'object']); + expect(objectState.ingest).not.toHaveBeenCalled(); + }); + + it('should patch specified object value into a object based State (default config)', () => { + objectState.patch({ name: 'frank' }); + + expect(Utils.flatMerge).toHaveBeenCalledWith( + { age: 10, name: 'jeff' }, + { name: 'frank' }, + { addNewProperties: true } + ); + expect(objectState.nextStateValue).toStrictEqual({ + age: 10, + name: 'frank', + }); + expect(objectState.ingest).toHaveBeenCalledWith({}); + }); + + it('should patch specified object value into a object based State (specific config)', () => { + objectState.patch( + { name: 'frank' }, + { + addNewProperties: false, + background: true, + force: true, + overwrite: true, + sideEffects: { + enabled: false, + }, + } + ); + + expect(Utils.flatMerge).toHaveBeenCalledWith( + { age: 10, name: 'jeff' }, + { name: 'frank' }, + { addNewProperties: false } + ); + expect(objectState.nextStateValue).toStrictEqual({ + age: 10, + name: 'frank', + }); + expect(objectState.ingest).toHaveBeenCalledWith({ + background: true, + force: true, + overwrite: true, + sideEffects: { + enabled: false, + }, + }); + }); + + it('should patch specified array value into a array based State (default config)', () => { + arrayState.patch(['hi']); + + expect(Utils.flatMerge).not.toHaveBeenCalled(); + expect(arrayState.nextStateValue).toStrictEqual(['jeff', 'hi']); + expect(arrayState.ingest).toHaveBeenCalledWith({}); + }); + + it('should patch specified array value into a object based State', () => { + objectState.patch(['hi'], { addNewProperties: true }); + + expect(Utils.flatMerge).toHaveBeenCalledWith( + { age: 10, name: 'jeff' }, + ['hi'], + { addNewProperties: true } + ); + expect(objectState.nextStateValue).toStrictEqual({ + 0: 'hi', + age: 10, + name: 'jeff', + }); + expect(objectState.ingest).toHaveBeenCalledWith({}); + }); + }); + + describe('watch function tests', () => { + let dummyCallbackFunction; + + beforeEach(() => { + jest.spyOn(numberState, 'addSideEffect'); + dummyCallbackFunction = jest.fn(); + }); + + it('should add passed watcherFunction to watchers at passed key', () => { + const response = numberState.watch('dummyKey', dummyCallbackFunction); + + expect(response).toBe(numberState); + expect(numberState.addSideEffect).toHaveBeenCalledWith( + 'dummyKey', + expect.any(Function), + { weight: 0 } + ); + + // Test whether registered callback function is called + numberState.sideEffects['dummyKey'].callback(numberState); + expect(dummyCallbackFunction).toHaveBeenCalledWith( + numberState._value, + 'dummyKey' + ); + }); + + it('should add passed watcherFunction to watchers at random key if no key passed and return that generated key', () => { + jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); + + const response = numberState.watch(dummyCallbackFunction); + + expect(response).toBe('randomKey'); + expect(numberState.addSideEffect).toHaveBeenCalledWith( + 'randomKey', + expect.any(Function), + { weight: 0 } + ); + expect(Utils.generateId).toHaveBeenCalled(); + + // Test whether registered callback function is called + numberState.sideEffects['randomKey'].callback(numberState); + expect(dummyCallbackFunction).toHaveBeenCalledWith( + numberState._value, + 'randomKey' + ); + }); + + it("shouldn't add passed invalid watcherFunction to watchers at passed key", () => { + const response = numberState.watch( + 'dummyKey', + 'noFunction hehe' as any + ); + + expect(response).toBe(numberState); + expect(numberState.addSideEffect).not.toHaveBeenCalled(); + LogMock.hasLoggedCode('00:03:01', ['Watcher Callback', 'function']); + }); + }); + + describe('removeWatcher function tests', () => { + beforeEach(() => { + jest.spyOn(numberState, 'removeSideEffect'); + }); + + it('should remove watcher at key from State', () => { + numberState.removeWatcher('dummyKey'); + + expect(numberState.removeSideEffect).toHaveBeenCalledWith('dummyKey'); + }); + }); + + describe('onInaugurated function tests', () => { + let dummyCallbackFunction; + + beforeEach(() => { + jest.spyOn(numberState, 'watch'); + jest.spyOn(numberState, 'removeSideEffect'); + dummyCallbackFunction = jest.fn(); + }); + + it('should add watcher called InauguratedWatcherKey to State', () => { + numberState.onInaugurated(dummyCallbackFunction); + + expect(numberState.watch).toHaveBeenCalledWith( + 'InauguratedWatcherKey', + expect.any(Function) + ); + }); + + it('should remove itself after invoking', () => { + numberState.onInaugurated(dummyCallbackFunction); + + // Call Inaugurated Watcher + numberState.sideEffects['InauguratedWatcherKey'].callback(numberState); + + expect(dummyCallbackFunction).toHaveBeenCalledWith( + numberState.value, + 'InauguratedWatcherKey' + ); + expect(numberState.removeSideEffect).toHaveBeenCalledWith( + 'InauguratedWatcherKey' + ); + }); + }); + + describe('persist function tests', () => { + it('should create persistent with StateKey (default config)', () => { + numberState.persist(); + + expect(numberState.persistent).toBeInstanceOf(StatePersistent); + expect(StatePersistent).toHaveBeenCalledWith(numberState, { + instantiate: true, + storageKeys: [], + key: numberState._key, + defaultStorageKey: null, + }); + }); + + it('should create persistent with StateKey (specific config)', () => { + numberState.persist({ + storageKeys: ['test1', 'test2'], + loadValue: false, + defaultStorageKey: 'test1', + }); + + expect(numberState.persistent).toBeInstanceOf(StatePersistent); + expect(StatePersistent).toHaveBeenCalledWith(numberState, { + instantiate: false, + storageKeys: ['test1', 'test2'], + key: numberState._key, + defaultStorageKey: 'test1', + }); + }); + + it('should create persistent with passed Key (default config)', () => { + numberState.persist('passedKey'); + + expect(numberState.persistent).toBeInstanceOf(StatePersistent); + expect(StatePersistent).toHaveBeenCalledWith(numberState, { + instantiate: true, + storageKeys: [], + key: 'passedKey', + defaultStorageKey: null, + }); + }); + + it('should create persistent with passed Key (specific config)', () => { + numberState.persist('passedKey', { + storageKeys: ['test1', 'test2'], + loadValue: false, + defaultStorageKey: 'test1', + }); + + expect(numberState.persistent).toBeInstanceOf(StatePersistent); + expect(StatePersistent).toHaveBeenCalledWith(numberState, { + instantiate: false, + storageKeys: ['test1', 'test2'], + key: 'passedKey', + defaultStorageKey: 'test1', + }); + }); + + it("shouldn't overwrite existing Persistent", () => { + const dummyPersistent = new StatePersistent(numberState); + numberState.persistent = dummyPersistent; + numberState.isPersisted = true; + jest.clearAllMocks(); + + numberState.persist('newPersistentKey'); + + expect(numberState.persistent).toBe(dummyPersistent); + // expect(numberState.persistent._key).toBe("newPersistentKey"); // Can not test because of Mocking Persistent + expect(StatePersistent).not.toHaveBeenCalled(); + }); + }); + + describe('onLoad function tests', () => { + const dummyCallbackFunction = jest.fn(); + + it("should set onLoad function if State is persisted and shouldn't call it initially (state.isPersisted = false)", () => { + numberState.persistent = new StatePersistent(numberState); + numberState.isPersisted = false; + + numberState.onLoad(dummyCallbackFunction); + + expect(numberState.persistent.onLoad).toBe(dummyCallbackFunction); + expect(dummyCallbackFunction).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); + }); + + it('should set onLoad function if State is persisted and should call it initially (state.isPersisted = true)', () => { + numberState.persistent = new StatePersistent(numberState); + numberState.isPersisted = true; + + numberState.onLoad(dummyCallbackFunction); + + expect(numberState.persistent.onLoad).toBe(dummyCallbackFunction); + expect(dummyCallbackFunction).toHaveBeenCalledWith(true); + LogMock.hasNotLogged('warn'); + }); + + it("shouldn't set onLoad function if State isn't persisted", () => { + numberState.onLoad(dummyCallbackFunction); + + expect(numberState?.persistent?.onLoad).toBeUndefined(); + expect(dummyCallbackFunction).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); + }); + + it("shouldn't set invalid onLoad callback function", () => { + numberState.persistent = new StatePersistent(numberState); + numberState.isPersisted = false; + + numberState.onLoad(10 as any); + + expect(numberState?.persistent?.onLoad).toBeUndefined(); + LogMock.hasLoggedCode('00:03:01', ['OnLoad Callback', 'function']); + }); + }); + + describe('interval function tests', () => { + const dummyCallbackFunction = jest.fn(); + const dummyCallbackFunction2 = jest.fn(); + + beforeEach(() => { + jest.useFakeTimers(); + numberState.set = jest.fn(); + }); + + afterEach(() => { + jest.clearAllTimers(); + }); + + it('should create an interval (without custom milliseconds)', () => { + dummyCallbackFunction.mockReturnValueOnce(10); + + numberState.interval(dummyCallbackFunction); + + jest.runTimersToTime(1000); // travel 1000s in time -> execute interval + + expect(setInterval).toHaveBeenCalledTimes(1); + expect(setInterval).toHaveBeenLastCalledWith( + expect.any(Function), + 1000 + ); + expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value); + expect(numberState.set).toHaveBeenCalledWith(10); + expect(numberState.currentInterval).toEqual({ + id: expect.anything(), + ref: expect.anything(), + unref: expect.anything(), + }); + LogMock.hasNotLogged('warn'); + }); + + it('should create an interval (with custom milliseconds)', () => { + dummyCallbackFunction.mockReturnValueOnce(10); + + numberState.interval(dummyCallbackFunction, 2000); + + jest.runTimersToTime(2000); // travel 2000 in time -> execute interval + + expect(setInterval).toHaveBeenCalledTimes(1); + expect(setInterval).toHaveBeenLastCalledWith( + expect.any(Function), + 2000 + ); + expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value); + expect(numberState.set).toHaveBeenCalledWith(10); + expect(numberState.currentInterval).toEqual({ + id: expect.anything(), + ref: expect.anything(), + unref: expect.anything(), + }); + LogMock.hasNotLogged('warn'); + }); + + it("shouldn't be able to create second interval and print warning", () => { + numberState.interval(dummyCallbackFunction, 3000); + const currentInterval = numberState.currentInterval; + numberState.interval(dummyCallbackFunction2); + + expect(setInterval).toHaveBeenCalledTimes(1); + expect(setInterval).toHaveBeenLastCalledWith( + expect.any(Function), + 3000 + ); + expect(numberState.currentInterval).toStrictEqual(currentInterval); + LogMock.hasLoggedCode('14:03:03', [], numberState.currentInterval); + }); + + it("shouldn't set invalid interval callback function", () => { + numberState.interval(10 as any); + + expect(setInterval).not.toHaveBeenCalled(); + expect(numberState.currentInterval).toBeUndefined(); + LogMock.hasLoggedCode('00:03:01', ['Interval Callback', 'function']); + }); + }); + + describe('clearInterval function tests', () => { + const dummyCallbackFunction = jest.fn(); + + beforeEach(() => { + jest.useFakeTimers(); + numberState.set = jest.fn(); + }); + + afterEach(() => { + jest.clearAllTimers(); + }); + + it('should clear existing interval', () => { + numberState.interval(dummyCallbackFunction); + const currentInterval = numberState.currentInterval; + + numberState.clearInterval(); + + expect(clearInterval).toHaveBeenCalledTimes(1); + expect(clearInterval).toHaveBeenLastCalledWith(currentInterval); + expect(numberState.currentInterval).toBeUndefined(); + }); + + it("shouldn't clear not existing interval", () => { + numberState.clearInterval(); + + expect(clearInterval).not.toHaveBeenCalled(); + expect(numberState.currentInterval).toBeUndefined(); + }); + }); + + describe('exists get function tests', () => { + it('should return true if State is no placeholder and computeExistsMethod returns true', () => { + numberState.computeExistsMethod = jest.fn().mockReturnValueOnce(true); + numberState.isPlaceholder = false; + + expect(numberState.exists).toBeTruthy(); + expect(numberState.computeExistsMethod).toHaveBeenCalledWith( + numberState.value + ); + }); + + it('should return false if State is no placeholder and computeExistsMethod returns false', () => { + numberState.computeExistsMethod = jest.fn().mockReturnValueOnce(false); + numberState.isPlaceholder = false; + + expect(numberState.exists).toBeFalsy(); + expect(numberState.computeExistsMethod).toHaveBeenCalledWith( + numberState.value + ); + }); + + it('should return false if State is placeholder"', () => { + numberState.computeExistsMethod = jest.fn(() => true); + numberState.isPlaceholder = true; + + expect(numberState.exists).toBeFalsy(); + expect(numberState.computeExistsMethod).not.toHaveBeenCalled(); // since isPlaceholder gets checked first + }); + }); + + describe('computeExists function tests', () => { + it('should assign passed function to computeExistsMethod', () => { + const computeMethod = (value) => value === null; + + numberState.computeExists(computeMethod); + + expect(numberState.computeExistsMethod).toBe(computeMethod); + LogMock.hasNotLogged('warn'); + }); + + it("shouldn't assign passed invalid function to computeExistsMethod", () => { + numberState.computeExists(10 as any); + + expect(numberState.computeExistsMethod).toBeInstanceOf(Function); + LogMock.hasLoggedCode('00:03:01', [ + 'Compute Exists Method', + 'function', + ]); + }); + }); + + describe('is function tests', () => { + beforeEach(() => { + jest.spyOn(Utils, 'equal'); + }); + + it('should return true if passed value is equal to the current StateValue', () => { + const response = numberState.is(10); + + expect(response).toBeTruthy(); + expect(Utils.equal).toHaveBeenCalledWith(10, numberState._value); + }); + + it('should return false if passed value is not equal to the current StateValue', () => { + const response = numberState.is(20); + + expect(response).toBeFalsy(); + expect(Utils.equal).toHaveBeenCalledWith(20, numberState._value); + }); + }); + + describe('isNot function tests', () => { + beforeEach(() => { + jest.spyOn(Utils, 'notEqual'); + }); + + it('should return false if passed value is equal to the current StateValue', () => { + const response = numberState.isNot(10); + + expect(response).toBeFalsy(); + expect(Utils.notEqual).toHaveBeenCalledWith(10, numberState._value); + }); + + it('should return true if passed value is not equal to the current StateValue', () => { + const response = numberState.isNot(20); + + expect(response).toBeTruthy(); + expect(Utils.notEqual).toHaveBeenCalledWith(20, numberState._value); + }); + }); + + describe('invert function tests', () => { + let dummyState: EnhancedState; + + beforeEach(() => { + dummyState = new EnhancedState(dummyAgile, null); + + dummyState.set = jest.fn(); + }); + + it('should invert value of the type boolean', () => { + dummyState.nextStateValue = false; + + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith(true); + }); + + it('should invert value of the type number', () => { + dummyState.nextStateValue = 10; + + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith(-10); + }); + + it('should invert value of the type array', () => { + dummyState.nextStateValue = ['1', '2', '3']; + + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith(['3', '2', '1']); + }); + + it('should invert value of the type string', () => { + dummyState.nextStateValue = 'jeff'; + + dummyState.invert(); + + expect(dummyState.set).toHaveBeenCalledWith('ffej'); + }); + + it("shouldn't invert not invertible types like function, null, undefined, object", () => { + dummyState.nextStateValue = () => { + // empty + }; + + dummyState.invert(); + + expect(dummyState.set).not.toHaveBeenCalled(); + LogMock.hasLoggedCode('14:03:04', ['function']); + }); + }); + + describe('computeValue function tests', () => { + beforeEach(() => { + numberState.set = jest.fn(); + }); + + it('should assign passed function to computeValueMethod and compute State value initially', () => { + const computeMethod = () => 10; + + numberState.computeValue(computeMethod); + + expect(numberState.set).toHaveBeenCalledWith(10); + expect(numberState.computeValueMethod).toBe(computeMethod); + LogMock.hasNotLogged('warn'); + }); + + it("shouldn't assign passed invalid function to computeValueMethod", () => { + numberState.computeValue(10 as any); + + expect(numberState.set).not.toHaveBeenCalled(); + expect(numberState.computeValueMethod).toBeUndefined(); + LogMock.hasLoggedCode('00:03:01', ['Compute Value Method', 'function']); + }); + }); + }); +}); diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index 4155c813..1b46a1aa 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -3,10 +3,8 @@ import { Agile, StateObserver, Observer, - StatePersistent, ComputedTracker, } from '../../../src'; -import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../helper/logMock'; jest.mock('../../../src/state/state.persistent'); @@ -17,7 +15,7 @@ describe('State Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.spyOn(State.prototype, 'set'); @@ -42,10 +40,6 @@ describe('State Tests', () => { expect(Array.from(state.observers['value'].dependents)).toStrictEqual([]); expect(state.observers['value']._key).toBeUndefined(); expect(state.sideEffects).toStrictEqual({}); - expect(state.computeValueMethod).toBeUndefined(); - expect(state.computeExistsMethod).toBeInstanceOf(Function); - expect(state.isPersisted).toBeFalsy(); - expect(state.persistent).toBeUndefined(); }); it('should create State and should call initial set (specific config)', () => { @@ -73,10 +67,6 @@ describe('State Tests', () => { ]); expect(state.observers['value']._key).toBe('coolState'); expect(state.sideEffects).toStrictEqual({}); - expect(state.computeValueMethod).toBeUndefined(); - expect(state.computeExistsMethod).toBeInstanceOf(Function); - expect(state.isPersisted).toBeFalsy(); - expect(state.persistent).toBeUndefined(); }); it("should create State and shouldn't call initial set (config.isPlaceholder = true)", () => { @@ -97,10 +87,6 @@ describe('State Tests', () => { expect(Array.from(state.observers['value'].dependents)).toStrictEqual([]); expect(state.observers['value']._key).toBeUndefined(); expect(state.sideEffects).toStrictEqual({}); - expect(state.computeValueMethod).toBeUndefined(); - expect(state.computeExistsMethod).toBeInstanceOf(Function); - expect(state.isPersisted).toBeFalsy(); - expect(state.persistent).toBeUndefined(); }); describe('State Function Tests', () => { @@ -175,45 +161,31 @@ describe('State Tests', () => { beforeEach(() => { dummyOutputObserver = new StateObserver(numberState, { key: 'oldKey' }); - numberState.persistent = new StatePersistent(numberState); numberState.observers['output'] = dummyOutputObserver; - - numberState.persistent.setKey = jest.fn(); }); it('should update existing Key in all instances', () => { - if (numberState.persistent) - numberState.persistent._key = 'numberStateKey'; - numberState.setKey('newKey'); expect(numberState._key).toBe('newKey'); expect(numberState.observers['value']._key).toBe('newKey'); expect(numberState.observers['output']._key).toBe('newKey'); - expect(numberState.persistent?.setKey).toHaveBeenCalledWith('newKey'); }); it("should update existing Key in all instances except persistent if the StateKey and PersistKey aren't equal", () => { - if (numberState.persistent) numberState.persistent._key = 'randomKey'; - numberState.setKey('newKey'); expect(numberState._key).toBe('newKey'); expect(numberState.observers['value']._key).toBe('newKey'); expect(numberState.observers['output']._key).toBe('newKey'); - expect(numberState.persistent?.setKey).not.toHaveBeenCalled(); }); it('should update existing Key in all instances except persistent if new StateKey is undefined', () => { - if (numberState.persistent) - numberState.persistent._key = 'numberStateKey'; - numberState.setKey(undefined); expect(numberState._key).toBeUndefined(); expect(numberState.observers['value']._key).toBeUndefined(); expect(numberState.observers['output']._key).toBeUndefined(); - expect(numberState.persistent?.setKey).not.toHaveBeenCalled(); }); }); @@ -277,9 +249,10 @@ describe('State Tests', () => { LogMock.hasNotLogged('warn'); LogMock.hasNotLogged('error'); - expect( - numberState.observers['value'].ingestValue - ).toHaveBeenCalledWith('coolValue', { force: false }); + expect(numberState.observers['value'].ingestValue).toHaveBeenCalledWith( + 'coolValue', + { force: false } + ); }); }); @@ -307,668 +280,6 @@ describe('State Tests', () => { }); }); - describe('undo function tests', () => { - beforeEach(() => { - numberState.set = jest.fn(); - }); - - it('should assign previousStateValue to currentValue (default config)', () => { - numberState.previousStateValue = 99; - - numberState.undo(); - - expect(numberState.set).toHaveBeenCalledWith( - numberState.previousStateValue, - {} - ); - }); - - it('should assign previousStateValue to currentValue (specific config)', () => { - numberState.previousStateValue = 99; - - numberState.undo({ - force: true, - storage: false, - }); - - expect(numberState.set).toHaveBeenCalledWith( - numberState.previousStateValue, - { - force: true, - storage: false, - } - ); - }); - }); - - describe('reset function tests', () => { - beforeEach(() => { - numberState.set = jest.fn(); - }); - - it('should assign initialStateValue to currentValue (default config)', () => { - numberState.initialStateValue = 99; - - numberState.reset(); - - expect(numberState.set).toHaveBeenCalledWith( - numberState.initialStateValue, - {} - ); - }); - - it('should assign initialStateValue to currentValue (specific config)', () => { - numberState.initialStateValue = 99; - - numberState.reset({ - force: true, - storage: false, - }); - - expect(numberState.set).toHaveBeenCalledWith( - numberState.initialStateValue, - { - force: true, - storage: false, - } - ); - }); - }); - - describe('patch function tests', () => { - beforeEach(() => { - objectState.ingest = jest.fn(); - numberState.ingest = jest.fn(); - arrayState.ingest = jest.fn(); - jest.spyOn(Utils, 'flatMerge'); - }); - - it("shouldn't patch specified object value into a not object based State (default config)", () => { - numberState.patch({ changed: 'object' }); - - LogMock.hasLoggedCode('14:03:02'); - expect(objectState.ingest).not.toHaveBeenCalled(); - }); - - it("shouldn't patch specified non object value into a object based State (default config)", () => { - objectState.patch('number' as any); - - LogMock.hasLoggedCode('00:03:01', ['TargetWithChanges', 'object']); - expect(objectState.ingest).not.toHaveBeenCalled(); - }); - - it('should patch specified object value into a object based State (default config)', () => { - objectState.patch({ name: 'frank' }); - - expect(Utils.flatMerge).toHaveBeenCalledWith( - { age: 10, name: 'jeff' }, - { name: 'frank' }, - { addNewProperties: true } - ); - expect(objectState.nextStateValue).toStrictEqual({ - age: 10, - name: 'frank', - }); - expect(objectState.ingest).toHaveBeenCalledWith({}); - }); - - it('should patch specified object value into a object based State (specific config)', () => { - objectState.patch( - { name: 'frank' }, - { - addNewProperties: false, - background: true, - force: true, - overwrite: true, - sideEffects: { - enabled: false, - }, - } - ); - - expect(Utils.flatMerge).toHaveBeenCalledWith( - { age: 10, name: 'jeff' }, - { name: 'frank' }, - { addNewProperties: false } - ); - expect(objectState.nextStateValue).toStrictEqual({ - age: 10, - name: 'frank', - }); - expect(objectState.ingest).toHaveBeenCalledWith({ - background: true, - force: true, - overwrite: true, - sideEffects: { - enabled: false, - }, - }); - }); - - it('should patch specified array value into a array based State (default config)', () => { - arrayState.patch(['hi']); - - expect(Utils.flatMerge).not.toHaveBeenCalled(); - expect(arrayState.nextStateValue).toStrictEqual(['jeff', 'hi']); - expect(arrayState.ingest).toHaveBeenCalledWith({}); - }); - - it('should patch specified array value into a object based State', () => { - objectState.patch(['hi'], { addNewProperties: true }); - - expect(Utils.flatMerge).toHaveBeenCalledWith( - { age: 10, name: 'jeff' }, - ['hi'], - { addNewProperties: true } - ); - expect(objectState.nextStateValue).toStrictEqual({ - 0: 'hi', - age: 10, - name: 'jeff', - }); - expect(objectState.ingest).toHaveBeenCalledWith({}); - }); - }); - - describe('watch function tests', () => { - let dummyCallbackFunction; - - beforeEach(() => { - jest.spyOn(numberState, 'addSideEffect'); - dummyCallbackFunction = jest.fn(); - }); - - it('should add passed watcherFunction to watchers at passed key', () => { - const response = numberState.watch('dummyKey', dummyCallbackFunction); - - expect(response).toBe(numberState); - expect(numberState.addSideEffect).toHaveBeenCalledWith( - 'dummyKey', - expect.any(Function), - { weight: 0 } - ); - - // Test whether registered callback function is called - numberState.sideEffects['dummyKey'].callback(numberState); - expect(dummyCallbackFunction).toHaveBeenCalledWith( - numberState._value, - 'dummyKey' - ); - }); - - it('should add passed watcherFunction to watchers at random key if no key passed and return that generated key', () => { - jest.spyOn(Utils, 'generateId').mockReturnValue('randomKey'); - - const response = numberState.watch(dummyCallbackFunction); - - expect(response).toBe('randomKey'); - expect(numberState.addSideEffect).toHaveBeenCalledWith( - 'randomKey', - expect.any(Function), - { weight: 0 } - ); - expect(Utils.generateId).toHaveBeenCalled(); - - // Test whether registered callback function is called - numberState.sideEffects['randomKey'].callback(numberState); - expect(dummyCallbackFunction).toHaveBeenCalledWith( - numberState._value, - 'randomKey' - ); - }); - - it("shouldn't add passed invalid watcherFunction to watchers at passed key", () => { - const response = numberState.watch( - 'dummyKey', - 'noFunction hehe' as any - ); - - expect(response).toBe(numberState); - expect(numberState.addSideEffect).not.toHaveBeenCalled(); - LogMock.hasLoggedCode('00:03:01', ['Watcher Callback', 'function']); - }); - }); - - describe('removeWatcher function tests', () => { - beforeEach(() => { - jest.spyOn(numberState, 'removeSideEffect'); - }); - - it('should remove watcher at key from State', () => { - numberState.removeWatcher('dummyKey'); - - expect(numberState.removeSideEffect).toHaveBeenCalledWith('dummyKey'); - }); - }); - - describe('onInaugurated function tests', () => { - let dummyCallbackFunction; - - beforeEach(() => { - jest.spyOn(numberState, 'watch'); - jest.spyOn(numberState, 'removeSideEffect'); - dummyCallbackFunction = jest.fn(); - }); - - it('should add watcher called InauguratedWatcherKey to State', () => { - numberState.onInaugurated(dummyCallbackFunction); - - expect(numberState.watch).toHaveBeenCalledWith( - 'InauguratedWatcherKey', - expect.any(Function) - ); - }); - - it('should remove itself after invoking', () => { - numberState.onInaugurated(dummyCallbackFunction); - - // Call Inaugurated Watcher - numberState.sideEffects['InauguratedWatcherKey'].callback(numberState); - - expect(dummyCallbackFunction).toHaveBeenCalledWith( - numberState.value, - 'InauguratedWatcherKey' - ); - expect(numberState.removeSideEffect).toHaveBeenCalledWith( - 'InauguratedWatcherKey' - ); - }); - }); - - describe('persist function tests', () => { - it('should create persistent with StateKey (default config)', () => { - numberState.persist(); - - expect(numberState.persistent).toBeInstanceOf(StatePersistent); - expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: true, - storageKeys: [], - key: numberState._key, - defaultStorageKey: null, - }); - }); - - it('should create persistent with StateKey (specific config)', () => { - numberState.persist({ - storageKeys: ['test1', 'test2'], - loadValue: false, - defaultStorageKey: 'test1', - }); - - expect(numberState.persistent).toBeInstanceOf(StatePersistent); - expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: false, - storageKeys: ['test1', 'test2'], - key: numberState._key, - defaultStorageKey: 'test1', - }); - }); - - it('should create persistent with passed Key (default config)', () => { - numberState.persist('passedKey'); - - expect(numberState.persistent).toBeInstanceOf(StatePersistent); - expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: true, - storageKeys: [], - key: 'passedKey', - defaultStorageKey: null, - }); - }); - - it('should create persistent with passed Key (specific config)', () => { - numberState.persist('passedKey', { - storageKeys: ['test1', 'test2'], - loadValue: false, - defaultStorageKey: 'test1', - }); - - expect(numberState.persistent).toBeInstanceOf(StatePersistent); - expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: false, - storageKeys: ['test1', 'test2'], - key: 'passedKey', - defaultStorageKey: 'test1', - }); - }); - - it("shouldn't overwrite existing Persistent", () => { - const dummyPersistent = new StatePersistent(numberState); - numberState.persistent = dummyPersistent; - numberState.isPersisted = true; - jest.clearAllMocks(); - - numberState.persist('newPersistentKey'); - - expect(numberState.persistent).toBe(dummyPersistent); - // expect(numberState.persistent._key).toBe("newPersistentKey"); // Can not test because of Mocking Persistent - expect(StatePersistent).not.toHaveBeenCalled(); - }); - }); - - describe('onLoad function tests', () => { - const dummyCallbackFunction = jest.fn(); - - it("should set onLoad function if State is persisted and shouldn't call it initially (state.isPersisted = false)", () => { - numberState.persistent = new StatePersistent(numberState); - numberState.isPersisted = false; - - numberState.onLoad(dummyCallbackFunction); - - expect(numberState.persistent.onLoad).toBe(dummyCallbackFunction); - expect(dummyCallbackFunction).not.toHaveBeenCalled(); - LogMock.hasNotLogged('warn'); - }); - - it('should set onLoad function if State is persisted and should call it initially (state.isPersisted = true)', () => { - numberState.persistent = new StatePersistent(numberState); - numberState.isPersisted = true; - - numberState.onLoad(dummyCallbackFunction); - - expect(numberState.persistent.onLoad).toBe(dummyCallbackFunction); - expect(dummyCallbackFunction).toHaveBeenCalledWith(true); - LogMock.hasNotLogged('warn'); - }); - - it("shouldn't set onLoad function if State isn't persisted", () => { - numberState.onLoad(dummyCallbackFunction); - - expect(numberState?.persistent?.onLoad).toBeUndefined(); - expect(dummyCallbackFunction).not.toHaveBeenCalled(); - LogMock.hasNotLogged('warn'); - }); - - it("shouldn't set invalid onLoad callback function", () => { - numberState.persistent = new StatePersistent(numberState); - numberState.isPersisted = false; - - numberState.onLoad(10 as any); - - expect(numberState?.persistent?.onLoad).toBeUndefined(); - LogMock.hasLoggedCode('00:03:01', ['OnLoad Callback', 'function']); - }); - }); - - describe('interval function tests', () => { - const dummyCallbackFunction = jest.fn(); - const dummyCallbackFunction2 = jest.fn(); - - beforeEach(() => { - jest.useFakeTimers(); - numberState.set = jest.fn(); - }); - - afterEach(() => { - jest.clearAllTimers(); - }); - - it('should create an interval (without custom milliseconds)', () => { - dummyCallbackFunction.mockReturnValueOnce(10); - - numberState.interval(dummyCallbackFunction); - - jest.runTimersToTime(1000); // travel 1000s in time -> execute interval - - expect(setInterval).toHaveBeenCalledTimes(1); - expect(setInterval).toHaveBeenLastCalledWith( - expect.any(Function), - 1000 - ); - expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value); - expect(numberState.set).toHaveBeenCalledWith(10); - expect(numberState.currentInterval).toEqual({ - id: expect.anything(), - ref: expect.anything(), - unref: expect.anything(), - }); - LogMock.hasNotLogged('warn'); - }); - - it('should create an interval (with custom milliseconds)', () => { - dummyCallbackFunction.mockReturnValueOnce(10); - - numberState.interval(dummyCallbackFunction, 2000); - - jest.runTimersToTime(2000); // travel 2000 in time -> execute interval - - expect(setInterval).toHaveBeenCalledTimes(1); - expect(setInterval).toHaveBeenLastCalledWith( - expect.any(Function), - 2000 - ); - expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value); - expect(numberState.set).toHaveBeenCalledWith(10); - expect(numberState.currentInterval).toEqual({ - id: expect.anything(), - ref: expect.anything(), - unref: expect.anything(), - }); - LogMock.hasNotLogged('warn'); - }); - - it("shouldn't be able to create second interval and print warning", () => { - numberState.interval(dummyCallbackFunction, 3000); - const currentInterval = numberState.currentInterval; - numberState.interval(dummyCallbackFunction2); - - expect(setInterval).toHaveBeenCalledTimes(1); - expect(setInterval).toHaveBeenLastCalledWith( - expect.any(Function), - 3000 - ); - expect(numberState.currentInterval).toStrictEqual(currentInterval); - LogMock.hasLoggedCode('14:03:03', [], numberState.currentInterval); - }); - - it("shouldn't set invalid interval callback function", () => { - numberState.interval(10 as any); - - expect(setInterval).not.toHaveBeenCalled(); - expect(numberState.currentInterval).toBeUndefined(); - LogMock.hasLoggedCode('00:03:01', ['Interval Callback', 'function']); - }); - }); - - describe('clearInterval function tests', () => { - const dummyCallbackFunction = jest.fn(); - - beforeEach(() => { - jest.useFakeTimers(); - numberState.set = jest.fn(); - }); - - afterEach(() => { - jest.clearAllTimers(); - }); - - it('should clear existing interval', () => { - numberState.interval(dummyCallbackFunction); - const currentInterval = numberState.currentInterval; - - numberState.clearInterval(); - - expect(clearInterval).toHaveBeenCalledTimes(1); - expect(clearInterval).toHaveBeenLastCalledWith(currentInterval); - expect(numberState.currentInterval).toBeUndefined(); - }); - - it("shouldn't clear not existing interval", () => { - numberState.clearInterval(); - - expect(clearInterval).not.toHaveBeenCalled(); - expect(numberState.currentInterval).toBeUndefined(); - }); - }); - - describe('exists get function tests', () => { - it('should return true if State is no placeholder and computeExistsMethod returns true', () => { - numberState.computeExistsMethod = jest.fn().mockReturnValueOnce(true); - numberState.isPlaceholder = false; - - expect(numberState.exists).toBeTruthy(); - expect(numberState.computeExistsMethod).toHaveBeenCalledWith( - numberState.value - ); - }); - - it('should return false if State is no placeholder and computeExistsMethod returns false', () => { - numberState.computeExistsMethod = jest.fn().mockReturnValueOnce(false); - numberState.isPlaceholder = false; - - expect(numberState.exists).toBeFalsy(); - expect(numberState.computeExistsMethod).toHaveBeenCalledWith( - numberState.value - ); - }); - - it('should return false if State is placeholder"', () => { - numberState.computeExistsMethod = jest.fn(() => true); - numberState.isPlaceholder = true; - - expect(numberState.exists).toBeFalsy(); - expect(numberState.computeExistsMethod).not.toHaveBeenCalled(); // since isPlaceholder gets checked first - }); - }); - - describe('computeExists function tests', () => { - it('should assign passed function to computeExistsMethod', () => { - const computeMethod = (value) => value === null; - - numberState.computeExists(computeMethod); - - expect(numberState.computeExistsMethod).toBe(computeMethod); - LogMock.hasNotLogged('warn'); - }); - - it("shouldn't assign passed invalid function to computeExistsMethod", () => { - numberState.computeExists(10 as any); - - expect(numberState.computeExistsMethod).toBeInstanceOf(Function); - LogMock.hasLoggedCode('00:03:01', [ - 'Compute Exists Method', - 'function', - ]); - }); - }); - - describe('is function tests', () => { - beforeEach(() => { - jest.spyOn(Utils, 'equal'); - }); - - it('should return true if passed value is equal to the current StateValue', () => { - const response = numberState.is(10); - - expect(response).toBeTruthy(); - expect(Utils.equal).toHaveBeenCalledWith(10, numberState._value); - }); - - it('should return false if passed value is not equal to the current StateValue', () => { - const response = numberState.is(20); - - expect(response).toBeFalsy(); - expect(Utils.equal).toHaveBeenCalledWith(20, numberState._value); - }); - }); - - describe('isNot function tests', () => { - beforeEach(() => { - jest.spyOn(Utils, 'notEqual'); - }); - - it('should return false if passed value is equal to the current StateValue', () => { - const response = numberState.isNot(10); - - expect(response).toBeFalsy(); - expect(Utils.notEqual).toHaveBeenCalledWith(10, numberState._value); - }); - - it('should return true if passed value is not equal to the current StateValue', () => { - const response = numberState.isNot(20); - - expect(response).toBeTruthy(); - expect(Utils.notEqual).toHaveBeenCalledWith(20, numberState._value); - }); - }); - - describe('invert function tests', () => { - let dummyState: State; - - beforeEach(() => { - dummyState = new State(dummyAgile, null); - - dummyState.set = jest.fn(); - }); - - it('should invert value of the type boolean', () => { - dummyState.nextStateValue = false; - - dummyState.invert(); - - expect(dummyState.set).toHaveBeenCalledWith(true); - }); - - it('should invert value of the type number', () => { - dummyState.nextStateValue = 10; - - dummyState.invert(); - - expect(dummyState.set).toHaveBeenCalledWith(-10); - }); - - it('should invert value of the type array', () => { - dummyState.nextStateValue = ['1', '2', '3']; - - dummyState.invert(); - - expect(dummyState.set).toHaveBeenCalledWith(['3', '2', '1']); - }); - - it('should invert value of the type string', () => { - dummyState.nextStateValue = 'jeff'; - - dummyState.invert(); - - expect(dummyState.set).toHaveBeenCalledWith('ffej'); - }); - - it("shouldn't invert not invertible types like function, null, undefined, object", () => { - dummyState.nextStateValue = () => { - // empty - }; - - dummyState.invert(); - - expect(dummyState.set).not.toHaveBeenCalled(); - LogMock.hasLoggedCode('14:03:04', ['function']); - }); - }); - - describe('computeValue function tests', () => { - beforeEach(() => { - numberState.set = jest.fn(); - }); - - it('should assign passed function to computeValueMethod and compute State value initially', () => { - const computeMethod = () => 10; - - numberState.computeValue(computeMethod); - - expect(numberState.set).toHaveBeenCalledWith(10); - expect(numberState.computeValueMethod).toBe(computeMethod); - LogMock.hasNotLogged('warn'); - }); - - it("shouldn't assign passed invalid function to computeValueMethod", () => { - numberState.computeValue(10 as any); - - expect(numberState.set).not.toHaveBeenCalled(); - expect(numberState.computeValueMethod).toBeUndefined(); - LogMock.hasLoggedCode('00:03:01', ['Compute Value Method', 'function']); - }); - }); - describe('addSideEffect function tests', () => { const sideEffectFunction = () => { /* empty function */ From 0c0a10ab7066e844c19ae4f9c17bbc26451c0143 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Mon, 23 Aug 2021 17:47:56 +0200 Subject: [PATCH 20/44] fixed typos --- .../collection/collection.persistent.test.ts | 20 +-- .../tests/unit/collection/collection.test.ts | 10 +- .../collection/group/group.observer.test.ts | 2 +- .../tests/unit/collection/group/group.test.ts | 2 +- .../core/tests/unit/collection/item.test.ts | 6 +- .../tests/unit/collection/selector.test.ts | 50 ++----- .../core/tests/unit/computed/computed.test.ts | 2 +- .../unit/computed/computed.tracker.test.ts | 2 +- .../unit/integrations/integrations.test.ts | 2 +- .../tests/unit/runtime/runtime.job.test.ts | 2 +- .../core/tests/unit/runtime/runtime.test.ts | 2 +- .../subscription/sub.controller.test.ts | 139 ++++++++++-------- .../tests/unit/state/state.observer.test.ts | 2 +- .../tests/unit/state/state.persistent.test.ts | 6 +- .../unit/state/state.runtime.job.test.ts | 2 +- .../tests/unit/storages/persistent.test.ts | 2 +- .../core/tests/unit/storages/storages.test.ts | 2 +- packages/core/tests/unit/utils.test.ts | 22 ++- 18 files changed, 128 insertions(+), 147 deletions(-) diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 838587fd..a3cd2cef 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -22,7 +22,7 @@ describe('CollectionPersistent Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyCollection = new Collection(dummyAgile, { key: 'dummyCollectionKey', }); @@ -417,9 +417,10 @@ describe('CollectionPersistent Tests', () => { expect( placeholderItem1?.persistent?.loadPersistedValue ).toHaveBeenCalledTimes(1); - expect( - dummyCollection.assignItem - ).toHaveBeenCalledWith(placeholderItem1, { overwrite: true }); + expect(dummyCollection.assignItem).toHaveBeenCalledWith( + placeholderItem1, + { overwrite: true } + ); expect(placeholderItem1.isPersisted).toBeTruthy(); // Placeholder Item 2 @@ -568,9 +569,10 @@ describe('CollectionPersistent Tests', () => { expect( placeholderItem1?.persistent?.loadPersistedValue ).toHaveBeenCalledTimes(1); - expect( - dummyCollection.assignItem - ).toHaveBeenCalledWith(placeholderItem1, { overwrite: true }); + expect(dummyCollection.assignItem).toHaveBeenCalledWith( + placeholderItem1, + { overwrite: true } + ); expect(placeholderItem1.isPersisted).toBeTruthy(); expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( @@ -840,9 +842,7 @@ describe('CollectionPersistent Tests', () => { it("shouldn't add rebuild Storage side effect to the default Group", () => { collectionPersistent.setupSideEffects(); - expect( - dummyDefaultGroup.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyDefaultGroup.addSideEffect).toHaveBeenCalledWith( CollectionPersistent.defaultGroupSideEffectKey, expect.any(Function), { weight: 0 } diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 086079f4..89684604 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -24,7 +24,7 @@ describe('Collection Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.spyOn(Collection.prototype, 'initSelectors'); jest.spyOn(Collection.prototype, 'initGroups'); @@ -264,10 +264,10 @@ describe('Collection Tests', () => { key: 'group1Key', }); - expect(collection.createGroup).toHaveBeenCalledWith('group1Key', [ - 1, - 2, - ]); + expect(collection.createGroup).toHaveBeenCalledWith( + 'group1Key', + [1, 2] + ); LogMock.hasLoggedCode('1B:02:00'); expect(response).toBeInstanceOf(Group); diff --git a/packages/core/tests/unit/collection/group/group.observer.test.ts b/packages/core/tests/unit/collection/group/group.observer.test.ts index a295262e..488147b1 100644 --- a/packages/core/tests/unit/collection/group/group.observer.test.ts +++ b/packages/core/tests/unit/collection/group/group.observer.test.ts @@ -26,7 +26,7 @@ describe('GroupObserver Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyCollection = new Collection(dummyAgile); dummyGroup = new Group(dummyCollection, [], { key: 'dummyGroup', diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 2a7525f1..58788ede 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -23,7 +23,7 @@ describe('Group Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyCollection = new Collection(dummyAgile, { key: 'dummyCollection', }); diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index 38538da9..3e6b41df 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -20,7 +20,7 @@ describe('Item Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyCollection = new Collection(dummyAgile); jest.spyOn(Item.prototype, 'addRebuildGroupThatIncludeItemKeySideEffect'); @@ -315,9 +315,7 @@ describe('Item Tests', () => { it('should add rebuildGroupThatIncludeItemKey sideEffect to Item', () => { item.addRebuildGroupThatIncludeItemKeySideEffect('itemKey'); - expect( - item.addSideEffect - ).toHaveBeenCalledWith( + expect(item.addSideEffect).toHaveBeenCalledWith( Item.updateGroupSideEffectKey, expect.any(Function), { weight: 100 } diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index 68378e32..45ef999f 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -13,7 +13,7 @@ describe('Selector Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyCollection = new Collection(dummyAgile); jest.spyOn(Selector.prototype, 'select'); @@ -258,17 +258,13 @@ describe('Selector Tests', () => { overwrite: false, storage: true, }); - expect( - selector.addSideEffect - ).toHaveBeenCalledWith( + expect(selector.addSideEffect).toHaveBeenCalledWith( Selector.rebuildItemSideEffectKey, expect.any(Function), { weight: 90 } ); - expect( - dummyItem2.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyItem2.addSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey, expect.any(Function), { weight: 100 } @@ -306,17 +302,13 @@ describe('Selector Tests', () => { overwrite: true, storage: true, }); - expect( - selector.addSideEffect - ).toHaveBeenCalledWith( + expect(selector.addSideEffect).toHaveBeenCalledWith( Selector.rebuildItemSideEffectKey, expect.any(Function), { weight: 90 } ); - expect( - dummyItem2.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyItem2.addSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey, expect.any(Function), { weight: 100 } @@ -367,17 +359,13 @@ describe('Selector Tests', () => { overwrite: false, storage: true, }); - expect( - selector.addSideEffect - ).toHaveBeenCalledWith( + expect(selector.addSideEffect).toHaveBeenCalledWith( Selector.rebuildItemSideEffectKey, expect.any(Function), { weight: 90 } ); - expect( - dummyItem1.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyItem1.addSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey, expect.any(Function), { weight: 100 } @@ -428,17 +416,13 @@ describe('Selector Tests', () => { overwrite: false, storage: true, }); - expect( - selector.addSideEffect - ).toHaveBeenCalledWith( + expect(selector.addSideEffect).toHaveBeenCalledWith( Selector.rebuildItemSideEffectKey, expect.any(Function), { weight: 90 } ); - expect( - dummyItem2.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyItem2.addSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey, expect.any(Function), { weight: 100 } @@ -470,17 +454,13 @@ describe('Selector Tests', () => { overwrite: true, storage: true, }); - expect( - selector.addSideEffect - ).toHaveBeenCalledWith( + expect(selector.addSideEffect).toHaveBeenCalledWith( Selector.rebuildItemSideEffectKey, expect.any(Function), { weight: 90 } ); - expect( - dummyItem2.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyItem2.addSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey, expect.any(Function), { weight: 100 } @@ -512,17 +492,13 @@ describe('Selector Tests', () => { overwrite: false, storage: true, }); - expect( - selector.addSideEffect - ).toHaveBeenCalledWith( + expect(selector.addSideEffect).toHaveBeenCalledWith( Selector.rebuildItemSideEffectKey, expect.any(Function), { weight: 90 } ); - expect( - dummyItem2.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyItem2.addSideEffect).toHaveBeenCalledWith( Selector.rebuildSelectorSideEffectKey, expect.any(Function), { weight: 100 } diff --git a/packages/core/tests/unit/computed/computed.test.ts b/packages/core/tests/unit/computed/computed.test.ts index 160ddd2c..d50326c9 100644 --- a/packages/core/tests/unit/computed/computed.test.ts +++ b/packages/core/tests/unit/computed/computed.test.ts @@ -16,7 +16,7 @@ describe('Computed Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.spyOn(Computed.prototype, 'recompute'); jest.spyOn(Utils, 'extractRelevantObservers'); diff --git a/packages/core/tests/unit/computed/computed.tracker.test.ts b/packages/core/tests/unit/computed/computed.tracker.test.ts index 20e09dc1..97f970ce 100644 --- a/packages/core/tests/unit/computed/computed.tracker.test.ts +++ b/packages/core/tests/unit/computed/computed.tracker.test.ts @@ -7,7 +7,7 @@ describe('ComputedTracker Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); // Reset ComputedTracker (because it works static) ComputedTracker.isTracking = false; diff --git a/packages/core/tests/unit/integrations/integrations.test.ts b/packages/core/tests/unit/integrations/integrations.test.ts index a7598d61..845fff56 100644 --- a/packages/core/tests/unit/integrations/integrations.test.ts +++ b/packages/core/tests/unit/integrations/integrations.test.ts @@ -9,7 +9,7 @@ describe('Integrations Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyIntegration1 = new Integration({ key: 'dummyIntegration1', }); diff --git a/packages/core/tests/unit/runtime/runtime.job.test.ts b/packages/core/tests/unit/runtime/runtime.job.test.ts index b01c3765..9d0845f2 100644 --- a/packages/core/tests/unit/runtime/runtime.job.test.ts +++ b/packages/core/tests/unit/runtime/runtime.job.test.ts @@ -9,7 +9,7 @@ describe('RuntimeJob Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyIntegration = new Integration({ key: 'myIntegration', }); diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 5bcc3014..9601a7cc 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -17,7 +17,7 @@ describe('Runtime Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.clearAllMocks(); }); diff --git a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts index 6d73d851..72063f9d 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -14,7 +14,7 @@ describe('SubController Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.clearAllMocks(); }); @@ -215,10 +215,11 @@ describe('SubController Tests', () => { const dummyIntegration = () => { /* empty function */ }; - const callbackSubscriptionContainer = subController.createCallbackSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + const callbackSubscriptionContainer = + subController.createCallbackSubscriptionContainer(dummyIntegration, [ + dummyObserver1, + dummyObserver2, + ]); callbackSubscriptionContainer.removeSubscription = jest.fn(); subController.unsubscribe(callbackSubscriptionContainer); @@ -240,10 +241,11 @@ describe('SubController Tests', () => { const dummyIntegration: any = { dummy: 'integration', }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + const componentSubscriptionContainer = + subController.createComponentSubscriptionContainer(dummyIntegration, [ + dummyObserver1, + dummyObserver2, + ]); componentSubscriptionContainer.removeSubscription = jest.fn(); subController.unsubscribe(componentSubscriptionContainer); @@ -269,15 +271,17 @@ describe('SubController Tests', () => { dummy: 'integration', componentSubscriptionContainers: [], }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + const componentSubscriptionContainer = + subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2] + ); componentSubscriptionContainer.removeSubscription = jest.fn(); - const componentSubscriptionContainer2 = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + const componentSubscriptionContainer2 = + subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2] + ); componentSubscriptionContainer2.removeSubscription = jest.fn(); subController.unsubscribe(dummyIntegration); @@ -320,11 +324,12 @@ describe('SubController Tests', () => { dummy: 'integration', }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: false } - ); + const componentSubscriptionContainer = + subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: false } + ); expect(componentSubscriptionContainer).toBeInstanceOf( ComponentSubscriptionContainer @@ -362,11 +367,12 @@ describe('SubController Tests', () => { componentSubscriptionContainers: [], }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: false } - ); + const componentSubscriptionContainer = + subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: false } + ); expect( dummyIntegration.componentSubscriptionContainers @@ -383,11 +389,12 @@ describe('SubController Tests', () => { dummy: 'integration', }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: false, componentId: 'testID', key: 'dummyKey' } - ); + const componentSubscriptionContainer = + subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: false, componentId: 'testID', key: 'dummyKey' } + ); expect(componentSubscriptionContainer).toBeInstanceOf( ComponentSubscriptionContainer @@ -420,11 +427,12 @@ describe('SubController Tests', () => { dummy: 'integration', }; - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: true } - ); + const componentSubscriptionContainer = + subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: true } + ); expect(componentSubscriptionContainer).toBeInstanceOf( ComponentSubscriptionContainer @@ -453,11 +461,12 @@ describe('SubController Tests', () => { }; subController.mount(dummyIntegration); - const componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { waitForMount: true } - ); + const componentSubscriptionContainer = + subController.createComponentSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { waitForMount: true } + ); expect(componentSubscriptionContainer).toBeInstanceOf( ComponentSubscriptionContainer @@ -487,10 +496,11 @@ describe('SubController Tests', () => { /* empty function */ }; - const callbackSubscriptionContainer = subController.createCallbackSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + const callbackSubscriptionContainer = + subController.createCallbackSubscriptionContainer(dummyIntegration, [ + dummyObserver1, + dummyObserver2, + ]); expect(callbackSubscriptionContainer).toBeInstanceOf( CallbackSubscriptionContainer @@ -520,15 +530,16 @@ describe('SubController Tests', () => { /* empty function */ }; - const callbackSubscriptionContainer = subController.createCallbackSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2], - { - waitForMount: false, - componentId: 'testID', - key: 'dummyKey', - } - ); + const callbackSubscriptionContainer = + subController.createCallbackSubscriptionContainer( + dummyIntegration, + [dummyObserver1, dummyObserver2], + { + waitForMount: false, + componentId: 'testID', + key: 'dummyKey', + } + ); expect(callbackSubscriptionContainer).toBeInstanceOf( CallbackSubscriptionContainer @@ -557,10 +568,11 @@ describe('SubController Tests', () => { beforeEach(() => { dummyAgile.config.waitForMount = true; - componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + componentSubscriptionContainer = + subController.createComponentSubscriptionContainer(dummyIntegration, [ + dummyObserver1, + dummyObserver2, + ]); }); it( @@ -585,10 +597,11 @@ describe('SubController Tests', () => { beforeEach(() => { dummyAgile.config.waitForMount = true; - componentSubscriptionContainer = subController.createComponentSubscriptionContainer( - dummyIntegration, - [dummyObserver1, dummyObserver2] - ); + componentSubscriptionContainer = + subController.createComponentSubscriptionContainer(dummyIntegration, [ + dummyObserver1, + dummyObserver2, + ]); subController.mount(dummyIntegration); }); diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts index 53ebd1dd..432e6c12 100644 --- a/packages/core/tests/unit/state/state.observer.test.ts +++ b/packages/core/tests/unit/state/state.observer.test.ts @@ -19,7 +19,7 @@ describe('StateObserver Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyState = new State(dummyAgile, 'dummyValue', { key: 'dummyState' }); jest.clearAllMocks(); diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index 65603523..d02103ea 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -14,7 +14,7 @@ describe('StatePersistent Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyState = new State(dummyAgile, 'dummyValue'); jest.spyOn(StatePersistent.prototype, 'instantiatePersistent'); @@ -308,9 +308,7 @@ describe('StatePersistent Tests', () => { () => { statePersistent.setupSideEffects(); - expect( - dummyState.addSideEffect - ).toHaveBeenCalledWith( + expect(dummyState.addSideEffect).toHaveBeenCalledWith( StatePersistent.storeValueSideEffectKey, expect.any(Function), { weight: 0 } diff --git a/packages/core/tests/unit/state/state.runtime.job.test.ts b/packages/core/tests/unit/state/state.runtime.job.test.ts index 85df6e56..fc2d2461 100644 --- a/packages/core/tests/unit/state/state.runtime.job.test.ts +++ b/packages/core/tests/unit/state/state.runtime.job.test.ts @@ -18,7 +18,7 @@ describe('RuntimeJob Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyIntegration = new Integration({ key: 'myIntegration', }); diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index a0dd3407..750d90d9 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -7,7 +7,7 @@ describe('Persistent Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.spyOn(Persistent.prototype, 'instantiatePersistent'); diff --git a/packages/core/tests/unit/storages/storages.test.ts b/packages/core/tests/unit/storages/storages.test.ts index 139a7e29..ac95a196 100644 --- a/packages/core/tests/unit/storages/storages.test.ts +++ b/packages/core/tests/unit/storages/storages.test.ts @@ -7,7 +7,7 @@ describe('Storages Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.spyOn(Storages.prototype, 'instantiateLocalStorage'); diff --git a/packages/core/tests/unit/utils.test.ts b/packages/core/tests/unit/utils.test.ts index 96decb42..46e8aafd 100644 --- a/packages/core/tests/unit/utils.test.ts +++ b/packages/core/tests/unit/utils.test.ts @@ -16,7 +16,7 @@ describe('Utils Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); // @ts-ignore | Reset globalThis globalThis = {}; @@ -118,13 +118,11 @@ describe('Utils Tests', () => { // State with multiple Observer dummyStateWithMultipleObserver = new State(dummyAgile, null); dummyStateValueObserver = new StateObserver(dummyState); - dummyStateWithMultipleObserver.observers[ - 'value' - ] = dummyStateValueObserver; + dummyStateWithMultipleObserver.observers['value'] = + dummyStateValueObserver; dummyStateRandomObserver = new StateObserver(dummyState); - dummyStateWithMultipleObserver.observers[ - 'random' - ] = dummyStateRandomObserver; + dummyStateWithMultipleObserver.observers['random'] = + dummyStateRandomObserver; // Collection dummyCollection = new Collection(dummyAgile); @@ -216,13 +214,11 @@ describe('Utils Tests', () => { // State with multiple Observer dummyStateWithMultipleObserver = new State(dummyAgile, null); dummyStateValueObserver = new StateObserver(dummyState); - dummyStateWithMultipleObserver.observers[ - 'value' - ] = dummyStateValueObserver; + dummyStateWithMultipleObserver.observers['value'] = + dummyStateValueObserver; dummyStateRandomObserver = new StateObserver(dummyState); - dummyStateWithMultipleObserver.observers[ - 'random' - ] = dummyStateRandomObserver; + dummyStateWithMultipleObserver.observers['random'] = + dummyStateRandomObserver; // Collection dummyCollection = new Collection(dummyAgile); From c36956206b68e8b1ad82c481e659c51bd4445dc5 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Tue, 24 Aug 2021 18:16:52 +0200 Subject: [PATCH 21/44] fixed typos --- .../collection/collection.persistent.test.ts | 62 +++++++++++-------- .../tests/unit/collection/group/group.test.ts | 38 +++++++----- .../core/tests/unit/collection/item.test.ts | 44 +++++++------ .../core/tests/unit/computed/computed.test.ts | 12 ---- .../tests/unit/state/state.observer.test.ts | 8 ++- .../tests/unit/state/state.persistent.test.ts | 58 +++++++++-------- 6 files changed, 122 insertions(+), 100 deletions(-) diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index a3cd2cef..b4b1c4b1 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -7,8 +7,13 @@ import { StatePersistent, Group, Item, + registerSharedStorageManager, + createStorageManager, + getStorageManager, + Storages, } from '../../../src'; import { LogMock } from '../../helper/logMock'; +import waitForExpect from 'wait-for-expect'; describe('CollectionPersistent Tests', () => { interface ItemInterface { @@ -18,6 +23,7 @@ describe('CollectionPersistent Tests', () => { let dummyAgile: Agile; let dummyCollection: Collection; + let storageManager: Storages; beforeEach(() => { LogMock.mockLogs(); @@ -27,6 +33,10 @@ describe('CollectionPersistent Tests', () => { key: 'dummyCollectionKey', }); + // Register Storage Manager + registerSharedStorageManager(createStorageManager()); + storageManager = getStorageManager() as any; + jest.spyOn(CollectionPersistent.prototype, 'instantiatePersistent'); jest.spyOn(CollectionPersistent.prototype, 'initialLoading'); @@ -169,7 +179,7 @@ describe('CollectionPersistent Tests', () => { key: 'collectionPersistentKey', storageKeys: ['dummyStorage'], }); - dummyAgile.registerStorage( + storageManager.register( new Storage({ key: 'dummyStorage', methods: { @@ -226,8 +236,10 @@ describe('CollectionPersistent Tests', () => { it('should call initialLoad in parent and set Collection.isPersisted to true', async () => { await collectionPersistent.initialLoading(); - expect(Persistent.prototype.initialLoading).toHaveBeenCalled(); - expect(dummyCollection.isPersisted).toBeTruthy(); + await waitForExpect(() => { + expect(Persistent.prototype.initialLoading).toHaveBeenCalled(); + expect(dummyCollection.isPersisted).toBeTruthy(); + }); }); }); @@ -266,7 +278,7 @@ describe('CollectionPersistent Tests', () => { ); dummyCollection.assignItem = jest.fn(); - dummyAgile.storages.get = jest.fn(); + storageManager.get = jest.fn(); }); it( @@ -277,7 +289,7 @@ describe('CollectionPersistent Tests', () => { dummyCollection.data = { ['3']: dummyItem3, }; - dummyAgile.storages.get = jest + storageManager.get = jest .fn() .mockReturnValueOnce(Promise.resolve(true)); dummyDefaultGroup._value = ['3']; @@ -285,7 +297,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); @@ -338,7 +350,7 @@ describe('CollectionPersistent Tests', () => { collectionPersistent.ready = true; dummyCollection.data = {}; dummyCollection.size = 0; - dummyAgile.storages.get = jest + storageManager.get = jest .fn() .mockReturnValueOnce(Promise.resolve(true)); placeholderItem1.persist = jest.fn(function () { @@ -373,7 +385,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); @@ -487,7 +499,7 @@ describe('CollectionPersistent Tests', () => { ['3']: dummyItem3, }; dummyCollection.size = 1; - dummyAgile.storages.get = jest + storageManager.get = jest .fn() .mockReturnValueOnce(Promise.resolve(true)); placeholderItem1.persist = jest.fn(function () { @@ -508,7 +520,7 @@ describe('CollectionPersistent Tests', () => { ); expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( 'dummyKey', collectionPersistent.config.defaultStorageKey ); @@ -583,14 +595,14 @@ describe('CollectionPersistent Tests', () => { it("shouldn't load default Group and its Items if Collection flag isn't persisted", async () => { collectionPersistent.ready = true; - dummyAgile.storages.get = jest + storageManager.get = jest .fn() .mockReturnValueOnce(Promise.resolve(undefined)); const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); @@ -614,7 +626,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.get).not.toHaveBeenCalled(); + expect(storageManager.get).not.toHaveBeenCalled(); expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); @@ -631,7 +643,7 @@ describe('CollectionPersistent Tests', () => { it("shouldn't load default Group and its Items if Collection has no defaultGroup", async () => { collectionPersistent.ready = true; - dummyAgile.storages.get = jest + storageManager.get = jest .fn() .mockReturnValueOnce(Promise.resolve(true)); dummyCollection.getDefaultGroup = jest.fn(() => undefined); @@ -639,7 +651,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.config.defaultStorageKey ); @@ -684,7 +696,7 @@ describe('CollectionPersistent Tests', () => { () => dummyDefaultGroup as any ); - dummyAgile.storages.set = jest.fn(); + storageManager.set = jest.fn(); }); it('should persist default Group and its Items (persistentKey)', async () => { @@ -693,7 +705,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.persistValue(); expect(response).toBeTruthy(); - expect(dummyAgile.storages.set).toHaveBeenCalledWith( + expect(storageManager.set).toHaveBeenCalledWith( collectionPersistent._key, true, collectionPersistent.storageKeys @@ -745,7 +757,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.persistValue('dummyKey'); expect(response).toBeTruthy(); - expect(dummyAgile.storages.set).toHaveBeenCalledWith( + expect(storageManager.set).toHaveBeenCalledWith( 'dummyKey', true, collectionPersistent.storageKeys @@ -791,7 +803,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.persistValue('dummyKey'); expect(response).toBeFalsy(); - expect(dummyAgile.storages.set).not.toHaveBeenCalled(); + expect(storageManager.set).not.toHaveBeenCalled(); expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); @@ -810,7 +822,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.persistValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.set).not.toHaveBeenCalled(); + expect(storageManager.set).not.toHaveBeenCalled(); expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect(dummyDefaultGroup.persist).not.toHaveBeenCalled(); @@ -919,7 +931,7 @@ describe('CollectionPersistent Tests', () => { if (dummyItem3.persistent) dummyItem3.persistent.removePersistedValue = jest.fn(); - dummyAgile.storages.remove = jest.fn(); + storageManager.remove = jest.fn(); }); it('should remove persisted default Group and its Items from Storage (persistentKey)', async () => { @@ -928,7 +940,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.removePersistedValue(); expect(response).toBeTruthy(); - expect(dummyAgile.storages.remove).toHaveBeenCalledWith( + expect(storageManager.remove).toHaveBeenCalledWith( collectionPersistent._key, collectionPersistent.storageKeys ); @@ -974,7 +986,7 @@ describe('CollectionPersistent Tests', () => { ); expect(response).toBeTruthy(); - expect(dummyAgile.storages.remove).toHaveBeenCalledWith( + expect(storageManager.remove).toHaveBeenCalledWith( 'dummyKey', collectionPersistent.storageKeys ); @@ -1012,7 +1024,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.removePersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.remove).not.toHaveBeenCalled(); + expect(storageManager.remove).not.toHaveBeenCalled(); expect(dummyCollection.getDefaultGroup).not.toHaveBeenCalled(); expect( @@ -1037,7 +1049,7 @@ describe('CollectionPersistent Tests', () => { const response = await collectionPersistent.removePersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.remove).not.toHaveBeenCalled(); + expect(storageManager.remove).not.toHaveBeenCalled(); expect(dummyCollection.getDefaultGroup).toHaveBeenCalled(); expect( diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 58788ede..17574a02 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -5,9 +5,9 @@ import { StateObserver, ComputedTracker, Item, - State, CollectionPersistent, GroupObserver, + EnhancedState, } from '../../../../src'; import { LogMock } from '../../../helper/logMock'; @@ -407,13 +407,13 @@ describe('Group Tests', () => { describe('persist function tests', () => { beforeEach(() => { - jest.spyOn(State.prototype, 'persist'); + jest.spyOn(EnhancedState.prototype, 'persist'); }); it('should persist Group with formatted groupKey (default config)', () => { group.persist(); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getGroupStorageKey( group._key, dummyCollection._key @@ -433,7 +433,7 @@ describe('Group Tests', () => { defaultStorageKey: 'test1', }); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getGroupStorageKey( group._key, dummyCollection._key @@ -449,7 +449,7 @@ describe('Group Tests', () => { it('should persist Group with formatted specified key (default config)', () => { group.persist('dummyKey'); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getGroupStorageKey( 'dummyKey', dummyCollection._key @@ -469,7 +469,7 @@ describe('Group Tests', () => { defaultStorageKey: 'test1', }); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getGroupStorageKey( 'dummyKey', dummyCollection._key @@ -485,21 +485,27 @@ describe('Group Tests', () => { it('should persist Group with groupKey (config.followCollectionPersistKeyPattern = false)', () => { group.persist({ followCollectionPersistKeyPattern: false }); - expect(State.prototype.persist).toHaveBeenCalledWith(group._key, { - loadValue: true, - storageKeys: [], - defaultStorageKey: null, - }); + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( + group._key, + { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + } + ); }); it('should persist Group with specified key (config.followCollectionPersistKeyPattern = false)', () => { group.persist('dummyKey', { followCollectionPersistKeyPattern: false }); - expect(State.prototype.persist).toHaveBeenCalledWith('dummyKey', { - loadValue: true, - storageKeys: [], - defaultStorageKey: null, - }); + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( + 'dummyKey', + { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + } + ); }); }); diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index 3e6b41df..b5fdeb4f 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -3,7 +3,7 @@ import { Collection, Agile, StateObserver, - State, + EnhancedState, CollectionPersistent, } from '../../../src'; import { LogMock } from '../../helper/logMock'; @@ -145,13 +145,13 @@ describe('Item Tests', () => { beforeEach(() => { item.removeSideEffect = jest.fn(); item.patch = jest.fn(); - jest.spyOn(State.prototype, 'setKey'); + jest.spyOn(EnhancedState.prototype, 'setKey'); }); it('should call State setKey, add rebuildGroup sideEffect to Item and patch newItemKey into Item (default config)', () => { item.setKey('myNewKey'); - expect(State.prototype.setKey).toHaveBeenCalledWith('myNewKey'); + expect(EnhancedState.prototype.setKey).toHaveBeenCalledWith('myNewKey'); expect(item.removeSideEffect).toHaveBeenCalledWith( Item.updateGroupSideEffectKey ); @@ -184,7 +184,7 @@ describe('Item Tests', () => { force: true, }); - expect(State.prototype.setKey).toHaveBeenCalledWith('myNewKey'); + expect(EnhancedState.prototype.setKey).toHaveBeenCalledWith('myNewKey'); expect(item.removeSideEffect).toHaveBeenCalledWith( Item.updateGroupSideEffectKey ); @@ -210,13 +210,13 @@ describe('Item Tests', () => { describe('persist function tests', () => { beforeEach(() => { - jest.spyOn(State.prototype, 'persist'); + jest.spyOn(EnhancedState.prototype, 'persist'); }); it('should persist Item with formatted itemKey (default config)', () => { item.persist(); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( item._key, dummyCollection._key @@ -236,7 +236,7 @@ describe('Item Tests', () => { defaultStorageKey: 'test1', }); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( item._key, dummyCollection._key @@ -252,7 +252,7 @@ describe('Item Tests', () => { it('should persist Item with formatted specified key (default config)', () => { item.persist('dummyKey'); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( 'dummyKey', dummyCollection._key @@ -272,7 +272,7 @@ describe('Item Tests', () => { defaultStorageKey: 'test1', }); - expect(State.prototype.persist).toHaveBeenCalledWith( + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( CollectionPersistent.getItemStorageKey( 'dummyKey', dummyCollection._key @@ -288,21 +288,27 @@ describe('Item Tests', () => { it('should persist Item with itemKey (config.followCollectionPersistKeyPattern = false)', () => { item.persist({ followCollectionPersistKeyPattern: false }); - expect(State.prototype.persist).toHaveBeenCalledWith(item._key, { - loadValue: true, - storageKeys: [], - defaultStorageKey: null, - }); + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( + item._key, + { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + } + ); }); it('should persist Item with specified key (config.followCollectionPersistKeyPattern = false)', () => { item.persist('dummyKey', { followCollectionPersistKeyPattern: false }); - expect(State.prototype.persist).toHaveBeenCalledWith('dummyKey', { - loadValue: true, - storageKeys: [], - defaultStorageKey: null, - }); + expect(EnhancedState.prototype.persist).toHaveBeenCalledWith( + 'dummyKey', + { + loadValue: true, + storageKeys: [], + defaultStorageKey: null, + } + ); }); }); diff --git a/packages/core/tests/unit/computed/computed.test.ts b/packages/core/tests/unit/computed/computed.test.ts index d50326c9..59b1d3ed 100644 --- a/packages/core/tests/unit/computed/computed.test.ts +++ b/packages/core/tests/unit/computed/computed.test.ts @@ -55,10 +55,6 @@ describe('Computed Tests', () => { ); expect(computed.observers['value']._key).toBeUndefined(); expect(computed.sideEffects).toStrictEqual({}); - expect(computed.computeValueMethod).toBeUndefined(); - expect(computed.computeExistsMethod).toBeInstanceOf(Function); - expect(computed.isPersisted).toBeFalsy(); - expect(computed.persistent).toBeUndefined(); }); it('should create Computed with a not async compute method (specific config)', () => { @@ -123,10 +119,6 @@ describe('Computed Tests', () => { ]); expect(computed.observers['value']._key).toBe('coolComputed'); expect(computed.sideEffects).toStrictEqual({}); - expect(computed.computeValueMethod).toBeUndefined(); - expect(computed.computeExistsMethod).toBeInstanceOf(Function); - expect(computed.isPersisted).toBeFalsy(); - expect(computed.persistent).toBeUndefined(); }); it('should create Computed with an async compute method (default config)', () => { @@ -160,10 +152,6 @@ describe('Computed Tests', () => { ); expect(computed.observers['value']._key).toBeUndefined(); expect(computed.sideEffects).toStrictEqual({}); - expect(computed.computeValueMethod).toBeUndefined(); - expect(computed.computeExistsMethod).toBeInstanceOf(Function); - expect(computed.isPersisted).toBeFalsy(); - expect(computed.persistent).toBeUndefined(); }); describe('Computed Function Tests', () => { diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts index 432e6c12..8a40d53c 100644 --- a/packages/core/tests/unit/state/state.observer.test.ts +++ b/packages/core/tests/unit/state/state.observer.test.ts @@ -3,10 +3,10 @@ import { Computed, StateRuntimeJob, Observer, - State, StateObserver, StatePersistent, SubscriptionContainer, + EnhancedState, } from '../../../src'; import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../helper/logMock'; @@ -14,13 +14,15 @@ import waitForExpect from 'wait-for-expect'; describe('StateObserver Tests', () => { let dummyAgile: Agile; - let dummyState: State; + let dummyState: EnhancedState; beforeEach(() => { LogMock.mockLogs(); dummyAgile = new Agile(); - dummyState = new State(dummyAgile, 'dummyValue', { key: 'dummyState' }); + dummyState = new EnhancedState(dummyAgile, 'dummyValue', { + key: 'dummyState', + }); jest.clearAllMocks(); }); diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index d02103ea..a5870b98 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -1,21 +1,31 @@ import { Agile, - State, StatePersistent, Storage, Persistent, + EnhancedState, + getStorageManager, + Storages, + registerSharedStorageManager, + createStorageManager, } from '../../../src'; import { LogMock } from '../../helper/logMock'; +import waitForExpect from 'wait-for-expect'; describe('StatePersistent Tests', () => { let dummyAgile: Agile; - let dummyState: State; + let dummyState: EnhancedState; + let storageManager: Storages; beforeEach(() => { LogMock.mockLogs(); dummyAgile = new Agile(); - dummyState = new State(dummyAgile, 'dummyValue'); + dummyState = new EnhancedState(dummyAgile, 'dummyValue'); + + // Register Storage Manager + registerSharedStorageManager(createStorageManager()); + storageManager = getStorageManager() as any; jest.spyOn(StatePersistent.prototype, 'instantiatePersistent'); jest.spyOn(StatePersistent.prototype, 'initialLoading'); @@ -122,7 +132,7 @@ describe('StatePersistent Tests', () => { key: 'statePersistentKey', storageKeys: ['dummyStorage'], }); - dummyAgile.registerStorage( + storageManager.register( new Storage({ key: 'dummyStorage', methods: { @@ -142,8 +152,10 @@ describe('StatePersistent Tests', () => { it('should initialLoad and set isPersisted in State to true', async () => { await statePersistent.initialLoading(); - expect(Persistent.prototype.initialLoading).toHaveBeenCalled(); - expect(dummyState.isPersisted).toBeTruthy(); + await waitForExpect(() => { + expect(Persistent.prototype.initialLoading).toHaveBeenCalled(); + expect(dummyState.isPersisted).toBeTruthy(); + }); }); }); @@ -158,14 +170,14 @@ describe('StatePersistent Tests', () => { 'and apply it to the State if the loading was successful', async () => { statePersistent.ready = true; - dummyAgile.storages.get = jest.fn(() => + storageManager.get = jest.fn(() => Promise.resolve('dummyValue' as any) ); const response = await statePersistent.loadPersistedValue(); expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( statePersistent._key, statePersistent.config.defaultStorageKey ); @@ -184,14 +196,12 @@ describe('StatePersistent Tests', () => { "and apply it to the State if the loading wasn't successful", async () => { statePersistent.ready = true; - dummyAgile.storages.get = jest.fn(() => - Promise.resolve(undefined as any) - ); + storageManager.get = jest.fn(() => Promise.resolve(undefined as any)); const response = await statePersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( statePersistent._key, statePersistent.config.defaultStorageKey ); @@ -205,14 +215,14 @@ describe('StatePersistent Tests', () => { 'and apply it to the State if the loading was successful', async () => { statePersistent.ready = true; - dummyAgile.storages.get = jest.fn(() => + storageManager.get = jest.fn(() => Promise.resolve('dummyValue' as any) ); const response = await statePersistent.loadPersistedValue('coolKey'); expect(response).toBeTruthy(); - expect(dummyAgile.storages.get).toHaveBeenCalledWith( + expect(storageManager.get).toHaveBeenCalledWith( 'coolKey', statePersistent.config.defaultStorageKey ); @@ -231,14 +241,12 @@ describe('StatePersistent Tests', () => { "if Persistent isn't ready yet", async () => { statePersistent.ready = false; - dummyAgile.storages.get = jest.fn(() => - Promise.resolve(undefined as any) - ); + storageManager.get = jest.fn(() => Promise.resolve(undefined as any)); const response = await statePersistent.loadPersistedValue(); expect(response).toBeFalsy(); - expect(dummyAgile.storages.get).not.toHaveBeenCalled(); + expect(storageManager.get).not.toHaveBeenCalled(); expect(dummyState.set).not.toHaveBeenCalled(); expect(statePersistent.setupSideEffects).not.toHaveBeenCalled(); } @@ -362,7 +370,7 @@ describe('StatePersistent Tests', () => { describe('removePersistedValue function tests', () => { beforeEach(() => { dummyState.removeSideEffect = jest.fn(); - dummyAgile.storages.remove = jest.fn(); + storageManager.remove = jest.fn(); statePersistent.isPersisted = true; }); @@ -376,7 +384,7 @@ describe('StatePersistent Tests', () => { expect(dummyState.removeSideEffect).toHaveBeenCalledWith( StatePersistent.storeValueSideEffectKey ); - expect(dummyAgile.storages.remove).toHaveBeenCalledWith( + expect(storageManager.remove).toHaveBeenCalledWith( statePersistent._key, statePersistent.storageKeys ); @@ -392,7 +400,7 @@ describe('StatePersistent Tests', () => { expect(dummyState.removeSideEffect).toHaveBeenCalledWith( StatePersistent.storeValueSideEffectKey ); - expect(dummyAgile.storages.remove).toHaveBeenCalledWith( + expect(storageManager.remove).toHaveBeenCalledWith( 'coolKey', statePersistent.storageKeys ); @@ -406,7 +414,7 @@ describe('StatePersistent Tests', () => { expect(response).toBeFalsy(); expect(dummyState.removeSideEffect).not.toHaveBeenCalled(); - expect(dummyAgile.storages.remove).not.toHaveBeenCalled(); + expect(storageManager.remove).not.toHaveBeenCalled(); expect(statePersistent.isPersisted).toBeTruthy(); }); }); @@ -448,13 +456,13 @@ describe('StatePersistent Tests', () => { describe('rebuildStorageSideEffect function tests', () => { beforeEach(() => { - dummyAgile.storages.set = jest.fn(); + storageManager.set = jest.fn(); }); it('should store current State value in the corresponding Storage (default config)', () => { statePersistent.rebuildStorageSideEffect(dummyState, 'coolKey'); - expect(dummyAgile.storages.set).toHaveBeenCalledWith( + expect(storageManager.set).toHaveBeenCalledWith( 'coolKey', dummyState.getPersistableValue(), statePersistent.storageKeys @@ -466,7 +474,7 @@ describe('StatePersistent Tests', () => { storage: false, }); - expect(dummyAgile.storages.set).not.toHaveBeenCalled(); + expect(storageManager.set).not.toHaveBeenCalled(); }); }); }); From 4c322d011f1db97f574b359c22eb35f65f2b331d Mon Sep 17 00:00:00 2001 From: BennoDev Date: Wed, 25 Aug 2021 13:39:50 +0200 Subject: [PATCH 22/44] fixed typos --- .../functional-component-ts/src/core/index.ts | 2 +- examples/react/release/boxes/package.json | 6 ++-- examples/react/release/boxes/yarn.lock | 4 +-- packages/core/src/storages/index.ts | 9 ++++++ packages/core/src/storages/persistent.ts | 8 ++++- .../tests/unit/storages/persistent.test.ts | 32 ++++++++++++------- 6 files changed, 41 insertions(+), 20 deletions(-) diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index 89ce58f3..207bce0d 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -18,7 +18,7 @@ assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); export const App = new Agile(); assignSharedAgileInstance(App); -const storageManager = createStorageManager({ localStorage: true }); +export const storageManager = createStorageManager({ localStorage: true }); registerSharedStorageManager(storageManager); // Register custom second Storage diff --git a/examples/react/release/boxes/package.json b/examples/react/release/boxes/package.json index ebb40840..078a67a8 100644 --- a/examples/react/release/boxes/package.json +++ b/examples/react/release/boxes/package.json @@ -4,9 +4,9 @@ "private": true, "dependencies": { "@agile-ts/core": "file:.yalc/@agile-ts/core", - "@agile-ts/logger": "^0.0.7", - "@agile-ts/proxytree": "^0.0.5", - "@agile-ts/react": "^0.1.2", + "@agile-ts/logger": "file:.yalc/@agile-ts/logger", + "@agile-ts/proxytree": "file:.yalc/@agile-ts/proxytree", + "@agile-ts/react": "file:.yalc/@agile-ts/react", "lodash": "^4.17.21", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/examples/react/release/boxes/yarn.lock b/examples/react/release/boxes/yarn.lock index 18e63613..a63eec3f 100644 --- a/examples/react/release/boxes/yarn.lock +++ b/examples/react/release/boxes/yarn.lock @@ -2,10 +2,8 @@ # yarn lockfile v1 -"@agile-ts/core@^0.1.2": +"@agile-ts/core@file:.yalc/@agile-ts/core": version "0.1.2" - resolved "https://registry.yarnpkg.com/@agile-ts/core/-/core-0.1.2.tgz#5a3974ba0c57a51a19bcdf81b2055e091c884f5e" - integrity sha512-9031MGUrPpg/ZL1ErpwUlHX751HKEtOfbc5Ae7W7x/POGH89Gka09hMAhqQlDrKF2+olVs3sf6PAsAHRv6paGw== dependencies: "@agile-ts/utils" "^0.0.7" diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 1db40db5..8afb0ad0 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -8,6 +8,7 @@ import { defineConfig, removeProperties, LogCodeManager, + runsOnServer, } from '../internal'; export * from './storages'; @@ -61,6 +62,14 @@ export function createStorageManager( * Returns the current registered Storage Manager. */ export function getStorageManager(): Storages | null { + if (storageManager == null) { + const newStorageManager = createStorageManager({ + localStorage: !runsOnServer(), + }); + registerSharedStorageManager(newStorageManager); + return newStorageManager; + } + return storageManager; } diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 8d869790..10d5967f 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -126,7 +126,13 @@ export class Persistent { ) { this._key = this.formatKey(config.key) ?? Persistent.placeHolderKey; this.assignStorageKeys(config.storageKeys, config.defaultStorageKey); - this.validatePersistent(); + const isValid = this.validatePersistent(); + + // Register Persistent to Storage Manager + const storageManager = getStorageManager(); + if (isValid && storageManager != null) { + storageManager.persistentInstances[this._key] = this; + } } /** diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index 750d90d9..9001741c 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -1,14 +1,28 @@ -import { Agile, Persistent, Storage, createStorage } from '../../../src'; +import { + Agile, + Persistent, + Storage, + createStorage, + Storages, + registerSharedStorageManager, + createStorageManager, + getStorageManager, +} from '../../../src'; import { LogMock } from '../../helper/logMock'; describe('Persistent Tests', () => { let dummyAgile: Agile; + let storageManager: Storages; beforeEach(() => { LogMock.mockLogs(); dummyAgile = new Agile(); + // Register Storage Manager + registerSharedStorageManager(createStorageManager()); + storageManager = getStorageManager() as any; + jest.spyOn(Persistent.prototype, 'instantiatePersistent'); jest.clearAllMocks(); @@ -28,9 +42,7 @@ describe('Persistent Tests', () => { key: undefined, defaultStorageKey: null, }); - expect( - dummyAgile.storages.persistentInstances.has(persistent) - ).toBeTruthy(); + expect(storageManager.persistentInstances.has(persistent)).toBeTruthy(); expect(persistent._key).toBe(Persistent.placeHolderKey); expect(persistent.ready).toBeFalsy(); @@ -58,9 +70,7 @@ describe('Persistent Tests', () => { key: 'persistentKey', defaultStorageKey: 'test1', }); - expect( - dummyAgile.storages.persistentInstances.has(persistent) - ).toBeTruthy(); + expect(storageManager.persistentInstances.has(persistent)).toBeTruthy(); expect(persistent._key).toBe(Persistent.placeHolderKey); expect(persistent.ready).toBeFalsy(); @@ -80,9 +90,7 @@ describe('Persistent Tests', () => { expect(persistent).toBeInstanceOf(Persistent); expect(persistent.instantiatePersistent).not.toHaveBeenCalled(); - expect( - dummyAgile.storages.persistentInstances.has(persistent) - ).toBeTruthy(); + expect(storageManager.persistentInstances.has(persistent)).toBeTruthy(); expect(persistent._key).toBe(Persistent.placeHolderKey); expect(persistent.ready).toBeFalsy(); @@ -262,7 +270,7 @@ describe('Persistent Tests', () => { }); it('should return true if set key and set StorageKeys', () => { - dummyAgile.storages.register( + storageManager.register( createStorage({ key: 'test', methods: { @@ -337,7 +345,7 @@ describe('Persistent Tests', () => { 'should try to get default StorageKey from Agile if no StorageKey was specified ' + 'and assign it as StorageKey, if it is a valid StorageKey', () => { - dummyAgile.storages.register( + storageManager.register( new Storage({ key: 'storage1', methods: { From ad1ad60b2c0bfb98960632c2167740978c3a8a66 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Wed, 25 Aug 2021 15:05:38 +0200 Subject: [PATCH 23/44] fixed persistent tests --- .../tests/unit/storages/persistent.test.ts | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index 9001741c..988a1579 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -42,7 +42,6 @@ describe('Persistent Tests', () => { key: undefined, defaultStorageKey: null, }); - expect(storageManager.persistentInstances.has(persistent)).toBeTruthy(); expect(persistent._key).toBe(Persistent.placeHolderKey); expect(persistent.ready).toBeFalsy(); @@ -70,7 +69,6 @@ describe('Persistent Tests', () => { key: 'persistentKey', defaultStorageKey: 'test1', }); - expect(storageManager.persistentInstances.has(persistent)).toBeTruthy(); expect(persistent._key).toBe(Persistent.placeHolderKey); expect(persistent.ready).toBeFalsy(); @@ -90,7 +88,6 @@ describe('Persistent Tests', () => { expect(persistent).toBeInstanceOf(Persistent); expect(persistent.instantiatePersistent).not.toHaveBeenCalled(); - expect(storageManager.persistentInstances.has(persistent)).toBeTruthy(); expect(persistent._key).toBe(Persistent.placeHolderKey); expect(persistent.ready).toBeFalsy(); @@ -196,25 +193,67 @@ describe('Persistent Tests', () => { }); describe('instantiatePersistent function tests', () => { - it('should call assign key to formatKey and call assignStorageKeys, validatePersistent', () => { + beforeEach(() => { jest.spyOn(persistent, 'formatKey'); jest.spyOn(persistent, 'assignStorageKeys'); - jest.spyOn(persistent, 'validatePersistent'); + }); - persistent.instantiatePersistent({ - key: 'persistentKey', - storageKeys: ['myName', 'is', 'jeff'], - defaultStorageKey: 'jeff', - }); + it( + 'should call formatKey, assignStorageKeys, validatePersistent ' + + 'and add Persistent to the Storage Manager when Persistent is valid', + () => { + jest + .spyOn(persistent, 'validatePersistent') + .mockReturnValueOnce(true); + + persistent.instantiatePersistent({ + key: 'persistentKey', + storageKeys: ['myName', 'is', 'jeff'], + defaultStorageKey: 'jeff', + }); + + expect(persistent._key).toBe('persistentKey'); + expect(persistent.formatKey).toHaveBeenCalledWith('persistentKey'); + expect(persistent.assignStorageKeys).toHaveBeenCalledWith( + ['myName', 'is', 'jeff'], + 'jeff' + ); + expect(persistent.validatePersistent).toHaveBeenCalled(); + expect(storageManager.persistentInstances).toHaveProperty( + 'persistentKey' + ); + expect(storageManager.persistentInstances['persistentKey']).toBe( + persistent + ); + } + ); - expect(persistent._key).toBe('persistentKey'); - expect(persistent.formatKey).toHaveBeenCalledWith('persistentKey'); - expect(persistent.assignStorageKeys).toHaveBeenCalledWith( - ['myName', 'is', 'jeff'], - 'jeff' - ); - expect(persistent.validatePersistent).toHaveBeenCalled(); - }); + it( + 'should call formatKey, assignStorageKeys, validatePersistent ' + + "and shouldn't add Persistent to the Storage Manager when Persistent isn't valid", + () => { + jest + .spyOn(persistent, 'validatePersistent') + .mockReturnValueOnce(false); + + persistent.instantiatePersistent({ + key: 'persistentKey', + storageKeys: ['myName', 'is', 'jeff'], + defaultStorageKey: 'jeff', + }); + + expect(persistent._key).toBe('persistentKey'); + expect(persistent.formatKey).toHaveBeenCalledWith('persistentKey'); + expect(persistent.assignStorageKeys).toHaveBeenCalledWith( + ['myName', 'is', 'jeff'], + 'jeff' + ); + expect(persistent.validatePersistent).toHaveBeenCalled(); + expect(storageManager.persistentInstances).not.toHaveProperty( + 'persistentKey' + ); + } + ); }); describe('validatePersistent function tests', () => { From 6a8e069c8e9796698add25c38d5b31500804f7f5 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Wed, 25 Aug 2021 15:35:45 +0200 Subject: [PATCH 24/44] fixed tests --- packages/core/src/storages/persistent.ts | 4 ++-- .../tests/unit/storages/persistent.test.ts | 18 +++++------------- .../core/tests/unit/storages/storages.test.ts | 15 +++++++++++---- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 10d5967f..11deec56 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -126,11 +126,11 @@ export class Persistent { ) { this._key = this.formatKey(config.key) ?? Persistent.placeHolderKey; this.assignStorageKeys(config.storageKeys, config.defaultStorageKey); - const isValid = this.validatePersistent(); + this.validatePersistent(); // Register Persistent to Storage Manager const storageManager = getStorageManager(); - if (isValid && storageManager != null) { + if (this._key !== Persistent.placeHolderKey && storageManager != null) { storageManager.persistentInstances[this._key] = this; } } diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index 988a1579..a6673cb0 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -196,16 +196,13 @@ describe('Persistent Tests', () => { beforeEach(() => { jest.spyOn(persistent, 'formatKey'); jest.spyOn(persistent, 'assignStorageKeys'); + jest.spyOn(persistent, 'validatePersistent'); }); it( 'should call formatKey, assignStorageKeys, validatePersistent ' + - 'and add Persistent to the Storage Manager when Persistent is valid', + 'and add Persistent to the Storage Manager when Persistent has a valid key', () => { - jest - .spyOn(persistent, 'validatePersistent') - .mockReturnValueOnce(true); - persistent.instantiatePersistent({ key: 'persistentKey', storageKeys: ['myName', 'is', 'jeff'], @@ -230,20 +227,15 @@ describe('Persistent Tests', () => { it( 'should call formatKey, assignStorageKeys, validatePersistent ' + - "and shouldn't add Persistent to the Storage Manager when Persistent isn't valid", + "and shouldn't add Persistent to the Storage Manager when Persistent has no valid key", () => { - jest - .spyOn(persistent, 'validatePersistent') - .mockReturnValueOnce(false); - persistent.instantiatePersistent({ - key: 'persistentKey', storageKeys: ['myName', 'is', 'jeff'], defaultStorageKey: 'jeff', }); - expect(persistent._key).toBe('persistentKey'); - expect(persistent.formatKey).toHaveBeenCalledWith('persistentKey'); + expect(persistent._key).toBe(Persistent.placeHolderKey); + expect(persistent.formatKey).toHaveBeenCalledWith(undefined); expect(persistent.assignStorageKeys).toHaveBeenCalledWith( ['myName', 'is', 'jeff'], 'jeff' diff --git a/packages/core/tests/unit/storages/storages.test.ts b/packages/core/tests/unit/storages/storages.test.ts index ac95a196..f19d3df1 100644 --- a/packages/core/tests/unit/storages/storages.test.ts +++ b/packages/core/tests/unit/storages/storages.test.ts @@ -1,4 +1,10 @@ -import { Storages, Agile, Storage, Persistent } from '../../../src'; +import { + Storages, + Agile, + Storage, + Persistent, + registerSharedStorageManager, +} from '../../../src'; import { LogMock } from '../../helper/logMock'; describe('Storages Tests', () => { @@ -19,7 +25,7 @@ describe('Storages Tests', () => { expect(storages.config).toStrictEqual({ defaultStorageKey: null }); expect(storages.storages).toStrictEqual({}); - expect(storages.persistentInstances.size).toBe(0); + expect(storages.persistentInstances).toStrictEqual({}); expect(storages.instantiateLocalStorage).not.toHaveBeenCalled(); }); @@ -31,7 +37,7 @@ describe('Storages Tests', () => { expect(storages.config).toStrictEqual({ defaultStorageKey: 'jeff' }); expect(storages.storages).toStrictEqual({}); - expect(storages.persistentInstances.size).toBe(0); + expect(storages.persistentInstances).toStrictEqual({}); expect(storages.instantiateLocalStorage).toHaveBeenCalled(); }); @@ -44,7 +50,8 @@ describe('Storages Tests', () => { beforeEach(() => { storages = new Storages(dummyAgile); - dummyAgile.storages = storages; + registerSharedStorageManager(storages); + dummyStorageMethods = { get: jest.fn(), set: jest.fn(), From fd0f56909e28ba023c2ca57781e1c5e92404a11e Mon Sep 17 00:00:00 2001 From: BennoDev Date: Wed, 25 Aug 2021 16:03:51 +0200 Subject: [PATCH 25/44] fixed typos --- packages/core/src/state/index.ts | 2 +- packages/core/src/state/state.enhanced.ts | 18 ++++++++++++++++++ packages/core/src/storages/index.ts | 8 ++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 84b1ece9..a6152f54 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -46,7 +46,7 @@ export function createLightState( /** * Returns a newly created enhanced State. * - * A enhanced State manages, like a normal State, a piece of Information + * An enhanced State manages, like a normal State, a piece of Information * that we need to remember globally at a later point in time. * While providing a toolkit to use and mutate this piece of Information. * diff --git a/packages/core/src/state/state.enhanced.ts b/packages/core/src/state/state.enhanced.ts index 5dcde925..fe3a4c67 100644 --- a/packages/core/src/state/state.enhanced.ts +++ b/packages/core/src/state/state.enhanced.ts @@ -32,6 +32,24 @@ export class EnhancedState extends State { // When an interval is active, the 'intervalId' to clear the interval is temporary stored here public currentInterval?: NodeJS.Timer | number; + /** + * An enhanced State manages, like a normal State, a piece of Information + * that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this piece of Information. + * + * The main difference to a normal State is however + * that an enhanced State provides a wider variety of inbuilt utilities (like a persist, undo, watch functionality) + * but requires a larger bundle size in return. + * + * You can create as many global enhanced States as you need. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstate) + * + * @public + * @param agileInstance - Instance of Agile the State belongs to. + * @param initialValue - Initial value of the State. + * @param config - Configuration object + */ constructor( agileInstance: Agile, initialValue: ValueType, diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 8afb0ad0..86992999 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -40,7 +40,7 @@ export function createStorage(config: CreateStorageConfigInterface): Storage { /** * Returns a newly created Storage Manager. * - * A Storage Manager manages all external Storages for an AgileTs + * A Storage Manager manages all external Storages for AgileTs * and provides an interface to easily store, * load and remove values from multiple Storages at once. * @@ -59,9 +59,10 @@ export function createStorageManager( } /** - * Returns the current registered Storage Manager. + * Returns the shared Storage Manager + * or creates a new one when no shared Storage Manager exists. */ -export function getStorageManager(): Storages | null { +export function getStorageManager(): Storages { if (storageManager == null) { const newStorageManager = createStorageManager({ localStorage: !runsOnServer(), @@ -69,7 +70,6 @@ export function getStorageManager(): Storages | null { registerSharedStorageManager(newStorageManager); return newStorageManager; } - return storageManager; } From ae48e86e12b5ac32c426d0c36a06dd424fd7fefa Mon Sep 17 00:00:00 2001 From: BennoDev Date: Wed, 25 Aug 2021 17:49:12 +0200 Subject: [PATCH 26/44] fixed typos --- packages/core/src/state/index.ts | 1 + packages/core/src/state/state.observer.ts | 2 +- packages/core/src/storages/index.ts | 2 +- packages/core/src/storages/storages.ts | 2 +- .../core/tests/unit/state/state.observer.test.ts | 13 ++++++------- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index a6152f54..7bc1d192 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -43,6 +43,7 @@ export function createLightState( ); } +// TODO 'createState' doesn't get entirely treeshaken away /** * Returns a newly created enhanced State. * diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index b7cd7327..f2ef181c 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -107,7 +107,7 @@ export class StateObserver extends Observer { } // Assign next State value to Observer and compute it if necessary (enhanced State) - this.nextStateValue = (state as EnhancedState).computeValueMethod + this.nextStateValue = (state as any).computeValueMethod ? copy((state as any).computeValueMethod(newStateValue)) : copy(newStateValue); diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 86992999..020a2525 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -42,7 +42,7 @@ export function createStorage(config: CreateStorageConfigInterface): Storage { * * A Storage Manager manages all external Storages for AgileTs * and provides an interface to easily store, - * load and remove values from multiple Storages at once. + * load and remove values from multiple external Storages at once. * * @param config - Configuration object */ diff --git a/packages/core/src/storages/storages.ts b/packages/core/src/storages/storages.ts index dd0f9ea1..36a05903 100644 --- a/packages/core/src/storages/storages.ts +++ b/packages/core/src/storages/storages.ts @@ -97,7 +97,7 @@ export class Storages { this.storages[storage.key] = storage; if (config.default) this.config.defaultStorageKey = storage.key; - for (const persistentKey in this.persistentInstances) { + for (const persistentKey in Object.keys(this.persistentInstances)) { const persistent = this.persistentInstances[persistentKey]; // Revalidate Persistent, which contains key/name identifier of the newly registered Storage diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts index 8a40d53c..e6e64367 100644 --- a/packages/core/tests/unit/state/state.observer.test.ts +++ b/packages/core/tests/unit/state/state.observer.test.ts @@ -4,9 +4,9 @@ import { StateRuntimeJob, Observer, StateObserver, - StatePersistent, - SubscriptionContainer, EnhancedState, + SubscriptionContainer, + State, } from '../../../src'; import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../helper/logMock'; @@ -14,13 +14,13 @@ import waitForExpect from 'wait-for-expect'; describe('StateObserver Tests', () => { let dummyAgile: Agile; - let dummyState: EnhancedState; + let dummyState: State; beforeEach(() => { LogMock.mockLogs(); dummyAgile = new Agile(); - dummyState = new EnhancedState(dummyAgile, 'dummyValue', { + dummyState = new State(dummyAgile, 'dummyValue', { key: 'dummyState', }); @@ -319,7 +319,8 @@ describe('StateObserver Tests', () => { 'should ingest the State into the Runtime and compute its new value ' + 'if the State has a set compute function (default config)', () => { - dummyState.computeValueMethod = (value) => `cool value '${value}'`; + (dummyState as EnhancedState).computeValueMethod = (value) => + `cool value '${value}'`; stateObserver.ingestValue('updatedDummyValue'); @@ -343,8 +344,6 @@ describe('StateObserver Tests', () => { dummyJob = new StateRuntimeJob(stateObserver, { key: 'dummyJob', }); - dummyState.persistent = new StatePersistent(dummyState); - dummyState.isPersisted = true; stateObserver.sideEffects = jest.fn(); }); From b490410f35c1ad5fd5a69b32ae4da6543ab10769 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Thu, 26 Aug 2021 07:19:01 +0200 Subject: [PATCH 27/44] fixed typos --- .../react/develop/tree-shaking/package.json | 25 +++++++++++++++ .../react/develop/tree-shaking/src/App.jsx | 9 ++++++ .../react/develop/tree-shaking/src/core.js | 3 ++ .../react/develop/tree-shaking/src/index.js | 9 ++++++ .../develop/tree-shaking/webpack.config.js | 32 +++++++++++++++++++ .../boxes/src/core/entities/ui/ui.actions.ts | 1 - .../src/core/entities/ui/ui.controller.ts | 8 ++--- packages/core/src/storages/index.ts | 8 ++--- .../collection/collection.persistent.test.ts | 4 +-- .../tests/unit/state/state.persistent.test.ts | 4 +-- .../tests/unit/storages/persistent.test.ts | 8 ++--- .../core/tests/unit/storages/storages.test.ts | 4 +-- 12 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 examples/react/develop/tree-shaking/package.json create mode 100644 examples/react/develop/tree-shaking/src/App.jsx create mode 100644 examples/react/develop/tree-shaking/src/core.js create mode 100644 examples/react/develop/tree-shaking/src/index.js create mode 100644 examples/react/develop/tree-shaking/webpack.config.js diff --git a/examples/react/develop/tree-shaking/package.json b/examples/react/develop/tree-shaking/package.json new file mode 100644 index 00000000..05202eaf --- /dev/null +++ b/examples/react/develop/tree-shaking/package.json @@ -0,0 +1,25 @@ +{ + "name": "tree-shaking", + "version": "1.0.0", + "main": "src/index.js", + "license": "MIT", + "scripts": { + "build": "npx webpack --config webpack.config.js", + "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/logger & yarn install", + "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/logger & yarn install" + }, + "devDependencies": { + "babel-core": "^6.26.3", + "babel-loader": "^8.2.2", + "babel-preset-env": "^1.7.0", + "babel-preset-react": "^6.24.1", + "webpack": "^5.51.1", + "webpack-cli": "^4.8.0" + }, + "dependencies": { + "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/logger": "file:.yalc/@agile-ts/logger", + "@agile-ts/react": "file:.yalc/@agile-ts/react", + "react": "^17.0.2" + } +} diff --git a/examples/react/develop/tree-shaking/src/App.jsx b/examples/react/develop/tree-shaking/src/App.jsx new file mode 100644 index 00000000..ac5157ce --- /dev/null +++ b/examples/react/develop/tree-shaking/src/App.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export const FooComponent = ({ name }) => ( +
Hello from FooComponent, {name ?? 'unknown'}!
+); + +export const BarComponent = ({ name }) => ( +
Hello from BarComponent, {name ?? 'unknown'}!
+); diff --git a/examples/react/develop/tree-shaking/src/core.js b/examples/react/develop/tree-shaking/src/core.js new file mode 100644 index 00000000..6dcfb7c0 --- /dev/null +++ b/examples/react/develop/tree-shaking/src/core.js @@ -0,0 +1,3 @@ +import { createLightState } from '@agile-ts/core'; + +export const MY_STATE = createLightState('jeff'); diff --git a/examples/react/develop/tree-shaking/src/index.js b/examples/react/develop/tree-shaking/src/index.js new file mode 100644 index 00000000..7fb253f5 --- /dev/null +++ b/examples/react/develop/tree-shaking/src/index.js @@ -0,0 +1,9 @@ +import { BarComponent } from './App'; +import { MY_STATE } from './core'; + +MY_STATE.set('jeff'); + +// we could do something with BarComponent here, +// like ReactDOM.render, but let's just dump it to +// console for simplicity +console.log(BarComponent); diff --git a/examples/react/develop/tree-shaking/webpack.config.js b/examples/react/develop/tree-shaking/webpack.config.js new file mode 100644 index 00000000..370e6b55 --- /dev/null +++ b/examples/react/develop/tree-shaking/webpack.config.js @@ -0,0 +1,32 @@ +const path = require('path'); +const packageJson = require('./package.json'); +const dependencies = packageJson.dependencies || {}; + +module.exports = { + mode: 'production', + entry: './src/index.js', + externals: Object.keys(dependencies), + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'app.js', + }, + resolve: { extensions: ['.js', '.jsx'] }, + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [['@babel/env', { modules: false }], '@babel/react'], + }, + }, + }, + ], + }, + optimization: { + providedExports: true, + usedExports: true, + }, +}; diff --git a/examples/react/release/boxes/src/core/entities/ui/ui.actions.ts b/examples/react/release/boxes/src/core/entities/ui/ui.actions.ts index c3d537cd..2fe86360 100644 --- a/examples/react/release/boxes/src/core/entities/ui/ui.actions.ts +++ b/examples/react/release/boxes/src/core/entities/ui/ui.actions.ts @@ -8,7 +8,6 @@ import { SCREEN, } from './ui.controller'; import core from '../../index'; -import { copy } from '@agile-ts/utils'; export const addDefaultElement = (image: boolean = false) => { if (image) addElement(defaultElementStyle, getRandomImage()); diff --git a/examples/react/release/boxes/src/core/entities/ui/ui.controller.ts b/examples/react/release/boxes/src/core/entities/ui/ui.controller.ts index c92dcf93..071de3b3 100644 --- a/examples/react/release/boxes/src/core/entities/ui/ui.controller.ts +++ b/examples/react/release/boxes/src/core/entities/ui/ui.controller.ts @@ -1,22 +1,22 @@ -import { App } from '../../app'; import { CanvasInterface, ElementInterface, ScreenInterface, } from './ui.interfaces'; +import { createCollection, createState } from '@agile-ts/core'; export const defaultElementStyle = { position: { top: 0, left: 0 }, size: { width: 200, height: 200 }, }; -export const CANVAS = App.createState({ +export const CANVAS = createState({ width: 5000, height: 5000, }); -export const SCREEN = App.createState({ width: 0, height: 0 }); +export const SCREEN = createState({ width: 0, height: 0 }); -export const ELEMENTS = App.createCollection(); +export const ELEMENTS = createCollection(); export const SELECTED_ELEMENT = ELEMENTS.createSelector( 'selectedElement', diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 020a2525..0342165e 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -67,19 +67,19 @@ export function getStorageManager(): Storages { const newStorageManager = createStorageManager({ localStorage: !runsOnServer(), }); - registerSharedStorageManager(newStorageManager); + assignSharedAgileStorageManager(newStorageManager); return newStorageManager; } return storageManager; } /** - * Registers the specified Storage Manager - * as default Storage Manager for all Agile Instances. + * Assigns the specified Storage Manager + * as default (shared) Storage Manager for all Agile Instances. * * @param instance - Storage Manager to be registered as the default Storage Manager. */ -export const registerSharedStorageManager = (instance: Storages) => { +export const assignSharedAgileStorageManager = (instance: Storages) => { if (storageManager != null) { LogCodeManager.log('11:02:06', [], storageManager); } diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index b4b1c4b1..0c3844a1 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -7,7 +7,7 @@ import { StatePersistent, Group, Item, - registerSharedStorageManager, + assignSharedAgileStorageManager, createStorageManager, getStorageManager, Storages, @@ -34,7 +34,7 @@ describe('CollectionPersistent Tests', () => { }); // Register Storage Manager - registerSharedStorageManager(createStorageManager()); + assignSharedAgileStorageManager(createStorageManager()); storageManager = getStorageManager() as any; jest.spyOn(CollectionPersistent.prototype, 'instantiatePersistent'); diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index a5870b98..b226d5d8 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -6,7 +6,7 @@ import { EnhancedState, getStorageManager, Storages, - registerSharedStorageManager, + assignSharedAgileStorageManager, createStorageManager, } from '../../../src'; import { LogMock } from '../../helper/logMock'; @@ -24,7 +24,7 @@ describe('StatePersistent Tests', () => { dummyState = new EnhancedState(dummyAgile, 'dummyValue'); // Register Storage Manager - registerSharedStorageManager(createStorageManager()); + assignSharedAgileStorageManager(createStorageManager()); storageManager = getStorageManager() as any; jest.spyOn(StatePersistent.prototype, 'instantiatePersistent'); diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index a6673cb0..da35c44f 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -4,7 +4,7 @@ import { Storage, createStorage, Storages, - registerSharedStorageManager, + assignSharedAgileStorageManager, createStorageManager, getStorageManager, } from '../../../src'; @@ -20,7 +20,7 @@ describe('Persistent Tests', () => { dummyAgile = new Agile(); // Register Storage Manager - registerSharedStorageManager(createStorageManager()); + assignSharedAgileStorageManager(createStorageManager()); storageManager = getStorageManager() as any; jest.spyOn(Persistent.prototype, 'instantiatePersistent'); @@ -201,7 +201,7 @@ describe('Persistent Tests', () => { it( 'should call formatKey, assignStorageKeys, validatePersistent ' + - 'and add Persistent to the Storage Manager when Persistent has a valid key', + 'and add Persistent to the shared Storage Manager if Persistent has a valid key', () => { persistent.instantiatePersistent({ key: 'persistentKey', @@ -227,7 +227,7 @@ describe('Persistent Tests', () => { it( 'should call formatKey, assignStorageKeys, validatePersistent ' + - "and shouldn't add Persistent to the Storage Manager when Persistent has no valid key", + "and shouldn't add Persistent to the shared Storage Manager if Persistent has no valid key", () => { persistent.instantiatePersistent({ storageKeys: ['myName', 'is', 'jeff'], diff --git a/packages/core/tests/unit/storages/storages.test.ts b/packages/core/tests/unit/storages/storages.test.ts index f19d3df1..b54833b2 100644 --- a/packages/core/tests/unit/storages/storages.test.ts +++ b/packages/core/tests/unit/storages/storages.test.ts @@ -3,7 +3,7 @@ import { Agile, Storage, Persistent, - registerSharedStorageManager, + assignSharedAgileStorageManager, } from '../../../src'; import { LogMock } from '../../helper/logMock'; @@ -50,7 +50,7 @@ describe('Storages Tests', () => { beforeEach(() => { storages = new Storages(dummyAgile); - registerSharedStorageManager(storages); + assignSharedAgileStorageManager(storages); dummyStorageMethods = { get: jest.fn(), From 51b486ad1285ca20c7afc7882a60b8305854a1cf Mon Sep 17 00:00:00 2001 From: BennoDev Date: Thu, 26 Aug 2021 07:31:30 +0200 Subject: [PATCH 28/44] fixed typo --- examples/react/develop/tree-shaking/webpack.config.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/react/develop/tree-shaking/webpack.config.js b/examples/react/develop/tree-shaking/webpack.config.js index 370e6b55..1b3fb79c 100644 --- a/examples/react/develop/tree-shaking/webpack.config.js +++ b/examples/react/develop/tree-shaking/webpack.config.js @@ -1,11 +1,8 @@ const path = require('path'); -const packageJson = require('./package.json'); -const dependencies = packageJson.dependencies || {}; module.exports = { - mode: 'production', + mode: 'development', entry: './src/index.js', - externals: Object.keys(dependencies), output: { path: path.resolve(__dirname, 'dist'), filename: 'app.js', @@ -26,7 +23,9 @@ module.exports = { ], }, optimization: { - providedExports: true, usedExports: true, + innerGraph: true, + sideEffects: true, }, + devtool: false, }; From d8ee8bf8ca66382f2047fe853dd168800b8aa45e Mon Sep 17 00:00:00 2001 From: BennoDev Date: Thu, 26 Aug 2021 09:40:13 +0200 Subject: [PATCH 29/44] fixed typo --- packages/core/src/storages/storages.ts | 3 ++- .../collection.persistent.integration.test.ts | 16 +++++++++++++--- packages/core/tests/unit/agile.test.ts | 1 - .../collection/collection.persistent.test.ts | 5 ++--- .../tests/unit/state/state.persistent.test.ts | 5 ++--- .../core/tests/unit/storages/persistent.test.ts | 5 ++--- .../core/tests/unit/storages/storages.test.ts | 2 +- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/core/src/storages/storages.ts b/packages/core/src/storages/storages.ts index 36a05903..99fcfa9b 100644 --- a/packages/core/src/storages/storages.ts +++ b/packages/core/src/storages/storages.ts @@ -97,8 +97,9 @@ export class Storages { this.storages[storage.key] = storage; if (config.default) this.config.defaultStorageKey = storage.key; - for (const persistentKey in Object.keys(this.persistentInstances)) { + for (const persistentKey of Object.keys(this.persistentInstances)) { const persistent = this.persistentInstances[persistentKey]; + if (persistent == null) continue; // Revalidate Persistent, which contains key/name identifier of the newly registered Storage if (persistent.storageKeys.includes(storage.key)) { diff --git a/packages/core/tests/integration/collection.persistent.integration.test.ts b/packages/core/tests/integration/collection.persistent.integration.test.ts index bac84d63..c9a85992 100644 --- a/packages/core/tests/integration/collection.persistent.integration.test.ts +++ b/packages/core/tests/integration/collection.persistent.integration.test.ts @@ -1,4 +1,11 @@ -import { Agile, Item, createStorage, createCollection } from '../../src'; +import { + Agile, + Item, + createStorage, + createCollection, + createStorageManager, + assignSharedAgileStorageManager, +} from '../../src'; import { LogMock } from '../helper/logMock'; describe('Collection Persist Function Tests', () => { @@ -28,8 +35,11 @@ describe('Collection Persist Function Tests', () => { LogMock.mockLogs(); jest.clearAllMocks(); - App = new Agile({ localStorage: false }); - App.registerStorage( + App = new Agile(); + + const storageManager = createStorageManager({ localStorage: false }); + assignSharedAgileStorageManager(storageManager); + storageManager.register( createStorage({ key: 'testStorage', prefix: 'test', diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 9d18f45b..300c9be6 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -3,7 +3,6 @@ import { Runtime, SubController, Integrations, - Storage, Storages, } from '../../src'; import testIntegration from '../helper/test.integration'; diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 0c3844a1..b4f64ef1 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -9,7 +9,6 @@ import { Item, assignSharedAgileStorageManager, createStorageManager, - getStorageManager, Storages, } from '../../../src'; import { LogMock } from '../../helper/logMock'; @@ -34,8 +33,8 @@ describe('CollectionPersistent Tests', () => { }); // Register Storage Manager - assignSharedAgileStorageManager(createStorageManager()); - storageManager = getStorageManager() as any; + storageManager = createStorageManager(); + assignSharedAgileStorageManager(storageManager); jest.spyOn(CollectionPersistent.prototype, 'instantiatePersistent'); jest.spyOn(CollectionPersistent.prototype, 'initialLoading'); diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index b226d5d8..1fa45381 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -4,7 +4,6 @@ import { Storage, Persistent, EnhancedState, - getStorageManager, Storages, assignSharedAgileStorageManager, createStorageManager, @@ -24,8 +23,8 @@ describe('StatePersistent Tests', () => { dummyState = new EnhancedState(dummyAgile, 'dummyValue'); // Register Storage Manager - assignSharedAgileStorageManager(createStorageManager()); - storageManager = getStorageManager() as any; + storageManager = createStorageManager(); + assignSharedAgileStorageManager(storageManager); jest.spyOn(StatePersistent.prototype, 'instantiatePersistent'); jest.spyOn(StatePersistent.prototype, 'initialLoading'); diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index da35c44f..44d09106 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -6,7 +6,6 @@ import { Storages, assignSharedAgileStorageManager, createStorageManager, - getStorageManager, } from '../../../src'; import { LogMock } from '../../helper/logMock'; @@ -20,8 +19,8 @@ describe('Persistent Tests', () => { dummyAgile = new Agile(); // Register Storage Manager - assignSharedAgileStorageManager(createStorageManager()); - storageManager = getStorageManager() as any; + storageManager = createStorageManager(); + assignSharedAgileStorageManager(storageManager); jest.spyOn(Persistent.prototype, 'instantiatePersistent'); diff --git a/packages/core/tests/unit/storages/storages.test.ts b/packages/core/tests/unit/storages/storages.test.ts index b54833b2..9f09d002 100644 --- a/packages/core/tests/unit/storages/storages.test.ts +++ b/packages/core/tests/unit/storages/storages.test.ts @@ -190,7 +190,7 @@ describe('Storages Tests', () => { expect(response).toBeTruthy(); }); - it('should revalidate and initial load Persistents that have no defined defaultStorage', () => { + it('should revalidate and initial load persistent Instances that have no defined defaultStorage', () => { const dummyPersistent1 = new Persistent(dummyAgile, { key: 'dummyPersistent1', }); From aa20d8664ae53f1946a108c3fa69a41461e283d7 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Thu, 26 Aug 2021 18:11:53 +0200 Subject: [PATCH 30/44] fixed typos --- packages/core/src/state/index.ts | 2 +- packages/core/src/storages/index.ts | 2 +- packages/core/tests/unit/agile.test.ts | 5 - packages/core/tests/unit/shared.test.ts | 72 -------- packages/core/tests/unit/state/index.test.ts | 100 +++++++++++ .../core/tests/unit/storages/index.test.ts | 164 ++++++++++++++++++ 6 files changed, 266 insertions(+), 79 deletions(-) create mode 100644 packages/core/tests/unit/state/index.test.ts create mode 100644 packages/core/tests/unit/storages/index.test.ts diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index 7bc1d192..6a208125 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -43,7 +43,7 @@ export function createLightState( ); } -// TODO 'createState' doesn't get entirely treeshaken away +// TODO 'createState' doesn't get entirely treeshaken away (React project) /** * Returns a newly created enhanced State. * diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 0342165e..b373d0fc 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -79,7 +79,7 @@ export function getStorageManager(): Storages { * * @param instance - Storage Manager to be registered as the default Storage Manager. */ -export const assignSharedAgileStorageManager = (instance: Storages) => { +export const assignSharedAgileStorageManager = (instance: Storages | null) => { if (storageManager != null) { LogCodeManager.log('11:02:06', [], storageManager); } diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 300c9be6..85521f63 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -24,11 +24,6 @@ jest.mock('../../src/runtime/subscription/sub.controller', () => { SubController: jest.fn(), }; }); -jest.mock('../../src/storages', () => { - return { - Storages: jest.fn(), - }; -}); // https://gist.github.com/virgs/d9c50e878fc69832c01f8085f2953f12 // https://medium.com/@masonlgoetz/mock-static-class-methods-in-jest-1ceda967b47f diff --git a/packages/core/tests/unit/shared.test.ts b/packages/core/tests/unit/shared.test.ts index e93d0d97..246215d1 100644 --- a/packages/core/tests/unit/shared.test.ts +++ b/packages/core/tests/unit/shared.test.ts @@ -3,27 +3,15 @@ import { Collection, Computed, shared, - State, - Storage, - createStorage, - createState, createCollection, createComputed, assignSharedAgileInstance, } from '../../src'; import { LogMock } from '../helper/logMock'; -jest.mock('../../src/storages/storage'); jest.mock('../../src/collection/collection'); jest.mock('../../src/computed/computed'); -// https://github.com/facebook/jest/issues/5023 -jest.mock('../../src/state/state', () => { - return { - State: jest.fn(), - }; -}); - describe('Shared Tests', () => { let sharedAgileInstance: Agile; @@ -46,66 +34,6 @@ describe('Shared Tests', () => { }); }); - describe('createStorage function tests', () => { - const StorageMock = Storage as jest.MockedClass; - - beforeEach(() => { - StorageMock.mockClear(); - }); - - it('should create Storage', () => { - const storageConfig = { - prefix: 'test', - methods: { - get: () => { - /* empty function */ - }, - set: () => { - /* empty function */ - }, - remove: () => { - /* empty function */ - }, - }, - key: 'myTestStorage', - }; - - const storage = createStorage(storageConfig); - - expect(storage).toBeInstanceOf(Storage); - expect(StorageMock).toHaveBeenCalledWith(storageConfig); - }); - }); - - describe('createState function tests', () => { - const StateMock = State as jest.MockedClass; - - it('should create State with the shared Agile Instance', () => { - const state = createState('testValue', { - key: 'myCoolState', - }); - - expect(state).toBeInstanceOf(State); - expect(StateMock).toHaveBeenCalledWith(sharedAgileInstance, 'testValue', { - key: 'myCoolState', - }); - }); - - it('should create State with a specified Agile Instance', () => { - const agile = new Agile(); - - const state = createState('testValue', { - key: 'myCoolState', - agileInstance: agile, - }); - - expect(state).toBeInstanceOf(State); - expect(StateMock).toHaveBeenCalledWith(agile, 'testValue', { - key: 'myCoolState', - }); - }); - }); - describe('createCollection function tests', () => { const CollectionMock = Collection as jest.MockedClass; diff --git a/packages/core/tests/unit/state/index.test.ts b/packages/core/tests/unit/state/index.test.ts new file mode 100644 index 00000000..1b84c772 --- /dev/null +++ b/packages/core/tests/unit/state/index.test.ts @@ -0,0 +1,100 @@ +// https://github.com/facebook/jest/issues/5023 +import { + Agile, + assignSharedAgileInstance, + createState, + createLightState, + State, + EnhancedState, +} from '../../../src'; +import { LogMock } from '../../helper/logMock'; + +// https://github.com/facebook/jest/issues/5023 +jest.mock('../../../src/state/state', () => { + return { + State: jest.fn(), + }; +}); +// https://github.com/facebook/jest/issues/5023 +jest.mock('../../../src/state/state.enhanced', () => { + return { + EnhancedState: jest.fn(), + }; +}); + +describe('State Index', () => { + let sharedAgileInstance: Agile; + + beforeEach(() => { + LogMock.mockLogs(); + + sharedAgileInstance = new Agile(); + assignSharedAgileInstance(sharedAgileInstance); + + jest.clearAllMocks(); + }); + + describe('createState function tests', () => { + const EnhancedStateMock = EnhancedState as jest.MockedClass< + typeof EnhancedState + >; + + it('should create enhanced State with the shared Agile Instance', () => { + const state = createState('testValue', { + key: 'myCoolState', + }); + + // expect(state).toBeInstanceOf(EnhancedState); // Because 'State' is completely overwritten with a mock (mockImplementation) + expect(EnhancedStateMock).toHaveBeenCalledWith( + sharedAgileInstance, + 'testValue', + { + key: 'myCoolState', + } + ); + }); + + it('should create enhanced State with a specified Agile Instance', () => { + const agile = new Agile(); + + const state = createState('testValue', { + key: 'myCoolState', + agileInstance: agile, + }); + + // expect(state).toBeInstanceOf(EnhancedState); // Because 'State' is completely overwritten with a mock (mockImplementation) + expect(EnhancedStateMock).toHaveBeenCalledWith(agile, 'testValue', { + key: 'myCoolState', + }); + }); + }); + + describe('createLightState function tests', () => { + const StateMock = State as jest.MockedClass; + + it('should create State with the shared Agile Instance', () => { + const state = createLightState('testValue', { + key: 'myCoolState', + }); + + // expect(state).toBeInstanceOf(State); // Because 'State' is completely overwritten with a mock (mockImplementation) + expect(StateMock).toHaveBeenCalledWith(sharedAgileInstance, 'testValue', { + key: 'myCoolState', + }); + }); + + it('should create State with a specified Agile Instance', () => { + const agile = new Agile(); + + const state = createLightState('testValue', { + key: 'myCoolState', + agileInstance: agile, + }); + + // expect(state).toBeInstanceOf(State); // Because 'State' is completely overwritten with a mock (mockImplementation) + expect(StateMock).toHaveBeenCalledWith(agile, 'testValue', { + key: 'myCoolState', + }); + }); + }); +}); diff --git a/packages/core/tests/unit/storages/index.test.ts b/packages/core/tests/unit/storages/index.test.ts new file mode 100644 index 00000000..c26137e2 --- /dev/null +++ b/packages/core/tests/unit/storages/index.test.ts @@ -0,0 +1,164 @@ +import { + Agile, + Storages, + Storage, + assignSharedAgileInstance, +} from '../../../src'; +import * as StorageIndex from '../../../src/storages/index'; +import { LogMock } from '../../helper/logMock'; +jest.mock('../../../src/storages/storages'); +jest.mock('../../../src/storages/storage'); + +describe('Storages Index', () => { + let sharedAgileInstance: Agile; + + beforeEach(() => { + LogMock.mockLogs(); + + sharedAgileInstance = new Agile(); + assignSharedAgileInstance(sharedAgileInstance); + + StorageIndex.assignSharedAgileStorageManager(null); + + jest.clearAllMocks(); + }); + + describe('createStorage function tests', () => { + const StorageMock = Storage as jest.MockedClass; + + beforeEach(() => { + StorageMock.mockClear(); + }); + + it('should create Storage', () => { + const storageConfig = { + prefix: 'test', + methods: { + get: () => { + /* empty function */ + }, + set: () => { + /* empty function */ + }, + remove: () => { + /* empty function */ + }, + }, + key: 'myTestStorage', + }; + + const storage = StorageIndex.createStorage(storageConfig); + + expect(storage).toBeInstanceOf(Storage); + expect(StorageMock).toHaveBeenCalledWith(storageConfig); + }); + }); + + describe('createStorageManager function tests', () => { + const StoragesMock = Storages as jest.MockedClass; + + beforeEach(() => { + StoragesMock.mockClear(); + }); + + it('should create Storage Manager (Storages) with the shared Agile Instance', () => { + const storageManager = StorageIndex.createStorageManager({ + localStorage: true, + }); + + expect(storageManager).toBeInstanceOf(Storages); + expect(StoragesMock).toHaveBeenCalledWith(sharedAgileInstance, { + localStorage: true, + }); + }); + + it('should create Storage Manager (Storages) with a specified Agile Instance', () => { + const agile = new Agile(); + + const storageManager = StorageIndex.createStorageManager({ + agileInstance: agile, + localStorage: true, + }); + + expect(storageManager).toBeInstanceOf(Storages); + expect(StoragesMock).toHaveBeenCalledWith(agile, { localStorage: true }); + }); + }); + + describe('getStorageManager function tests', () => { + beforeEach(() => { + StorageIndex.assignSharedAgileStorageManager(null); + + jest.spyOn(StorageIndex, 'assignSharedAgileStorageManager'); + jest.spyOn(StorageIndex, 'createStorageManager'); + }); + + it('should return shared Storage Manager', () => { + const createdStorageManager = new Storages(sharedAgileInstance, { + localStorage: false, + }); + StorageIndex.assignSharedAgileStorageManager(createdStorageManager); + jest.clearAllMocks(); + + const returnedStorageManager = StorageIndex.getStorageManager(); + + expect(returnedStorageManager).toBeInstanceOf(Storages); + expect(returnedStorageManager).toBe(createdStorageManager); + expect(StorageIndex.createStorageManager).not.toHaveBeenCalled(); + expect( + StorageIndex.assignSharedAgileStorageManager + ).not.toHaveBeenCalled(); + }); + + // TODO doesn't work although it should 100% work?! + // it( + // 'should return newly created Storage Manager ' + + // 'if no shared Storage Manager was registered yet', + // () => { + // const createdStorageManager = new Storages(sharedAgileInstance, { + // localStorage: false, + // }); + // jest + // .spyOn(StorageIndex, 'createStorageManager') + // .mockReturnValueOnce(createdStorageManager); + // + // const returnedStorageManager = StorageIndex.getStorageManager(); + // + // expect(returnedStorageManager).toBeInstanceOf(Storages); + // expect(returnedStorageManager).toBe(createdStorageManager); + // expect(StorageIndex.createStorageManager).toHaveBeenCalledWith({ + // localStorage: false, + // }); + // expect( + // StorageIndex.assignSharedAgileStorageManager + // ).toHaveBeenCalledWith(createdStorageManager); + // } + // ); + }); + + describe('assignSharedAgileStorageManager function tests', () => { + it('should assign the specified Storage Manager as shared Storage Manager', () => { + const storageManager = new Storages(sharedAgileInstance); + + StorageIndex.assignSharedAgileStorageManager(storageManager); + + expect(StorageIndex.getStorageManager()).toBe(storageManager); + LogMock.hasNotLoggedCode('11:02:06'); + }); + + it( + 'should assign the specified Storage Manager as shared Storage Manager' + + 'and print warning if a shared Storage Manager is already set', + () => { + const oldStorageManager = new Storages(sharedAgileInstance); + StorageIndex.assignSharedAgileStorageManager(oldStorageManager); + const storageManager = new Storages(sharedAgileInstance); + + StorageIndex.assignSharedAgileStorageManager(storageManager); + + expect(StorageIndex.getStorageManager()).toBe(storageManager); + LogMock.hasLoggedCode('11:02:06', [], oldStorageManager); + } + ); + }); +}); From fe46dabd9c6b34916a3046dcb197e83f7352be6c Mon Sep 17 00:00:00 2001 From: BennoDev Date: Thu, 26 Aug 2021 18:20:13 +0200 Subject: [PATCH 31/44] outsourced collection and computed shared tests --- .../core/tests/unit/collection/index.test.ts | 62 ++++++++++ .../core/tests/unit/computed/index.test.ts | 86 ++++++++++++++ packages/core/tests/unit/shared.test.ts | 107 ------------------ 3 files changed, 148 insertions(+), 107 deletions(-) create mode 100644 packages/core/tests/unit/collection/index.test.ts create mode 100644 packages/core/tests/unit/computed/index.test.ts diff --git a/packages/core/tests/unit/collection/index.test.ts b/packages/core/tests/unit/collection/index.test.ts new file mode 100644 index 00000000..50aa234a --- /dev/null +++ b/packages/core/tests/unit/collection/index.test.ts @@ -0,0 +1,62 @@ +import { + Agile, + assignSharedAgileInstance, + Collection, + createCollection, +} from '../../../src'; +import { LogMock } from '../../helper/logMock'; + +jest.mock('../../../src/collection/collection'); + +describe('Collection Index', () => { + let sharedAgileInstance: Agile; + + beforeEach(() => { + LogMock.mockLogs(); + + sharedAgileInstance = new Agile(); + assignSharedAgileInstance(sharedAgileInstance); + + jest.clearAllMocks(); + }); + + describe('createCollection function tests', () => { + const CollectionMock = Collection as jest.MockedClass; + + beforeEach(() => { + CollectionMock.mockClear(); + }); + + it('should create Collection with the shared Agile Instance', () => { + const collectionConfig = { + selectors: ['test', 'test1'], + groups: ['test2', 'test10'], + defaultGroupKey: 'frank', + key: 'myCoolCollection', + }; + + const collection = createCollection(collectionConfig); + + expect(collection).toBeInstanceOf(Collection); + expect(CollectionMock).toHaveBeenCalledWith( + sharedAgileInstance, + collectionConfig + ); + }); + + it('should create Collection with a specified Agile Instance', () => { + const agile = new Agile(); + const collectionConfig = { + selectors: ['test', 'test1'], + groups: ['test2', 'test10'], + defaultGroupKey: 'frank', + key: 'myCoolCollection', + }; + + const collection = createCollection(collectionConfig, agile); + + expect(collection).toBeInstanceOf(Collection); + expect(CollectionMock).toHaveBeenCalledWith(agile, collectionConfig); + }); + }); +}); diff --git a/packages/core/tests/unit/computed/index.test.ts b/packages/core/tests/unit/computed/index.test.ts new file mode 100644 index 00000000..262ba1c0 --- /dev/null +++ b/packages/core/tests/unit/computed/index.test.ts @@ -0,0 +1,86 @@ +import { + Agile, + assignSharedAgileInstance, + Computed, + createComputed, +} from '../../../src'; +import { LogMock } from '../../helper/logMock'; + +jest.mock('../../../src/computed/computed'); + +describe('Computed Index', () => { + let sharedAgileInstance: Agile; + + beforeEach(() => { + LogMock.mockLogs(); + + sharedAgileInstance = new Agile(); + assignSharedAgileInstance(sharedAgileInstance); + + jest.clearAllMocks(); + }); + + describe('createComputed function tests', () => { + const ComputedMock = Computed as jest.MockedClass; + const computedFunction = () => { + // empty + }; + + beforeEach(() => { + ComputedMock.mockClear(); + }); + + it('should create Computed with the shared Agile Instance (default config)', () => { + const response = createComputed(computedFunction, ['dummyDep' as any]); + + expect(response).toBeInstanceOf(Computed); + expect(ComputedMock).toHaveBeenCalledWith( + sharedAgileInstance, + computedFunction, + { + computedDeps: ['dummyDep' as any], + } + ); + }); + + it('should create Computed with the shared Agile Instance (specific config)', () => { + const computedConfig = { + key: 'jeff', + isPlaceholder: false, + computedDeps: ['dummyDep' as any], + autodetect: true, + }; + + const response = createComputed(computedFunction, computedConfig); + + expect(response).toBeInstanceOf(Computed); + expect(ComputedMock).toHaveBeenCalledWith( + sharedAgileInstance, + computedFunction, + computedConfig + ); + }); + + it('should create Computed with a specified Agile Instance (specific config)', () => { + const agile = new Agile(); + const computedConfig = { + key: 'jeff', + isPlaceholder: false, + computedDeps: ['dummyDep' as any], + autodetect: true, + }; + + const response = createComputed(computedFunction, { + ...computedConfig, + ...{ agileInstance: agile }, + }); + + expect(response).toBeInstanceOf(Computed); + expect(ComputedMock).toHaveBeenCalledWith( + agile, + computedFunction, + computedConfig + ); + }); + }); +}); diff --git a/packages/core/tests/unit/shared.test.ts b/packages/core/tests/unit/shared.test.ts index 246215d1..b8d5e048 100644 --- a/packages/core/tests/unit/shared.test.ts +++ b/packages/core/tests/unit/shared.test.ts @@ -9,9 +9,6 @@ import { } from '../../src'; import { LogMock } from '../helper/logMock'; -jest.mock('../../src/collection/collection'); -jest.mock('../../src/computed/computed'); - describe('Shared Tests', () => { let sharedAgileInstance: Agile; @@ -33,108 +30,4 @@ describe('Shared Tests', () => { expect(shared).toBe(newAgileInstance); }); }); - - describe('createCollection function tests', () => { - const CollectionMock = Collection as jest.MockedClass; - - beforeEach(() => { - CollectionMock.mockClear(); - }); - - it('should create Collection with the shared Agile Instance', () => { - const collectionConfig = { - selectors: ['test', 'test1'], - groups: ['test2', 'test10'], - defaultGroupKey: 'frank', - key: 'myCoolCollection', - }; - - const collection = createCollection(collectionConfig); - - expect(collection).toBeInstanceOf(Collection); - expect(CollectionMock).toHaveBeenCalledWith( - sharedAgileInstance, - collectionConfig - ); - }); - - it('should create Collection with a specified Agile Instance', () => { - const agile = new Agile(); - const collectionConfig = { - selectors: ['test', 'test1'], - groups: ['test2', 'test10'], - defaultGroupKey: 'frank', - key: 'myCoolCollection', - }; - - const collection = createCollection(collectionConfig, agile); - - expect(collection).toBeInstanceOf(Collection); - expect(CollectionMock).toHaveBeenCalledWith(agile, collectionConfig); - }); - }); - - describe('createComputed function tests', () => { - const ComputedMock = Computed as jest.MockedClass; - const computedFunction = () => { - // empty - }; - - beforeEach(() => { - ComputedMock.mockClear(); - }); - - it('should create Computed with the shared Agile Instance (default config)', () => { - const response = createComputed(computedFunction, ['dummyDep' as any]); - - expect(response).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith( - sharedAgileInstance, - computedFunction, - { - computedDeps: ['dummyDep' as any], - } - ); - }); - - it('should create Computed with the shared Agile Instance (specific config)', () => { - const computedConfig = { - key: 'jeff', - isPlaceholder: false, - computedDeps: ['dummyDep' as any], - autodetect: true, - }; - - const response = createComputed(computedFunction, computedConfig); - - expect(response).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith( - sharedAgileInstance, - computedFunction, - computedConfig - ); - }); - - it('should create Computed with a specified Agile Instance (specific config)', () => { - const agile = new Agile(); - const computedConfig = { - key: 'jeff', - isPlaceholder: false, - computedDeps: ['dummyDep' as any], - autodetect: true, - }; - - const response = createComputed(computedFunction, { - ...computedConfig, - ...{ agileInstance: agile }, - }); - - expect(response).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith( - agile, - computedFunction, - computedConfig - ); - }); - }); }); From 4ee147218d8d6a118f852123c38d61740130be3b Mon Sep 17 00:00:00 2001 From: BennoDev Date: Fri, 27 Aug 2021 07:18:30 +0200 Subject: [PATCH 32/44] fixed multieditor issue --- packages/core/tests/unit/agile.test.ts | 10 +--------- packages/core/tests/unit/state/index.test.ts | 8 ++++++++ packages/core/tests/unit/storages/index.test.ts | 1 + packages/multieditor/src/item.ts | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 85521f63..40682716 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -1,10 +1,4 @@ -import { - Agile, - Runtime, - SubController, - Integrations, - Storages, -} from '../../src'; +import { Agile, Runtime, SubController, Integrations } from '../../src'; import testIntegration from '../helper/test.integration'; import { LogMock } from '../helper/logMock'; @@ -49,7 +43,6 @@ describe('Agile Tests', () => { const SubControllerMock = SubController as jest.MockedClass< typeof SubController >; - const StoragesMock = Storages as jest.MockedClass; const IntegrationsMock = Integrations as jest.MockedClass< typeof Integrations >; @@ -60,7 +53,6 @@ describe('Agile Tests', () => { // Clear specified mocks RuntimeMock.mockClear(); SubControllerMock.mockClear(); - StoragesMock.mockClear(); IntegrationsMock.mockClear(); // Reset globalThis diff --git a/packages/core/tests/unit/state/index.test.ts b/packages/core/tests/unit/state/index.test.ts index 1b84c772..7899b192 100644 --- a/packages/core/tests/unit/state/index.test.ts +++ b/packages/core/tests/unit/state/index.test.ts @@ -39,6 +39,10 @@ describe('State Index', () => { typeof EnhancedState >; + beforeEach(() => { + EnhancedStateMock.mockClear(); + }); + it('should create enhanced State with the shared Agile Instance', () => { const state = createState('testValue', { key: 'myCoolState', @@ -72,6 +76,10 @@ describe('State Index', () => { describe('createLightState function tests', () => { const StateMock = State as jest.MockedClass; + beforeEach(() => { + StateMock.mockClear(); + }); + it('should create State with the shared Agile Instance', () => { const state = createLightState('testValue', { key: 'myCoolState', diff --git a/packages/core/tests/unit/storages/index.test.ts b/packages/core/tests/unit/storages/index.test.ts index c26137e2..dde743f3 100644 --- a/packages/core/tests/unit/storages/index.test.ts +++ b/packages/core/tests/unit/storages/index.test.ts @@ -18,6 +18,7 @@ describe('Storages Index', () => { sharedAgileInstance = new Agile(); assignSharedAgileInstance(sharedAgileInstance); + // Reset Storage Manager StorageIndex.assignSharedAgileStorageManager(null); jest.clearAllMocks(); diff --git a/packages/multieditor/src/item.ts b/packages/multieditor/src/item.ts index b2199b4b..32493150 100644 --- a/packages/multieditor/src/item.ts +++ b/packages/multieditor/src/item.ts @@ -1,11 +1,11 @@ import { - State, StateRuntimeJobConfigInterface, defineConfig, + EnhancedState, } from '@agile-ts/core'; import { MultiEditor, Validator, Status, ItemKey } from './internal'; -export class Item extends State { +export class Item extends EnhancedState { public editor: () => MultiEditor; public isValid = false; From d0e6435ca7009e19f5eb473aec2cf23260838256 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Fri, 27 Aug 2021 07:53:43 +0200 Subject: [PATCH 33/44] fixed typo --- .../tests/unit/collection/collection.test.ts | 18 ++++++++++++++++++ packages/react/src/hooks/useWatcher.ts | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 89684604..86a019a1 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -1286,6 +1286,24 @@ describe('Collection Tests', () => { }); }); + describe('select function tests', () => { + beforeEach(() => { + collection.createSelector = jest.fn(); + }); + it( + 'should call createSelector with the specified itemKey ' + + 'as key of the Selector and as selected item key', + () => { + collection.select('test'); + + expect(collection.createSelector).toHaveBeenCalledWith( + 'test', + 'test' + ); + } + ); + }); + describe('hasSelector function tests', () => { let dummySelector: Selector; diff --git a/packages/react/src/hooks/useWatcher.ts b/packages/react/src/hooks/useWatcher.ts index 39fb3238..e0cb08b8 100644 --- a/packages/react/src/hooks/useWatcher.ts +++ b/packages/react/src/hooks/useWatcher.ts @@ -1,8 +1,8 @@ import React from 'react'; -import { StateWatcherCallback, State } from '@agile-ts/core'; +import { StateWatcherCallback, EnhancedState } from '@agile-ts/core'; export function useWatcher( - state: State, + state: EnhancedState, callback: StateWatcherCallback ): void { React.useEffect(() => { From 0e73f5c40e8ed9935915522e958e204eef962674 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Fri, 27 Aug 2021 12:15:12 +0200 Subject: [PATCH 34/44] split useAgile hook --- .../runtime/subscription/sub.controller.ts | 2 +- packages/react/src/hooks/useAgile.ts | 207 +++--------------- packages/react/src/hooks/useBaseAgile.ts | 119 ++++++++++ packages/react/src/hooks/useProxy.ts | 98 ++++++++- packages/react/src/hooks/useSelector.ts | 72 ++++-- packages/react/src/hooks/useValue.ts | 7 +- 6 files changed, 302 insertions(+), 203 deletions(-) create mode 100644 packages/react/src/hooks/useBaseAgile.ts diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 51a76d4b..e83653be 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -331,7 +331,7 @@ export class SubController { } } -interface RegisterSubscriptionConfigInterface +export interface RegisterSubscriptionConfigInterface extends SubscriptionContainerConfigInterface { /** * Whether the Subscription Container shouldn't be ready diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index 56d51419..dd1d2f4c 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -1,30 +1,18 @@ -import React from 'react'; -import Agile, { - getAgileInstance, +import { Observer, State, - SubscriptionContainerKeyType, - isValidObject, generateId, - ProxyWeakMapType, - ComponentIdType, extractRelevantObservers, - SelectorWeakMapType, - SelectorMethodType, - LogCodeManager, normalizeArray, defineConfig, } from '@agile-ts/core'; import type { Collection, Group } from '@agile-ts/core'; // Only import Collection and Group type for better Treeshaking -import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; - -// TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work -let proxyPackage: any = null; -try { - proxyPackage = require('@agile-ts/proxytree'); -} catch (e) { - // empty catch block -} +import { + BaseAgileHookConfigInterface, + getReturnValue, + SubscribableAgileInstancesType, + useBaseAgile, +} from './useBaseAgile'; /** * A React Hook for binding the most relevant value of multiple Agile Instances @@ -67,162 +55,38 @@ export function useAgile< ): AgileOutputHookArrayType | AgileOutputHookType { config = defineConfig(config, { key: generateId(), - proxyBased: false, agileInstance: null as any, componentId: undefined, observerType: undefined, deps: [], + handleReturn: (dep: Observer | undefined) => { + return dep != null ? dep.value : undefined; + }, }); const depsArray = extractRelevantObservers( normalizeArray(deps), config.observerType ); - const proxyTreeWeakMap = new WeakMap(); - - // Builds return value, - // depending on whether the deps were provided in array shape or not - const getReturnValue = ( - depsArray: (Observer | undefined)[] - ): AgileOutputHookArrayType | AgileOutputHookType => { - const handleReturn = ( - dep: Observer | undefined - ): AgileOutputHookType => { - if (dep == null) return undefined as any; - const value = dep.value; - - // If proxyBased and the value is of the type object. - // Wrap a Proxy around the object to track the accessed properties. - if (config.proxyBased && isValidObject(value, true)) { - if (proxyPackage != null) { - const { ProxyTree } = proxyPackage; - const proxyTree = new ProxyTree(value); - proxyTreeWeakMap.set(dep, proxyTree); - return proxyTree.proxy; - } else { - console.error( - 'In order to use the Agile proxy functionality, ' + - `the installation of an additional package called '@agile-ts/proxytree' is required!` - ); - } - } - - // If specified selector function and the value is of type object. - // Return the selected value. - // (Destroys the type of the useAgile hook, - // however the type can be adjusted in the useSelector hook) - if (config.selector && isValidObject(value, true)) { - return config.selector(value); - } - - return value; - }; - - // Handle single dep return value - if (depsArray.length === 1 && !Array.isArray(deps)) { - return handleReturn(depsArray[0]); - } - - // Handle deps array return value - return depsArray.map((dep) => { - return handleReturn(dep); - }) as AgileOutputHookArrayType; - }; - - // Trigger State, used to force Component to rerender - const [, forceRender] = React.useReducer((s) => s + 1, 0); - - useIsomorphicLayoutEffect(() => { - let agileInstance = config.agileInstance; - - // https://github.com/microsoft/TypeScript/issues/20812 - const observers: Observer[] = depsArray.filter( - (dep): dep is Observer => dep !== undefined - ); - - // Try to extract Agile Instance from the specified Instance/s - if (!agileInstance) agileInstance = getAgileInstance(observers[0]); - if (!agileInstance || !agileInstance.subController) { - LogCodeManager.getLogger()?.error( - 'Failed to subscribe Component with deps because of missing valid Agile Instance.', - deps - ); - return; - } - - // TODO Proxy doesn't work as expected when 'selecting' a not yet existing property. - // For example you select the 'user.data.name' property, but the 'user' object is undefined. - // -> No correct Proxy Path could be created on the Component mount, since the to select property doesn't exist - // -> Selector was created based on the not complete Proxy Path - // -> Component re-renders to often - // - // Build Proxy Path WeakMap based on the Proxy Tree WeakMap - // by extracting the routes from the Proxy Tree. - // Building the Path WeakMap in the 'useIsomorphicLayoutEffect' - // because the 'useIsomorphicLayoutEffect' is called after the rerender. - // -> All used paths in the UI-Component were successfully tracked. - let proxyWeakMap: ProxyWeakMapType | undefined = undefined; - if (config.proxyBased && proxyPackage != null) { - proxyWeakMap = new WeakMap(); - for (const observer of observers) { - const proxyTree = proxyTreeWeakMap.get(observer); - if (proxyTree != null) { - proxyWeakMap.set(observer, { - paths: proxyTree.getUsedRoutes() as any, - }); - } - } - } - - // Build Selector WeakMap based on the specified selector method - let selectorWeakMap: SelectorWeakMapType | undefined = undefined; - if (config.selector != null) { - selectorWeakMap = new WeakMap(); - for (const observer of observers) { - selectorWeakMap.set(observer, { methods: [config.selector] }); - } - } - - // Create Callback based Subscription - const subscriptionContainer = agileInstance.subController.subscribe( - () => { - forceRender(); - }, - observers, - { - key: config.key, - proxyWeakMap, - waitForMount: false, - componentId: config.componentId, - selectorWeakMap, - } - ); - // Unsubscribe Callback based Subscription on unmount - return () => { - agileInstance?.subController.unsubscribe(subscriptionContainer); - }; - }, config.deps); + useBaseAgile( + depsArray, + () => ({ + key: config.key, + waitForMount: false, + componentId: config.componentId, + }), + config.deps || [], + config.agileInstance + ); - return getReturnValue(depsArray); + return getReturnValue( + depsArray, + config.handleReturn as any, + Array.isArray(deps) + ); } -export type SubscribableAgileInstancesType = - | State - | Collection //https://stackoverflow.com/questions/66987727/type-classa-id-number-name-string-is-not-assignable-to-type-classar - | Observer - | undefined; - -export interface AgileHookConfigInterface { - /** - * Key/Name identifier of the Subscription Container to be created. - * @default undefined - */ - key?: SubscriptionContainerKeyType; - /** - * Instance of Agile the Subscription Container belongs to. - * @default `undefined` if no Agile Instance could be extracted from the provided Instances. - */ - agileInstance?: Agile; +export interface AgileHookConfigInterface extends BaseAgileHookConfigInterface { /** * Whether to wrap a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) * around the bound Agile Instance value object, @@ -234,7 +98,7 @@ export interface AgileHookConfigInterface { * * @default false */ - proxyBased?: boolean; + // proxyBased?: boolean; /** * Equality comparison function * that allows you to customize the way the selected Agile Instance @@ -245,12 +109,8 @@ export interface AgileHookConfigInterface { * * @default undefined */ - selector?: SelectorMethodType; - /** - * Key/Name identifier of the UI-Component the Subscription Container is bound to. - * @default undefined - */ - componentId?: ComponentIdType; + // selector?: SelectorMethodType; + /** * What type of Observer to be bound to the UI-Component. * @@ -261,14 +121,9 @@ export interface AgileHookConfigInterface { */ observerType?: string; /** - * Dependencies that determine, in addition to unmounting and remounting the React-Component, - * when the specified Agile Sub Instances should be re-subscribed to the React-Component. - * - * [Github issue](https://github.com/agile-ts/agile/issues/170) - * - * @default [] + * TODO */ - deps?: any[]; + handleReturn?: (dep: Observer | undefined) => any; } // Array Type diff --git a/packages/react/src/hooks/useBaseAgile.ts b/packages/react/src/hooks/useBaseAgile.ts new file mode 100644 index 00000000..be25f4ed --- /dev/null +++ b/packages/react/src/hooks/useBaseAgile.ts @@ -0,0 +1,119 @@ +import React from 'react'; +import Agile, { + Collection, + ComponentIdType, + getAgileInstance, + LogCodeManager, + Observer, + State, + SubscriptionContainerKeyType, + RegisterSubscriptionConfigInterface, +} from '@agile-ts/core'; +import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; + +export const useBaseAgile = ( + depsArray: (Observer | undefined)[], + getSubContainerConfig: ( + observers: Observer[] + ) => RegisterSubscriptionConfigInterface, + deps: any[], + agileInstance?: Agile +) => { + // Trigger State, used to force Component to rerender + const [, forceRender] = React.useReducer((s) => s + 1, 0); + + useIsomorphicLayoutEffect(() => { + // https://github.com/microsoft/TypeScript/issues/20812 + const observers = depsArray.filter( + (dep): dep is Observer => dep !== undefined + ); + + const subContainerConfig = getSubContainerConfig(observers); + + const _agileInstance = extractAgileInstance(observers, agileInstance); + if (_agileInstance == null) return; + + // Create Callback based Subscription + const subscriptionContainer = _agileInstance.subController.subscribe( + () => { + forceRender(); + }, + observers, + subContainerConfig + ); + + // Unsubscribe Callback based Subscription on unmount + return () => { + _agileInstance.subController.unsubscribe(subscriptionContainer); + }; + }, deps); +}; + +export const extractAgileInstance = ( + observers: Observer[], + agileInstance?: Agile +): Agile | undefined => { + if (agileInstance != null) return agileInstance; + + // Try to extract Agile Instance from the specified Observers + agileInstance = getAgileInstance(observers[0]); + if (!agileInstance || !agileInstance.subController) { + LogCodeManager.getLogger()?.error( + 'Failed to subscribe to React Component because of missing valid Agile Instance.', + observers + ); + return undefined; + } + return agileInstance; +}; + +// Builds return value, +// depending on whether the deps were provided in array shape or not +export const getReturnValue = ( + depsArray: (Observer | undefined)[], + handleReturn: (dep: Observer | undefined) => any, + wasProvidedAsArray: boolean +): any => { + // Handle single dep return value + if (depsArray.length === 1 && !wasProvidedAsArray) { + return handleReturn(depsArray[0]); + } + + // Handle deps array return value + return depsArray.map((dep) => { + return handleReturn(dep); + }); +}; + +export type SubscribableAgileInstancesType = + | State + | Collection //https://stackoverflow.com/questions/66987727/type-classa-id-number-name-string-is-not-assignable-to-type-classar + | Observer + | undefined; + +export interface BaseAgileHookConfigInterface { + /** + * Key/Name identifier of the Subscription Container to be created. + * @default undefined + */ + key?: SubscriptionContainerKeyType; + /** + * Instance of Agile the Subscription Container belongs to. + * @default `undefined` if no Agile Instance could be extracted from the provided Instances. + */ + agileInstance?: Agile; + /** + * Key/Name identifier of the UI-Component the Subscription Container is bound to. + * @default undefined + */ + componentId?: ComponentIdType; + /** + * Dependencies that determine, in addition to unmounting and remounting the React-Component, + * when the specified Agile Sub Instances should be re-subscribed to the React-Component. + * + * [Github issue](https://github.com/agile-ts/agile/issues/170) + * + * @default [] + */ + deps?: any[]; +} diff --git a/packages/react/src/hooks/useProxy.ts b/packages/react/src/hooks/useProxy.ts index e565e3a7..50d85b21 100644 --- a/packages/react/src/hooks/useProxy.ts +++ b/packages/react/src/hooks/useProxy.ts @@ -1,11 +1,28 @@ import { - AgileHookConfigInterface, + defineConfig, + extractRelevantObservers, + Observer, + ProxyWeakMapType, +} from '@agile-ts/core'; +import { generateId, isValidObject, normalizeArray } from '@agile-ts/utils'; +import { + getReturnValue, SubscribableAgileInstancesType, - useAgile, + useBaseAgile, +} from './useBaseAgile'; +import { + AgileHookConfigInterface, AgileOutputHookArrayType, AgileOutputHookType, } from './useAgile'; -import { defineConfig } from '@agile-ts/core'; + +// TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work +let proxyPackage: any = null; +try { + proxyPackage = require('@agile-ts/proxytree'); +} catch (e) { + // empty catch block +} export function useProxy>( deps: X | [], @@ -24,10 +41,75 @@ export function useProxy< deps: X | Y, config: AgileHookConfigInterface = {} ): AgileOutputHookArrayType | AgileOutputHookType { - return useAgile( - deps as any, - defineConfig(config, { - proxyBased: true, - }) + config = defineConfig(config, { + key: generateId(), + agileInstance: null as any, + componentId: undefined, + deps: [], + }); + const depsArray = extractRelevantObservers(normalizeArray(deps)); + const proxyTreeWeakMap = new WeakMap(); + + const handleReturn = (dep: Observer | undefined) => { + if (dep == null) return undefined as any; + const value = dep.value; + + // If proxyBased and the value is of the type object. + // Wrap a Proxy around the object to track the accessed properties. + if (isValidObject(value, true)) { + if (proxyPackage != null) { + const { ProxyTree } = proxyPackage; + const proxyTree = new ProxyTree(value); + proxyTreeWeakMap.set(dep, proxyTree); + return proxyTree.proxy; + } else { + console.error( + 'In order to use the Agile proxy functionality, ' + + `the installation of an additional package called '@agile-ts/proxytree' is required!` + ); + } + } + + return value; + }; + + useBaseAgile( + depsArray, + (observers) => { + // TODO Proxy doesn't work as expected when 'selecting' a not yet existing property. + // For example you select the 'user.data.name' property, but the 'user' object is undefined. + // -> No correct Proxy Path could be created on the Component mount, since the to select property doesn't exist + // -> Selector was created based on the not complete Proxy Path + // -> Component re-renders to often + // + // Build Proxy Path WeakMap based on the Proxy Tree WeakMap + // by extracting the routes from the Proxy Tree. + // Building the Path WeakMap in the 'useIsomorphicLayoutEffect' + // because the 'useIsomorphicLayoutEffect' is called after the rerender. + // -> All used paths in the UI-Component were successfully tracked. + let proxyWeakMap: ProxyWeakMapType | undefined = undefined; + if (proxyPackage != null) { + proxyWeakMap = new WeakMap(); + for (const observer of observers) { + const proxyTree = proxyTreeWeakMap.get(observer); + if (proxyTree != null) { + proxyWeakMap.set(observer, { + paths: proxyTree.getUsedRoutes() as any, + }); + } + } + } + + return { + key: config.key, + waitForMount: false, + componentId: config.componentId, + proxyWeakMap, + }; + }, + config.deps || [], + config.agileInstance ); + + return getReturnValue(depsArray, handleReturn, Array.isArray(deps)); } diff --git a/packages/react/src/hooks/useSelector.ts b/packages/react/src/hooks/useSelector.ts index 1d694065..7664b84a 100644 --- a/packages/react/src/hooks/useSelector.ts +++ b/packages/react/src/hooks/useSelector.ts @@ -1,9 +1,17 @@ import { - AgileHookConfigInterface, + SelectorMethodType, + defineConfig, + Observer, + SelectorWeakMapType, + extractRelevantObservers, +} from '@agile-ts/core'; +import { generateId, isValidObject } from '@agile-ts/utils'; +import { + BaseAgileHookConfigInterface, + getReturnValue, SubscribableAgileInstancesType, - useAgile, -} from './useAgile'; -import { SelectorMethodType, defineConfig } from '@agile-ts/core'; + useBaseAgile, +} from './useBaseAgile'; import { AgileValueHookType } from './useValue'; export function useSelector< @@ -13,13 +21,13 @@ export function useSelector< >( dep: X, selector: SelectorMethodType, - config?: AgileHookConfigInterface + config?: BaseAgileHookConfigInterface ): ReturnType; export function useSelector( dep: SubscribableAgileInstancesType, selector: SelectorMethodType, - config?: AgileHookConfigInterface + config?: BaseAgileHookConfigInterface ): ReturnType; export function useSelector< @@ -29,12 +37,50 @@ export function useSelector< >( dep: X, selector: SelectorMethodType, - config: AgileHookConfigInterface = {} + config: BaseAgileHookConfigInterface = {} ): ReturnType { - return useAgile( - dep as any, - defineConfig(config, { - selector: selector, - }) - ) as any; + config = defineConfig(config, { + key: generateId(), + agileInstance: null as any, + componentId: undefined, + }); + const depsArray = extractRelevantObservers([dep]); + + const handleReturn = (dep: Observer | undefined): any => { + if (dep == null) return undefined as any; + const value = dep.value; + + // If specified selector function and the value is of type object. + // Return the selected value. + // (Destroys the type of the useAgile hook, + // however the type can be adjusted in the useSelector hook) + if (isValidObject(value, true)) { + return selector(value); + } + + return value; + }; + + useBaseAgile( + depsArray, + (observers) => { + // Build Selector WeakMap based on the specified selector method + let selectorWeakMap: SelectorWeakMapType | undefined = undefined; + selectorWeakMap = new WeakMap(); + for (const observer of observers) { + selectorWeakMap.set(observer, { methods: [selector] }); + } + + return { + key: config.key, + waitForMount: false, + componentId: config.componentId, + selectorWeakMap, + }; + }, + config.deps || [], + config.agileInstance + ); + + return getReturnValue(depsArray, handleReturn, false); } diff --git a/packages/react/src/hooks/useValue.ts b/packages/react/src/hooks/useValue.ts index d1e50ad6..e4f71fa7 100644 --- a/packages/react/src/hooks/useValue.ts +++ b/packages/react/src/hooks/useValue.ts @@ -5,11 +5,8 @@ import { State, defineConfig, } from '@agile-ts/core'; -import { - AgileHookConfigInterface, - SubscribableAgileInstancesType, - useAgile, -} from './useAgile'; +import { AgileHookConfigInterface, useAgile } from './useAgile'; +import { SubscribableAgileInstancesType } from './useBaseAgile'; export function useValue>( deps: X | [], From dcaf6cd4e6393cf2c5e49e4485fbb1953280bf6d Mon Sep 17 00:00:00 2001 From: BennoDev Date: Fri, 27 Aug 2021 19:45:13 +0200 Subject: [PATCH 35/44] fixed typos --- .../event/tests/unit/event.observer.test.ts | 2 +- packages/event/tests/unit/event.test.ts | 2 +- packages/react/src/hooks/useAgile.ts | 41 ++----------- packages/react/src/hooks/useBaseAgile.ts | 57 +++++++++++-------- packages/react/src/hooks/useProxy.ts | 40 ++++++++++++- packages/react/src/hooks/useSelector.ts | 15 +++++ 6 files changed, 94 insertions(+), 63 deletions(-) diff --git a/packages/event/tests/unit/event.observer.test.ts b/packages/event/tests/unit/event.observer.test.ts index b700a72f..2732e219 100644 --- a/packages/event/tests/unit/event.observer.test.ts +++ b/packages/event/tests/unit/event.observer.test.ts @@ -9,7 +9,7 @@ describe('EventObserver Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); dummyEvent = new Event(dummyAgile); jest.clearAllMocks(); diff --git a/packages/event/tests/unit/event.test.ts b/packages/event/tests/unit/event.test.ts index bd369952..5885471b 100644 --- a/packages/event/tests/unit/event.test.ts +++ b/packages/event/tests/unit/event.test.ts @@ -9,7 +9,7 @@ describe('Event Tests', () => { beforeEach(() => { LogMock.mockLogs(); - dummyAgile = new Agile({ localStorage: false }); + dummyAgile = new Agile(); jest.clearAllMocks(); }); diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index dd1d2f4c..15d33408 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -59,15 +59,16 @@ export function useAgile< componentId: undefined, observerType: undefined, deps: [], - handleReturn: (dep: Observer | undefined) => { - return dep != null ? dep.value : undefined; - }, }); const depsArray = extractRelevantObservers( normalizeArray(deps), config.observerType ); + const handleReturn = (dep: Observer | undefined) => { + return dep != null ? dep.value : undefined; + }; + useBaseAgile( depsArray, () => ({ @@ -79,38 +80,10 @@ export function useAgile< config.agileInstance ); - return getReturnValue( - depsArray, - config.handleReturn as any, - Array.isArray(deps) - ); + return getReturnValue(depsArray, handleReturn, Array.isArray(deps)); } export interface AgileHookConfigInterface extends BaseAgileHookConfigInterface { - /** - * Whether to wrap a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) - * around the bound Agile Instance value object, - * to automatically constrain the way the selected Agile Instance - * is compared to determine whether the Component needs to be re-rendered - * based on the object's used properties. - * - * Requires an additional package called `@agile-ts/proxytree`! - * - * @default false - */ - // proxyBased?: boolean; - /** - * Equality comparison function - * that allows you to customize the way the selected Agile Instance - * is compared to determine whether the Component needs to be re-rendered. - * - * * Note that setting this property can destroy the useAgile type. - * -> should only be used internal! - * - * @default undefined - */ - // selector?: SelectorMethodType; - /** * What type of Observer to be bound to the UI-Component. * @@ -120,10 +93,6 @@ export interface AgileHookConfigInterface extends BaseAgileHookConfigInterface { * @default undefined */ observerType?: string; - /** - * TODO - */ - handleReturn?: (dep: Observer | undefined) => any; } // Array Type diff --git a/packages/react/src/hooks/useBaseAgile.ts b/packages/react/src/hooks/useBaseAgile.ts index be25f4ed..f6b872a2 100644 --- a/packages/react/src/hooks/useBaseAgile.ts +++ b/packages/react/src/hooks/useBaseAgile.ts @@ -11,6 +11,19 @@ import Agile, { } from '@agile-ts/core'; import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; +/** + * An internal used React Hook + * to create a Callback based Subscription Container + * based on the specified depsArray + * and thus bind these dependencies to a Functional React Component. + * + * @internal + * @param depsArray - Observers to be bound to the Functional Component. + * @param getSubContainerConfig - Method to get the Subscription Container configuration object. + * @param deps - Dependencies that determine, in addition to unmounting and remounting the React-Component, + * when the specified Agile Sub Instances should be re-subscribed to the React-Component. + * @param agileInstance - Agile Instance the to create Subscription Container belongs to. + */ export const useBaseAgile = ( depsArray: (Observer | undefined)[], getSubContainerConfig: ( @@ -30,11 +43,18 @@ export const useBaseAgile = ( const subContainerConfig = getSubContainerConfig(observers); - const _agileInstance = extractAgileInstance(observers, agileInstance); - if (_agileInstance == null) return; + // Try to extract Agile Instance from the specified Instance/s + if (agileInstance == null) agileInstance = getAgileInstance(observers[0]); + if (agileInstance == null || agileInstance.subController == null) { + LogCodeManager.getLogger()?.error( + 'Failed to subscribe Component with deps because of missing valid Agile Instance.', + deps + ); + return; + } // Create Callback based Subscription - const subscriptionContainer = _agileInstance.subController.subscribe( + const subscriptionContainer = agileInstance.subController.subscribe( () => { forceRender(); }, @@ -44,31 +64,20 @@ export const useBaseAgile = ( // Unsubscribe Callback based Subscription on unmount return () => { - _agileInstance.subController.unsubscribe(subscriptionContainer); + agileInstance?.subController.unsubscribe(subscriptionContainer); }; }, deps); }; -export const extractAgileInstance = ( - observers: Observer[], - agileInstance?: Agile -): Agile | undefined => { - if (agileInstance != null) return agileInstance; - - // Try to extract Agile Instance from the specified Observers - agileInstance = getAgileInstance(observers[0]); - if (!agileInstance || !agileInstance.subController) { - LogCodeManager.getLogger()?.error( - 'Failed to subscribe to React Component because of missing valid Agile Instance.', - observers - ); - return undefined; - } - return agileInstance; -}; - -// Builds return value, -// depending on whether the deps were provided in array shape or not +/** + * Builds return value for Agile Instance 'binding' Hooks, + * depending on whether the dependencies were provided in array shape or not. + * + * @internal + * @param depsArray - Dependencies to extract the return value from. + * @param handleReturn - Method to handle the return value. + * @param wasProvidedAsArray - Whether the specified depsArray was provided as array in the Hook. + */ export const getReturnValue = ( depsArray: (Observer | undefined)[], handleReturn: (dep: Observer | undefined) => any, diff --git a/packages/react/src/hooks/useProxy.ts b/packages/react/src/hooks/useProxy.ts index 50d85b21..76356f28 100644 --- a/packages/react/src/hooks/useProxy.ts +++ b/packages/react/src/hooks/useProxy.ts @@ -24,11 +24,48 @@ try { // empty catch block } +/** + * A React Hook for binding the most relevant value of multiple Agile Instances + * (like the Collection's output or the State's value) + * to a React Functional Component. + * + * This binding ensures that the Component re-renders + * whenever the most relevant Observer of an Agile Instance mutates. + * + * In addition the the default 'useAgile' Hook, + * the useProxy Hooks wraps a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) + * around the to bind Agile Instance value objects, + * to automatically constraint the way the selected Agile Instances + * are compared to determine whether the React Component needs to be re-rendered + * based on the object's used properties. + * + * @public + * @param deps - Agile Sub Instances to be bound to the Functional Component. + * @param config - Configuration object + */ export function useProxy>( deps: X | [], config?: AgileHookConfigInterface ): AgileOutputHookArrayType; - +/** + * A React Hook for binding the most relevant Agile Instance value + * (like the Collection's output or the State's value) + * to a React Functional Component. + * + * This binding ensures that the Component re-renders + * whenever the most relevant Observer of the Agile Instance mutates. + * + * In addition the the default 'useAgile' Hook, + * the useProxy Hooks wraps a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) + * around the to bind Agile Instance value objects, + * to automatically constraint the way the selected Agile Instances + * are compared to determine whether the React Component needs to be re-rendered + * based on the object's used properties. + * + * @public + * @param dep - Agile Sub Instance to be bound to the Functional Component. + * @param config - Configuration object + */ export function useProxy( dep: X, config?: AgileHookConfigInterface @@ -63,6 +100,7 @@ export function useProxy< proxyTreeWeakMap.set(dep, proxyTree); return proxyTree.proxy; } else { + // TODO add LogCode Manager logcode console.error( 'In order to use the Agile proxy functionality, ' + `the installation of an additional package called '@agile-ts/proxytree' is required!` diff --git a/packages/react/src/hooks/useSelector.ts b/packages/react/src/hooks/useSelector.ts index 7664b84a..75a9cd34 100644 --- a/packages/react/src/hooks/useSelector.ts +++ b/packages/react/src/hooks/useSelector.ts @@ -24,6 +24,21 @@ export function useSelector< config?: BaseAgileHookConfigInterface ): ReturnType; +/** + * A React Hook for binding a selected value of an Agile Instance + * (like the Collection's output or the State's value) + * to a React Functional Component. + * + * This binding ensures that the Component re-renders + * whenever the selected value of an Agile Instance mutates. + * + * @public + * @param dep - Agile Sub Instance to be bound to the Functional Component. + * @param selector - Equality comparison function + * that allows you to customize the way the selected Agile Instance + * is compared to determine whether the Component needs to be re-rendered. + * @param config - Configuration object + */ export function useSelector( dep: SubscribableAgileInstancesType, selector: SelectorMethodType, From fc9266b24dd2cb45ca436e2f79bda5c15fcaa610 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Fri, 27 Aug 2021 19:53:34 +0200 Subject: [PATCH 36/44] fixed example --- .../react/develop/functional-component-ts/src/core/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index 207bce0d..06de957a 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -6,7 +6,7 @@ import Agile, { createStorage, createStorageManager, Item, - registerSharedStorageManager, + assignSharedAgileStorageManager, } from '@agile-ts/core'; import Event from '@agile-ts/event'; import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger'; @@ -19,7 +19,7 @@ export const App = new Agile(); assignSharedAgileInstance(App); export const storageManager = createStorageManager({ localStorage: true }); -registerSharedStorageManager(storageManager); +assignSharedAgileStorageManager(storageManager); // Register custom second Storage storageManager.register( From 075735c16cf41a1616a87c1a9c3363f251dc2cd0 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sat, 28 Aug 2021 07:59:40 +0200 Subject: [PATCH 37/44] fixed typos --- .../develop/AwesomeTSProject/core/index.ts | 20 ++++++-------- .../class-component-ts/src/core/index.ts | 26 +++++++++---------- .../functional-component-ts/src/App.tsx | 18 +++++++++---- .../functional-component-ts/src/core/index.ts | 4 +-- .../develop/multieditor-ts/src/core/agile.ts | 6 +---- examples/react/release/boxes/yarn.lock | 16 ++++-------- .../stopwatch-query-url/src/core/index.ts | 11 +++++--- examples/vue/develop/my-project/src/core.js | 24 ++++++++--------- packages/react/src/hooks/useValue.ts | 13 ++++++---- 9 files changed, 70 insertions(+), 68 deletions(-) diff --git a/examples/react-native/develop/AwesomeTSProject/core/index.ts b/examples/react-native/develop/AwesomeTSProject/core/index.ts index 011ceb0e..3d4dc07f 100644 --- a/examples/react-native/develop/AwesomeTSProject/core/index.ts +++ b/examples/react-native/develop/AwesomeTSProject/core/index.ts @@ -1,20 +1,16 @@ -import { Agile } from '@agile-ts/core'; -import { Event } from '@agile-ts/event'; +import { createState, createComputed, createCollection } from '@agile-ts/core'; +import { createEvent, Event } from '@agile-ts/event'; import { Alert } from 'react-native'; -export const App = new Agile({ - logConfig: { active: true }, -}); - -export const MY_STATE = App.createState('MyState', { key: 'my-state' }); //.persist(); -export const MY_STATE_2 = App.createState('MyState2'); //.persist("my-state2"); -export const MY_STATE_3 = App.createState(1); //.persist("my-state2"); +export const MY_STATE = createState('MyState', { key: 'my-state' }); //.persist(); +export const MY_STATE_2 = createState('MyState2'); //.persist("my-state2"); +export const MY_STATE_3 = createState(1); //.persist("my-state2"); MY_STATE.watch('test', (value: any) => { console.log('Watch ' + value); }); -export const MY_COMPUTED = App.createComputed(() => { +export const MY_COMPUTED = createComputed(() => { return 'test' + MY_STATE.value + '_computed_' + MY_STATE_2.value; }); @@ -23,7 +19,7 @@ interface collectionValueInterface { name: string; } -export const MY_COLLECTION = App.createCollection( +export const MY_COLLECTION = createCollection( (collection) => ({ key: 'my-collection', groups: { @@ -43,7 +39,7 @@ MY_COLLECTION.getGroup('myGroup')?.persist({ console.log('Initial: myCollection ', MY_COLLECTION); -export const MY_EVENT = new Event<{ name: string }>(App); +export const MY_EVENT = createEvent<{ name: string }>(); MY_EVENT.on('Test', (payload) => { Alert.alert( diff --git a/examples/react/develop/class-component-ts/src/core/index.ts b/examples/react/develop/class-component-ts/src/core/index.ts index 1e8c9784..81b85b00 100644 --- a/examples/react/develop/class-component-ts/src/core/index.ts +++ b/examples/react/develop/class-component-ts/src/core/index.ts @@ -1,25 +1,25 @@ -import { Agile, clone, Logger } from '@agile-ts/core'; -import { Event } from '@agile-ts/event'; +import { + clone, + createState, + createComputed, + createCollection, +} from '@agile-ts/core'; +import { createEvent, Event } from '@agile-ts/event'; -export const App = new Agile({ - logConfig: { level: Logger.level.DEBUG, timestamp: true }, - waitForMount: false, -}); - -export const MY_STATE = App.createState('MyState'); //.persist(); -export const MY_STATE_2 = App.createState('MyState2', { +export const MY_STATE = createState('MyState'); //.persist(); +export const MY_STATE_2 = createState('MyState2', { key: 'myState2', }).persist(); MY_STATE_2.onLoad(() => { console.log('On Load'); }); -export const MY_STATE_3 = App.createState(1); //.persist("my-state2"); +export const MY_STATE_3 = createState(1); //.persist("my-state2"); MY_STATE.watch('test', (value: any) => { console.log('Watch ' + value); }); -export const MY_COMPUTED = App.createComputed(() => { +export const MY_COMPUTED = createComputed(() => { return 'test' + MY_STATE.value + '_computed_' + MY_STATE_2.value; }, []).setKey('myComputed'); @@ -28,7 +28,7 @@ interface collectionValueInterface { name: string; } -export const MY_COLLECTION = App.createCollection( +export const MY_COLLECTION = createCollection( (collection) => ({ key: 'my-collection', groups: { @@ -48,7 +48,7 @@ MY_COLLECTION.getGroup('myGroup')?.persist({ console.log('Initial: myCollection ', clone(MY_COLLECTION)); -export const MY_EVENT = new Event<{ name: string }>(App, { +export const MY_EVENT = createEvent<{ name: string }>({ delay: 3000, key: 'myEvent', }); diff --git a/examples/react/develop/functional-component-ts/src/App.tsx b/examples/react/develop/functional-component-ts/src/App.tsx index 860f0656..13aa298d 100644 --- a/examples/react/develop/functional-component-ts/src/App.tsx +++ b/examples/react/develop/functional-component-ts/src/App.tsx @@ -44,9 +44,12 @@ const App = (props: any) => { ]); const [myGroup] = useAgile([MY_COLLECTION.getGroupWithReference('myGroup')]); - const selectedObjectItem = useSelector(STATE_OBJECT, (value) => { - return value.age; - }); + const stateObjectAge = useSelector( + STATE_OBJECT, + (value) => { + return value.age; + } + ); const [stateObject, item2, collection2] = useProxy( [STATE_OBJECT, MY_COLLECTION.getItem('id2'), MY_COLLECTION], @@ -56,8 +59,6 @@ const App = (props: any) => { console.log('Item1: ', item2?.name); console.log('Collection: ', collection2.slice(0, 2)); - // const myCollection2 = useAgile(MY_COLLECTION); - const mySelector = useAgile(MY_COLLECTION.getSelector('mySelector')); useEvent(MY_EVENT, () => { @@ -126,6 +127,13 @@ const App = (props: any) => { }}> Change shallow name +

Age: {stateObjectAge}

+
diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index 06de957a..5d74ad72 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -8,7 +8,7 @@ import Agile, { Item, assignSharedAgileStorageManager, } from '@agile-ts/core'; -import Event from '@agile-ts/event'; +import { createEvent } from '@agile-ts/event'; import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger'; import { clone } from '@agile-ts/utils'; @@ -114,7 +114,7 @@ export const externalCreatedItem = new Item(MY_COLLECTION, { console.log('Initial: myCollection ', clone(MY_COLLECTION)); -export const MY_EVENT = new Event<{ name: string }>(App, { +export const MY_EVENT = createEvent<{ name: string }>({ delay: 3000, key: 'myEvent', }); diff --git a/examples/react/develop/multieditor-ts/src/core/agile.ts b/examples/react/develop/multieditor-ts/src/core/agile.ts index e212a41d..d0e37e07 100644 --- a/examples/react/develop/multieditor-ts/src/core/agile.ts +++ b/examples/react/develop/multieditor-ts/src/core/agile.ts @@ -1,10 +1,6 @@ import { Agile, globalBind } from '@agile-ts/core'; -const App = new Agile({ - logConfig: { - active: true, - }, -}); +const App = new Agile(); export default App; diff --git a/examples/react/release/boxes/yarn.lock b/examples/react/release/boxes/yarn.lock index a63eec3f..07d40055 100644 --- a/examples/react/release/boxes/yarn.lock +++ b/examples/react/release/boxes/yarn.lock @@ -3,26 +3,20 @@ "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.1.2" + version "0.2.0-alpha.4" dependencies: "@agile-ts/utils" "^0.0.7" -"@agile-ts/logger@^0.0.7": +"@agile-ts/logger@file:.yalc/@agile-ts/logger": version "0.0.7" - resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.7.tgz#9e89e8d80f80a46901a508432696860f88d5e878" - integrity sha512-6N9qyooo/a7ibyl9L7HnBX0LyMlSwaEYgObYs58KzR19JGF00PX/sUFfQAVplXXsMfT/8HvLyI+4TssmyI6DdQ== dependencies: "@agile-ts/utils" "^0.0.7" -"@agile-ts/proxytree@^0.0.5": +"@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree": version "0.0.5" - resolved "https://registry.yarnpkg.com/@agile-ts/proxytree/-/proxytree-0.0.5.tgz#81c40970707271822a176ee59f93b9230df6311d" - integrity sha512-KODknVD30ld9xPCyt0UCf0yGcroy/0CHEncAdmTFwEvDSMipMaqFQRsAYZ0tgB4bMfFzab40aUmYTK8XDkwdHw== -"@agile-ts/react@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@agile-ts/react/-/react-0.1.2.tgz#d07f6b935d9322cd60d2e9e3871da554b04460af" - integrity sha512-W4u2+X6KCeXPdkjit/NsMJG5nBsa7dNFaEzyfTsp5Cqbs99zLqY6dO8LUIYyhRt/+HBvEW9o64i/6Kqd59WM1Q== +"@agile-ts/react@file:.yalc/@agile-ts/react": + version "0.2.0-alpha.1" "@agile-ts/utils@^0.0.7": version "0.0.7" diff --git a/examples/react/release/stopwatch-query-url/src/core/index.ts b/examples/react/release/stopwatch-query-url/src/core/index.ts index b9e3cf5e..8ec09cb5 100644 --- a/examples/react/release/stopwatch-query-url/src/core/index.ts +++ b/examples/react/release/stopwatch-query-url/src/core/index.ts @@ -1,4 +1,9 @@ -import { createState, globalBind, shared } from '@agile-ts/core'; +import { + createState, + globalBind, + createStorage, + getStorageManager, +} from '@agile-ts/core'; import queryString from 'query-string'; export type StopwatchStateType = @@ -7,7 +12,7 @@ export type StopwatchStateType = | 'initial'; // Stopwatch is reset // Create Query Storage to store the State in the query (url) -const queryUrlStorage = shared.createStorage({ +const queryUrlStorage = createStorage({ key: 'query-url', methods: { set: (key, value) => { @@ -30,7 +35,7 @@ const queryUrlStorage = shared.createStorage({ }); // Register Query Storage to the shared Agile Instance and set it as default -shared.registerStorage(queryUrlStorage, { default: true }); +getStorageManager().register(queryUrlStorage, { default: true }); // State to keep track of the current time of the Stopwatch const TIME = createState( diff --git a/examples/vue/develop/my-project/src/core.js b/examples/vue/develop/my-project/src/core.js index 0512ec9b..8606056e 100644 --- a/examples/vue/develop/my-project/src/core.js +++ b/examples/vue/develop/my-project/src/core.js @@ -1,24 +1,24 @@ -import { Agile, assignSharedAgileInstance, globalBind } from '@agile-ts/core'; +import { + globalBind, + createState, + createComputed, + createCollection, +} from '@agile-ts/core'; import { Logger, assignSharedAgileLoggerConfig } from '@agile-ts/logger'; import '@agile-ts/vue'; assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); -// Create Agile Instance -export const App = new Agile({ localStorage: true }); -assignSharedAgileInstance(App); - // console.debug('hi'); // Doesn't work here idk why // Create State -export const MY_STATE = App.createState('World', { +export const MY_STATE = createState('World', { key: 'my-state', -}) - .computeValue((v) => { - return `Hello ${v}`; - }); +}).computeValue((v) => { + return `Hello ${v}`; +}); -export const MY_COMPUTED = App.createComputed( +export const MY_COMPUTED = createComputed( async () => { await new Promise((resolve) => setTimeout(resolve, 3000)); return `${MY_STATE.value} Frank`; @@ -27,7 +27,7 @@ export const MY_COMPUTED = App.createComputed( ); // Create Collection -export const TODOS = App.createCollection({ +export const TODOS = createCollection({ initialData: [{ id: 1, name: 'Clean Bathroom' }], selectors: [1], }).persist('todos'); diff --git a/packages/react/src/hooks/useValue.ts b/packages/react/src/hooks/useValue.ts index e4f71fa7..46670532 100644 --- a/packages/react/src/hooks/useValue.ts +++ b/packages/react/src/hooks/useValue.ts @@ -5,17 +5,20 @@ import { State, defineConfig, } from '@agile-ts/core'; -import { AgileHookConfigInterface, useAgile } from './useAgile'; -import { SubscribableAgileInstancesType } from './useBaseAgile'; +import { useAgile } from './useAgile'; +import { + BaseAgileHookConfigInterface, + SubscribableAgileInstancesType, +} from './useBaseAgile'; export function useValue>( deps: X | [], - config?: AgileHookConfigInterface + config?: BaseAgileHookConfigInterface ): AgileValueHookArrayType; export function useValue( dep: X, - config?: AgileHookConfigInterface + config?: BaseAgileHookConfigInterface ): AgileValueHookType; export function useValue< @@ -23,7 +26,7 @@ export function useValue< Y extends SubscribableAgileInstancesType >( deps: X | Y, - config: AgileHookConfigInterface = {} + config: BaseAgileHookConfigInterface = {} ): AgileValueHookArrayType | AgileValueHookType { return useAgile( deps as any, From 59bd613cbd09254c577c9b38debf04cf9b35c85f Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sat, 28 Aug 2021 08:15:32 +0200 Subject: [PATCH 38/44] updated event structure --- .../develop/functional-component-ts/src/App.tsx | 2 +- packages/event/src/{ => event}/event.observer.ts | 2 +- .../{event.job.ts => event/event.runtime.job.ts} | 2 +- packages/event/src/{ => event}/event.ts | 8 ++++---- packages/event/src/{shared.ts => event/index.ts} | 16 ++++++++-------- packages/event/src/index.ts | 1 - packages/event/src/internal.ts | 7 ++----- packages/event/src/{ => react}/hooks/useEvent.ts | 2 +- .../hooks/useIsomorphicLayoutEffect.ts | 0 packages/event/src/react/index.ts | 1 + .../tests/unit/{ => event}/event.job.test.ts | 11 +++++++---- .../unit/{ => event}/event.observer.test.ts | 4 ++-- .../event/tests/unit/{ => event}/event.test.ts | 4 ++-- .../unit/{shared.test.ts => event/index.test.ts} | 6 +++--- 14 files changed, 33 insertions(+), 33 deletions(-) rename packages/event/src/{ => event}/event.observer.ts (97%) rename packages/event/src/{event.job.ts => event/event.runtime.job.ts} (90%) rename packages/event/src/{ => event}/event.ts (97%) rename packages/event/src/{shared.ts => event/index.ts} (67%) rename packages/event/src/{ => react}/hooks/useEvent.ts (95%) rename packages/event/src/{ => react}/hooks/useIsomorphicLayoutEffect.ts (100%) create mode 100644 packages/event/src/react/index.ts rename packages/event/tests/unit/{ => event}/event.job.test.ts (74%) rename packages/event/tests/unit/{ => event}/event.observer.test.ts (94%) rename packages/event/tests/unit/{ => event}/event.test.ts (99%) rename packages/event/tests/unit/{shared.test.ts => event/index.test.ts} (88%) diff --git a/examples/react/develop/functional-component-ts/src/App.tsx b/examples/react/develop/functional-component-ts/src/App.tsx index 13aa298d..7c0849b3 100644 --- a/examples/react/develop/functional-component-ts/src/App.tsx +++ b/examples/react/develop/functional-component-ts/src/App.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import './App.css'; import { useAgile, useWatcher, useProxy, useSelector } from '@agile-ts/react'; -import { useEvent } from '@agile-ts/event'; +import { useEvent } from '@agile-ts/event/dist/react'; import { COUNTUP, externalCreatedItem, diff --git a/packages/event/src/event.observer.ts b/packages/event/src/event/event.observer.ts similarity index 97% rename from packages/event/src/event.observer.ts rename to packages/event/src/event/event.observer.ts index 97da3216..1baa1303 100644 --- a/packages/event/src/event.observer.ts +++ b/packages/event/src/event/event.observer.ts @@ -4,7 +4,7 @@ import { ObserverKey, SubscriptionContainer, } from '@agile-ts/core'; -import { Event } from './internal'; +import { Event } from '../internal'; export class EventObserver extends Observer { public event: () => Event; diff --git a/packages/event/src/event.job.ts b/packages/event/src/event/event.runtime.job.ts similarity index 90% rename from packages/event/src/event.job.ts rename to packages/event/src/event/event.runtime.job.ts index f192cadd..dc7547a3 100644 --- a/packages/event/src/event.job.ts +++ b/packages/event/src/event/event.runtime.job.ts @@ -1,4 +1,4 @@ -export class EventJob { +export class EventRuntimeJob { public payload: PayloadType; public creationTimestamp: number; public keys?: string[]; diff --git a/packages/event/src/event.ts b/packages/event/src/event/event.ts similarity index 97% rename from packages/event/src/event.ts rename to packages/event/src/event/event.ts index b54660f9..403ff902 100644 --- a/packages/event/src/event.ts +++ b/packages/event/src/event/event.ts @@ -5,7 +5,7 @@ import { LogCodeManager, Observer, } from '@agile-ts/core'; -import { EventObserver, EventJob } from './internal'; +import { EventObserver, EventRuntimeJob } from '../internal'; import { defineConfig } from '@agile-ts/utils'; export class Event { @@ -21,7 +21,7 @@ export class Event { public observer: EventObserver; public currentTimeout: any; // Timeout that is active right now (delayed Event) - public queue: Array = []; // Queue of delayed Events + public queue: Array = []; // Queue of delayed Events // @ts-ignore public payload: PayloadType; // Holds type of Payload so that it can be read external (never defined) @@ -251,7 +251,7 @@ export class Event { * @param keys - Keys of Callback Functions that get triggered (Note: if not passed all registered Events will be triggered) */ public delayedTrigger(payload: PayloadType, delay: number, keys?: string[]) { - const eventJob = new EventJob(payload, keys); + const eventJob = new EventRuntimeJob(payload, keys); // Execute Event no matter if another event is currently active if (this.config.overlap) { @@ -268,7 +268,7 @@ export class Event { } // Executes EventJob and calls itself again if queue isn't empty to execute the next EventJob - const looper = (eventJob: EventJob) => { + const looper = (eventJob: EventRuntimeJob) => { this.currentTimeout = setTimeout(() => { this.currentTimeout = undefined; this.normalTrigger(eventJob.payload, eventJob.keys); diff --git a/packages/event/src/shared.ts b/packages/event/src/event/index.ts similarity index 67% rename from packages/event/src/shared.ts rename to packages/event/src/event/index.ts index fae03ce1..ebd8344f 100644 --- a/packages/event/src/shared.ts +++ b/packages/event/src/event/index.ts @@ -1,14 +1,14 @@ import { - CreateAgileSubInstanceInterface, - removeProperties, - shared, - defineConfig, -} from '@agile-ts/core'; -import { - Event, CreateEventConfigInterface, DefaultEventPayload, -} from './internal'; + Event, +} from './event'; +import { defineConfig, removeProperties } from '@agile-ts/utils'; +import { CreateAgileSubInstanceInterface, shared } from '@agile-ts/core'; + +export * from './event'; +// export * from './event.observer'; +// export * from './event.job'; export function createEvent( config: CreateEventConfigInterfaceWithAgile = {} diff --git a/packages/event/src/index.ts b/packages/event/src/index.ts index f3396fb7..3cb08d20 100644 --- a/packages/event/src/index.ts +++ b/packages/event/src/index.ts @@ -1,5 +1,4 @@ import { Event } from './internal'; export * from './internal'; -export { useEvent } from './hooks/useEvent'; export default Event; diff --git a/packages/event/src/internal.ts b/packages/event/src/internal.ts index 695b0fbb..758f4fe4 100644 --- a/packages/event/src/internal.ts +++ b/packages/event/src/internal.ts @@ -5,9 +5,6 @@ // !! All internal Agile Editor modules must be imported from here!! // Event -export * from './event.job'; -export * from './event.observer'; +export * from './event/event.runtime.job'; +export * from './event/event.observer'; export * from './event'; - -// Shared -export * from './shared'; diff --git a/packages/event/src/hooks/useEvent.ts b/packages/event/src/react/hooks/useEvent.ts similarity index 95% rename from packages/event/src/hooks/useEvent.ts rename to packages/event/src/react/hooks/useEvent.ts index 5a1d3168..3ef41a70 100644 --- a/packages/event/src/hooks/useEvent.ts +++ b/packages/event/src/react/hooks/useEvent.ts @@ -5,7 +5,7 @@ import { LogCodeManager, SubscriptionContainerKeyType, } from '@agile-ts/core'; -import { Event, EventCallbackFunction } from '../internal'; +import { Event, EventCallbackFunction } from '../../internal'; import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; export function useEvent>( diff --git a/packages/event/src/hooks/useIsomorphicLayoutEffect.ts b/packages/event/src/react/hooks/useIsomorphicLayoutEffect.ts similarity index 100% rename from packages/event/src/hooks/useIsomorphicLayoutEffect.ts rename to packages/event/src/react/hooks/useIsomorphicLayoutEffect.ts diff --git a/packages/event/src/react/index.ts b/packages/event/src/react/index.ts new file mode 100644 index 00000000..1501534e --- /dev/null +++ b/packages/event/src/react/index.ts @@ -0,0 +1 @@ +export { useEvent } from './hooks/useEvent'; diff --git a/packages/event/tests/unit/event.job.test.ts b/packages/event/tests/unit/event/event.job.test.ts similarity index 74% rename from packages/event/tests/unit/event.job.test.ts rename to packages/event/tests/unit/event/event.job.test.ts index 90397943..0e67edda 100644 --- a/packages/event/tests/unit/event.job.test.ts +++ b/packages/event/tests/unit/event/event.job.test.ts @@ -1,5 +1,5 @@ -import { EventJob } from '../../src'; -import { LogMock } from '../../../core/tests/helper/logMock'; +import { EventRuntimeJob } from '../../../src'; +import { LogMock } from '../../../../core/tests/helper/logMock'; describe('EventJob Tests', () => { beforeEach(() => { @@ -8,7 +8,7 @@ describe('EventJob Tests', () => { }); it('should create EventJob (without keys)', () => { - const eventJob = new EventJob('myPayload'); + const eventJob = new EventRuntimeJob('myPayload'); expect(eventJob.payload).toBe('myPayload'); expect(eventJob.creationTimestamp).toBeCloseTo( @@ -19,7 +19,10 @@ describe('EventJob Tests', () => { }); it('should create EventJob (with keys)', () => { - const eventJob = new EventJob('myPayload', ['dummyKey1', 'dummyKey2']); + const eventJob = new EventRuntimeJob('myPayload', [ + 'dummyKey1', + 'dummyKey2', + ]); expect(eventJob.payload).toBe('myPayload'); expect(eventJob.creationTimestamp).toBeCloseTo( diff --git a/packages/event/tests/unit/event.observer.test.ts b/packages/event/tests/unit/event/event.observer.test.ts similarity index 94% rename from packages/event/tests/unit/event.observer.test.ts rename to packages/event/tests/unit/event/event.observer.test.ts index 2732e219..2ee5d487 100644 --- a/packages/event/tests/unit/event.observer.test.ts +++ b/packages/event/tests/unit/event/event.observer.test.ts @@ -1,6 +1,6 @@ -import { EventObserver, Event } from '../../src'; +import { EventObserver, Event } from '../../../src'; import { Agile, Observer, SubscriptionContainer } from '@agile-ts/core'; -import { LogMock } from '../../../core/tests/helper/logMock'; +import { LogMock } from '../../../../core/tests/helper/logMock'; describe('EventObserver Tests', () => { let dummyAgile: Agile; diff --git a/packages/event/tests/unit/event.test.ts b/packages/event/tests/unit/event/event.test.ts similarity index 99% rename from packages/event/tests/unit/event.test.ts rename to packages/event/tests/unit/event/event.test.ts index 5885471b..a719ce06 100644 --- a/packages/event/tests/unit/event.test.ts +++ b/packages/event/tests/unit/event/event.test.ts @@ -1,7 +1,7 @@ -import { Event, EventObserver } from '../../src'; +import { Event, EventObserver } from '../../../src'; import { Agile, Observer } from '@agile-ts/core'; import * as Utils from '@agile-ts/utils'; -import { LogMock } from '../../../core/tests/helper/logMock'; +import { LogMock } from '../../../../core/tests/helper/logMock'; describe('Event Tests', () => { let dummyAgile: Agile; diff --git a/packages/event/tests/unit/shared.test.ts b/packages/event/tests/unit/event/index.test.ts similarity index 88% rename from packages/event/tests/unit/shared.test.ts rename to packages/event/tests/unit/event/index.test.ts index 7b5719a0..9d8a6b6a 100644 --- a/packages/event/tests/unit/shared.test.ts +++ b/packages/event/tests/unit/event/index.test.ts @@ -1,8 +1,8 @@ import { Agile, assignSharedAgileInstance } from '@agile-ts/core'; -import { Event, createEvent } from '../../src'; -import { LogMock } from '../../../core/tests/helper/logMock'; +import { Event, createEvent } from '../../../src'; +import { LogMock } from '../../../../core/tests/helper/logMock'; -jest.mock('../../src/event'); +jest.mock('../../../src/event/event'); describe('Shared Tests', () => { let sharedAgileInstance: Agile; From 5ea27e28dabdda9f48ad31cb160a22b10dc5cad9 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sat, 28 Aug 2021 10:05:18 +0200 Subject: [PATCH 39/44] fixed typos --- packages/multieditor/src/multieditor/index.ts | 22 ++ .../src/{ => multieditor}/multieditor.ts | 2 +- packages/utils/src/index.ts | 189 ++++++++---------- 3 files changed, 104 insertions(+), 109 deletions(-) create mode 100644 packages/multieditor/src/multieditor/index.ts rename packages/multieditor/src/{ => multieditor}/multieditor.ts (99%) diff --git a/packages/multieditor/src/multieditor/index.ts b/packages/multieditor/src/multieditor/index.ts new file mode 100644 index 00000000..5353b95c --- /dev/null +++ b/packages/multieditor/src/multieditor/index.ts @@ -0,0 +1,22 @@ +import { defineConfig } from '@agile-ts/utils'; +import { Agile, shared } from '@agile-ts/core'; +import { EditorConfig, MultiEditor } from '../internal'; + +export * from './multieditor'; + +export function createMultieditor< + DataType = any, + SubmitReturnType = void, + OnSubmitConfigType = any +>( + config: EditorConfig, + agileInstance: Agile = shared +): MultiEditor { + config = defineConfig(config, { + agileInstance: shared, + }); + return new MultiEditor( + config, + agileInstance as any + ); +} diff --git a/packages/multieditor/src/multieditor.ts b/packages/multieditor/src/multieditor/multieditor.ts similarity index 99% rename from packages/multieditor/src/multieditor.ts rename to packages/multieditor/src/multieditor/multieditor.ts index a686a6c0..3e000768 100644 --- a/packages/multieditor/src/multieditor.ts +++ b/packages/multieditor/src/multieditor/multieditor.ts @@ -13,7 +13,7 @@ import { StatusType, StatusInterface, ValidationMethodInterface, -} from './internal'; +} from '../internal'; export class MultiEditor< DataType = any, diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 6161059c..c06a7505 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,20 +1,17 @@ -//========================================================================================================= -// Copy -//========================================================================================================= /** - * @internal - * Creates a fresh copy of an Array/Object + * Creates a fresh (deep) copy of the specified value. * https://www.samanthaming.com/tidbits/70-3-ways-to-clone-objects/ - * @param value - Array/Object that gets copied + * + * @public + * @param value - Value to be copied. */ export function copy(value: T): T { // Extra checking 'value == null' because 'typeof null === object' if (value == null || typeof value !== 'object') return value; // Ignore everything that is no object or array but has the type of an object (e.g. classes) - const valConstructorName = Object.getPrototypeOf( - value - ).constructor.name.toLowerCase(); + const valConstructorName = + Object.getPrototypeOf(value).constructor.name.toLowerCase(); if (valConstructorName !== 'object' && valConstructorName !== 'array') return value; @@ -27,15 +24,13 @@ export function copy(value: T): T { return newObject as T; } -//========================================================================================================= -// Is Valid Object -//========================================================================================================= /** - * @internal - * Checks if passed value is a valid Object + * Checks whether the specified value is a valid object. * https://stackoverflow.com/questions/12996871/why-does-typeof-array-with-objects-return-object-and-not-array - * @param value - Value that is tested for its correctness - * @param considerArray - Whether Arrays should be considered as object + * + * @public + * @param value - Value + * @param considerArray - Whether to considered an array as an object. */ export function isValidObject(value: any, considerArray = false): boolean { function isHTMLElement(obj: any) { @@ -59,12 +54,10 @@ export function isValidObject(value: any, considerArray = false): boolean { ); } -//========================================================================================================= -// Includes Array -//========================================================================================================= /** - * @internal - * Check if array1 contains all elements of array2 + * Checks whether 'array1' contains all elements of 'array2'. + * + * @public * @param array1 - Array 1 * @param array2 - Array 2 */ @@ -75,13 +68,11 @@ export function includesArray( return array2.every((element) => array1.includes(element)); } -//========================================================================================================= -// Normalize Array -//========================================================================================================= /** - * @internal - * Transforms Item/s to an Item Array - * @param items - Item/s that gets transformed to an Array + * Transforms Item/s into an array of Items. + * + * @public + * @param items - Item/s to be transformed into an array of Items. * @param config - Config */ export function normalizeArray( @@ -95,25 +86,21 @@ export function normalizeArray( return Array.isArray(items) ? items : [items as DataType]; } -//========================================================================================================= -// Is Function -//========================================================================================================= /** - * @internal - * Checks if value is a function - * @param value - Value that gets tested if its a function + * Checks whether the specified function is a function. + * + * @public + * @param value - Value to be checked */ export function isFunction(value: any): boolean { return typeof value === 'function'; } -//========================================================================================================= -// Is Async Function -//========================================================================================================= /** - * @internal - * Checks if value is an async function - * @param value - Value that gets tested if its an async function + * Checks whether the specified function is an async function. + * + * @public + * @param value - Value to be checked. */ export function isAsyncFunction(value: any): boolean { const valueString = value.toString(); @@ -124,13 +111,11 @@ export function isAsyncFunction(value: any): boolean { ); } -//========================================================================================================= -// Is Json String -//========================================================================================================= /** - * @internal - * Checks if value is valid JsonString - * @param value - Value that gets checked + * Checks whether the specified value is a valid JSON string + * + * @public + * @param value - Value to be checked. */ export function isJsonString(value: any): boolean { if (typeof value !== 'string') return false; @@ -142,15 +127,13 @@ export function isJsonString(value: any): boolean { return true; } -//========================================================================================================= -// Define Config -//========================================================================================================= /** - * @internal - * Merges default values/properties into config object - * @param config - Config object that receives default values - * @param defaults - Default values object that gets merged into config object - * @param overwriteUndefinedProperties - If undefined Properties in config gets overwritten by the default value + * Merges the default values object ('defaults') into the configuration object ('config'). + * + * @public + * @param config - Configuration object to merge the default values in. + * @param defaults - Default values object to be merged into the configuration object. + * @param overwriteUndefinedProperties - Whether to overwrite 'undefined' set properties with default values. */ export function defineConfig( config: ConfigInterface, @@ -174,24 +157,22 @@ export function defineConfig( return shallowCopiedConfig; } -//========================================================================================================= -// Flat Merge -//========================================================================================================= -/** - * @internal - * @param addNewProperties - Adds new properties to source Object - */ export interface FlatMergeConfigInterface { + /** + * + * Whether to add new properties (properties that doesn't exist in the source object yet) to the source object. + * @default true + */ addNewProperties?: boolean; } /** - * @internal - * Merges items into object, be aware that the merge will only happen at the top level of the object. - * Initially it adds new properties of the changes object into the source object. + * Merges the 'changes' object into the 'source' object at top level. + * + * @public * @param source - Source object - * @param changes - Changes that get merged into the source object - * @param config - Config + * @param changes - Changes object to be merged into the source object + * @param config - Configuration object */ export function flatMerge( source: DataType, @@ -219,14 +200,12 @@ export function flatMerge( return _source; } -//========================================================================================================= -// Equals -//========================================================================================================= /** - * @internal - * Check if two values are equal - * @param value1 - First Value - * @param value2 - Second Value + * Checks whether the two specified values are equivalent. + * + * @public + * @param value1 - First value. + * @param value2 - Second value. */ export function equal(value1: any, value2: any): boolean { return ( @@ -239,46 +218,44 @@ export function equal(value1: any, value2: any): boolean { ); } -//========================================================================================================= -// Not Equals -//========================================================================================================= /** - * @internal - * Checks if two values aren't equal - * @param value1 - First Value - * @param value2 - Second Value + * Checks whether the two specified values are NOT equivalent. + * + * @public + * @param value1 - First value. + * @param value2 - Second value. */ export function notEqual(value1: any, value2: any): boolean { return !equal(value1, value2); } -//========================================================================================================= -// Generate Id -//========================================================================================================= /** - * @internal - * Generates random Id - * @param length - Length of generated Id + * Generates a randomized id based on alphabetic and numeric characters. + * + * @public + * @param length - Length of the to generate id (default = 5). + * @param characters - Characters to generate the id from (default = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'). */ -export function generateId(length?: number): string { - const characters = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +export function generateId( + length: number = 5, + characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' +): string { const charactersLength = characters.length; let result = ''; - if (!length) length = 5; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } -//========================================================================================================= -// Create Array From Object -//========================================================================================================= /** - * @internal - * Transforms Object to Array - * @param object - Object that gets transformed + * Transforms the specified object into an array. + * + * Example: + * {"1": 'jeff', 2: 'frank'} -> [{key: "1", instance: 'jeff'}, {key: 2, instance: 'frank'}] + * + * @public + * @param object - Object to be transformed to an array. */ export function createArrayFromObject

(object: { [key: string]: P; @@ -293,13 +270,11 @@ export function createArrayFromObject

(object: { return array; } -//========================================================================================================= -// Clone -//========================================================================================================= /** - * @internal - * Clones a Class - * @param instance - Instance of Class you want to clone + * Clones the specified class. + * + * @public + * @param instance - Class to be cloned. */ export function clone(instance: T): T { // Clone Class @@ -312,14 +287,12 @@ export function clone(instance: T): T { return objectClone; } -//========================================================================================================= -// Remove Properties -//========================================================================================================= /** - * @internal - * Removes properties from Object - * @param object - Object from which the properties get removed - * @param properties - Properties that get removed from the object + * Removes specified properties from the defined object. + * + * @public + * @param object - Object to remove the specified properties from. + * @param properties - Property keys to be removed from the specified object. */ export function removeProperties( object: T, From dcf742fda1d7179d7df7d980152dab21ed8ef576 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sun, 29 Aug 2021 17:06:56 +0200 Subject: [PATCH 40/44] added log code manager to react package --- packages/core/src/logCodeManager.ts | 76 ++++++++++++++---------- packages/logger/src/index.ts | 18 +++--- packages/react/src/hooks/useBaseAgile.ts | 7 +-- packages/react/src/hooks/useProxy.ts | 7 +-- packages/react/src/logCodeManager.ts | 26 ++++++++ 5 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 packages/react/src/logCodeManager.ts diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 2e10408e..79d36451 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -23,7 +23,7 @@ const logCodeTypes = { // --- // 00:00:|00| third digits are based on the Log Message (ascending counted) -const niceLogCodeMessages = { +const logCodeMessages = { // Agile '10:00:00': 'Created new AgileInstance.', '10:02:00': @@ -165,13 +165,6 @@ const niceLogCodeMessages = { '00:03:01': "'${0}' has to be of the type ${1}!", }; -// Note: Not outsource the 'production' env check, -// because then webpack can't treeshake based on the current env -const logCodeMessages: typeof niceLogCodeMessages = - typeof process === 'object' && process.env.NODE_ENV !== 'production' - ? niceLogCodeMessages - : ({} as any); - /** * Returns the log message according to the specified log code. * @@ -257,20 +250,22 @@ function logIfTags>( // Handle logging with Logger logger.if.tag(tags)[logType](getLog(logCode, replacers), ...data); } -/** - * The Log Code Manager keeps track - * and manages all important Logs of AgileTs. - * - * @internal - */ -let tempLogCodeManager: { - getLog: typeof getLog; - log: typeof log; - logCodeLogTypes: typeof logCodeTypes; - logCodeMessages: typeof logCodeMessages; - getLogger: () => any; - logIfTags: typeof logIfTags; -}; + +export function assignAdditionalLogs< + NewLogCodeMessages, + OldLogCodeMessages = typeof logCodeMessages +>( + additionalLogs: { [key: string]: string }, + logCodeManager: LogCodeManagerInterface +): LogCodeManagerInterface { + logCodeManager.logCodeMessages = { + ...LogCodeManager.logCodeMessages, + ...additionalLogs, + } as any; + return logCodeManager as any; +} + +let tempLogCodeManager: LogCodeManagerInterface; if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { let loggerPackage: any = null; try { @@ -284,11 +279,7 @@ if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { log, logCodeLogTypes: logCodeTypes, logCodeMessages: logCodeMessages, - // Not doing 'logger: loggerPackage?.sharedAgileLogger' - // because only by calling a function (now 'getLogger()') the 'sharedLogger' is refetched - getLogger: () => { - return loggerPackage?.sharedAgileLogger ?? null; - }, + getLogger: loggerPackage.getLogger, logIfTags, }; } else { @@ -297,20 +288,43 @@ if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { getLog: (logCode, replacers) => logCode, log, logCodeLogTypes: logCodeTypes, - logCodeMessages: logCodeMessages, - // Not doing 'logger: loggerPackage?.sharedAgileLogger' - // because only by calling a function (now 'getLogger()') the 'sharedLogger' is refetched + logCodeMessages: {} as any, getLogger: () => { return null; }, logIfTags: (tags, logCode, replacers) => { - /* empty */ + /* empty because logs with tags can't be that important */ }, }; } + +/** + * The Log Code Manager keeps track + * and manages all important Logs for the '@agile-ts/core' package. + * + * @internal + */ export const LogCodeManager = tempLogCodeManager; export type LogCodesArrayType = { [K in keyof T]: T[K] extends string ? K : never; }[keyof T] & string; + +export interface LogCodeManagerInterface { + getLog: (logCode: LogCodesArrayType, replacers?: any[]) => string; + log: ( + logCode: LogCodesArrayType, + replacers?: any[], + ...data: any[] + ) => void; + logCodeLogTypes: typeof logCodeTypes; + logCodeMessages: T; + getLogger: () => any; + logIfTags: ( + tags: string[], + logCode: LogCodesArrayType, + replacers?: any[], + ...data: any[] + ) => void; +} diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 00aa3e3e..a76a7efc 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,6 +1,9 @@ import { CreateLoggerConfigInterface, Logger } from './logger'; import { defineConfig } from '@agile-ts/utils'; +export * from './logger'; +export default Logger; + const defaultLogConfig = { prefix: 'Agile', active: true, @@ -9,9 +12,6 @@ const defaultLogConfig = { allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], }; -/** - * Shared Agile Logger. - */ let sharedAgileLogger = new Logger(defaultLogConfig); /** @@ -19,8 +19,7 @@ let sharedAgileLogger = new Logger(defaultLogConfig); * * @param config - Configuration object */ -// https://stackoverflow.com/questions/32558514/javascript-es6-export-const-vs-export-let -function assignSharedAgileLoggerConfig( +export function assignSharedAgileLoggerConfig( config: CreateLoggerConfigInterface = {} ): Logger { config = defineConfig(config, defaultLogConfig); @@ -28,6 +27,9 @@ function assignSharedAgileLoggerConfig( return sharedAgileLogger; } -export { sharedAgileLogger, assignSharedAgileLoggerConfig }; -export * from './logger'; -export default Logger; +/** + * Returns the shared Agile Logger. + */ +export function getLogger(): Logger { + return sharedAgileLogger; +} diff --git a/packages/react/src/hooks/useBaseAgile.ts b/packages/react/src/hooks/useBaseAgile.ts index f6b872a2..b566b756 100644 --- a/packages/react/src/hooks/useBaseAgile.ts +++ b/packages/react/src/hooks/useBaseAgile.ts @@ -3,13 +3,13 @@ import Agile, { Collection, ComponentIdType, getAgileInstance, - LogCodeManager, Observer, State, SubscriptionContainerKeyType, RegisterSubscriptionConfigInterface, } from '@agile-ts/core'; import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; +import { LogCodeManager } from '../logCodeManager'; /** * An internal used React Hook @@ -46,10 +46,7 @@ export const useBaseAgile = ( // Try to extract Agile Instance from the specified Instance/s if (agileInstance == null) agileInstance = getAgileInstance(observers[0]); if (agileInstance == null || agileInstance.subController == null) { - LogCodeManager.getLogger()?.error( - 'Failed to subscribe Component with deps because of missing valid Agile Instance.', - deps - ); + LogCodeManager.log('30:03:00', deps); return; } diff --git a/packages/react/src/hooks/useProxy.ts b/packages/react/src/hooks/useProxy.ts index 76356f28..02b100ef 100644 --- a/packages/react/src/hooks/useProxy.ts +++ b/packages/react/src/hooks/useProxy.ts @@ -15,6 +15,7 @@ import { AgileOutputHookArrayType, AgileOutputHookType, } from './useAgile'; +import { LogCodeManager } from '../logCodeManager'; // TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work let proxyPackage: any = null; @@ -100,11 +101,7 @@ export function useProxy< proxyTreeWeakMap.set(dep, proxyTree); return proxyTree.proxy; } else { - // TODO add LogCode Manager logcode - console.error( - 'In order to use the Agile proxy functionality, ' + - `the installation of an additional package called '@agile-ts/proxytree' is required!` - ); + LogCodeManager.log('31:03:00'); } } diff --git a/packages/react/src/logCodeManager.ts b/packages/react/src/logCodeManager.ts new file mode 100644 index 00000000..0ddfd542 --- /dev/null +++ b/packages/react/src/logCodeManager.ts @@ -0,0 +1,26 @@ +import { + LogCodeManager as CoreLogCodeManager, + assignAdditionalLogs, +} from '@agile-ts/core'; + +const additionalLogs = { + '30:03:00': + 'Failed to subscribe Component with deps because of missing valid Agile Instance.', + '31:03:00': + "In order to use the Agile proxy functionality, the installation of an additional package called '@agile-ts/proxytree' is required!", +}; + +/** + * The Log Code Manager keeps track + * and manages all important Logs for the '@agile-ts/react' package. + * + * @internal + */ +export const LogCodeManager = + typeof process === 'object' && process.env.NODE_ENV !== 'production' + ? assignAdditionalLogs< + typeof CoreLogCodeManager.logCodeMessages & typeof additionalLogs + >(additionalLogs, CoreLogCodeManager) + : assignAdditionalLogs< + typeof CoreLogCodeManager.logCodeMessages & typeof additionalLogs + >({}, CoreLogCodeManager); From 321a8aeea573a4e7cc71e73baa6bf026241ba0e7 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sun, 29 Aug 2021 17:30:50 +0200 Subject: [PATCH 41/44] fixed typo --- packages/core/src/logCodeManager.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 79d36451..7c92dbca 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -1,3 +1,5 @@ +import { copy } from '@agile-ts/utils'; + // The Log Code Manager keeps track // and manages all important Logs of AgileTs. // @@ -9,7 +11,7 @@ // 00 = General // 10 = Agile // 11 = Storage -// .. +// ... // // --- // 00:|00|:00 second digits are based on the Log Type @@ -251,6 +253,13 @@ function logIfTags>( logger.if.tag(tags)[logType](getLog(logCode, replacers), ...data); } +/** + * Creates an extension of the specified LogCodeManager + * and assigns the provided additional log messages to it. + * + * @param additionalLogs - Log messages to be added to the LogCodeManager. + * @param logCodeManager - LogCodeManager to create an extension from. + */ export function assignAdditionalLogs< NewLogCodeMessages, OldLogCodeMessages = typeof logCodeMessages @@ -258,11 +267,12 @@ export function assignAdditionalLogs< additionalLogs: { [key: string]: string }, logCodeManager: LogCodeManagerInterface ): LogCodeManagerInterface { - logCodeManager.logCodeMessages = { - ...LogCodeManager.logCodeMessages, + const copiedLogCodeManager = copy(logCodeManager); + copiedLogCodeManager.logCodeMessages = { + ...copiedLogCodeManager.logCodeMessages, ...additionalLogs, } as any; - return logCodeManager as any; + return copiedLogCodeManager as any; } let tempLogCodeManager: LogCodeManagerInterface; From 6600cd5741576a1a62e1d99e6f14604f677c585d Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sun, 29 Aug 2021 17:50:23 +0200 Subject: [PATCH 42/44] fixed linter --- packages/react/src/hocs/AgileHOC.ts | 19 +++++++------------ packages/react/src/logCodeManager.ts | 1 + packages/utils/src/index.ts | 4 ++-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts index 99e161d2..7bfcd4a6 100644 --- a/packages/react/src/hocs/AgileHOC.ts +++ b/packages/react/src/hocs/AgileHOC.ts @@ -8,9 +8,9 @@ import Agile, { flatMerge, extractRelevantObservers, normalizeArray, - LogCodeManager, } from '@agile-ts/core'; -import type { Collection } from '@agile-ts/core'; // Only import Collection and Group type for better Treeshaking +import type { Collection } from '@agile-ts/core'; +import { LogCodeManager } from '../logCodeManager'; // Only import Collection and Group type for better Treeshaking /** * A Higher order Component for binding the most relevant value of multiple Agile Instances @@ -57,10 +57,7 @@ export function AgileHOC( } } if (!agileInstance || !agileInstance.subController) { - LogCodeManager.getLogger()?.error( - 'Failed to subscribe Component with deps', - deps - ); + LogCodeManager.log('32:03:00', [deps]); return reactComponent; } @@ -93,9 +90,8 @@ const createHOC = ( public agileInstance: Agile; public waitForMount: boolean; - public componentSubscriptionContainers: Array< - ComponentSubscriptionContainer - > = []; // Represents all Subscription Container subscribed to this Component (set by subController) + public componentSubscriptionContainers: Array = + []; // Represents all Subscription Container subscribed to this Component (set by subController) public agileProps = {}; // Props of subscribed Agile Instances (are merged into the normal props) constructor(props: any) { @@ -235,9 +231,8 @@ const formatDepsWithIndicator = ( export class AgileReactComponent extends React.Component { // @ts-ignore public agileInstance: Agile; - public componentSubscriptionContainers: Array< - ComponentSubscriptionContainer - > = []; + public componentSubscriptionContainers: Array = + []; public agileProps = {}; constructor(props: any) { diff --git a/packages/react/src/logCodeManager.ts b/packages/react/src/logCodeManager.ts index 0ddfd542..b3601ba2 100644 --- a/packages/react/src/logCodeManager.ts +++ b/packages/react/src/logCodeManager.ts @@ -8,6 +8,7 @@ const additionalLogs = { 'Failed to subscribe Component with deps because of missing valid Agile Instance.', '31:03:00': "In order to use the Agile proxy functionality, the installation of an additional package called '@agile-ts/proxytree' is required!", + '32:03:00': 'Failed to subscribe Component with deps', }; /** diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index c06a7505..ffef55ed 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -237,8 +237,8 @@ export function notEqual(value1: any, value2: any): boolean { * @param characters - Characters to generate the id from (default = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'). */ export function generateId( - length: number = 5, - characters: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + length = 5, + characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' ): string { const charactersLength = characters.length; let result = ''; From 71648dda7c1d8a3a3ef4875af45a50eb1b9567ef Mon Sep 17 00:00:00 2001 From: BennoDev Date: Mon, 30 Aug 2021 19:51:36 +0200 Subject: [PATCH 43/44] fixed typo --- .../plainjs/develop/tree-shaking/package.json | 2 +- packages/core/src/logCodeManager.ts | 9 +++++- packages/react/src/hocs/AgileHOC.ts | 4 +-- packages/react/src/hooks/useSelector.ts | 29 ++++++++++++++----- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/examples/plainjs/develop/tree-shaking/package.json b/examples/plainjs/develop/tree-shaking/package.json index 9f6bb167..4af19172 100644 --- a/examples/plainjs/develop/tree-shaking/package.json +++ b/examples/plainjs/develop/tree-shaking/package.json @@ -13,7 +13,7 @@ "license": "ISC", "devDependencies": { "webpack": "^5.47.0", - "webpack-cli": "^4.7.2" + "webpack-cli": "^4.8.0" }, "dependencies": { "@agile-ts/core": "file:.yalc/@agile-ts/core" diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 7c92dbca..d0fb84c9 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -25,7 +25,7 @@ const logCodeTypes = { // --- // 00:00:|00| third digits are based on the Log Message (ascending counted) -const logCodeMessages = { +const niceLogCodeMessages = { // Agile '10:00:00': 'Created new AgileInstance.', '10:02:00': @@ -167,6 +167,13 @@ const logCodeMessages = { '00:03:01': "'${0}' has to be of the type ${1}!", }; +// Note: Not outsource the 'production' env check, +// because then webpack can't treeshake based on the current env +const logCodeMessages: typeof niceLogCodeMessages = + typeof process === 'object' && process.env.NODE_ENV !== 'production' + ? niceLogCodeMessages + : ({} as any); + /** * Returns the log message according to the specified log code. * diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts index 7bfcd4a6..01b787ed 100644 --- a/packages/react/src/hocs/AgileHOC.ts +++ b/packages/react/src/hocs/AgileHOC.ts @@ -9,8 +9,8 @@ import Agile, { extractRelevantObservers, normalizeArray, } from '@agile-ts/core'; -import type { Collection } from '@agile-ts/core'; -import { LogCodeManager } from '../logCodeManager'; // Only import Collection and Group type for better Treeshaking +import type { Collection } from '@agile-ts/core'; // Only import Collection and Group type for better Treeshaking +import { LogCodeManager } from '../logCodeManager'; /** * A Higher order Component for binding the most relevant value of multiple Agile Instances diff --git a/packages/react/src/hooks/useSelector.ts b/packages/react/src/hooks/useSelector.ts index 75a9cd34..043df0c2 100644 --- a/packages/react/src/hooks/useSelector.ts +++ b/packages/react/src/hooks/useSelector.ts @@ -14,16 +14,30 @@ import { } from './useBaseAgile'; import { AgileValueHookType } from './useValue'; +/** + * A React Hook for binding a selected value of an Agile Instance + * (like the Collection's output or the State's value) + * to a React Functional Component. + * + * This binding ensures that the Component re-renders + * whenever the selected value of an Agile Instance mutates. + * + * @public + * @param dep - Agile Sub Instance to be bound to the Functional Component. + * @param selectorMethod - Equality comparison function. + * that allows you to customize the way the selected Agile Instance + * is compared to determine whether the Component needs to be re-rendered. + * @param config - Configuration object + */ export function useSelector< ReturnType, X extends SubscribableAgileInstancesType, ValueType extends AgileValueHookType >( dep: X, - selector: SelectorMethodType, + selectorMethod: SelectorMethodType, config?: BaseAgileHookConfigInterface ): ReturnType; - /** * A React Hook for binding a selected value of an Agile Instance * (like the Collection's output or the State's value) @@ -34,14 +48,14 @@ export function useSelector< * * @public * @param dep - Agile Sub Instance to be bound to the Functional Component. - * @param selector - Equality comparison function + * @param selectorMethod - Equality comparison function. * that allows you to customize the way the selected Agile Instance * is compared to determine whether the Component needs to be re-rendered. * @param config - Configuration object */ export function useSelector( dep: SubscribableAgileInstancesType, - selector: SelectorMethodType, + selectorMethod: SelectorMethodType, config?: BaseAgileHookConfigInterface ): ReturnType; @@ -51,13 +65,14 @@ export function useSelector< ReturnType = any >( dep: X, - selector: SelectorMethodType, + selectorMethod: SelectorMethodType, config: BaseAgileHookConfigInterface = {} ): ReturnType { config = defineConfig(config, { key: generateId(), agileInstance: null as any, componentId: undefined, + deps: [], }); const depsArray = extractRelevantObservers([dep]); @@ -70,7 +85,7 @@ export function useSelector< // (Destroys the type of the useAgile hook, // however the type can be adjusted in the useSelector hook) if (isValidObject(value, true)) { - return selector(value); + return selectorMethod(value); } return value; @@ -83,7 +98,7 @@ export function useSelector< let selectorWeakMap: SelectorWeakMapType | undefined = undefined; selectorWeakMap = new WeakMap(); for (const observer of observers) { - selectorWeakMap.set(observer, { methods: [selector] }); + selectorWeakMap.set(observer, { methods: [selectorMethod] }); } return { From 5907794cb3046482ef1136d312c0c82e417836df Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 31 Aug 2021 18:01:19 +0200 Subject: [PATCH 44/44] added feature to completely block the agile logcodemanager --- .../1000fields/bench/agilets/collection.tsx | 5 ++--- .../1000fields/bench/agilets/nestedState.tsx | 5 ++--- .../react/1000fields/bench/agilets/state.tsx | 5 ++--- .../computed/bench/agilets/autoTracking.tsx | 10 +++++++--- .../react/computed/bench/agilets/hardCoded.tsx | 10 +++++++--- .../benchmarks/react/counter/bench/agilets.tsx | 5 ++--- packages/core/src/logCodeManager.ts | 18 ++++++++++++++++-- 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx index 0d9482bb..22155afa 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx @@ -1,10 +1,9 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createCollection, shared } from '@agile-ts/core'; +import { createCollection, LogCodeManager, shared } from '@agile-ts/core'; import reactIntegration, { useAgile, useValue } from '@agile-ts/react'; -import { assignSharedAgileLoggerConfig } from '@agile-ts/logger'; -assignSharedAgileLoggerConfig({ active: false }); +LogCodeManager.setAllowLogging(false); shared.integrate(reactIntegration); export default function (target: HTMLElement, fieldsCount: number) { diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx index 313154df..213f2fe4 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx @@ -1,10 +1,9 @@ import * as React from 'react'; import * as ReactDom from 'react-dom'; -import { createState, shared, State } from '@agile-ts/core'; +import { createState, LogCodeManager, shared, State } from '@agile-ts/core'; import reactIntegration, { useAgile } from '@agile-ts/react'; -import { assignSharedAgileLoggerConfig } from '@agile-ts/logger'; -assignSharedAgileLoggerConfig({ active: false }); +LogCodeManager.setAllowLogging(false); shared.integrate(reactIntegration); export default function (target: HTMLElement, fieldsCount: number) { diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx index 6db059cd..6190049b 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx @@ -1,10 +1,9 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createState, shared } from '@agile-ts/core'; +import { createState, LogCodeManager, shared } from '@agile-ts/core'; import reactIntegration, { useAgile } from '@agile-ts/react'; -import { assignSharedAgileLoggerConfig } from '@agile-ts/logger'; -assignSharedAgileLoggerConfig({ active: false }); +LogCodeManager.setAllowLogging(false); shared.integrate(reactIntegration); export default function (target: HTMLElement, fieldsCount: number) { diff --git a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx index c87de1e6..d6f93317 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx @@ -1,10 +1,14 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createComputed, createState, shared } from '@agile-ts/core'; +import { + createComputed, + createState, + LogCodeManager, + shared, +} from '@agile-ts/core'; import reactIntegration, { useAgile } from '@agile-ts/react'; -import { assignSharedAgileLoggerConfig } from '@agile-ts/logger'; -assignSharedAgileLoggerConfig({ active: false }); +LogCodeManager.setAllowLogging(false); shared.integrate(reactIntegration); const COUNT = createState(0); diff --git a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx index 55e4c693..e206c482 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx @@ -1,10 +1,14 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createComputed, createState, shared } from '@agile-ts/core'; +import { + createComputed, + createState, + LogCodeManager, + shared, +} from '@agile-ts/core'; import reactIntegration, { useAgile } from '@agile-ts/react'; -import { assignSharedAgileLoggerConfig } from '@agile-ts/logger'; -assignSharedAgileLoggerConfig({ active: false }); +LogCodeManager.setAllowLogging(false); shared.integrate(reactIntegration); const COUNT = createState(0); diff --git a/benchmark/benchmarks/react/counter/bench/agilets.tsx b/benchmark/benchmarks/react/counter/bench/agilets.tsx index 1c9de3f7..9445ae0f 100644 --- a/benchmark/benchmarks/react/counter/bench/agilets.tsx +++ b/benchmark/benchmarks/react/counter/bench/agilets.tsx @@ -1,10 +1,9 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createState, shared } from '@agile-ts/core'; +import { createState, LogCodeManager, shared } from '@agile-ts/core'; import reactIntegration, { useAgile } from '@agile-ts/react'; -import { assignSharedAgileLoggerConfig } from '@agile-ts/logger'; -assignSharedAgileLoggerConfig({ active: false }); +LogCodeManager.setAllowLogging(false); shared.integrate(reactIntegration); const COUNT = createState(0); diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index d0fb84c9..70aa95e3 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -25,6 +25,7 @@ const logCodeTypes = { // --- // 00:00:|00| third digits are based on the Log Message (ascending counted) +let allowLogging = true; const niceLogCodeMessages = { // Agile '10:00:00': 'Created new AgileInstance.', @@ -174,6 +175,16 @@ const logCodeMessages: typeof niceLogCodeMessages = ? niceLogCodeMessages : ({} as any); +/** + * Specifies whether the LogCodeManager is allowed to print any logs. + * + * @internal + * @param logging - Whether the LogCodeManager is allowed to print any logs. + */ +function setAllowLogging(logging: boolean) { + allowLogging = logging; +} + /** * Returns the log message according to the specified log code. * @@ -213,7 +224,7 @@ function log>( ...data: any[] ): void { const logger = LogCodeManager.getLogger(); - if (logger != null && !logger.isActive) return; + if ((logger != null && !logger.isActive) || !allowLogging) return; const logType = logCodeTypes[logCode.substr(3, 2)]; if (typeof logType !== 'string') return; @@ -246,7 +257,7 @@ function logIfTags>( ...data: any[] ): void { const logger = LogCodeManager.getLogger(); - if (logger != null && !logger.isActive) return; + if ((logger != null && !logger.isActive) || !allowLogging) return; const logType = logCodeTypes[logCode.substr(3, 2)]; if (typeof logType !== 'string') return; @@ -298,6 +309,7 @@ if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { logCodeMessages: logCodeMessages, getLogger: loggerPackage.getLogger, logIfTags, + setAllowLogging, }; } else { tempLogCodeManager = { @@ -312,6 +324,7 @@ if (typeof process === 'object' && process.env.NODE_ENV !== 'production') { logIfTags: (tags, logCode, replacers) => { /* empty because logs with tags can't be that important */ }, + setAllowLogging, }; } @@ -344,4 +357,5 @@ export interface LogCodeManagerInterface { replacers?: any[], ...data: any[] ) => void; + setAllowLogging: (logging: boolean) => void; }