From eed02b9abcaa444e6c161edb0e214480dd12e64e Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 28 Jun 2021 18:35:10 +0200 Subject: [PATCH 01/19] added shared agile-instance --- packages/core/src/agile.ts | 24 +++-- packages/core/src/index.ts | 180 ++++++++++++++++++++++++++++++++++++- 2 files changed, 198 insertions(+), 6 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index adf0419c..d259b400 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -23,6 +23,12 @@ import { CreateComputedConfigInterface, ComputeFunctionType, } from './internal'; +import { + createCollection, + createComputed, + createState, + createStorage, +} from './index'; export class Agile { public config: AgileConfigInterface; @@ -50,6 +56,9 @@ export class Agile { // Identifier used to bind an Agile Instance globally static globalKey = '__agile__'; + // Shared Agile Instance that is used when no Agile Instance was specified + static shared = new Agile(); + /** * The Agile Class is the main Instance of AgileTs * and should be unique to your application. @@ -128,7 +137,7 @@ export class Agile { * @param config - Configuration object */ public createStorage(config: CreateStorageConfigInterface): Storage { - return new Storage(config); + return createStorage({ ...config, ...{ agileInstance: this } }); } /** @@ -150,7 +159,10 @@ export class Agile { initialValue: ValueType, config: StateConfigInterface = {} ): State { - return new State(this, initialValue, config); + return createState(initialValue, { + ...config, + ...{ agileInstance: this }, + }); } /** @@ -174,7 +186,7 @@ export class Agile { public createCollection( config?: CollectionConfig ): Collection { - return new Collection(this, config); + return createCollection(config, this); } /** @@ -232,12 +244,14 @@ export class Agile { if (Array.isArray(configOrDeps)) { _config = flatMerge(_config, { computedDeps: configOrDeps, + agileInstance: this, }); } else { - if (configOrDeps) _config = configOrDeps; + if (configOrDeps) + _config = { ...configOrDeps, ...{ agileInstance: this } }; } - return new Computed(this, computeFunction, _config); + return createComputed(computeFunction, _config); } /** diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6469f1fa..6cdc0b7e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,182 @@ -import { Agile } from './internal'; +import { + Agile, + State, + Storage, + defineConfig, + removeProperties, + flatMerge, + ComputeFunctionType, + Computed, + DependableAgileInstancesType, + CreateComputedConfigInterface, + StateConfigInterface, + CollectionConfig, + DefaultItem, + Collection, +} from './internal'; +import {} from '@agile-ts/utils'; +import { CreateStorageConfigInterface } from './internal'; + +/** + * Returns a newly created Storage. + * + * A Storage Class serves as an interface to external storages, + * such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or + * [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). + * + * It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) + * (like States or Collections) in nearly any external storage. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) + * + * @public + * @param config - Configuration object + */ +export function createStorage(config: CreateStorageConfigInterface): Storage { + return new Storage(config); +} + +/** + * 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 = {} +): State { + config = defineConfig(config, { + agileInstance: Agile.shared, + }); + return new State( + config.agileInstance as any, + initialValue, + removeProperties(config, ['agileInstance']) + ); +} + +/** + * Returns a newly created Collection. + * + * A Collection manages a reactive set of Information + * that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this set of Information. + * + * It is designed for arrays of data objects following the same pattern. + * + * Each of these data object must have a unique `primaryKey` to be correctly identified later. + * + * You can create as many global Collections as you need. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection) + * + * @public + * @param config - Configuration object + * @param agileInstance - Instance of Agile the Collection belongs to. + */ +export function createCollection( + config?: CollectionConfig, + agileInstance: Agile = Agile.shared +): Collection { + return new Collection(agileInstance, config); +} + +/** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a specified compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * 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) + * + * @public + * @param computeFunction - Function to compute the computed value. + * @param config - Configuration object + */ +export function createComputed( + computeFunction: ComputeFunctionType, + config?: CreateComputedConfigInterfaceWithAgile +): Computed; +/** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a specified compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * 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#createcomputed) + * + * @public + * @param computeFunction - Function to compute the computed value. + * @param deps - Hard-coded dependencies on which the Computed Class should depend. + */ +export function createComputed( + computeFunction: ComputeFunctionType, + deps?: Array +): Computed; +export function createComputed( + computeFunction: ComputeFunctionType, + configOrDeps?: + | CreateComputedConfigInterface + | Array +): Computed { + let _config: CreateComputedConfigInterfaceWithAgile = {}; + + if (Array.isArray(configOrDeps)) { + _config = flatMerge(_config, { + computedDeps: configOrDeps, + }); + } else { + if (configOrDeps) _config = configOrDeps; + } + + _config = defineConfig(_config, { + agileInstance: Agile.shared, + }); + + return new Computed( + _config.agileInstance as any, + computeFunction, + removeProperties(_config, ['agileInstance']) + ); +} + +export interface CreateAgileSubInstanceInterface { + /** + * Instance of Agile the Instance belongs to. + * @default Agile.shared + */ + agileInstance?: Agile; +} export * from './internal'; export default Agile; + +export interface CreateStateConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + StateConfigInterface {} + +export interface CreateComputedConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + CreateComputedConfigInterface {} From 7fa6a834af194b3175d0544726db8554cfbc1cf9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 28 Jun 2021 19:20:17 +0200 Subject: [PATCH 02/19] moved single functions into agile file --- packages/core/src/agile.ts | 168 ++++++++++++++++++++++++++++++++-- packages/core/src/index.ts | 180 +------------------------------------ 2 files changed, 163 insertions(+), 185 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index d259b400..2c56a5c7 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -22,13 +22,8 @@ import { DependableAgileInstancesType, CreateComputedConfigInterface, ComputeFunctionType, + removeProperties, } from './internal'; -import { - createCollection, - createComputed, - createState, - createStorage, -} from './index'; export class Agile { public config: AgileConfigInterface; @@ -317,6 +312,167 @@ export class Agile { } } +/** + * Returns a newly created Storage. + * + * A Storage Class serves as an interface to external storages, + * such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or + * [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). + * + * It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) + * (like States or Collections) in nearly any external storage. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) + * + * @public + * @param config - Configuration object + */ +export function createStorage(config: CreateStorageConfigInterface): Storage { + return new Storage(config); +} + +/** + * 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 = {} +): State { + config = defineConfig(config, { + agileInstance: Agile.shared, + }); + return new State( + config.agileInstance as any, + initialValue, + removeProperties(config, ['agileInstance']) + ); +} + +/** + * Returns a newly created Collection. + * + * A Collection manages a reactive set of Information + * that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this set of Information. + * + * It is designed for arrays of data objects following the same pattern. + * + * Each of these data object must have a unique `primaryKey` to be correctly identified later. + * + * You can create as many global Collections as you need. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection) + * + * @public + * @param config - Configuration object + * @param agileInstance - Instance of Agile the Collection belongs to. + */ +export function createCollection( + config?: CollectionConfig, + agileInstance: Agile = Agile.shared +): Collection { + return new Collection(agileInstance, config); +} + +/** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a specified compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * 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) + * + * @public + * @param computeFunction - Function to compute the computed value. + * @param config - Configuration object + */ +export function createComputed( + computeFunction: ComputeFunctionType, + config?: CreateComputedConfigInterfaceWithAgile +): Computed; +/** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a specified compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * 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#createcomputed) + * + * @public + * @param computeFunction - Function to compute the computed value. + * @param deps - Hard-coded dependencies on which the Computed Class should depend. + */ +export function createComputed( + computeFunction: ComputeFunctionType, + deps?: Array +): Computed; +export function createComputed( + computeFunction: ComputeFunctionType, + configOrDeps?: + | CreateComputedConfigInterface + | Array +): Computed { + let _config: CreateComputedConfigInterfaceWithAgile = {}; + + if (Array.isArray(configOrDeps)) { + _config = flatMerge(_config, { + computedDeps: configOrDeps, + }); + } else { + if (configOrDeps) _config = configOrDeps; + } + + _config = defineConfig(_config, { + agileInstance: Agile.shared, + }); + + return new Computed( + _config.agileInstance as any, + computeFunction, + removeProperties(_config, ['agileInstance']) + ); +} + +export interface CreateAgileSubInstanceInterface { + /** + * Instance of Agile the Instance belongs to. + * @default Agile.shared + */ + agileInstance?: Agile; +} + +export interface CreateStateConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + StateConfigInterface {} + +export interface CreateComputedConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + CreateComputedConfigInterface {} + export interface CreateAgileConfigInterface { /** * Configures the logging behaviour of AgileTs. diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6cdc0b7e..6469f1fa 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,182 +1,4 @@ -import { - Agile, - State, - Storage, - defineConfig, - removeProperties, - flatMerge, - ComputeFunctionType, - Computed, - DependableAgileInstancesType, - CreateComputedConfigInterface, - StateConfigInterface, - CollectionConfig, - DefaultItem, - Collection, -} from './internal'; -import {} from '@agile-ts/utils'; -import { CreateStorageConfigInterface } from './internal'; - -/** - * Returns a newly created Storage. - * - * A Storage Class serves as an interface to external storages, - * such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or - * [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). - * - * It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) - * (like States or Collections) in nearly any external storage. - * - * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) - * - * @public - * @param config - Configuration object - */ -export function createStorage(config: CreateStorageConfigInterface): Storage { - return new Storage(config); -} - -/** - * 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 = {} -): State { - config = defineConfig(config, { - agileInstance: Agile.shared, - }); - return new State( - config.agileInstance as any, - initialValue, - removeProperties(config, ['agileInstance']) - ); -} - -/** - * Returns a newly created Collection. - * - * A Collection manages a reactive set of Information - * that we need to remember globally at a later point in time. - * While providing a toolkit to use and mutate this set of Information. - * - * It is designed for arrays of data objects following the same pattern. - * - * Each of these data object must have a unique `primaryKey` to be correctly identified later. - * - * You can create as many global Collections as you need. - * - * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection) - * - * @public - * @param config - Configuration object - * @param agileInstance - Instance of Agile the Collection belongs to. - */ -export function createCollection( - config?: CollectionConfig, - agileInstance: Agile = Agile.shared -): Collection { - return new Collection(agileInstance, config); -} - -/** - * Returns a newly created Computed. - * - * A Computed is an extension of the State Class - * that computes its value based on a specified compute function. - * - * The computed value will be cached to avoid unnecessary recomputes - * and is only recomputed when one of its direct dependencies changes. - * - * 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) - * - * @public - * @param computeFunction - Function to compute the computed value. - * @param config - Configuration object - */ -export function createComputed( - computeFunction: ComputeFunctionType, - config?: CreateComputedConfigInterfaceWithAgile -): Computed; -/** - * Returns a newly created Computed. - * - * A Computed is an extension of the State Class - * that computes its value based on a specified compute function. - * - * The computed value will be cached to avoid unnecessary recomputes - * and is only recomputed when one of its direct dependencies changes. - * - * 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#createcomputed) - * - * @public - * @param computeFunction - Function to compute the computed value. - * @param deps - Hard-coded dependencies on which the Computed Class should depend. - */ -export function createComputed( - computeFunction: ComputeFunctionType, - deps?: Array -): Computed; -export function createComputed( - computeFunction: ComputeFunctionType, - configOrDeps?: - | CreateComputedConfigInterface - | Array -): Computed { - let _config: CreateComputedConfigInterfaceWithAgile = {}; - - if (Array.isArray(configOrDeps)) { - _config = flatMerge(_config, { - computedDeps: configOrDeps, - }); - } else { - if (configOrDeps) _config = configOrDeps; - } - - _config = defineConfig(_config, { - agileInstance: Agile.shared, - }); - - return new Computed( - _config.agileInstance as any, - computeFunction, - removeProperties(_config, ['agileInstance']) - ); -} - -export interface CreateAgileSubInstanceInterface { - /** - * Instance of Agile the Instance belongs to. - * @default Agile.shared - */ - agileInstance?: Agile; -} +import { Agile } from './internal'; export * from './internal'; export default Agile; - -export interface CreateStateConfigInterfaceWithAgile - extends CreateAgileSubInstanceInterface, - StateConfigInterface {} - -export interface CreateComputedConfigInterfaceWithAgile - extends CreateAgileSubInstanceInterface, - CreateComputedConfigInterface {} From ccabaf0abf18cb127c8b7dec9f78d005a22301a6 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 28 Jun 2021 20:22:45 +0200 Subject: [PATCH 03/19] moved single functions into agile file --- README.md | 86 ++++++++++++++++++++--------------- packages/core/src/agile.ts | 10 ++-- packages/core/src/internal.ts | 11 +++-- packages/core/src/shared.ts | 6 +++ 4 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 packages/core/src/shared.ts diff --git a/README.md b/README.md index 3a1ae771..fe934d68 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ AgileTs -> Global, simple, spacy State and Logic Framework +> Global State and Logic Framework
@@ -45,15 +45,17 @@ // 1️⃣ Create Instance of AgileTs const App = new Agile(); -// 2️⃣ Create State with help of before defined Agile Instance +// 2️⃣ Create State with the initial value "Hello Friend!" const MY_FIRST_STATE = App.createState("Hello Friend!"); // -- MyComponent.whatever ------------------------------------------ -// 3️⃣ Bind initialized State to desired UI-Component -// And wolla, it's reactive. Everytime the State mutates the Component rerenders -const myFirstState = useAgile(MY_FIRST_STATE); // Returns value of State ("Hello Friend!") +// 3️⃣ Bind initialized State to the desired UI-Component. +// And wolla, the Component is reactive. +// Everytime the State mutates the Component re-renders. +const myFirstState = useAgile(MY_FIRST_STATE); +console.log(myFirstState); // Returns "Hello Friend!" ``` Want to learn more? Check out our [Quick Start Guides](https://agile-ts.org/docs/Installation.md). @@ -75,53 +77,49 @@ More examples can be found in the [Example Section](https://agile-ts.org/docs/ex
Why should I use AgileTs? -AgileTs is a global, simple, well-tested State Management Framework implemented in Typescript. +AgileTs is a global State and Logic Framework implemented in Typescript. It offers a reimagined API that focuses on **developer experience** -and allows you to **easily** manage your States. -Besides States, AgileTs offers some other powerful APIs that make your life easier. +and allows you to **easily** and **flexible** manage your application States globally. +Besides [States](https://agile-ts.org/docs/core/state), +AgileTs offers some other powerful APIs that make your life easier, +such as [Collections](https://agile-ts.org/docs/core/collection) +or [Computed States](https://agile-ts.org/docs/core/computed). The philosophy behind AgileTs is simple: ### 🚅 Straightforward Write minimalistic, boilerplate-free code that captures your intent. ```ts -const MY_STATE = App.createState('frank'); // Create State -MY_STATE.set('jeff'); // Update State value -MY_STATE.undo(); // Undo latest State value change -MY_STATE.is({hello: "jeff"}); // Check if State has the value '{hello: "jeff"}' -MY_STATE.watch((value) => {console.log(value);}); // Watch on State changes -``` +// Create State with inital value 'frank' +const MY_STATE = createState('frank'); -**Some more straightforward syntax examples:** +// Update State value from 'frank' to 'jeff' +MY_STATE.set('jeff'); -- Store State in any Storage, like the [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp) - ```ts - MY_STATE.persist("storage-key"); - ``` -- Create a reactive Array of States - ```ts - const MY_COLLECTION = App.createCollection(); - MY_COLLECTION.collect({id: 1, name: "Frank"}); - MY_COLLECTION.collect({id: 2, name: "Dieter"}); - MY_COLLECTION.update(1, {name: "Jeff"}); - ``` -- Compute State depending on other States - ```ts - const MY_INTRODUCTION = App.createComputed(() => { - return `Hello I am '${MY_NAME.vale}' and I use ${MY_STATE_MANAGER.value} for State Management.`; - }); - ``` +// Undo latest State value change +MY_STATE.undo(); + +// Reset State value to its initial value +MY_STATE.reset(); + +// Permanently store State value in an external Storage +MY_STATE.persist("storage-key"); +``` ### 🤸‍ Flexible -- Works in nearly any UI-Layer. Check [here](https://agile-ts.org/docs/Frameworks) if your preferred Framework is supported too. -- Surly behaves with the workflow which suits you best. No need for _reducers_, _actions_, .. -- Has **0** external dependencies +- Works in nearly any UI-Framework (currently supported are React, React-Native and Vue). +- Surly behaves with the workflow that suits you best. + No need for _reducers_, _actions_, .. +- Has **0** external dependencies. ### ⛳️ Centralize -AgileTs is designed to take all business logic out of UI-Components and put them in a central place, often called `core`. -The benefit of keeping logic separate to UI-Components is to make your code more decoupled, portable, scalable, and above all, easily testable. +AgileTs is designed to take all business logic out of the UI-Components +and put them in a central place, often called `core`. +The benefit of keeping logic separate to UI-Components, +is to make your code more decoupled, portable, scalable, +and above all, easily testable. ### 🎯 Easy to Use @@ -129,6 +127,20 @@ Learn the powerful tools of AgileTs in a short amount of time. An excellent plac our [Quick Start Guides](https://agile-ts.org/docs/Installation), or if you don't like to follow any tutorials, you can jump straight into our [Example](https://agile-ts.org/docs/examples/Introduction) Section. +### 👾 Extra Utilities + +The AgileTs package includes some other powerful APIs, +which are included in the `core` package or have to be installed separately. + +#### Collection +TODO + +#### Computed +TODO + +#### Multieditor [WIP] +TODO +
diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 2c56a5c7..11792275 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -23,6 +23,7 @@ import { CreateComputedConfigInterface, ComputeFunctionType, removeProperties, + shared, } from './internal'; export class Agile { @@ -51,9 +52,6 @@ export class Agile { // Identifier used to bind an Agile Instance globally static globalKey = '__agile__'; - // Shared Agile Instance that is used when no Agile Instance was specified - static shared = new Agile(); - /** * The Agile Class is the main Instance of AgileTs * and should be unique to your application. @@ -351,7 +349,7 @@ export function createState( config: CreateStateConfigInterfaceWithAgile = {} ): State { config = defineConfig(config, { - agileInstance: Agile.shared, + agileInstance: shared, }); return new State( config.agileInstance as any, @@ -381,7 +379,7 @@ export function createState( */ export function createCollection( config?: CollectionConfig, - agileInstance: Agile = Agile.shared + agileInstance: Agile = shared ): Collection { return new Collection(agileInstance, config); } @@ -447,7 +445,7 @@ export function createComputed( } _config = defineConfig(_config, { - agileInstance: Agile.shared, + agileInstance: shared, }); return new Computed( diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 6e403e5a..a8f5c217 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -4,14 +4,14 @@ // !! All internal Agile modules must be imported from here!! -// Logger -export * from '@agile-ts/logger'; -export * from './logCodeManager'; - // Utils export * from './utils'; export * from '@agile-ts/utils'; +// Logger +export * from '@agile-ts/logger'; +export * from './logCodeManager'; + // Agile export * from './agile'; @@ -50,3 +50,6 @@ export * from './collection/collection.persistent'; // Integrations export * from './integrations'; export * from './integrations/integration'; + +// Shared +export * from './shared'; diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts new file mode 100644 index 00000000..7c773d3c --- /dev/null +++ b/packages/core/src/shared.ts @@ -0,0 +1,6 @@ +import { Agile } from './agile'; + +// Shared Agile Instance that is used when no Agile Instance was specified +// eslint-disable-next-line prefer-const +export let shared = new Agile(); +// if (!process.env.DISABLE_SHARED_AGILE_INSTANCE) shared = new Agile(); From 973079c6e0af1754b7f1f29c8fc81abbb6dcb3ac Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 29 Jun 2021 06:43:51 +0200 Subject: [PATCH 04/19] added external integration registered Listener --- packages/core/src/agile.ts | 31 ++++++++++++++++---- packages/core/src/integrations/index.ts | 39 +++++++++++++++++++++---- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 11792275..5e94b0d1 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -38,15 +38,13 @@ export class Agile { // Integrations (UI-Frameworks) that are integrated into AgileTs public integrations: Integrations; - // External added Integrations that are to integrate into AgileTs when it is instantiated - static initialIntegrations: Integration[] = []; // Static AgileTs Logger with the default config // (-> is overwritten by the last created Agile Instance) static logger = new Logger({ prefix: 'Agile', active: true, - level: Logger.level.WARN, + level: Logger.level.SUCCESS, }); // Identifier used to bind an Agile Instance globally @@ -88,7 +86,7 @@ export class Agile { config.logConfig = defineConfig(config.logConfig, { prefix: 'Agile', active: true, - level: Logger.level.WARN, + level: Logger.level.SUCCESS, canUseCustomStyles: true, allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], }); @@ -102,8 +100,13 @@ export class Agile { localStorage: config.localStorage, }); + // Setup listener to be notified when a external registered Integration was added + Integrations.onRegisteredExternalIntegration((integration) => { + this.integrate(integration); + }); + // Assign customized Logger config to the static Logger - Agile.logger = new Logger(config.logConfig); + this.configureLogger(config.logConfig); LogCodeManager.log('10:00:00', [], this, Agile.logger); @@ -114,6 +117,24 @@ export class Agile { if (!globalBind(Agile.globalKey, this)) LogCodeManager.log('10:02:00'); } + /** + * Configures the logging behaviour of AgileTs. + * + * @public + * @param config - Configuration object + */ + public configureLogger(config: CreateLoggerConfigInterface = {}): this { + config = defineConfig(config, { + prefix: 'Agile', + active: true, + level: Logger.level.SUCCESS, + canUseCustomStyles: true, + allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], + }); + Agile.logger = new Logger(config); + return this; + } + /** * Returns a newly created Storage. * diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 038166db..6bc8661a 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -1,5 +1,9 @@ import { Agile, Integration, LogCodeManager } from '../internal'; +const registeredExternalIntegrationsCallbacks: (( + integration: Integration +) => void)[] = []; + export class Integrations { // Agile Instance the Integrations belongs to public agileInstance: () => Agile; @@ -7,6 +11,34 @@ export class Integrations { // Registered Integrations public integrations: Set = new Set(); + // External added Integrations that are to integrate into AgileTs, + // with a proxy wrapped around to listen on external added Integrations. + static initialIntegrations: Integration[] = new Proxy([], { + set: (target, property, value) => { + target[property] = value; + + // Executed external registered Integrations callbacks + if (value instanceof Integration) + registeredExternalIntegrationsCallbacks.forEach((callback) => + callback(value) + ); + + return true; + }, + }); + + /** + * Fires on each external added Integration. + * + * @public + * @param callback - Callback to be fired when an Integration was added externally. + */ + static onRegisteredExternalIntegration( + callback: (integration: Integration) => void + ): void { + registeredExternalIntegrationsCallbacks.push(callback); + } + /** * The Integrations Class manages all Integrations for an Agile Instance * and provides an interface to easily update @@ -17,11 +49,6 @@ export class Integrations { */ constructor(agileInstance: Agile) { this.agileInstance = () => agileInstance; - - // Integrate initial Integrations which were statically set externally - Agile.initialIntegrations.forEach((integration) => - this.integrate(integration) - ); } /** @@ -33,7 +60,7 @@ export class Integrations { */ public async integrate(integration: Integration): Promise { // Check if Integration is valid - if (!integration._key) { + if (integration._key == null) { LogCodeManager.log('18:03:00', [integration._key], integration); return false; } From 6cfeb21788237eed8c110a41a14f256fa29902f4 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 29 Jun 2021 08:21:06 +0200 Subject: [PATCH 05/19] fixed react and vue integration --- packages/react/src/react.integration.ts | 4 ++-- packages/vue/src/vue.integration.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/src/react.integration.ts b/packages/react/src/react.integration.ts index a0bf8774..e0176826 100644 --- a/packages/react/src/react.integration.ts +++ b/packages/react/src/react.integration.ts @@ -1,4 +1,4 @@ -import { Agile, flatMerge, Integration } from '@agile-ts/core'; +import { flatMerge, Integration, Integrations } from '@agile-ts/core'; import { AgileReactComponent } from './hocs/AgileHOC'; import React from 'react'; @@ -24,6 +24,6 @@ const reactIntegration = new Integration({ } }, }); -Agile.initialIntegrations.push(reactIntegration); +Integrations.initialIntegration.push(reactIntegration); export default reactIntegration; diff --git a/packages/vue/src/vue.integration.ts b/packages/vue/src/vue.integration.ts index cf2268b9..1e38344f 100644 --- a/packages/vue/src/vue.integration.ts +++ b/packages/vue/src/vue.integration.ts @@ -1,4 +1,4 @@ -import Agile, { Integration } from '@agile-ts/core'; +import Agile, { Integration, Integrations } from '@agile-ts/core'; import Vue from 'vue'; import { bindAgileInstances, DepsType } from './bindAgileInstances'; @@ -80,6 +80,6 @@ const vueIntegration = new Integration({ return Promise.resolve(true); }, }); -Agile.initialIntegrations.push(vueIntegration); +Integrations.initialIntegration.push(vueIntegration); export default vueIntegration; From 8028c75e7eeb05de2b283bbcc6a88f9571f67ba3 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 29 Jun 2021 20:37:21 +0200 Subject: [PATCH 06/19] added runsOnServer property --- packages/core/src/agile.ts | 7 ++++++- packages/core/src/internal.ts | 5 +++++ packages/react/src/hooks/useIsomorphicLayoutEffect.ts | 10 ++++------ packages/react/src/react.integration.ts | 2 +- packages/vue/src/vue.integration.ts | 2 +- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 5e94b0d1..5bf7bf5b 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -24,6 +24,7 @@ import { ComputeFunctionType, removeProperties, shared, + runsOnServer, } from './internal'; export class Agile { @@ -113,7 +114,7 @@ export class Agile { // Create a global instance of the Agile Instance. // Why? 'getAgileInstance()' returns the global Agile Instance // if it couldn't find any Agile Instance in the specified Instance. - if (config.bindGlobal) + if (config.bindGlobal && !runsOnServer) if (!globalBind(Agile.globalKey, this)) LogCodeManager.log('10:02:00'); } @@ -124,6 +125,10 @@ export class Agile { * @param config - Configuration object */ public configureLogger(config: CreateLoggerConfigInterface = {}): this { + if (runsOnServer) { + Agile.logger = new Logger({ active: false }); + return this; + } config = defineConfig(config, { prefix: 'Agile', active: true, diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index a8f5c217..2c446a7d 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -53,3 +53,8 @@ export * from './integrations/integration'; // Shared export * from './shared'; + +export const runsOnServer = + typeof window !== 'undefined' && + typeof window.document !== 'undefined' && + typeof window.document.createElement !== 'undefined'; diff --git a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts index 9e397c06..e87a9732 100644 --- a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts +++ b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts @@ -1,4 +1,5 @@ import { useEffect, useLayoutEffect } from 'react'; +import { runsOnServer } from '@agile-ts/core'; // React currently throws a warning when using useLayoutEffect on the server. // To get around it, we can conditionally useEffect on the server (no-op) and @@ -9,9 +10,6 @@ import { useEffect, useLayoutEffect } from 'react'; // is created synchronously, otherwise a store update may occur before the // subscription is created and an inconsistent state may be observed -export const useIsomorphicLayoutEffect = - typeof window !== 'undefined' && - typeof window.document !== 'undefined' && - typeof window.document.createElement !== 'undefined' - ? useLayoutEffect - : useEffect; +export const useIsomorphicLayoutEffect = runsOnServer + ? useLayoutEffect + : useEffect; diff --git a/packages/react/src/react.integration.ts b/packages/react/src/react.integration.ts index e0176826..78a92f5a 100644 --- a/packages/react/src/react.integration.ts +++ b/packages/react/src/react.integration.ts @@ -24,6 +24,6 @@ const reactIntegration = new Integration({ } }, }); -Integrations.initialIntegration.push(reactIntegration); +Integrations.initialIntegrations.push(reactIntegration); export default reactIntegration; diff --git a/packages/vue/src/vue.integration.ts b/packages/vue/src/vue.integration.ts index 1e38344f..deb214b5 100644 --- a/packages/vue/src/vue.integration.ts +++ b/packages/vue/src/vue.integration.ts @@ -80,6 +80,6 @@ const vueIntegration = new Integration({ return Promise.resolve(true); }, }); -Integrations.initialIntegration.push(vueIntegration); +Integrations.initialIntegrations.push(vueIntegration); export default vueIntegration; From 35c3fcc19f06ceee7338df013ffb900ecdada7b9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 30 Jun 2021 08:15:03 +0200 Subject: [PATCH 07/19] tried to fix mocking in agile --- packages/core/src/internal.ts | 8 ++--- packages/core/src/shared.ts | 1 - packages/core/tests/unit/agile.test.ts | 42 +++++++++++++++++++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 2c446a7d..a4218233 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -15,6 +15,10 @@ export * from './logCodeManager'; // Agile export * from './agile'; +// Integrations +export * from './integrations'; +export * from './integrations/integration'; + // Runtime export * from './runtime'; export * from './runtime/observer'; @@ -47,10 +51,6 @@ export * from './collection/item'; export * from './collection/selector'; export * from './collection/collection.persistent'; -// Integrations -export * from './integrations'; -export * from './integrations/integration'; - // Shared export * from './shared'; diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index 7c773d3c..c2507536 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -3,4 +3,3 @@ import { Agile } from './agile'; // Shared Agile Instance that is used when no Agile Instance was specified // eslint-disable-next-line prefer-const export let shared = new Agile(); -// if (!process.env.DISABLE_SHARED_AGILE_INSTANCE) shared = new Agile(); diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index f1bdec49..a0cca53a 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -13,13 +13,42 @@ import { import testIntegration from '../helper/test.integration'; import { LogMock } from '../helper/logMock'; -jest.mock('../../src/runtime/index'); -jest.mock('../../src/runtime/subscription/sub.controller'); -jest.mock('../../src/storages/index'); -jest.mock('../../src/integrations/index'); +// https://github.com/facebook/jest/issues/5023 +// https://medium.com/@masonlgoetz/mock-static-class-methods-in-jest-1ceda967b47f +// https://gist.github.com/virgs/d9c50e878fc69832c01f8085f2953f12 +jest.mock('../../src/integrations', () => { + const mockedInstances = { + Integrations: jest.fn().mockImplementation(() => { + return { + integrate: jest.fn(), + }; + }), + }; + // @ts-ignore + mockedInstances.Integrations.onRegisteredExternalIntegration = jest.fn(); + return mockedInstances; +}); +jest.mock('../../src/runtime', () => { + return { + Runtime: jest.fn(), + }; +}); +jest.mock('../../src/runtime/subscription/sub.controller', () => { + return { + SubController: jest.fn(), + }; +}); +jest.mock('../../src/storages', () => { + return { + Storages: jest.fn(), + }; +}); jest.mock('../../src/storages/storage'); -jest.mock('../../src/collection/index'); -jest.mock('../../src/computed/index'); +jest.mock('../../src/collection'); +jest.mock('../../src/computed'); +// Can't mock State because mocks get instantiated before everything else +// -> I got the good old not loaded Object error https://github.com/kentcdodds/how-jest-mocking-works +// jest.mock("../../src/state/index"); /* Can't mock Logger because I somehow can't overwrite a static get method jest.mock("../../src/logger/index", () => { return class { @@ -37,7 +66,6 @@ jest.mock("../../src/logger/index", () => { }; }); */ -// jest.mock("../../src/state/index"); // Can't mock State because mocks get instantiated before everything else -> I got the good old not loaded Object error https://github.com/kentcdodds/how-jest-mocking-works describe('Agile Tests', () => { const RuntimeMock = Runtime as jest.MockedClass; From a6f7d87cd7529649508ccbcbc0805870e7802f28 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 1 Jul 2021 08:22:04 +0200 Subject: [PATCH 08/19] updated readme --- README.md | 83 ++++++++++++++++++++++++------------------------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index fe934d68..da0c7031 100644 --- a/README.md +++ b/README.md @@ -42,22 +42,20 @@ ```tsx // -- core.js ------------------------------------------ -// 1️⃣ Create Instance of AgileTs -const App = new Agile(); - -// 2️⃣ Create State with the initial value "Hello Friend!" -const MY_FIRST_STATE = App.createState("Hello Friend!"); +// 1️⃣ Create State with the initial value "Hello Friend!" +const MY_FIRST_STATE = createState("Hello Friend!"); // -- MyComponent.whatever ------------------------------------------ -// 3️⃣ Bind initialized State to the desired UI-Component. +// 2️⃣ Bind initialized State to the desired UI-Component. // And wolla, the Component is reactive. // Everytime the State mutates the Component re-renders. const myFirstState = useAgile(MY_FIRST_STATE); console.log(myFirstState); // Returns "Hello Friend!" ``` -Want to learn more? Check out our [Quick Start Guides](https://agile-ts.org/docs/Installation.md). +Want to learn how to implement AgileTs in your preferred UI-Framework? +Check out our [Quick Start Guides](https://agile-ts.org/docs/Installation.md). ### ⛳️ Sandbox Test AgileTs yourself in a [codesandbox](https://codesandbox.io/s/agilets-first-state-f12cz). @@ -79,11 +77,11 @@ More examples can be found in the [Example Section](https://agile-ts.org/docs/ex AgileTs is a global State and Logic Framework implemented in Typescript. It offers a reimagined API that focuses on **developer experience** -and allows you to **easily** and **flexible** manage your application States globally. +and allows you to **easily** and **flexible** manage your application States. Besides [States](https://agile-ts.org/docs/core/state), AgileTs offers some other powerful APIs that make your life easier, such as [Collections](https://agile-ts.org/docs/core/collection) -or [Computed States](https://agile-ts.org/docs/core/computed). +and [Computed States](https://agile-ts.org/docs/core/computed). The philosophy behind AgileTs is simple: ### 🚅 Straightforward @@ -123,23 +121,10 @@ and above all, easily testable. ### 🎯 Easy to Use -Learn the powerful tools of AgileTs in a short amount of time. An excellent place to start are -our [Quick Start Guides](https://agile-ts.org/docs/Installation), or if you don't like to follow any tutorials, -you can jump straight into our [Example](https://agile-ts.org/docs/examples/Introduction) Section. - -### 👾 Extra Utilities - -The AgileTs package includes some other powerful APIs, -which are included in the `core` package or have to be installed separately. - -#### Collection -TODO - -#### Computed -TODO - -#### Multieditor [WIP] -TODO +Learn the powerful tools of AgileTs in a short amount of time. +An excellent place to start are our [Quick Start Guides](https://agile-ts.org/docs/Installation), +or if you don't like to follow any tutorials, +you can jump straight into our [Example Section](https://agile-ts.org/docs/examples/Introduction).
@@ -148,17 +133,18 @@ TODO
Installation -In order to properly use AgileTs, in a UI-Framework, we need to install **two** packages. +In order to use AgileTs in a UI-Framework, we need to install two packages. -- The [`core`](https://agile-ts.org/docs/core) package, which contains the State Management Logic of AgileTs +- The [`core`](https://agile-ts.org/docs/core) package contains the State Management Logic of AgileTs and therefore offers powerful classes such as the [`State Class`](https://agile-ts.org/docs/core/state). ``` npm install @agile-ts/core ``` -- And on the other hand, a _fitting Integration_ for your preferred UI-Framework. - In my case, the [React Integration](https://www.npmjs.com/package/@agile-ts/react). - Check [here](https://agile-ts.org/docs/frameworks) if your desired Framework is supported, too. +- A _fitting Integration_ for the UI-Framework of your choice, on the other hand, + is an interface to the actual UI and provides useful functionalities + to bind States to UI-Components for reactivity. + I prefer React, so let's go with the [React Integration](https://www.npmjs.com/package/@agile-ts/react) for now. ``` npm install @agile-ts/react ``` @@ -170,10 +156,11 @@ In order to properly use AgileTs, in a UI-Framework, we need to install **two**
Documentation -Sounds AgileTs interesting to you? -Checkout our **[documentation](https://agile-ts.org/docs/introduction)**, to learn more. -And I promise you. You will be able to use AgileTs in no time. -If you have any further questions, don't hesitate to join our [Community Discord](https://discord.gg/T9GzreAwPH). +Does AgileTs sound interesting to you? +Take a look at our **[documentation](https://agile-ts.org/docs/introduction)**, +to learn more about its functionalities and how it works exactly. +If you have any further questions, +don't hesitate to join our [Community Discord](https://discord.gg/T9GzreAwPH).
@@ -196,17 +183,17 @@ To find out more about contributing, check out the [CONTRIBUTING.md](https://git
Packages of Agile -| Name | Latest Version | Description | -| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | -| [@agile-ts/core](/packages/core) | [![badge](https://img.shields.io/npm/v/@agile-ts/core.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/core) | State Manager | -| [@agile-ts/react](/packages/react) | [![badge](https://img.shields.io/npm/v/@agile-ts/react.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/react) | React Integration | -| [@agile-ts/vue](/packages/vue) | [![badge](https://img.shields.io/npm/v/@agile-ts/vue.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/vue) | Vue Integration | -| [@agile-ts/api](/packages/api) | [![badge](https://img.shields.io/npm/v/@agile-ts/api.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/api) | Promise based API | -| [@agile-ts/multieditor](/packages/multieditor) | [![badge](https://img.shields.io/npm/v/@agile-ts/multieditor.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/multieditor) | Simple Form Manager | -| [@agile-ts/event](/packages/event) | [![badge](https://img.shields.io/npm/v/@agile-ts/event.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/event) | Handy class for emitting UI Events | -| [@agile-ts/logger](/packages/logger) | [![badge](https://img.shields.io/npm/v/@agile-ts/logger.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/logger) | Manages the logging of AgileTs | -| [@agile-ts/utils](/packages/utils) | [![badge](https://img.shields.io/npm/v/@agile-ts/utils.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/utils) | Util functions of AgileTs | -| [@agile-ts/proxytree](/packages/proxytree) | [![badge](https://img.shields.io/npm/v/@agile-ts/proxytree.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/proxytree) | Create Proxy Tree | +| Name | Latest Version | Description | +| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| [@agile-ts/core](/packages/core) | [![badge](https://img.shields.io/npm/v/@agile-ts/core.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/core) | State Manager Logic | +| [@agile-ts/react](/packages/react) | [![badge](https://img.shields.io/npm/v/@agile-ts/react.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/react) | React Integration | +| [@agile-ts/vue](/packages/vue) | [![badge](https://img.shields.io/npm/v/@agile-ts/vue.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/vue) | Vue Integration | +| [@agile-ts/api](/packages/api) | [![badge](https://img.shields.io/npm/v/@agile-ts/api.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/api) | Promise based API | +| [@agile-ts/multieditor](/packages/multieditor) | [![badge](https://img.shields.io/npm/v/@agile-ts/multieditor.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/multieditor) | Simple Form Manager | +| [@agile-ts/event](/packages/event) | [![badge](https://img.shields.io/npm/v/@agile-ts/event.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/event) | Handy class for emitting UI Events | +| [@agile-ts/logger](/packages/logger) | [![badge](https://img.shields.io/npm/v/@agile-ts/logger.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/logger) | Logging API of AgileTs | +| [@agile-ts/utils](/packages/utils) | [![badge](https://img.shields.io/npm/v/@agile-ts/utils.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/utils) | Utilities of AgileTs | +| [@agile-ts/proxytree](/packages/proxytree) | [![badge](https://img.shields.io/npm/v/@agile-ts/proxytree.svg?style=flat-square)](https://www.npmjs.com/package/@agile-ts/proxytree) | Proxy Tree for tracking accessed properties | |
@@ -214,4 +201,6 @@ To find out more about contributing, check out the [CONTRIBUTING.md](https://git
Credits -AgileTs is inspired by [MVVM Frameworks](https://de.wikipedia.org/wiki/Model_View_ViewModel) like [MobX](https://mobx.js.org/README.html) and [PulseJs](https://github.com/pulse-framework/pulse). +AgileTs is inspired by [MVVM Frameworks](https://de.wikipedia.org/wiki/Model_View_ViewModel) +like [MobX](https://mobx.js.org/README.html) and [PulseJs](https://github.com/pulse-framework/pulse). +For the API, we were mainly inspired by [Svelte](https://svelte.dev/). From 4ca6166af3fed8f48325aeae20ccf09c71d4dc83 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 1 Jul 2021 20:01:29 +0200 Subject: [PATCH 09/19] fixed mocks in agile tests --- packages/core/src/agile.ts | 15 ++-- packages/core/src/internal.ts | 5 -- packages/core/src/utils.ts | 13 +++ packages/core/tests/unit/agile.test.ts | 111 ++++++++++++++----------- 4 files changed, 80 insertions(+), 64 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 5bf7bf5b..007430ef 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -84,13 +84,6 @@ export class Agile { logConfig: {}, bindGlobal: false, }); - config.logConfig = defineConfig(config.logConfig, { - prefix: 'Agile', - active: true, - level: Logger.level.SUCCESS, - canUseCustomStyles: true, - allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], - }); this.config = { waitForMount: config.waitForMount as any, }; @@ -114,8 +107,10 @@ export class Agile { // Create a global instance of the Agile Instance. // Why? 'getAgileInstance()' returns the global Agile Instance // if it couldn't find any Agile Instance in the specified Instance. - if (config.bindGlobal && !runsOnServer) - if (!globalBind(Agile.globalKey, this)) LogCodeManager.log('10:02:00'); + if (config.bindGlobal) + if (!globalBind(Agile.globalKey, this)) { + LogCodeManager.log('10:02:00'); + } } /** @@ -125,7 +120,7 @@ export class Agile { * @param config - Configuration object */ public configureLogger(config: CreateLoggerConfigInterface = {}): this { - if (runsOnServer) { + if (runsOnServer()) { Agile.logger = new Logger({ active: false }); return this; } diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index a4218233..ad6631d3 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -53,8 +53,3 @@ export * from './collection/collection.persistent'; // Shared export * from './shared'; - -export const runsOnServer = - typeof window !== 'undefined' && - typeof window.document !== 'undefined' && - typeof window.document.createElement !== 'undefined'; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 56af1728..1220be5f 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -254,3 +254,16 @@ export function globalBind( } return false; } + +/** + * Returns a boolean indicating whether AgileTs is currently running on a server. + * + * @public + */ +export const runsOnServer = (): boolean => { + return ( + typeof window === 'undefined' || + typeof window.document === 'undefined' || + typeof window.document.createElement === 'undefined' + ); +}; diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index a0cca53a..1fe22ab9 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -12,6 +12,7 @@ import { } from '../../src'; import testIntegration from '../helper/test.integration'; import { LogMock } from '../helper/logMock'; +import * as Utils from '../../src/utils'; // https://github.com/facebook/jest/issues/5023 // https://medium.com/@masonlgoetz/mock-static-class-methods-in-jest-1ceda967b47f @@ -49,23 +50,6 @@ jest.mock('../../src/computed'); // Can't mock State because mocks get instantiated before everything else // -> I got the good old not loaded Object error https://github.com/kentcdodds/how-jest-mocking-works // jest.mock("../../src/state/index"); -/* Can't mock Logger because I somehow can't overwrite a static get method -jest.mock("../../src/logger/index", () => { - return class { - static get level() { - return { - TRACE: 1, - DEBUG: 2, - LOG: 5, - TABLE: 5, - INFO: 10, - WARN: 20, - ERROR: 50, - }; - } - }; -}); - */ describe('Agile Tests', () => { const RuntimeMock = Runtime as jest.MockedClass; @@ -81,13 +65,16 @@ describe('Agile Tests', () => { jest.clearAllMocks(); LogMock.mockLogs(); + // Clear specified mocks RuntimeMock.mockClear(); SubControllerMock.mockClear(); StoragesMock.mockClear(); IntegrationsMock.mockClear(); - // Reset Global This + // Reset globalThis globalThis[Agile.globalKey] = undefined; + + jest.spyOn(Agile.prototype, 'configureLogger'); }); it('should instantiate Agile (default config)', () => { @@ -98,7 +85,7 @@ describe('Agile Tests', () => { waitForMount: true, }); expect(IntegrationsMock).toHaveBeenCalledWith(agile); - expect(agile.integrations).toBeInstanceOf(Integrations); + // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock expect(RuntimeMock).toHaveBeenCalledWith(agile); expect(agile.runtime).toBeInstanceOf(Runtime); expect(SubControllerMock).toHaveBeenCalledWith(agile); @@ -108,22 +95,10 @@ describe('Agile Tests', () => { }); expect(agile.storages).toBeInstanceOf(Storages); - // Check if Static Logger has correct config - expect(Agile.logger.config).toStrictEqual({ - prefix: 'Agile', - level: Logger.level.WARN, - canUseCustomStyles: true, - timestamp: false, - }); - expect(Agile.logger.allowedTags).toStrictEqual([ - 'runtime', - 'storage', - 'subscription', - 'multieditor', - ]); - expect(Agile.logger.isActive).toBeTruthy(); - - // Check if global Agile Instance got created + // Check if Logger was configured correctly + expect(agile.configureLogger).toHaveBeenCalledWith({}); + + // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBeUndefined(); }); @@ -145,7 +120,7 @@ describe('Agile Tests', () => { waitForMount: false, }); expect(IntegrationsMock).toHaveBeenCalledWith(agile); - expect(agile.integrations).toBeInstanceOf(Integrations); + // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock expect(RuntimeMock).toHaveBeenCalledWith(agile); expect(agile.runtime).toBeInstanceOf(Runtime); expect(SubControllerMock).toHaveBeenCalledWith(agile); @@ -155,22 +130,15 @@ describe('Agile Tests', () => { }); expect(agile.storages).toBeInstanceOf(Storages); - // Check if Static Logger has correct config - expect(Agile.logger.config).toStrictEqual({ - prefix: 'Jeff', + // Check if Logger was configured correctly + expect(agile.configureLogger).toHaveBeenCalledWith({ + active: false, level: Logger.level.DEBUG, - canUseCustomStyles: true, + prefix: 'Jeff', timestamp: true, }); - expect(Agile.logger.allowedTags).toStrictEqual([ - 'runtime', - 'storage', - 'subscription', - 'multieditor', - ]); - expect(Agile.logger.isActive).toBeFalsy(); - - // Check if global Agile Instance got created + + // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBe(agile); }); @@ -195,6 +163,51 @@ describe('Agile Tests', () => { jest.clearAllMocks(); // Because creating Agile executes some mocks }); + describe('configureLogger function tests', () => { + it('should overwrite the static Logger with a new Logger Instance (runsOnServer = true)', () => { + jest.spyOn(Utils, 'runsOnServer').mockReturnValueOnce(true); + Agile.logger.config = 'outdated' as any; + + agile.configureLogger({ + active: true, + level: 0, + }); + + expect(Agile.logger.config).toStrictEqual({ + canUseCustomStyles: true, + level: 0, + prefix: '', + timestamp: false, + }); + expect(Agile.logger.isActive).toBeFalsy(); + expect(Agile.logger.allowedTags).toStrictEqual([]); + }); + + it('should overwrite the static Logger with a new Logger Instance (runsOnServer = false)', () => { + jest.spyOn(Utils, 'runsOnServer').mockReturnValueOnce(false); + Agile.logger.config = 'outdated' as any; + + agile.configureLogger({ + active: true, + level: 0, + }); + + expect(Agile.logger.config).toStrictEqual({ + canUseCustomStyles: true, + level: 0, + prefix: 'Agile', + timestamp: false, + }); + expect(Agile.logger.isActive).toBeTruthy(); + expect(Agile.logger.allowedTags).toStrictEqual([ + 'runtime', + 'storage', + 'subscription', + 'multieditor', + ]); + }); + }); + describe('createStorage function tests', () => { const StorageMock = Storage as jest.MockedClass; From c4a9973b4a8cfd30b5bb148b4ded1c0f10db9d5d Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 1 Jul 2021 20:31:29 +0200 Subject: [PATCH 10/19] added key to agile instance --- packages/core/src/agile.ts | 13 ++++++- packages/core/src/shared.ts | 2 +- packages/core/tests/unit/agile.test.ts | 49 +++++++++++++++++--------- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 007430ef..87f69a1d 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -30,6 +30,9 @@ import { export class Agile { public config: AgileConfigInterface; + // Key/Name identifier of Agile Instance + public key?: AgileKey; + // Queues and executes incoming Observer-based Jobs public runtime: Runtime; // Manages and simplifies the subscription to UI-Components @@ -87,6 +90,7 @@ export class Agile { this.config = { waitForMount: config.waitForMount as any, }; + this.key = config.key; this.integrations = new Integrations(this); this.runtime = new Runtime(this); this.subController = new SubController(this); @@ -151,7 +155,7 @@ export class Agile { * @param config - Configuration object */ public createStorage(config: CreateStorageConfigInterface): Storage { - return createStorage({ ...config, ...{ agileInstance: this } }); + return createStorage(config); } /** @@ -476,6 +480,8 @@ export function createComputed( ); } +export type AgileKey = string | number; + export interface CreateAgileSubInstanceInterface { /** * Instance of Agile the Instance belongs to. @@ -521,6 +527,11 @@ export interface CreateAgileConfigInterface { * @default false */ bindGlobal?: boolean; + /** + * Key/Name identifier of Agile Instance. + * @default undefined + */ + key?: AgileKey; } export interface AgileConfigInterface { diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index c2507536..c2a6d0c9 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -2,4 +2,4 @@ import { Agile } from './agile'; // Shared Agile Instance that is used when no Agile Instance was specified // eslint-disable-next-line prefer-const -export let shared = new Agile(); +export let shared = new Agile({ key: 'shared' }); diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 1fe22ab9..ee3038e5 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -15,20 +15,6 @@ import { LogMock } from '../helper/logMock'; import * as Utils from '../../src/utils'; // https://github.com/facebook/jest/issues/5023 -// https://medium.com/@masonlgoetz/mock-static-class-methods-in-jest-1ceda967b47f -// https://gist.github.com/virgs/d9c50e878fc69832c01f8085f2953f12 -jest.mock('../../src/integrations', () => { - const mockedInstances = { - Integrations: jest.fn().mockImplementation(() => { - return { - integrate: jest.fn(), - }; - }), - }; - // @ts-ignore - mockedInstances.Integrations.onRegisteredExternalIntegration = jest.fn(); - return mockedInstances; -}); jest.mock('../../src/runtime', () => { return { Runtime: jest.fn(), @@ -44,12 +30,34 @@ jest.mock('../../src/storages', () => { Storages: jest.fn(), }; }); + +// https://gist.github.com/virgs/d9c50e878fc69832c01f8085f2953f12 +// https://medium.com/@masonlgoetz/mock-static-class-methods-in-jest-1ceda967b47f +jest.mock('../../src/integrations', () => { + const mockedInstances = { + // https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn + Integrations: jest.fn().mockImplementation(() => { + return { + integrate: jest.fn(), + hasIntegration: jest.fn(), + }; + }), + }; + // @ts-ignore + mockedInstances.Integrations.onRegisteredExternalIntegration = jest.fn(); + return mockedInstances; +}); + jest.mock('../../src/storages/storage'); jest.mock('../../src/collection'); jest.mock('../../src/computed'); -// Can't mock State because mocks get instantiated before everything else -// -> I got the good old not loaded Object error https://github.com/kentcdodds/how-jest-mocking-works -// jest.mock("../../src/state/index"); + +// https://github.com/facebook/jest/issues/5023 +jest.mock('../../src/state', () => { + return { + State: jest.fn(), + }; +}); describe('Agile Tests', () => { const RuntimeMock = Runtime as jest.MockedClass; @@ -84,6 +92,7 @@ describe('Agile Tests', () => { expect(agile.config).toStrictEqual({ waitForMount: true, }); + expect(agile.key).toBeUndefined(); expect(IntegrationsMock).toHaveBeenCalledWith(agile); // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock expect(RuntimeMock).toHaveBeenCalledWith(agile); @@ -113,12 +122,14 @@ describe('Agile Tests', () => { timestamp: true, }, bindGlobal: true, + key: 'jeff', }); // Check if Agile properties got instantiated properly expect(agile.config).toStrictEqual({ waitForMount: false, }); + expect(agile.key).toBe('jeff'); expect(IntegrationsMock).toHaveBeenCalledWith(agile); // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock expect(RuntimeMock).toHaveBeenCalledWith(agile); @@ -358,6 +369,10 @@ describe('Agile Tests', () => { }); describe('hasStorage function tests', () => { + beforeEach(() => { + agile.storages.hasStorage = jest.fn(); + }); + it('should check if Agile has any registered Storage', () => { agile.hasStorage(); From 17af60329fe15fe4c74e5b65a6dd1b9fbc1f3ec9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 2 Jul 2021 06:49:44 +0200 Subject: [PATCH 11/19] fixed 'window' in jest nodejs environment --- jest.base.config.js | 1 + packages/core/jest.config.js | 4 ++ packages/core/src/agile.ts | 5 --- packages/core/src/utils.ts | 8 ++-- packages/core/tests/helper/logMock.ts | 2 +- packages/core/tests/unit/agile.test.ts | 25 ++--------- packages/core/tests/unit/utils.test.ts | 44 +++++++++++++------ .../src/hooks/useIsomorphicLayoutEffect.ts | 2 +- 8 files changed, 46 insertions(+), 45 deletions(-) diff --git a/jest.base.config.js b/jest.base.config.js index 33c9a7b1..9c42b9e0 100644 --- a/jest.base.config.js +++ b/jest.base.config.js @@ -15,5 +15,6 @@ module.exports = { 'ts-jest': { tsconfig: '/packages/tsconfig.default.json', }, + __DEV__: true, }, }; diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 692d3d79..74ee3ebd 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -8,4 +8,8 @@ module.exports = { roots: [`/packages/${packageName}`], name: packageName, displayName: packageName, + globals: { + ...baseConfig.globals, + ...{ window: {} }, // https://stackoverflow.com/questions/46274889/jest-test-fails-with-window-is-not-defined + }, }; diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 87f69a1d..f73f3659 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -24,7 +24,6 @@ import { ComputeFunctionType, removeProperties, shared, - runsOnServer, } from './internal'; export class Agile { @@ -124,10 +123,6 @@ export class Agile { * @param config - Configuration object */ public configureLogger(config: CreateLoggerConfigInterface = {}): this { - if (runsOnServer()) { - Agile.logger = new Logger({ active: false }); - return this; - } config = defineConfig(config, { prefix: 'Agile', active: true, diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 1220be5f..be77e747 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -261,9 +261,9 @@ export function globalBind( * @public */ export const runsOnServer = (): boolean => { - return ( - typeof window === 'undefined' || - typeof window.document === 'undefined' || - typeof window.document.createElement === 'undefined' + return !( + typeof window !== 'undefined' && + typeof window.document !== 'undefined' && + typeof window.document.createElement !== 'undefined' ); }; diff --git a/packages/core/tests/helper/logMock.ts b/packages/core/tests/helper/logMock.ts index 49c4c4dc..b431beb8 100644 --- a/packages/core/tests/helper/logMock.ts +++ b/packages/core/tests/helper/logMock.ts @@ -22,7 +22,7 @@ const logTypes = { }; function mockLogs(mockArg?: LogTypes[]): void { - const _mockArg = mockArg ?? ['warn', 'error']; + const _mockArg = mockArg ?? ['warn', 'error', 'log']; mockConsole(_mockArg); } diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index ee3038e5..37111073 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -12,7 +12,6 @@ import { } from '../../src'; import testIntegration from '../helper/test.integration'; import { LogMock } from '../helper/logMock'; -import * as Utils from '../../src/utils'; // https://github.com/facebook/jest/issues/5023 jest.mock('../../src/runtime', () => { @@ -175,27 +174,7 @@ describe('Agile Tests', () => { }); describe('configureLogger function tests', () => { - it('should overwrite the static Logger with a new Logger Instance (runsOnServer = true)', () => { - jest.spyOn(Utils, 'runsOnServer').mockReturnValueOnce(true); - Agile.logger.config = 'outdated' as any; - - agile.configureLogger({ - active: true, - level: 0, - }); - - expect(Agile.logger.config).toStrictEqual({ - canUseCustomStyles: true, - level: 0, - prefix: '', - timestamp: false, - }); - expect(Agile.logger.isActive).toBeFalsy(); - expect(Agile.logger.allowedTags).toStrictEqual([]); - }); - it('should overwrite the static Logger with a new Logger Instance (runsOnServer = false)', () => { - jest.spyOn(Utils, 'runsOnServer').mockReturnValueOnce(false); Agile.logger.config = 'outdated' as any; agile.configureLogger({ @@ -332,6 +311,10 @@ describe('Agile Tests', () => { }); describe('registerStorage function tests', () => { + beforeEach(() => { + agile.storages.register = jest.fn(); + }); + it('should register provided Storage', () => { const dummyStorage = new Storage({ prefix: 'test', diff --git a/packages/core/tests/unit/utils.test.ts b/packages/core/tests/unit/utils.test.ts index db4bd7e3..d0b7f8f1 100644 --- a/packages/core/tests/unit/utils.test.ts +++ b/packages/core/tests/unit/utils.test.ts @@ -1,6 +1,4 @@ import { - globalBind, - getAgileInstance, Agile, State, Observer, @@ -32,30 +30,30 @@ describe('Utils Tests', () => { it('should get agileInstance from State', () => { const dummyState = new State(dummyAgile, 'dummyValue'); - expect(getAgileInstance(dummyState)).toBe(dummyAgile); + expect(Utils.getAgileInstance(dummyState)).toBe(dummyAgile); }); it('should get agileInstance from Collection', () => { const dummyCollection = new Collection(dummyAgile); - expect(getAgileInstance(dummyCollection)).toBe(dummyAgile); + expect(Utils.getAgileInstance(dummyCollection)).toBe(dummyAgile); }); it('should get agileInstance from Observer', () => { const dummyObserver = new Observer(dummyAgile); - expect(getAgileInstance(dummyObserver)).toBe(dummyAgile); + expect(Utils.getAgileInstance(dummyObserver)).toBe(dummyAgile); }); it('should get agileInstance from globalThis if passed instance holds no agileInstance', () => { - expect(getAgileInstance('weiredInstance')).toBe(dummyAgile); + expect(Utils.getAgileInstance('weiredInstance')).toBe(dummyAgile); }); it('should print error if something went wrong', () => { // @ts-ignore | Destroy globalThis globalThis = undefined; - const response = getAgileInstance('weiredInstance'); + const response = Utils.getAgileInstance('weiredInstance'); expect(response).toBeUndefined(); LogMock.hasLoggedCode('20:03:00', [], 'weiredInstance'); @@ -360,23 +358,23 @@ describe('Utils Tests', () => { }); it('should bind Instance globally at the specified key (default config)', () => { - globalBind(dummyKey, 'dummyInstance'); + Utils.globalBind(dummyKey, 'dummyInstance'); expect(globalThis[dummyKey]).toBe('dummyInstance'); }); it("shouldn't overwrite already globally bound Instance at the same key (default config)", () => { - globalBind(dummyKey, 'I am first!'); + Utils.globalBind(dummyKey, 'I am first!'); - globalBind(dummyKey, 'dummyInstance'); + Utils.globalBind(dummyKey, 'dummyInstance'); expect(globalThis[dummyKey]).toBe('I am first!'); }); it('should overwrite already globally bound Instance at the same key (overwrite = true)', () => { - globalBind(dummyKey, 'I am first!'); + Utils.globalBind(dummyKey, 'I am first!'); - globalBind(dummyKey, 'dummyInstance', true); + Utils.globalBind(dummyKey, 'dummyInstance', true); expect(globalThis[dummyKey]).toBe('dummyInstance'); }); @@ -385,9 +383,29 @@ describe('Utils Tests', () => { // @ts-ignore | Destroy globalThis globalThis = undefined; - globalBind(dummyKey, 'dummyInstance'); + Utils.globalBind(dummyKey, 'dummyInstance'); LogMock.hasLoggedCode('20:03:01', [dummyKey]); }); }); + + describe('runsOnServer function tests', () => { + it("should return 'false' if the current environment isn't a server", () => { + // eslint-disable-next-line no-global-assign + window = { + document: { + createElement: 'isSet' as any, + } as any, + } as any; + + expect(Utils.runsOnServer()).toBeFalsy(); + }); + + it("should return 'true' if the current environment is a server", () => { + // eslint-disable-next-line no-global-assign + window = undefined as any; + + expect(Utils.runsOnServer()).toBeTruthy(); + }); + }); }); diff --git a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts index e87a9732..becea846 100644 --- a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts +++ b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts @@ -10,6 +10,6 @@ import { runsOnServer } from '@agile-ts/core'; // is created synchronously, otherwise a store update may occur before the // subscription is created and an inconsistent state may be observed -export const useIsomorphicLayoutEffect = runsOnServer +export const useIsomorphicLayoutEffect = runsOnServer() ? useLayoutEffect : useEffect; From 825be0136a7d932b5ff96ae0ed5a08669de3ec4d Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 2 Jul 2021 08:10:04 +0200 Subject: [PATCH 12/19] added autoIntegrate option --- packages/core/src/agile.ts | 23 +++++++-- packages/core/src/integrations/index.ts | 3 +- .../unit/integrations/integrations.test.ts | 51 +++++++++++-------- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index f73f3659..b62b8169 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -85,6 +85,7 @@ export class Agile { waitForMount: true, logConfig: {}, bindGlobal: false, + autoIntegrate: true, }); this.config = { waitForMount: config.waitForMount as any, @@ -97,10 +98,17 @@ export class Agile { localStorage: config.localStorage, }); - // Setup listener to be notified when a external registered Integration was added - Integrations.onRegisteredExternalIntegration((integration) => { - this.integrate(integration); - }); + if (config.autoIntegrate) { + // Integration to be integrated initially + Integrations.initialIntegrations.forEach((integration) => { + this.integrate(integration); + }); + + // Setup listener to be notified when a external registered Integration was added + Integrations.onRegisteredExternalIntegration((integration) => { + this.integrate(integration); + }); + } // Assign customized Logger config to the static Logger this.configureLogger(config.logConfig); @@ -527,6 +535,13 @@ export interface CreateAgileConfigInterface { * @default undefined */ key?: AgileKey; + /** + * Whether external added Integrations are integrated automatically. + * For example, when the package '@agile-ts/react' was installed, + * whether to automatically integrate the 'reactIntegration' into the Agile Instance. + * @default true + */ + autoIntegrate?: boolean; } export interface AgileConfigInterface { diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 6bc8661a..fbc9863d 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -18,10 +18,11 @@ export class Integrations { target[property] = value; // Executed external registered Integrations callbacks - if (value instanceof Integration) + if (value instanceof Integration) { registeredExternalIntegrationsCallbacks.forEach((callback) => callback(value) ); + } return true; }, diff --git a/packages/core/tests/unit/integrations/integrations.test.ts b/packages/core/tests/unit/integrations/integrations.test.ts index d4baf660..2d903629 100644 --- a/packages/core/tests/unit/integrations/integrations.test.ts +++ b/packages/core/tests/unit/integrations/integrations.test.ts @@ -9,7 +9,6 @@ describe('Integrations Tests', () => { LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); - Agile.initialIntegrations = []; jest.spyOn(Integrations.prototype, 'integrate'); }); @@ -17,27 +16,7 @@ describe('Integrations Tests', () => { it('should create Integrations', () => { const integrations = new Integrations(dummyAgile); - expect(integrations.integrations.size).toBe(0); - }); - - it('should create Integrations and integrate Agile initialIntegrations', async () => { - const dummyIntegration1 = new Integration({ - key: 'initialIntegration1', - }); - const dummyIntegration2 = new Integration({ - key: 'initialIntegration2', - }); - Agile.initialIntegrations.push(dummyIntegration1); - Agile.initialIntegrations.push(dummyIntegration2); - - const integrations = new Integrations(dummyAgile); - - expect(integrations.integrations.size).toBe(2); - expect(integrations.integrations.has(dummyIntegration1)).toBeTruthy(); - expect(integrations.integrations.has(dummyIntegration2)).toBeTruthy(); - - expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration1); - expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration2); + expect(Array.from(integrations.integrations)).toStrictEqual([]); }); describe('Integrations Function Tests', () => { @@ -55,6 +34,34 @@ describe('Integrations Tests', () => { }); }); + describe('onRegisteredExternalIntegration', () => { + let dummyIntegration1: Integration; + let dummyIntegration2: Integration; + + beforeEach(() => { + dummyIntegration1 = new Integration({ + key: 'initialIntegration1', + }); + dummyIntegration2 = new Integration({ + key: 'initialIntegration2', + }); + }); + + it('should register callback and fire it, when an external Integration was added', () => { + const callback = jest.fn(); + + Integrations.onRegisteredExternalIntegration(callback); + + Integrations.initialIntegrations.push(dummyIntegration1); + Integrations.initialIntegrations.push(undefined as any); + Integrations.initialIntegrations.push(dummyIntegration2); + + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledWith(dummyIntegration1); + expect(callback).toHaveBeenCalledWith(dummyIntegration2); + }); + }); + describe('integrate function tests', () => { it('should integrate valid integration with no bind function', async () => { const response = await integrations.integrate(dummyIntegration1); From d75a123735817d0488e1d913e75c14a4606a22b2 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 2 Jul 2021 17:19:35 +0200 Subject: [PATCH 13/19] fixed some tests --- packages/core/src/agile.ts | 10 +- packages/core/src/shared.ts | 7 +- packages/core/src/utils.ts | 6 + packages/core/tests/unit/agile.test.ts | 307 ++++++++++++++++++++----- 4 files changed, 267 insertions(+), 63 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index b62b8169..fb1d21ec 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -39,15 +39,15 @@ export class Agile { // Handles the permanent persistence of Agile Classes public storages: Storages; - // Integrations (UI-Frameworks) that are integrated into AgileTs + // Integrations (UI-Frameworks) that are integrated into the Agile Instance public integrations: Integrations; - // Static AgileTs Logger with the default config + // Static Agile Logger with the default config // (-> is overwritten by the last created Agile Instance) static logger = new Logger({ prefix: 'Agile', active: true, - level: Logger.level.SUCCESS, + level: Logger.level.WARN, }); // Identifier used to bind an Agile Instance globally @@ -99,12 +99,12 @@ export class Agile { }); if (config.autoIntegrate) { - // Integration to be integrated initially + // Integrate Integrations to be initially integrated Integrations.initialIntegrations.forEach((integration) => { this.integrate(integration); }); - // Setup listener to be notified when a external registered Integration was added + // Setup listener to be notified when an external registered Integration was added Integrations.onRegisteredExternalIntegration((integration) => { this.integrate(integration); }); diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index c2a6d0c9..47cd5d46 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -1,5 +1,8 @@ -import { Agile } from './agile'; +import { Agile, Logger } from './internal'; // Shared Agile Instance that is used when no Agile Instance was specified // eslint-disable-next-line prefer-const -export let shared = new Agile({ key: 'shared' }); +export let shared = new Agile({ + key: 'shared', + logConfig: { level: Logger.level.WARN }, +}); diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index be77e747..8748f28e 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -5,6 +5,7 @@ import { normalizeArray, isFunction, LogCodeManager, + shared, } from './internal'; /** @@ -25,6 +26,11 @@ export function getAgileInstance(instance: any): Agile | undefined { if (_agileInstance) return _agileInstance; } + // Try to get shared Agile Instance + if (shared instanceof Agile) { + return shared; + } + // Return global bound Agile Instance return globalThis[Agile.globalKey]; } catch (e) { diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 37111073..385d7931 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -9,9 +9,12 @@ import { Collection, Logger, Storages, + Integration, + shared, } from '../../src'; import testIntegration from '../helper/test.integration'; import { LogMock } from '../helper/logMock'; +import * as AgileFile from '../../src/agile'; // https://github.com/facebook/jest/issues/5023 jest.mock('../../src/runtime', () => { @@ -44,6 +47,8 @@ jest.mock('../../src/integrations', () => { }; // @ts-ignore mockedInstances.Integrations.onRegisteredExternalIntegration = jest.fn(); + // @ts-ignore + mockedInstances.Integrations.initialIntegrations = []; return mockedInstances; }); @@ -68,6 +73,8 @@ describe('Agile Tests', () => { typeof Integrations >; + let dummyIntegration: Integration; + beforeEach(() => { jest.clearAllMocks(); LogMock.mockLogs(); @@ -81,13 +88,17 @@ describe('Agile Tests', () => { // Reset globalThis globalThis[Agile.globalKey] = undefined; + dummyIntegration = new Integration({ key: 'dummyIntegrationKey' }); + jest.spyOn(Agile.prototype, 'configureLogger'); + jest.spyOn(Agile.prototype, 'integrate'); }); - it('should instantiate Agile (default config)', () => { + it('should instantiate Agile with initialIntegrations (default config)', () => { + Integrations.initialIntegrations = [dummyIntegration]; + const agile = new Agile(); - // Check if Agile properties got instantiated properly expect(agile.config).toStrictEqual({ waitForMount: true, }); @@ -102,15 +113,20 @@ describe('Agile Tests', () => { localStorage: true, }); expect(agile.storages).toBeInstanceOf(Storages); - - // Check if Logger was configured correctly expect(agile.configureLogger).toHaveBeenCalledWith({}); + expect(Integrations.onRegisteredExternalIntegration).toHaveBeenCalledWith( + expect.any(Function) + ); + expect(agile.integrate).toHaveBeenCalledWith(dummyIntegration); + // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBeUndefined(); }); - it('should instantiate Agile with (specific config)', () => { + it('should instantiate Agile with initialIntegrations (specific config)', () => { + Integrations.initialIntegrations = [dummyIntegration]; + const agile = new Agile({ waitForMount: false, localStorage: false, @@ -122,9 +138,9 @@ describe('Agile Tests', () => { }, bindGlobal: true, key: 'jeff', + autoIntegrate: false, }); - // Check if Agile properties got instantiated properly expect(agile.config).toStrictEqual({ waitForMount: false, }); @@ -139,8 +155,6 @@ describe('Agile Tests', () => { localStorage: false, }); expect(agile.storages).toBeInstanceOf(Storages); - - // Check if Logger was configured correctly expect(agile.configureLogger).toHaveBeenCalledWith({ active: false, level: Logger.level.DEBUG, @@ -148,33 +162,44 @@ describe('Agile Tests', () => { timestamp: true, }); + expect(Integrations.onRegisteredExternalIntegration).not.toHaveBeenCalled(); + expect(agile.integrate).not.toHaveBeenCalled(); + // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBe(agile); }); - it('should instantiate second Agile Instance and print warning if config.bindGlobal is set both times to true', () => { - const agile1 = new Agile({ - bindGlobal: true, - }); + it( + 'should instantiate second Agile Instance ' + + 'and print warning when an attempt is made to set the second Instance globally ' + + 'if the previously defined instance has also been set globally', + () => { + const agile1 = new Agile({ + bindGlobal: true, + }); - const agile2 = new Agile({ - bindGlobal: true, - }); + const agile2 = new Agile({ + bindGlobal: true, + }); - expect(globalThis[Agile.globalKey]).toBe(agile1); - LogMock.hasLoggedCode('10:02:00'); - }); + expect(agile1).toBeInstanceOf(Agile); + expect(agile2).toBeInstanceOf(Agile); + + expect(globalThis[Agile.globalKey]).toBe(agile1); + LogMock.hasLoggedCode('10:02:00'); + } + ); describe('Agile Function Tests', () => { let agile: Agile; beforeEach(() => { agile = new Agile(); - jest.clearAllMocks(); // Because creating Agile executes some mocks + jest.clearAllMocks(); // Because creating the Agile Instance calls some mocks }); describe('configureLogger function tests', () => { - it('should overwrite the static Logger with a new Logger Instance (runsOnServer = false)', () => { + it('should overwrite the static Logger with a new Logger Instance', () => { Agile.logger.config = 'outdated' as any; agile.configureLogger({ @@ -199,13 +224,11 @@ describe('Agile Tests', () => { }); describe('createStorage function tests', () => { - const StorageMock = Storage as jest.MockedClass; - beforeEach(() => { - StorageMock.mockClear(); + jest.spyOn(AgileFile, 'createStorage'); }); - it('should create Storage', () => { + it('should call createStorage', () => { const storageConfig = { prefix: 'test', methods: { @@ -221,31 +244,36 @@ describe('Agile Tests', () => { }, key: 'myTestStorage', }; - const storage = agile.createStorage(storageConfig); - expect(storage).toBeInstanceOf(Storage); - expect(StorageMock).toHaveBeenCalledWith(storageConfig); + const response = agile.createStorage(storageConfig); + + expect(response).toBeInstanceOf(Storage); + expect(AgileFile.createStorage).toHaveBeenCalledWith(storageConfig); }); }); - describe('state function tests', () => { - it('should create State', () => { - const state = agile.createState('testValue', { - key: 'myCoolState', - }); + describe('createState function tests', () => { + beforeEach(() => { + jest.spyOn(AgileFile, 'createState'); + }); + + it('should call createState with the Agile Instance it was called on', () => { + const response = agile.createState('jeff', { key: 'jeffState' }); - expect(state).toBeInstanceOf(State); + expect(response).toBeInstanceOf(State); + expect(AgileFile.createState).toHaveBeenCalledWith({ + key: 'jeffState', + agileInstance: agile, + }); }); }); describe('createCollection function tests', () => { - const CollectionMock = Collection as jest.MockedClass; - beforeEach(() => { - CollectionMock.mockClear(); + jest.spyOn(AgileFile, 'createCollection'); }); - it('should create Collection', () => { + it('should call createCollection with the Agile Instance it was called on', () => { const collectionConfig = { selectors: ['test', 'test1'], groups: ['test2', 'test10'], @@ -253,48 +281,53 @@ describe('Agile Tests', () => { key: 'myCoolCollection', }; - const collection = agile.createCollection(collectionConfig); + const response = agile.createCollection(collectionConfig); - expect(collection).toBeInstanceOf(Collection); - expect(CollectionMock).toHaveBeenCalledWith(agile, collectionConfig); + expect(response).toBeInstanceOf(Collection); + expect(AgileFile.createCollection).toHaveBeenCalledWith( + collectionConfig, + agile + ); }); }); describe('createComputed function tests', () => { - const ComputedMock = Computed as jest.MockedClass; const computedFunction = () => { - // console.log("Hello Jeff"); + // empty }; beforeEach(() => { - ComputedMock.mockClear(); + jest.spyOn(AgileFile, 'createComputed'); }); - it('should create Computed', () => { - const computed = agile.createComputed(computedFunction, [ + it('should call createComputed with the Agile Instance it was called on (default config)', () => { + const response = agile.createComputed(computedFunction, [ 'dummyDep' as any, ]); - expect(computed).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith(agile, computedFunction, { + expect(response).toBeInstanceOf(Computed); + expect(AgileFile.createComputed).toHaveBeenCalledWith({ computedDeps: ['dummyDep' as any], + agileInstance: agile, }); }); - it('should create Computed with config', () => { - const computed = agile.createComputed(computedFunction, { + it('should call createComputed with the Agile Instance it was called on (specific config)', () => { + const computedConfig = { key: 'jeff', isPlaceholder: false, computedDeps: ['dummyDep' as any], autodetect: true, - }); + }; - expect(computed).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith(agile, computedFunction, { - key: 'jeff', - isPlaceholder: false, - computedDeps: ['dummyDep' as any], - autodetect: true, + const response = agile.createComputed(computedFunction, computedConfig); + + expect(response).toBeInstanceOf(Computed); + expect(AgileFile.createComputed).toHaveBeenCalledWith({ + ...computedConfig, + ...{ + agileInstance: agile, + }, }); }); }); @@ -363,4 +396,166 @@ describe('Agile 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 = AgileFile.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 = AgileFile.createState('testValue', { + key: 'myCoolState', + }); + + expect(state).toBeInstanceOf(State); + expect(StateMock).toHaveBeenCalledWith(shared, 'testValue', { + key: 'myCoolState', + }); + }); + + it('should create State with a specified Agile Instance', () => { + const agile = new Agile(); + + const state = AgileFile.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; + + 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 = AgileFile.createCollection(collectionConfig); + + expect(collection).toBeInstanceOf(Collection); + expect(CollectionMock).toHaveBeenCalledWith(shared, 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 = AgileFile.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 = AgileFile.createComputed(computedFunction, [ + 'dummyDep' as any, + ]); + + expect(response).toBeInstanceOf(Computed); + expect(ComputedMock).toHaveBeenCalledWith(shared, 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 = AgileFile.createComputed( + computedFunction, + computedConfig + ); + + expect(response).toBeInstanceOf(Computed); + expect(ComputedMock).toHaveBeenCalledWith( + shared, + 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 = AgileFile.createComputed(computedFunction, { + ...computedConfig, + ...{ agileInstance: agile }, + }); + + expect(response).toBeInstanceOf(Computed); + expect(ComputedMock).toHaveBeenCalledWith( + agile, + computedFunction, + computedConfig + ); + }); + }); }); From 5ca53369db46cf4e5b3ffdcec5d06762d817eab9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 2 Jul 2021 17:38:20 +0200 Subject: [PATCH 14/19] fixed agile tests --- packages/core/src/agile.ts | 167 +------------------ packages/core/src/shared.ts | 180 ++++++++++++++++++++- packages/core/tests/unit/agile.test.ts | 205 +++--------------------- packages/core/tests/unit/shared.test.ts | 182 +++++++++++++++++++++ 4 files changed, 383 insertions(+), 351 deletions(-) create mode 100644 packages/core/tests/unit/shared.test.ts diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index fb1d21ec..5f3134a9 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -22,8 +22,10 @@ import { DependableAgileInstancesType, CreateComputedConfigInterface, ComputeFunctionType, - removeProperties, - shared, + createStorage, + createState, + createCollection, + createComputed, } from './internal'; export class Agile { @@ -338,169 +340,8 @@ export class Agile { } } -/** - * Returns a newly created Storage. - * - * A Storage Class serves as an interface to external storages, - * such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or - * [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). - * - * It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) - * (like States or Collections) in nearly any external storage. - * - * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) - * - * @public - * @param config - Configuration object - */ -export function createStorage(config: CreateStorageConfigInterface): Storage { - return new Storage(config); -} - -/** - * 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 = {} -): State { - config = defineConfig(config, { - agileInstance: shared, - }); - return new State( - config.agileInstance as any, - initialValue, - removeProperties(config, ['agileInstance']) - ); -} - -/** - * Returns a newly created Collection. - * - * A Collection manages a reactive set of Information - * that we need to remember globally at a later point in time. - * While providing a toolkit to use and mutate this set of Information. - * - * It is designed for arrays of data objects following the same pattern. - * - * Each of these data object must have a unique `primaryKey` to be correctly identified later. - * - * You can create as many global Collections as you need. - * - * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection) - * - * @public - * @param config - Configuration object - * @param agileInstance - Instance of Agile the Collection belongs to. - */ -export function createCollection( - config?: CollectionConfig, - agileInstance: Agile = shared -): Collection { - return new Collection(agileInstance, config); -} - -/** - * Returns a newly created Computed. - * - * A Computed is an extension of the State Class - * that computes its value based on a specified compute function. - * - * The computed value will be cached to avoid unnecessary recomputes - * and is only recomputed when one of its direct dependencies changes. - * - * 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) - * - * @public - * @param computeFunction - Function to compute the computed value. - * @param config - Configuration object - */ -export function createComputed( - computeFunction: ComputeFunctionType, - config?: CreateComputedConfigInterfaceWithAgile -): Computed; -/** - * Returns a newly created Computed. - * - * A Computed is an extension of the State Class - * that computes its value based on a specified compute function. - * - * The computed value will be cached to avoid unnecessary recomputes - * and is only recomputed when one of its direct dependencies changes. - * - * 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#createcomputed) - * - * @public - * @param computeFunction - Function to compute the computed value. - * @param deps - Hard-coded dependencies on which the Computed Class should depend. - */ -export function createComputed( - computeFunction: ComputeFunctionType, - deps?: Array -): Computed; -export function createComputed( - computeFunction: ComputeFunctionType, - configOrDeps?: - | CreateComputedConfigInterface - | Array -): Computed { - let _config: CreateComputedConfigInterfaceWithAgile = {}; - - if (Array.isArray(configOrDeps)) { - _config = flatMerge(_config, { - computedDeps: configOrDeps, - }); - } else { - if (configOrDeps) _config = configOrDeps; - } - - _config = defineConfig(_config, { - agileInstance: shared, - }); - - return new Computed( - _config.agileInstance as any, - computeFunction, - removeProperties(_config, ['agileInstance']) - ); -} - export type AgileKey = string | number; -export interface CreateAgileSubInstanceInterface { - /** - * Instance of Agile the Instance belongs to. - * @default Agile.shared - */ - agileInstance?: Agile; -} - -export interface CreateStateConfigInterfaceWithAgile - extends CreateAgileSubInstanceInterface, - StateConfigInterface {} - -export interface CreateComputedConfigInterfaceWithAgile - extends CreateAgileSubInstanceInterface, - CreateComputedConfigInterface {} - export interface CreateAgileConfigInterface { /** * Configures the logging behaviour of AgileTs. diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index 47cd5d46..91284fb2 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -1,4 +1,21 @@ -import { Agile, Logger } from './internal'; +import { + Agile, + Collection, + CollectionConfig, + Computed, + ComputeFunctionType, + CreateComputedConfigInterface, + CreateStorageConfigInterface, + DefaultItem, + defineConfig, + DependableAgileInstancesType, + flatMerge, + Logger, + removeProperties, + State, + StateConfigInterface, + Storage, +} from './internal'; // Shared Agile Instance that is used when no Agile Instance was specified // eslint-disable-next-line prefer-const @@ -6,3 +23,164 @@ export let shared = new Agile({ key: 'shared', logConfig: { level: Logger.level.WARN }, }); + +/** + * Returns a newly created Storage. + * + * A Storage Class serves as an interface to external storages, + * such as the [Async Storage](https://github.com/react-native-async-storage/async-storage) or + * [Local Storage](https://www.w3schools.com/html/html5_webstorage.asp). + * + * It creates the foundation to easily [`persist()`](https://agile-ts.org/docs/core/state/methods#persist) [Agile Sub Instances](https://agile-ts.org/docs/introduction/#agile-sub-instance) + * (like States or Collections) in nearly any external storage. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createstorage) + * + * @public + * @param config - Configuration object + */ +export function createStorage(config: CreateStorageConfigInterface): Storage { + return new Storage(config); +} + +/** + * 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 = {} +): State { + config = defineConfig(config, { + agileInstance: shared, + }); + return new State( + config.agileInstance as any, + initialValue, + removeProperties(config, ['agileInstance']) + ); +} + +/** + * Returns a newly created Collection. + * + * A Collection manages a reactive set of Information + * that we need to remember globally at a later point in time. + * While providing a toolkit to use and mutate this set of Information. + * + * It is designed for arrays of data objects following the same pattern. + * + * Each of these data object must have a unique `primaryKey` to be correctly identified later. + * + * You can create as many global Collections as you need. + * + * [Learn more..](https://agile-ts.org/docs/core/agile-instance/methods#createcollection) + * + * @public + * @param config - Configuration object + * @param agileInstance - Instance of Agile the Collection belongs to. + */ +export function createCollection( + config?: CollectionConfig, + agileInstance: Agile = shared +): Collection { + return new Collection(agileInstance, config); +} + +/** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a specified compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * 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) + * + * @public + * @param computeFunction - Function to compute the computed value. + * @param config - Configuration object + */ +export function createComputed( + computeFunction: ComputeFunctionType, + config?: CreateComputedConfigInterfaceWithAgile +): Computed; +/** + * Returns a newly created Computed. + * + * A Computed is an extension of the State Class + * that computes its value based on a specified compute function. + * + * The computed value will be cached to avoid unnecessary recomputes + * and is only recomputed when one of its direct dependencies changes. + * + * 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#createcomputed) + * + * @public + * @param computeFunction - Function to compute the computed value. + * @param deps - Hard-coded dependencies on which the Computed Class should depend. + */ +export function createComputed( + computeFunction: ComputeFunctionType, + deps?: Array +): Computed; +export function createComputed( + computeFunction: ComputeFunctionType, + configOrDeps?: + | CreateComputedConfigInterface + | Array +): Computed { + let _config: CreateComputedConfigInterfaceWithAgile = {}; + + if (Array.isArray(configOrDeps)) { + _config = flatMerge(_config, { + computedDeps: configOrDeps, + }); + } else { + if (configOrDeps) _config = configOrDeps; + } + + _config = defineConfig(_config, { + agileInstance: shared, + }); + + return new Computed( + _config.agileInstance as any, + computeFunction, + removeProperties(_config, ['agileInstance']) + ); +} + +export interface CreateAgileSubInstanceInterface { + /** + * Instance of Agile the Instance belongs to. + * @default Agile.shared + */ + agileInstance?: Agile; +} + +export interface CreateStateConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + StateConfigInterface {} + +export interface CreateComputedConfigInterfaceWithAgile + extends CreateAgileSubInstanceInterface, + CreateComputedConfigInterface {} diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 385d7931..9a5b2acf 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -10,16 +10,20 @@ import { Logger, Storages, Integration, - shared, } from '../../src'; import testIntegration from '../helper/test.integration'; import { LogMock } from '../helper/logMock'; -import * as AgileFile from '../../src/agile'; +import * as Shared from '../../src/shared'; // https://github.com/facebook/jest/issues/5023 jest.mock('../../src/runtime', () => { return { - Runtime: jest.fn(), + // https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn + Runtime: jest.fn().mockImplementation(() => { + return { + ingest: jest.fn(), + }; + }), }; }); jest.mock('../../src/runtime/subscription/sub.controller', () => { @@ -52,17 +56,6 @@ jest.mock('../../src/integrations', () => { return mockedInstances; }); -jest.mock('../../src/storages/storage'); -jest.mock('../../src/collection'); -jest.mock('../../src/computed'); - -// https://github.com/facebook/jest/issues/5023 -jest.mock('../../src/state', () => { - return { - State: jest.fn(), - }; -}); - describe('Agile Tests', () => { const RuntimeMock = Runtime as jest.MockedClass; const SubControllerMock = SubController as jest.MockedClass< @@ -106,7 +99,7 @@ describe('Agile Tests', () => { expect(IntegrationsMock).toHaveBeenCalledWith(agile); // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock expect(RuntimeMock).toHaveBeenCalledWith(agile); - expect(agile.runtime).toBeInstanceOf(Runtime); + // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock expect(SubControllerMock).toHaveBeenCalledWith(agile); expect(agile.subController).toBeInstanceOf(SubController); expect(StoragesMock).toHaveBeenCalledWith(agile, { @@ -148,7 +141,7 @@ describe('Agile Tests', () => { expect(IntegrationsMock).toHaveBeenCalledWith(agile); // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock expect(RuntimeMock).toHaveBeenCalledWith(agile); - expect(agile.runtime).toBeInstanceOf(Runtime); + // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock expect(SubControllerMock).toHaveBeenCalledWith(agile); expect(agile.subController).toBeInstanceOf(SubController); expect(StoragesMock).toHaveBeenCalledWith(agile, { @@ -225,7 +218,7 @@ describe('Agile Tests', () => { describe('createStorage function tests', () => { beforeEach(() => { - jest.spyOn(AgileFile, 'createStorage'); + jest.spyOn(Shared, 'createStorage'); }); it('should call createStorage', () => { @@ -248,20 +241,20 @@ describe('Agile Tests', () => { const response = agile.createStorage(storageConfig); expect(response).toBeInstanceOf(Storage); - expect(AgileFile.createStorage).toHaveBeenCalledWith(storageConfig); + expect(Shared.createStorage).toHaveBeenCalledWith(storageConfig); }); }); describe('createState function tests', () => { beforeEach(() => { - jest.spyOn(AgileFile, 'createState'); + jest.spyOn(Shared, 'createState'); }); it('should call createState with the Agile Instance it was called on', () => { const response = agile.createState('jeff', { key: 'jeffState' }); expect(response).toBeInstanceOf(State); - expect(AgileFile.createState).toHaveBeenCalledWith({ + expect(Shared.createState).toHaveBeenCalledWith('jeff', { key: 'jeffState', agileInstance: agile, }); @@ -270,7 +263,7 @@ describe('Agile Tests', () => { describe('createCollection function tests', () => { beforeEach(() => { - jest.spyOn(AgileFile, 'createCollection'); + jest.spyOn(Shared, 'createCollection'); }); it('should call createCollection with the Agile Instance it was called on', () => { @@ -284,7 +277,7 @@ describe('Agile Tests', () => { const response = agile.createCollection(collectionConfig); expect(response).toBeInstanceOf(Collection); - expect(AgileFile.createCollection).toHaveBeenCalledWith( + expect(Shared.createCollection).toHaveBeenCalledWith( collectionConfig, agile ); @@ -297,7 +290,7 @@ describe('Agile Tests', () => { }; beforeEach(() => { - jest.spyOn(AgileFile, 'createComputed'); + jest.spyOn(Shared, 'createComputed'); }); it('should call createComputed with the Agile Instance it was called on (default config)', () => { @@ -306,7 +299,7 @@ describe('Agile Tests', () => { ]); expect(response).toBeInstanceOf(Computed); - expect(AgileFile.createComputed).toHaveBeenCalledWith({ + expect(Shared.createComputed).toHaveBeenCalledWith(computedFunction, { computedDeps: ['dummyDep' as any], agileInstance: agile, }); @@ -323,7 +316,7 @@ describe('Agile Tests', () => { const response = agile.createComputed(computedFunction, computedConfig); expect(response).toBeInstanceOf(Computed); - expect(AgileFile.createComputed).toHaveBeenCalledWith({ + expect(Shared.createComputed).toHaveBeenCalledWith(computedFunction, { ...computedConfig, ...{ agileInstance: agile, @@ -396,166 +389,4 @@ describe('Agile 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 = AgileFile.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 = AgileFile.createState('testValue', { - key: 'myCoolState', - }); - - expect(state).toBeInstanceOf(State); - expect(StateMock).toHaveBeenCalledWith(shared, 'testValue', { - key: 'myCoolState', - }); - }); - - it('should create State with a specified Agile Instance', () => { - const agile = new Agile(); - - const state = AgileFile.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; - - 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 = AgileFile.createCollection(collectionConfig); - - expect(collection).toBeInstanceOf(Collection); - expect(CollectionMock).toHaveBeenCalledWith(shared, 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 = AgileFile.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 = AgileFile.createComputed(computedFunction, [ - 'dummyDep' as any, - ]); - - expect(response).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith(shared, 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 = AgileFile.createComputed( - computedFunction, - computedConfig - ); - - expect(response).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith( - shared, - 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 = AgileFile.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 new file mode 100644 index 00000000..5ca32ccb --- /dev/null +++ b/packages/core/tests/unit/shared.test.ts @@ -0,0 +1,182 @@ +import { + Agile, + Collection, + Computed, + shared, + State, + Storage, + createStorage, + createState, + createCollection, + createComputed, +} from '../../src'; + +jest.mock('../../src/storages/storage'); +jest.mock('../../src/collection'); +jest.mock('../../src/computed'); + +// https://github.com/facebook/jest/issues/5023 +jest.mock('../../src/state', () => { + return { + State: jest.fn(), + }; +}); + +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(shared, '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; + + 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(shared, 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(shared, 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( + shared, + 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 b26e1cd0351afc00484896665d177eaba01fe13b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 2 Jul 2021 20:08:52 +0200 Subject: [PATCH 15/19] fixed some more tests --- packages/core/src/logCodeManager.ts | 2 +- packages/core/src/shared.ts | 27 +++++++++++--- packages/core/tests/unit/shared.test.ts | 6 +++ packages/core/tests/unit/utils.test.ts | 37 +++++++++++++++---- packages/event/tests/unit/event.job.test.ts | 4 +- .../event/tests/unit/event.observer.test.ts | 4 +- packages/event/tests/unit/event.test.ts | 4 +- 7 files changed, 63 insertions(+), 21 deletions(-) diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 36a1eba0..d90deb35 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -33,7 +33,7 @@ const logCodeMessages = { // Storages '11:02:00': - "The 'Local Storage' is not available in your current environment." + + "The 'Local Storage' is not available in your current environment. " + "To use the '.persist()' functionality, please provide a custom Storage!", '11:02:01': 'The first allocated Storage for AgileTs must be set as the default Storage!', diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index 91284fb2..a9ab845e 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -12,17 +12,32 @@ import { flatMerge, Logger, removeProperties, + runsOnServer, State, StateConfigInterface, Storage, } from './internal'; -// Shared Agile Instance that is used when no Agile Instance was specified +/** + * Shared Agile Instance that is used when no Agile Instance was specified + */ // eslint-disable-next-line prefer-const -export let shared = new Agile({ +let sharedAgileInstance = new Agile({ key: 'shared', - logConfig: { level: Logger.level.WARN }, + logConfig: { prefix: 'Agile', level: Logger.level.WARN, active: true }, + localStorage: !runsOnServer(), }); +export { sharedAgileInstance as shared }; + +/** + * Assigns a new Agile Instance as the shared Agile Instance. + * + * @param agileInstance - Agile Instance to become the new shared Agile Instance. + */ +// https://stackoverflow.com/questions/32558514/javascript-es6-export-const-vs-export-let +export function assignSharedAgileInstance(agileInstance: Agile): void { + sharedAgileInstance = agileInstance; +} /** * Returns a newly created Storage. @@ -63,7 +78,7 @@ export function createState( config: CreateStateConfigInterfaceWithAgile = {} ): State { config = defineConfig(config, { - agileInstance: shared, + agileInstance: sharedAgileInstance, }); return new State( config.agileInstance as any, @@ -93,7 +108,7 @@ export function createState( */ export function createCollection( config?: CollectionConfig, - agileInstance: Agile = shared + agileInstance: Agile = sharedAgileInstance ): Collection { return new Collection(agileInstance, config); } @@ -159,7 +174,7 @@ export function createComputed( } _config = defineConfig(_config, { - agileInstance: shared, + agileInstance: sharedAgileInstance, }); return new Computed( diff --git a/packages/core/tests/unit/shared.test.ts b/packages/core/tests/unit/shared.test.ts index 5ca32ccb..f4000909 100644 --- a/packages/core/tests/unit/shared.test.ts +++ b/packages/core/tests/unit/shared.test.ts @@ -10,6 +10,7 @@ import { createCollection, createComputed, } from '../../src'; +import { LogMock } from '../helper/logMock'; jest.mock('../../src/storages/storage'); jest.mock('../../src/collection'); @@ -23,6 +24,11 @@ jest.mock('../../src/state', () => { }); describe('Shared Tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + LogMock.mockLogs(); + }); + describe('createStorage function tests', () => { const StorageMock = Storage as jest.MockedClass; diff --git a/packages/core/tests/unit/utils.test.ts b/packages/core/tests/unit/utils.test.ts index d0b7f8f1..d9e57b8d 100644 --- a/packages/core/tests/unit/utils.test.ts +++ b/packages/core/tests/unit/utils.test.ts @@ -5,6 +5,7 @@ import { Collection, StateObserver, GroupObserver, + assignSharedAgileInstance, } from '../../src'; import * as Utils from '../../src/utils'; import { LogMock } from '../helper/logMock'; @@ -24,35 +25,55 @@ describe('Utils Tests', () => { describe('getAgileInstance function tests', () => { beforeEach(() => { + assignSharedAgileInstance(dummyAgile); globalThis[Agile.globalKey] = dummyAgile; }); - it('should get agileInstance from State', () => { + it('should return Agile Instance from State', () => { const dummyState = new State(dummyAgile, 'dummyValue'); expect(Utils.getAgileInstance(dummyState)).toBe(dummyAgile); }); - it('should get agileInstance from Collection', () => { + it('should return Agile Instance from Collection', () => { const dummyCollection = new Collection(dummyAgile); expect(Utils.getAgileInstance(dummyCollection)).toBe(dummyAgile); }); - it('should get agileInstance from Observer', () => { + it('should return Agile Instance from Observer', () => { const dummyObserver = new Observer(dummyAgile); expect(Utils.getAgileInstance(dummyObserver)).toBe(dummyAgile); }); - it('should get agileInstance from globalThis if passed instance holds no agileInstance', () => { - expect(Utils.getAgileInstance('weiredInstance')).toBe(dummyAgile); - }); - - it('should print error if something went wrong', () => { + it( + 'should return shared Agile Instance' + + 'if specified Instance contains no valid Agile Instance', + () => { + expect(Utils.getAgileInstance('weiredInstance')).toBe(dummyAgile); + } + ); + + it( + 'should return Agile Instance from globalThis' + + 'if specified Instance contains no valid Agile Instance' + + 'and no shared Agile Instance was specified', + () => { + // Destroy shared Agile Instance + assignSharedAgileInstance(undefined as any); + + expect(Utils.getAgileInstance('weiredInstance')).toBe(dummyAgile); + } + ); + + it('should print error if no Agile Instance could be retrieved', () => { // @ts-ignore | Destroy globalThis globalThis = undefined; + // Destroy shared Agile Instance + assignSharedAgileInstance(undefined as any); + const response = Utils.getAgileInstance('weiredInstance'); expect(response).toBeUndefined(); diff --git a/packages/event/tests/unit/event.job.test.ts b/packages/event/tests/unit/event.job.test.ts index 7c475015..909908cf 100644 --- a/packages/event/tests/unit/event.job.test.ts +++ b/packages/event/tests/unit/event.job.test.ts @@ -1,10 +1,10 @@ import { EventJob } from '../../src'; -import mockConsole from 'jest-mock-console'; +import { LogMock } from '../../../core/tests/helper/logMock'; describe('EventJob Tests', () => { beforeEach(() => { jest.clearAllMocks(); - mockConsole(['error', 'warn']); + LogMock.mockLogs(); }); it('should create EventJob (without keys)', () => { diff --git a/packages/event/tests/unit/event.observer.test.ts b/packages/event/tests/unit/event.observer.test.ts index 221bd2b9..ba74713a 100644 --- a/packages/event/tests/unit/event.observer.test.ts +++ b/packages/event/tests/unit/event.observer.test.ts @@ -1,6 +1,6 @@ import { EventObserver, Event } from '../../src'; import { Agile, Observer, SubscriptionContainer } from '@agile-ts/core'; -import mockConsole from 'jest-mock-console'; +import { LogMock } from '../../../core/tests/helper/logMock'; describe('EventObserver Tests', () => { let dummyAgile: Agile; @@ -8,7 +8,7 @@ describe('EventObserver Tests', () => { beforeEach(() => { jest.clearAllMocks(); - mockConsole(['error', 'warn']); + LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); dummyEvent = new Event(dummyAgile); diff --git a/packages/event/tests/unit/event.test.ts b/packages/event/tests/unit/event.test.ts index f2bdd61f..a6564755 100644 --- a/packages/event/tests/unit/event.test.ts +++ b/packages/event/tests/unit/event.test.ts @@ -1,14 +1,14 @@ import { Event, EventObserver } from '../../src'; import { Agile, Observer } from '@agile-ts/core'; import * as Utils from '@agile-ts/utils'; -import mockConsole from 'jest-mock-console'; +import { LogMock } from '../../../core/tests/helper/logMock'; describe('Event Tests', () => { let dummyAgile: Agile; beforeEach(() => { jest.clearAllMocks(); - mockConsole(['error', 'warn']); + LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); }); From d0695a42be5fde9dec0eabf3ef1bbd39683e7e96 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 2 Jul 2021 20:33:40 +0200 Subject: [PATCH 16/19] fixed tests --- packages/core/jest.config.js | 4 ---- packages/core/src/agile.ts | 2 +- packages/core/tests/unit/utils.test.ts | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 74ee3ebd..692d3d79 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -8,8 +8,4 @@ module.exports = { roots: [`/packages/${packageName}`], name: packageName, displayName: packageName, - globals: { - ...baseConfig.globals, - ...{ window: {} }, // https://stackoverflow.com/questions/46274889/jest-test-fails-with-window-is-not-defined - }, }; diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 5f3134a9..02d59d1d 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -103,7 +103,7 @@ export class Agile { if (config.autoIntegrate) { // Integrate Integrations to be initially integrated Integrations.initialIntegrations.forEach((integration) => { - this.integrate(integration); + if (integration instanceof Integration) this.integrate(integration); }); // Setup listener to be notified when an external registered Integration was added diff --git a/packages/core/tests/unit/utils.test.ts b/packages/core/tests/unit/utils.test.ts index d9e57b8d..906baa57 100644 --- a/packages/core/tests/unit/utils.test.ts +++ b/packages/core/tests/unit/utils.test.ts @@ -413,7 +413,7 @@ describe('Utils Tests', () => { describe('runsOnServer function tests', () => { it("should return 'false' if the current environment isn't a server", () => { // eslint-disable-next-line no-global-assign - window = { + global.window = { document: { createElement: 'isSet' as any, } as any, @@ -424,7 +424,7 @@ describe('Utils Tests', () => { it("should return 'true' if the current environment is a server", () => { // eslint-disable-next-line no-global-assign - window = undefined as any; + global.window = undefined as any; expect(Utils.runsOnServer()).toBeTruthy(); }); From 9e66991f7886e1134ab2548f86603ca02cfec92a Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 07:45:37 +0200 Subject: [PATCH 17/19] fixed typos --- packages/core/src/agile.ts | 29 ++--- packages/core/src/integrations/index.ts | 71 ++++++++---- packages/core/src/shared.ts | 7 +- packages/core/tests/unit/agile.test.ts | 44 +++----- .../collection/collection.persistent.test.ts | 3 +- .../tests/unit/collection/collection.test.ts | 3 +- .../collection/group/group.observer.test.ts | 3 +- .../tests/unit/collection/group/group.test.ts | 3 +- .../core/tests/unit/collection/item.test.ts | 3 +- .../tests/unit/collection/selector.test.ts | 3 +- .../core/tests/unit/computed/computed.test.ts | 3 +- .../unit/computed/computed.tracker.test.ts | 3 +- .../unit/integrations/integration.test.ts | 2 +- .../unit/integrations/integrations.test.ts | 105 ++++++++++++------ .../core/tests/unit/runtime/observer.test.ts | 3 +- .../tests/unit/runtime/runtime.job.test.ts | 3 +- .../core/tests/unit/runtime/runtime.test.ts | 3 +- .../CallbackSubscriptionContainer.test.ts | 3 +- .../ComponentSubscriptionContainer.test.ts | 3 +- .../container/SubscriptionContainer.test.ts | 3 +- .../subscription/sub.controller.test.ts | 3 +- packages/core/tests/unit/shared.test.ts | 38 +++++-- .../tests/unit/state/state.observer.test.ts | 3 +- .../tests/unit/state/state.persistent.test.ts | 3 +- .../unit/state/state.runtime.job.test.ts | 3 +- packages/core/tests/unit/state/state.test.ts | 3 +- .../tests/unit/storages/persistent.test.ts | 3 +- .../core/tests/unit/storages/storage.test.ts | 3 +- .../core/tests/unit/storages/storages.test.ts | 3 +- packages/core/tests/unit/utils.test.ts | 11 +- packages/event/tests/unit/event.job.test.ts | 2 +- .../event/tests/unit/event.observer.test.ts | 3 +- packages/event/tests/unit/event.test.ts | 3 +- packages/react/src/react.integration.ts | 2 +- packages/vue/src/vue.integration.ts | 2 +- 35 files changed, 238 insertions(+), 147 deletions(-) diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 02d59d1d..9791ae40 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -26,6 +26,7 @@ import { createState, createCollection, createComputed, + IntegrationsConfigInterface, } from './internal'; export class Agile { @@ -93,25 +94,15 @@ export class Agile { waitForMount: config.waitForMount as any, }; this.key = config.key; - this.integrations = new Integrations(this); + this.integrations = new Integrations(this, { + autoIntegrate: config.autoIntegrate, + }); this.runtime = new Runtime(this); this.subController = new SubController(this); this.storages = new Storages(this, { localStorage: config.localStorage, }); - if (config.autoIntegrate) { - // Integrate Integrations to be initially integrated - Integrations.initialIntegrations.forEach((integration) => { - if (integration instanceof Integration) this.integrate(integration); - }); - - // Setup listener to be notified when an external registered Integration was added - Integrations.onRegisteredExternalIntegration((integration) => { - this.integrate(integration); - }); - } - // Assign customized Logger config to the static Logger this.configureLogger(config.logConfig); @@ -342,7 +333,8 @@ export class Agile { export type AgileKey = string | number; -export interface CreateAgileConfigInterface { +export interface CreateAgileConfigInterface + extends IntegrationsConfigInterface { /** * Configures the logging behaviour of AgileTs. * @default { @@ -372,17 +364,10 @@ export interface CreateAgileConfigInterface { */ bindGlobal?: boolean; /** - * Key/Name identifier of Agile Instance. + * Key/Name identifier of the Agile Instance. * @default undefined */ key?: AgileKey; - /** - * Whether external added Integrations are integrated automatically. - * For example, when the package '@agile-ts/react' was installed, - * whether to automatically integrate the 'reactIntegration' into the Agile Instance. - * @default true - */ - autoIntegrate?: boolean; } export interface AgileConfigInterface { diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index fbc9863d..1efb0805 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -1,6 +1,6 @@ -import { Agile, Integration, LogCodeManager } from '../internal'; +import { Agile, defineConfig, Integration, LogCodeManager } from '../internal'; -const registeredExternalIntegrationsCallbacks: (( +const onRegisterInitialIntegrationCallbacks: (( integration: Integration ) => void)[] = []; @@ -11,22 +11,26 @@ export class Integrations { // Registered Integrations public integrations: Set = new Set(); - // External added Integrations that are to integrate into AgileTs, - // with a proxy wrapped around to listen on external added Integrations. - static initialIntegrations: Integration[] = new Proxy([], { - set: (target, property, value) => { - target[property] = value; - - // Executed external registered Integrations callbacks - if (value instanceof Integration) { - registeredExternalIntegrationsCallbacks.forEach((callback) => - callback(value) - ); - } + // External added Integrations + // that are to integrate into each created Agile Instance + static initialIntegrations: Integration[] = []; + + /** + * Adds an external Integration to be registered in each Agile Instance created. + * + * @public + * @param integration - Integration to be registered in each Agile Instance created. + */ + static addInitialIntegration(integration: Integration): void { + if (integration instanceof Integration) { + // Executed external registered Integration callbacks + onRegisterInitialIntegrationCallbacks.forEach((callback) => + callback(integration) + ); - return true; - }, - }); + Integrations.initialIntegrations.push(integration); + } + } /** * Fires on each external added Integration. @@ -34,10 +38,10 @@ export class Integrations { * @public * @param callback - Callback to be fired when an Integration was added externally. */ - static onRegisteredExternalIntegration( + static onRegisterInitialIntegration( callback: (integration: Integration) => void ): void { - registeredExternalIntegrationsCallbacks.push(callback); + onRegisterInitialIntegrationCallbacks.push(callback); } /** @@ -47,9 +51,25 @@ export class Integrations { * * @internal * @param agileInstance - Instance of Agile the Integrations belongs to. + * @param config - Configuration object */ - constructor(agileInstance: Agile) { + constructor(agileInstance: Agile, config: IntegrationsConfigInterface = {}) { + config = defineConfig(config, { + autoIntegrate: true, + }); this.agileInstance = () => agileInstance; + + if (config.autoIntegrate) { + // Integrate Integrations to be initially integrated + Integrations.initialIntegrations.forEach((integration) => { + this.integrate(integration); + }); + + // Setup listener to be notified when an external registered Integration was added + Integrations.onRegisterInitialIntegration((integration) => { + this.integrate(integration); + }); + } } /** @@ -112,3 +132,14 @@ export class Integrations { return this.integrations.size > 0; } } + +export interface IntegrationsConfigInterface { + /** + * Whether external added Integrations + * are to integrate automatically into the Integrations Class. + * For example, when the package '@agile-ts/react' was installed, + * whether to automatically integrate the 'reactIntegration'. + * @default true + */ + autoIntegrate?: boolean; +} diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index a9ab845e..f76034ee 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -19,18 +19,17 @@ import { } from './internal'; /** - * Shared Agile Instance that is used when no Agile Instance was specified + * Shared Agile Instance that is used when no Agile Instance was specified. */ -// eslint-disable-next-line prefer-const let sharedAgileInstance = new Agile({ key: 'shared', logConfig: { prefix: 'Agile', level: Logger.level.WARN, active: true }, localStorage: !runsOnServer(), }); -export { sharedAgileInstance as shared }; +export const shared = sharedAgileInstance; /** - * Assigns a new Agile Instance as the shared Agile Instance. + * Assigns the specified Agile Instance as the shared Agile Instance. * * @param agileInstance - Agile Instance to become the new shared Agile Instance. */ diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 9a5b2acf..02ae2ef7 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -9,7 +9,6 @@ import { Collection, Logger, Storages, - Integration, } from '../../src'; import testIntegration from '../helper/test.integration'; import { LogMock } from '../helper/logMock'; @@ -66,10 +65,7 @@ describe('Agile Tests', () => { typeof Integrations >; - let dummyIntegration: Integration; - beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); // Clear specified mocks @@ -81,25 +77,25 @@ describe('Agile Tests', () => { // Reset globalThis globalThis[Agile.globalKey] = undefined; - dummyIntegration = new Integration({ key: 'dummyIntegrationKey' }); - jest.spyOn(Agile.prototype, 'configureLogger'); jest.spyOn(Agile.prototype, 'integrate'); - }); - it('should instantiate Agile with initialIntegrations (default config)', () => { - Integrations.initialIntegrations = [dummyIntegration]; + jest.clearAllMocks(); + }); + it('should instantiate Agile (default config)', () => { const agile = new Agile(); expect(agile.config).toStrictEqual({ waitForMount: true, }); expect(agile.key).toBeUndefined(); - expect(IntegrationsMock).toHaveBeenCalledWith(agile); - // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock + expect(IntegrationsMock).toHaveBeenCalledWith(agile, { + autoIntegrate: true, + }); + // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock (mockImplementation) expect(RuntimeMock).toHaveBeenCalledWith(agile); - // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock + // 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, { @@ -108,18 +104,11 @@ describe('Agile Tests', () => { expect(agile.storages).toBeInstanceOf(Storages); expect(agile.configureLogger).toHaveBeenCalledWith({}); - expect(Integrations.onRegisteredExternalIntegration).toHaveBeenCalledWith( - expect.any(Function) - ); - expect(agile.integrate).toHaveBeenCalledWith(dummyIntegration); - // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBeUndefined(); }); - it('should instantiate Agile with initialIntegrations (specific config)', () => { - Integrations.initialIntegrations = [dummyIntegration]; - + it('should instantiate Agile (specific config)', () => { const agile = new Agile({ waitForMount: false, localStorage: false, @@ -138,10 +127,12 @@ describe('Agile Tests', () => { waitForMount: false, }); expect(agile.key).toBe('jeff'); - expect(IntegrationsMock).toHaveBeenCalledWith(agile); - // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock + expect(IntegrationsMock).toHaveBeenCalledWith(agile, { + autoIntegrate: false, + }); + // expect(agile.integrations).toBeInstanceOf(Integrations); // Because 'Integrations' is completely overwritten with a mock (mockImplementation) expect(RuntimeMock).toHaveBeenCalledWith(agile); - // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock + // 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, { @@ -155,17 +146,14 @@ describe('Agile Tests', () => { timestamp: true, }); - expect(Integrations.onRegisteredExternalIntegration).not.toHaveBeenCalled(); - expect(agile.integrate).not.toHaveBeenCalled(); - // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBe(agile); }); it( 'should instantiate second Agile Instance ' + - 'and print warning when an attempt is made to set the second Instance globally ' + - 'if the previously defined instance has also been set globally', + 'and print warning when an attempt is made to set the second Agile Instance globally ' + + 'although the previously defined Agile Instance is already globally set', () => { const agile1 = new Agile({ bindGlobal: true, diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index 3ebbc97c..0c746a27 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -20,7 +20,6 @@ describe('CollectionPersistent Tests', () => { let dummyCollection: Collection; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -30,6 +29,8 @@ describe('CollectionPersistent Tests', () => { jest.spyOn(CollectionPersistent.prototype, 'instantiatePersistent'); jest.spyOn(CollectionPersistent.prototype, 'initialLoading'); + + jest.clearAllMocks(); }); it('should create CollectionPersistent and should call initialLoading if Persistent is ready (default config)', () => { diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 47db19a4..e5b0e686 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -22,7 +22,6 @@ describe('Collection Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -30,6 +29,8 @@ describe('Collection Tests', () => { jest.spyOn(Collection.prototype, 'initSelectors'); jest.spyOn(Collection.prototype, 'initGroups'); jest.spyOn(Collection.prototype, 'collect'); + + jest.clearAllMocks(); }); it('should create Collection (default config)', () => { 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 f24e14d6..a295262e 100644 --- a/packages/core/tests/unit/collection/group/group.observer.test.ts +++ b/packages/core/tests/unit/collection/group/group.observer.test.ts @@ -24,7 +24,6 @@ describe('GroupObserver Tests', () => { let dummyItem2: Item; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -40,6 +39,8 @@ describe('GroupObserver Tests', () => { id: 'dummyItem2Key', name: 'jeff', }); + + jest.clearAllMocks(); }); it('should create Group Observer (default config)', () => { diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 7aaceb4f..f9d48a74 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -21,7 +21,6 @@ describe('Group Tests', () => { let dummyCollection: Collection; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -31,6 +30,8 @@ describe('Group Tests', () => { jest.spyOn(Group.prototype, 'rebuild'); jest.spyOn(Group.prototype, 'addSideEffect'); + + jest.clearAllMocks(); }); it('should create Group with no initialItems (default config)', () => { diff --git a/packages/core/tests/unit/collection/item.test.ts b/packages/core/tests/unit/collection/item.test.ts index b50b620a..374352b8 100644 --- a/packages/core/tests/unit/collection/item.test.ts +++ b/packages/core/tests/unit/collection/item.test.ts @@ -18,13 +18,14 @@ describe('Item Tests', () => { let dummyCollection: Collection; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); dummyCollection = new Collection(dummyAgile); jest.spyOn(Item.prototype, 'addRebuildGroupThatIncludeItemKeySideEffect'); + + jest.clearAllMocks(); }); it('should create Item (default config)', () => { diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts index 092a0d4c..11080f9f 100644 --- a/packages/core/tests/unit/collection/selector.test.ts +++ b/packages/core/tests/unit/collection/selector.test.ts @@ -11,13 +11,14 @@ describe('Selector Tests', () => { let dummyCollection: Collection; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); dummyCollection = new Collection(dummyAgile); jest.spyOn(Selector.prototype, 'select'); + + jest.clearAllMocks(); }); it('should create Selector and call initial select (default config)', () => { diff --git a/packages/core/tests/unit/computed/computed.test.ts b/packages/core/tests/unit/computed/computed.test.ts index 7f4c58fe..ef88decc 100644 --- a/packages/core/tests/unit/computed/computed.test.ts +++ b/packages/core/tests/unit/computed/computed.test.ts @@ -14,13 +14,14 @@ describe('Computed Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); jest.spyOn(Computed.prototype, 'recompute'); jest.spyOn(Utils, 'extractRelevantObservers'); + + jest.clearAllMocks(); }); it('should create Computed with a not async compute method (default config)', () => { diff --git a/packages/core/tests/unit/computed/computed.tracker.test.ts b/packages/core/tests/unit/computed/computed.tracker.test.ts index 81483956..20e09dc1 100644 --- a/packages/core/tests/unit/computed/computed.tracker.test.ts +++ b/packages/core/tests/unit/computed/computed.tracker.test.ts @@ -5,7 +5,6 @@ describe('ComputedTracker Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -13,6 +12,8 @@ describe('ComputedTracker Tests', () => { // Reset ComputedTracker (because it works static) ComputedTracker.isTracking = false; ComputedTracker.trackedObservers = new Set(); + + jest.clearAllMocks(); }); describe('ComputedTracker Function Tests', () => { diff --git a/packages/core/tests/unit/integrations/integration.test.ts b/packages/core/tests/unit/integrations/integration.test.ts index f9cac640..a73cc5c9 100644 --- a/packages/core/tests/unit/integrations/integration.test.ts +++ b/packages/core/tests/unit/integrations/integration.test.ts @@ -3,8 +3,8 @@ import { LogMock } from '../../helper/logMock'; describe('Integration Tests', () => { beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); + jest.clearAllMocks(); }); it('should create Integration', () => { diff --git a/packages/core/tests/unit/integrations/integrations.test.ts b/packages/core/tests/unit/integrations/integrations.test.ts index 2d903629..b8550a22 100644 --- a/packages/core/tests/unit/integrations/integrations.test.ts +++ b/packages/core/tests/unit/integrations/integrations.test.ts @@ -3,63 +3,104 @@ import { LogMock } from '../../helper/logMock'; describe('Integrations Tests', () => { let dummyAgile: Agile; + let dummyIntegration1: Integration; + let dummyIntegration2: Integration; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); + dummyIntegration1 = new Integration({ + key: 'dummyIntegration1', + }); + dummyIntegration2 = new Integration({ + key: 'dummyIntegration2', + }); + + Integrations.initialIntegrations = []; jest.spyOn(Integrations.prototype, 'integrate'); + jest.spyOn(Integrations, 'onRegisterInitialIntegration'); + + jest.clearAllMocks(); }); - it('should create Integrations', () => { + it('should create Integrations with the before specified initial Integrations (default config)', () => { + Integrations.initialIntegrations = [dummyIntegration1, dummyIntegration2]; + const integrations = new Integrations(dummyAgile); + expect(Array.from(integrations.integrations)).toStrictEqual([ + dummyIntegration1, + dummyIntegration2, + ]); + + expect(Integrations.onRegisterInitialIntegration).toHaveBeenCalledWith( + expect.any(Function) + ); + expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration1); + expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration2); + }); + + it('should create Integrations without the before specified initial Integrations (specific config)', () => { + Integrations.initialIntegrations = [dummyIntegration1, dummyIntegration2]; + + const integrations = new Integrations(dummyAgile, { autoIntegrate: false }); + expect(Array.from(integrations.integrations)).toStrictEqual([]); + + expect(Integrations.onRegisterInitialIntegration).not.toHaveBeenCalled(); + expect(integrations.integrate).not.toHaveBeenCalled(); }); describe('Integrations Function Tests', () => { let integrations: Integrations; - let dummyIntegration1: Integration; - let dummyIntegration2: Integration; beforeEach(() => { integrations = new Integrations(dummyAgile); - dummyIntegration1 = new Integration({ - key: 'dummyIntegration1', - }); - dummyIntegration2 = new Integration({ - key: 'dummyIntegration2', - }); }); - describe('onRegisteredExternalIntegration', () => { - let dummyIntegration1: Integration; - let dummyIntegration2: Integration; - - beforeEach(() => { - dummyIntegration1 = new Integration({ - key: 'initialIntegration1', - }); - dummyIntegration2 = new Integration({ - key: 'initialIntegration2', - }); + describe('onRegisterInitialIntegration function tests', () => { + it('should register specified onRegisterInitialIntegration callback', () => { + // Nothing to testable }); + }); - it('should register callback and fire it, when an external Integration was added', () => { - const callback = jest.fn(); - - Integrations.onRegisteredExternalIntegration(callback); - - Integrations.initialIntegrations.push(dummyIntegration1); - Integrations.initialIntegrations.push(undefined as any); - Integrations.initialIntegrations.push(dummyIntegration2); + describe('addInitialIntegration function tests', () => { + const callback1 = jest.fn(); + const callback2 = jest.fn(); - expect(callback).toHaveBeenCalledTimes(2); - expect(callback).toHaveBeenCalledWith(dummyIntegration1); - expect(callback).toHaveBeenCalledWith(dummyIntegration2); + beforeEach(() => { + Integrations.onRegisterInitialIntegration(callback1); + Integrations.onRegisterInitialIntegration(callback2); }); + it( + 'should add valid Integration to the initialIntegrations array ' + + 'and fire the onRegisterInitialIntegration callbacks', + () => { + Integrations.addInitialIntegration(dummyIntegration1); + + expect(callback1).toHaveBeenCalledTimes(1); + expect(callback1).toHaveBeenCalledWith(dummyIntegration1); + expect(callback2).toHaveBeenCalledTimes(1); + expect(callback2).toHaveBeenCalledWith(dummyIntegration1); + expect(Integrations.initialIntegrations).toStrictEqual([ + dummyIntegration1, + ]); + } + ); + + it( + "shouldn't add invalid Integration to the initialIntegrations array " + + "and shouldn't fire the onRegisterInitialIntegration callbacks", + () => { + Integrations.addInitialIntegration(undefined as any); + + expect(callback1).not.toHaveBeenCalled(); + expect(callback2).not.toHaveBeenCalled(); + expect(Integrations.initialIntegrations).toStrictEqual([]); + } + ); }); describe('integrate function tests', () => { diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts index 59889d40..5810c138 100644 --- a/packages/core/tests/unit/runtime/observer.test.ts +++ b/packages/core/tests/unit/runtime/observer.test.ts @@ -15,7 +15,6 @@ describe('Observer Tests', () => { let dummySubscription2: SubscriptionContainer; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile(); @@ -26,6 +25,8 @@ describe('Observer Tests', () => { jest.spyOn(dummySubscription1, 'addSubscription'); jest.spyOn(dummySubscription2, 'addSubscription'); + + jest.clearAllMocks(); }); it('should create Observer (default config)', () => { diff --git a/packages/core/tests/unit/runtime/runtime.job.test.ts b/packages/core/tests/unit/runtime/runtime.job.test.ts index 4793df21..b01c3765 100644 --- a/packages/core/tests/unit/runtime/runtime.job.test.ts +++ b/packages/core/tests/unit/runtime/runtime.job.test.ts @@ -7,7 +7,6 @@ describe('RuntimeJob Tests', () => { let dummyObserver: Observer; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -15,6 +14,8 @@ describe('RuntimeJob Tests', () => { key: 'myIntegration', }); dummyObserver = new Observer(dummyAgile); + + jest.clearAllMocks(); }); it( diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 2906607d..f72b5736 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -14,10 +14,11 @@ describe('Runtime Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); + + jest.clearAllMocks(); }); it('should create Runtime', () => { diff --git a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts index e8bea19e..7f342dc9 100644 --- a/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/CallbackSubscriptionContainer.test.ts @@ -15,7 +15,6 @@ describe('CallbackSubscriptionContainer Tests', () => { let dummyProxyWeakMap: ProxyWeakMapType; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile(); @@ -23,6 +22,8 @@ describe('CallbackSubscriptionContainer Tests', () => { dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); dummySelectorWeakMap = new WeakMap(); dummyProxyWeakMap = new WeakMap(); + + jest.clearAllMocks(); }); it('should create CallbackSubscriptionContainer', () => { diff --git a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts index 4401cf88..9efe4262 100644 --- a/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/ComponentSubscriptionContainer.test.ts @@ -15,7 +15,6 @@ describe('ComponentSubscriptionContainer Tests', () => { let dummyProxyWeakMap: ProxyWeakMapType; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile(); @@ -23,6 +22,8 @@ describe('ComponentSubscriptionContainer Tests', () => { dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); dummySelectorWeakMap = new WeakMap(); dummyProxyWeakMap = new WeakMap(); + + jest.clearAllMocks(); }); it('should create ComponentSubscriptionContainer', () => { diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts index c3aa012d..ce983e95 100644 --- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts @@ -16,7 +16,6 @@ describe('SubscriptionContainer Tests', () => { let dummyProxyWeakMap: ProxyWeakMapType; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile(); @@ -24,6 +23,8 @@ describe('SubscriptionContainer Tests', () => { dummyObserver2 = new Observer(dummyAgile, { key: 'dummyObserver2' }); dummySelectorWeakMap = new WeakMap(); dummyProxyWeakMap = new WeakMap(); + + jest.clearAllMocks(); }); it('should create SubscriptionContainer with passed subs array (default config)', () => { 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 e52e81a1..6d73d851 100644 --- a/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts +++ b/packages/core/tests/unit/runtime/subscription/sub.controller.test.ts @@ -12,10 +12,11 @@ describe('SubController Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); + + jest.clearAllMocks(); }); it('should create SubController', () => { diff --git a/packages/core/tests/unit/shared.test.ts b/packages/core/tests/unit/shared.test.ts index f4000909..fc81e6e8 100644 --- a/packages/core/tests/unit/shared.test.ts +++ b/packages/core/tests/unit/shared.test.ts @@ -9,6 +9,7 @@ import { createState, createCollection, createComputed, + assignSharedAgileInstance, } from '../../src'; import { LogMock } from '../helper/logMock'; @@ -24,9 +25,25 @@ jest.mock('../../src/state', () => { }); describe('Shared Tests', () => { + let sharedAgileInstance: Agile; + beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); + + sharedAgileInstance = new Agile(); + assignSharedAgileInstance(sharedAgileInstance); + + jest.clearAllMocks(); + }); + + describe('assignSharedAgileInstance function tests', () => { + it('should assign the specified Agile Instance as new shared Agile Instance', () => { + const newAgileInstance = new Agile({ key: 'notShared' }); + + assignSharedAgileInstance(newAgileInstance); + + expect(shared).toBe(newAgileInstance); + }); }); describe('createStorage function tests', () => { @@ -69,7 +86,7 @@ describe('Shared Tests', () => { }); expect(state).toBeInstanceOf(State); - expect(StateMock).toHaveBeenCalledWith(shared, 'testValue', { + expect(StateMock).toHaveBeenCalledWith(sharedAgileInstance, 'testValue', { key: 'myCoolState', }); }); @@ -107,7 +124,10 @@ describe('Shared Tests', () => { const collection = createCollection(collectionConfig); expect(collection).toBeInstanceOf(Collection); - expect(CollectionMock).toHaveBeenCalledWith(shared, collectionConfig); + expect(CollectionMock).toHaveBeenCalledWith( + sharedAgileInstance, + collectionConfig + ); }); it('should create Collection with a specified Agile Instance', () => { @@ -140,9 +160,13 @@ describe('Shared Tests', () => { const response = createComputed(computedFunction, ['dummyDep' as any]); expect(response).toBeInstanceOf(Computed); - expect(ComputedMock).toHaveBeenCalledWith(shared, computedFunction, { - computedDeps: ['dummyDep' as any], - }); + expect(ComputedMock).toHaveBeenCalledWith( + sharedAgileInstance, + computedFunction, + { + computedDeps: ['dummyDep' as any], + } + ); }); it('should create Computed with the shared Agile Instance (specific config)', () => { @@ -157,7 +181,7 @@ describe('Shared Tests', () => { expect(response).toBeInstanceOf(Computed); expect(ComputedMock).toHaveBeenCalledWith( - shared, + sharedAgileInstance, computedFunction, computedConfig ); diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts index 32a99109..035ec4a5 100644 --- a/packages/core/tests/unit/state/state.observer.test.ts +++ b/packages/core/tests/unit/state/state.observer.test.ts @@ -17,11 +17,12 @@ describe('StateObserver Tests', () => { let dummyState: State; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); dummyState = new State(dummyAgile, 'dummyValue', { key: 'dummyState' }); + + jest.clearAllMocks(); }); it('should create State Observer (default config)', () => { diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index 1fc7d061..65603523 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -12,7 +12,6 @@ describe('StatePersistent Tests', () => { let dummyState: State; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -20,6 +19,8 @@ describe('StatePersistent Tests', () => { jest.spyOn(StatePersistent.prototype, 'instantiatePersistent'); jest.spyOn(StatePersistent.prototype, 'initialLoading'); + + jest.clearAllMocks(); }); it("should create StatePersistent and shouldn't call initialLoading if Persistent isn't ready (default config)", () => { 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 58906f56..85df6e56 100644 --- a/packages/core/tests/unit/state/state.runtime.job.test.ts +++ b/packages/core/tests/unit/state/state.runtime.job.test.ts @@ -16,7 +16,6 @@ describe('RuntimeJob Tests', () => { let dummyObserver: StateObserver; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); @@ -25,6 +24,8 @@ describe('RuntimeJob Tests', () => { }); dummyState = new State(dummyAgile, 'dummyValue'); dummyObserver = new StateObserver(dummyState); + + jest.clearAllMocks(); }); it( diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index 3bfe253f..78b3afe9 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -15,12 +15,13 @@ describe('State Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); jest.spyOn(State.prototype, 'set'); + + jest.clearAllMocks(); }); it('should create State and should call initial set (default config)', () => { diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index 79ca7ca2..b038fff3 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -5,12 +5,13 @@ describe('Persistent Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); jest.spyOn(Persistent.prototype, 'instantiatePersistent'); + + jest.clearAllMocks(); }); it('should create Persistent (default config)', () => { diff --git a/packages/core/tests/unit/storages/storage.test.ts b/packages/core/tests/unit/storages/storage.test.ts index 1d1442c8..bfe220c0 100644 --- a/packages/core/tests/unit/storages/storage.test.ts +++ b/packages/core/tests/unit/storages/storage.test.ts @@ -5,7 +5,6 @@ describe('Storage Tests', () => { let dummyStorageMethods; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyStorageMethods = { @@ -16,6 +15,8 @@ describe('Storage Tests', () => { // https://codewithhugo.com/jest-stub-mock-spy-set-clear/ jest.spyOn(Storage.prototype, 'validate'); + + jest.clearAllMocks(); }); it('should create not async Storage with normal Storage Methods (default config)', () => { diff --git a/packages/core/tests/unit/storages/storages.test.ts b/packages/core/tests/unit/storages/storages.test.ts index efdb57a3..139a7e29 100644 --- a/packages/core/tests/unit/storages/storages.test.ts +++ b/packages/core/tests/unit/storages/storages.test.ts @@ -5,12 +5,13 @@ describe('Storages Tests', () => { let dummyAgile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); jest.spyOn(Storages.prototype, 'instantiateLocalStorage'); + + jest.clearAllMocks(); }); it('should create Storages (default config)', () => { diff --git a/packages/core/tests/unit/utils.test.ts b/packages/core/tests/unit/utils.test.ts index 906baa57..96decb42 100644 --- a/packages/core/tests/unit/utils.test.ts +++ b/packages/core/tests/unit/utils.test.ts @@ -14,13 +14,14 @@ describe('Utils Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); // @ts-ignore | Reset globalThis globalThis = {}; + + jest.clearAllMocks(); }); describe('getAgileInstance function tests', () => { @@ -48,7 +49,7 @@ describe('Utils Tests', () => { }); it( - 'should return shared Agile Instance' + + 'should return shared Agile Instance ' + 'if specified Instance contains no valid Agile Instance', () => { expect(Utils.getAgileInstance('weiredInstance')).toBe(dummyAgile); @@ -56,9 +57,9 @@ describe('Utils Tests', () => { ); it( - 'should return Agile Instance from globalThis' + + 'should return globally bound Agile Instance' + 'if specified Instance contains no valid Agile Instance' + - 'and no shared Agile Instance was specified', + 'and no shared Agile Instance is specified', () => { // Destroy shared Agile Instance assignSharedAgileInstance(undefined as any); @@ -412,7 +413,6 @@ describe('Utils Tests', () => { describe('runsOnServer function tests', () => { it("should return 'false' if the current environment isn't a server", () => { - // eslint-disable-next-line no-global-assign global.window = { document: { createElement: 'isSet' as any, @@ -423,7 +423,6 @@ describe('Utils Tests', () => { }); it("should return 'true' if the current environment is a server", () => { - // eslint-disable-next-line no-global-assign global.window = undefined as any; expect(Utils.runsOnServer()).toBeTruthy(); diff --git a/packages/event/tests/unit/event.job.test.ts b/packages/event/tests/unit/event.job.test.ts index 909908cf..90397943 100644 --- a/packages/event/tests/unit/event.job.test.ts +++ b/packages/event/tests/unit/event.job.test.ts @@ -3,8 +3,8 @@ import { LogMock } from '../../../core/tests/helper/logMock'; describe('EventJob Tests', () => { beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); + jest.clearAllMocks(); }); it('should create EventJob (without keys)', () => { diff --git a/packages/event/tests/unit/event.observer.test.ts b/packages/event/tests/unit/event.observer.test.ts index ba74713a..b700a72f 100644 --- a/packages/event/tests/unit/event.observer.test.ts +++ b/packages/event/tests/unit/event.observer.test.ts @@ -7,11 +7,12 @@ describe('EventObserver Tests', () => { let dummyEvent: Event; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); dummyEvent = new Event(dummyAgile); + + jest.clearAllMocks(); }); it('should create EventObserver (default config)', () => { diff --git a/packages/event/tests/unit/event.test.ts b/packages/event/tests/unit/event.test.ts index a6564755..bd369952 100644 --- a/packages/event/tests/unit/event.test.ts +++ b/packages/event/tests/unit/event.test.ts @@ -7,10 +7,11 @@ describe('Event Tests', () => { let dummyAgile: Agile; beforeEach(() => { - jest.clearAllMocks(); LogMock.mockLogs(); dummyAgile = new Agile({ localStorage: false }); + + jest.clearAllMocks(); }); it('should create Event (default config)', () => { diff --git a/packages/react/src/react.integration.ts b/packages/react/src/react.integration.ts index 78a92f5a..e33cec11 100644 --- a/packages/react/src/react.integration.ts +++ b/packages/react/src/react.integration.ts @@ -24,6 +24,6 @@ const reactIntegration = new Integration({ } }, }); -Integrations.initialIntegrations.push(reactIntegration); +Integrations.addInitialIntegration.push(reactIntegration); export default reactIntegration; diff --git a/packages/vue/src/vue.integration.ts b/packages/vue/src/vue.integration.ts index deb214b5..c9502756 100644 --- a/packages/vue/src/vue.integration.ts +++ b/packages/vue/src/vue.integration.ts @@ -80,6 +80,6 @@ const vueIntegration = new Integration({ return Promise.resolve(true); }, }); -Integrations.initialIntegrations.push(vueIntegration); +Integrations.addInitialIntegration.push(vueIntegration); export default vueIntegration; From 61601d1376e99a749ed12b331596d6e3a95cbefb Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 08:04:12 +0200 Subject: [PATCH 18/19] fixed tests --- packages/core/src/shared.ts | 2 +- .../collection.persistent.integration.test.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index f76034ee..d433eaab 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -26,7 +26,7 @@ let sharedAgileInstance = new Agile({ logConfig: { prefix: 'Agile', level: Logger.level.WARN, active: true }, localStorage: !runsOnServer(), }); -export const shared = sharedAgileInstance; +export { sharedAgileInstance as shared }; /** * Assigns the specified Agile Instance as the shared Agile Instance. diff --git a/packages/core/tests/integration/collection.persistent.integration.test.ts b/packages/core/tests/integration/collection.persistent.integration.test.ts index 203fbfd4..e1d151ee 100644 --- a/packages/core/tests/integration/collection.persistent.integration.test.ts +++ b/packages/core/tests/integration/collection.persistent.integration.test.ts @@ -17,16 +17,7 @@ describe('Collection Persist Function Tests', () => { delete myStorage[key]; }), }; - - // Define Agile with Storage - const App = new Agile({ localStorage: false }); - App.registerStorage( - App.createStorage({ - key: 'testStorage', - prefix: 'test', - methods: storageMethods, - }) - ); + let App: Agile; interface User { id: number; @@ -36,6 +27,15 @@ describe('Collection Persist Function Tests', () => { beforeEach(() => { LogMock.mockLogs(); jest.clearAllMocks(); + + App = new Agile({ localStorage: false }); + App.registerStorage( + App.createStorage({ + key: 'testStorage', + prefix: 'test', + methods: storageMethods, + }) + ); }); describe('Collection', () => { From f5aa298ce81d69103408697d53a8d73e6056cbd9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 08:05:24 +0200 Subject: [PATCH 19/19] fixed typo --- packages/react/src/react.integration.ts | 2 +- packages/vue/src/vue.integration.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/react.integration.ts b/packages/react/src/react.integration.ts index e33cec11..2ba63ec8 100644 --- a/packages/react/src/react.integration.ts +++ b/packages/react/src/react.integration.ts @@ -24,6 +24,6 @@ const reactIntegration = new Integration({ } }, }); -Integrations.addInitialIntegration.push(reactIntegration); +Integrations.addInitialIntegration(reactIntegration); export default reactIntegration; diff --git a/packages/vue/src/vue.integration.ts b/packages/vue/src/vue.integration.ts index c9502756..d6a367ec 100644 --- a/packages/vue/src/vue.integration.ts +++ b/packages/vue/src/vue.integration.ts @@ -80,6 +80,6 @@ const vueIntegration = new Integration({ return Promise.resolve(true); }, }); -Integrations.addInitialIntegration.push(vueIntegration); +Integrations.addInitialIntegration(vueIntegration); export default vueIntegration;