diff --git a/README.md b/README.md
index 3a1ae771..da0c7031 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-> Global, simple, spacy State and Logic Framework
+> Global State and Logic Framework
@@ -42,20 +42,20 @@
```tsx
// -- core.js ------------------------------------------
-// 1️⃣ Create Instance of AgileTs
-const App = new Agile();
-
-// 2️⃣ Create State with help of before defined Agile Instance
-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 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!")
+// 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).
@@ -75,59 +75,56 @@ More examples can be found in the [Example Section](https://agile-ts.org/docs/ex
-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.
+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)
+and [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
-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.
+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).
@@ -136,17 +133,18 @@ you can jump straight into our [Example](https://agile-ts.org/docs/examples/Intr
-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
```
@@ -158,10 +156,11 @@ In order to properly use AgileTs, in a UI-Framework, we need to install **two**
-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).
@@ -184,17 +183,17 @@ To find out more about contributing, check out the [CONTRIBUTING.md](https://git
-| Name | Latest Version | Description |
-| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
-| [@agile-ts/core](/packages/core) | [](https://www.npmjs.com/package/@agile-ts/core) | State Manager |
-| [@agile-ts/react](/packages/react) | [](https://www.npmjs.com/package/@agile-ts/react) | React Integration |
-| [@agile-ts/vue](/packages/vue) | [](https://www.npmjs.com/package/@agile-ts/vue) | Vue Integration |
-| [@agile-ts/api](/packages/api) | [](https://www.npmjs.com/package/@agile-ts/api) | Promise based API |
-| [@agile-ts/multieditor](/packages/multieditor) | [](https://www.npmjs.com/package/@agile-ts/multieditor) | Simple Form Manager |
-| [@agile-ts/event](/packages/event) | [](https://www.npmjs.com/package/@agile-ts/event) | Handy class for emitting UI Events |
-| [@agile-ts/logger](/packages/logger) | [](https://www.npmjs.com/package/@agile-ts/logger) | Manages the logging of AgileTs |
-| [@agile-ts/utils](/packages/utils) | [](https://www.npmjs.com/package/@agile-ts/utils) | Util functions of AgileTs |
-| [@agile-ts/proxytree](/packages/proxytree) | [](https://www.npmjs.com/package/@agile-ts/proxytree) | Create Proxy Tree |
+| Name | Latest Version | Description |
+| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
+| [@agile-ts/core](/packages/core) | [](https://www.npmjs.com/package/@agile-ts/core) | State Manager Logic |
+| [@agile-ts/react](/packages/react) | [](https://www.npmjs.com/package/@agile-ts/react) | React Integration |
+| [@agile-ts/vue](/packages/vue) | [](https://www.npmjs.com/package/@agile-ts/vue) | Vue Integration |
+| [@agile-ts/api](/packages/api) | [](https://www.npmjs.com/package/@agile-ts/api) | Promise based API |
+| [@agile-ts/multieditor](/packages/multieditor) | [](https://www.npmjs.com/package/@agile-ts/multieditor) | Simple Form Manager |
+| [@agile-ts/event](/packages/event) | [](https://www.npmjs.com/package/@agile-ts/event) | Handy class for emitting UI Events |
+| [@agile-ts/logger](/packages/logger) | [](https://www.npmjs.com/package/@agile-ts/logger) | Logging API of AgileTs |
+| [@agile-ts/utils](/packages/utils) | [](https://www.npmjs.com/package/@agile-ts/utils) | Utilities of AgileTs |
+| [@agile-ts/proxytree](/packages/proxytree) | [](https://www.npmjs.com/package/@agile-ts/proxytree) | Proxy Tree for tracking accessed properties | |
@@ -202,4 +201,6 @@ To find out more about contributing, check out the [CONTRIBUTING.md](https://git
-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/).
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/src/agile.ts b/packages/core/src/agile.ts
index adf0419c..9791ae40 100644
--- a/packages/core/src/agile.ts
+++ b/packages/core/src/agile.ts
@@ -22,11 +22,19 @@ import {
DependableAgileInstancesType,
CreateComputedConfigInterface,
ComputeFunctionType,
+ createStorage,
+ createState,
+ createCollection,
+ createComputed,
+ IntegrationsConfigInterface,
} from './internal';
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
@@ -34,12 +42,10 @@ 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;
- // External added Integrations that are to integrate into AgileTs when it is instantiated
- static initialIntegrations: Integration[] = [];
- // 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',
@@ -82,18 +88,15 @@ export class Agile {
waitForMount: true,
logConfig: {},
bindGlobal: false,
- });
- config.logConfig = defineConfig(config.logConfig, {
- prefix: 'Agile',
- active: true,
- level: Logger.level.WARN,
- canUseCustomStyles: true,
- allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'],
+ autoIntegrate: true,
});
this.config = {
waitForMount: config.waitForMount as any,
};
- this.integrations = new Integrations(this);
+ this.key = config.key;
+ this.integrations = new Integrations(this, {
+ autoIntegrate: config.autoIntegrate,
+ });
this.runtime = new Runtime(this);
this.subController = new SubController(this);
this.storages = new Storages(this, {
@@ -101,7 +104,7 @@ export class Agile {
});
// 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);
@@ -109,7 +112,27 @@ export class Agile {
// Why? 'getAgileInstance()' returns the global Agile Instance
// if it couldn't find any Agile Instance in the specified Instance.
if (config.bindGlobal)
- if (!globalBind(Agile.globalKey, this)) LogCodeManager.log('10:02:00');
+ 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;
}
/**
@@ -128,7 +151,7 @@ export class Agile {
* @param config - Configuration object
*/
public createStorage(config: CreateStorageConfigInterface): Storage {
- return new Storage(config);
+ return createStorage(config);
}
/**
@@ -150,7 +173,10 @@ export class Agile {
initialValue: ValueType,
config: StateConfigInterface = {}
): State {
- return new State(this, initialValue, config);
+ return createState(initialValue, {
+ ...config,
+ ...{ agileInstance: this },
+ });
}
/**
@@ -174,7 +200,7 @@ export class Agile {
public createCollection(
config?: CollectionConfig
): Collection {
- return new Collection(this, config);
+ return createCollection(config, this);
}
/**
@@ -232,12 +258,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);
}
/**
@@ -303,7 +331,10 @@ export class Agile {
}
}
-export interface CreateAgileConfigInterface {
+export type AgileKey = string | number;
+
+export interface CreateAgileConfigInterface
+ extends IntegrationsConfigInterface {
/**
* Configures the logging behaviour of AgileTs.
* @default {
@@ -332,6 +363,11 @@ export interface CreateAgileConfigInterface {
* @default false
*/
bindGlobal?: boolean;
+ /**
+ * Key/Name identifier of the Agile Instance.
+ * @default undefined
+ */
+ key?: AgileKey;
}
export interface AgileConfigInterface {
diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts
index 038166db..1efb0805 100644
--- a/packages/core/src/integrations/index.ts
+++ b/packages/core/src/integrations/index.ts
@@ -1,4 +1,8 @@
-import { Agile, Integration, LogCodeManager } from '../internal';
+import { Agile, defineConfig, Integration, LogCodeManager } from '../internal';
+
+const onRegisterInitialIntegrationCallbacks: ((
+ integration: Integration
+) => void)[] = [];
export class Integrations {
// Agile Instance the Integrations belongs to
@@ -7,6 +11,39 @@ export class Integrations {
// Registered Integrations
public integrations: Set = new Set();
+ // 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)
+ );
+
+ Integrations.initialIntegrations.push(integration);
+ }
+ }
+
+ /**
+ * Fires on each external added Integration.
+ *
+ * @public
+ * @param callback - Callback to be fired when an Integration was added externally.
+ */
+ static onRegisterInitialIntegration(
+ callback: (integration: Integration) => void
+ ): void {
+ onRegisterInitialIntegrationCallbacks.push(callback);
+ }
+
/**
* The Integrations Class manages all Integrations for an Agile Instance
* and provides an interface to easily update
@@ -14,14 +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;
- // Integrate initial Integrations which were statically set externally
- Agile.initialIntegrations.forEach((integration) =>
- this.integrate(integration)
- );
+ 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);
+ });
+ }
}
/**
@@ -33,7 +81,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;
}
@@ -84,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/internal.ts b/packages/core/src/internal.ts
index 6e403e5a..ad6631d3 100644
--- a/packages/core/src/internal.ts
+++ b/packages/core/src/internal.ts
@@ -4,17 +4,21 @@
// !! 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';
+// Integrations
+export * from './integrations';
+export * from './integrations/integration';
+
// Runtime
export * from './runtime';
export * from './runtime/observer';
@@ -47,6 +51,5 @@ 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/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
new file mode 100644
index 00000000..d433eaab
--- /dev/null
+++ b/packages/core/src/shared.ts
@@ -0,0 +1,200 @@
+import {
+ Agile,
+ Collection,
+ CollectionConfig,
+ Computed,
+ ComputeFunctionType,
+ CreateComputedConfigInterface,
+ CreateStorageConfigInterface,
+ DefaultItem,
+ defineConfig,
+ DependableAgileInstancesType,
+ flatMerge,
+ Logger,
+ removeProperties,
+ runsOnServer,
+ State,
+ StateConfigInterface,
+ Storage,
+} from './internal';
+
+/**
+ * Shared Agile Instance that is used when no Agile Instance was specified.
+ */
+let sharedAgileInstance = new Agile({
+ key: 'shared',
+ logConfig: { prefix: 'Agile', level: Logger.level.WARN, active: true },
+ localStorage: !runsOnServer(),
+});
+export { sharedAgileInstance as shared };
+
+/**
+ * Assigns the specified 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.
+ *
+ * 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: sharedAgileInstance,
+ });
+ 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 = sharedAgileInstance
+): 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: sharedAgileInstance,
+ });
+
+ 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/src/utils.ts b/packages/core/src/utils.ts
index 56af1728..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) {
@@ -254,3 +260,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/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/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', () => {
diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts
index f1bdec49..02ae2ef7 100644
--- a/packages/core/tests/unit/agile.test.ts
+++ b/packages/core/tests/unit/agile.test.ts
@@ -12,32 +12,48 @@ import {
} from '../../src';
import testIntegration from '../helper/test.integration';
import { LogMock } from '../helper/logMock';
+import * as Shared from '../../src/shared';
-jest.mock('../../src/runtime/index');
-jest.mock('../../src/runtime/subscription/sub.controller');
-jest.mock('../../src/storages/index');
-jest.mock('../../src/integrations/index');
-jest.mock('../../src/storages/storage');
-jest.mock('../../src/collection/index');
-jest.mock('../../src/computed/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() {
+// https://github.com/facebook/jest/issues/5023
+jest.mock('../../src/runtime', () => {
+ return {
+ // https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn
+ Runtime: jest.fn().mockImplementation(() => {
return {
- TRACE: 1,
- DEBUG: 2,
- LOG: 5,
- TABLE: 5,
- INFO: 10,
- WARN: 20,
- ERROR: 50,
+ ingest: jest.fn(),
};
- }
+ }),
+ };
+});
+jest.mock('../../src/runtime/subscription/sub.controller', () => {
+ return {
+ SubController: jest.fn(),
+ };
+});
+jest.mock('../../src/storages', () => {
+ return {
+ Storages: jest.fn(),
+ };
+});
+
+// https://gist.github.com/virgs/d9c50e878fc69832c01f8085f2953f12
+// https://medium.com/@masonlgoetz/mock-static-class-methods-in-jest-1ceda967b47f
+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();
+ // @ts-ignore
+ mockedInstances.Integrations.initialIntegrations = [];
+ return mockedInstances;
});
- */
-// 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;
@@ -50,56 +66,49 @@ describe('Agile Tests', () => {
>;
beforeEach(() => {
- 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');
+ jest.spyOn(Agile.prototype, 'integrate');
+
+ jest.clearAllMocks();
});
it('should instantiate Agile (default config)', () => {
const agile = new Agile();
- // Check if Agile properties got instantiated properly
expect(agile.config).toStrictEqual({
waitForMount: true,
});
- expect(IntegrationsMock).toHaveBeenCalledWith(agile);
- expect(agile.integrations).toBeInstanceOf(Integrations);
+ expect(agile.key).toBeUndefined();
+ 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);
+ // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock (mockImplementation)
expect(SubControllerMock).toHaveBeenCalledWith(agile);
expect(agile.subController).toBeInstanceOf(SubController);
expect(StoragesMock).toHaveBeenCalledWith(agile, {
localStorage: true,
});
expect(agile.storages).toBeInstanceOf(Storages);
+ expect(agile.configureLogger).toHaveBeenCalledWith({});
- // 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 Agile Instance got bound globally
expect(globalThis[Agile.globalKey]).toBeUndefined();
});
- it('should instantiate Agile with (specific config)', () => {
+ it('should instantiate Agile (specific config)', () => {
const agile = new Agile({
waitForMount: false,
localStorage: false,
@@ -110,71 +119,97 @@ describe('Agile Tests', () => {
timestamp: true,
},
bindGlobal: true,
+ key: 'jeff',
+ autoIntegrate: false,
});
- // Check if Agile properties got instantiated properly
expect(agile.config).toStrictEqual({
waitForMount: false,
});
- expect(IntegrationsMock).toHaveBeenCalledWith(agile);
- expect(agile.integrations).toBeInstanceOf(Integrations);
+ expect(agile.key).toBe('jeff');
+ 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);
+ // expect(agile.runtime).toBeInstanceOf(Runtime); // Because 'Runtime' is completely overwritten with a mock (mockImplementation)
expect(SubControllerMock).toHaveBeenCalledWith(agile);
expect(agile.subController).toBeInstanceOf(SubController);
expect(StoragesMock).toHaveBeenCalledWith(agile, {
localStorage: false,
});
expect(agile.storages).toBeInstanceOf(Storages);
-
- // Check if Static Logger has correct config
- expect(Agile.logger.config).toStrictEqual({
- prefix: 'Jeff',
+ 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);
});
- 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 Agile Instance globally ' +
+ 'although the previously defined Agile Instance is already globally set',
+ () => {
+ 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('createStorage function tests', () => {
- const StorageMock = Storage as jest.MockedClass;
+ describe('configureLogger function tests', () => {
+ it('should overwrite the static Logger with a new Logger Instance', () => {
+ 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', () => {
beforeEach(() => {
- StorageMock.mockClear();
+ jest.spyOn(Shared, 'createStorage');
});
- it('should create Storage', () => {
+ it('should call createStorage', () => {
const storageConfig = {
prefix: 'test',
methods: {
@@ -190,31 +225,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(Shared.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(Shared, 'createState');
+ });
- expect(state).toBeInstanceOf(State);
+ it('should call createState with the Agile Instance it was called on', () => {
+ const response = agile.createState('jeff', { key: 'jeffState' });
+
+ expect(response).toBeInstanceOf(State);
+ expect(Shared.createState).toHaveBeenCalledWith('jeff', {
+ key: 'jeffState',
+ agileInstance: agile,
+ });
});
});
describe('createCollection function tests', () => {
- const CollectionMock = Collection as jest.MockedClass;
-
beforeEach(() => {
- CollectionMock.mockClear();
+ jest.spyOn(Shared, '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'],
@@ -222,48 +262,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(Shared.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(Shared, '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(Shared.createComputed).toHaveBeenCalledWith(computedFunction, {
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(Shared.createComputed).toHaveBeenCalledWith(computedFunction, {
+ ...computedConfig,
+ ...{
+ agileInstance: agile,
+ },
});
});
});
@@ -280,6 +325,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',
@@ -317,6 +366,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();
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 d4baf660..b8550a22 100644
--- a/packages/core/tests/unit/integrations/integrations.test.ts
+++ b/packages/core/tests/unit/integrations/integrations.test.ts
@@ -3,56 +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 });
- Agile.initialIntegrations = [];
+ dummyIntegration1 = new Integration({
+ key: 'dummyIntegration1',
+ });
+ dummyIntegration2 = new Integration({
+ key: 'dummyIntegration2',
+ });
- jest.spyOn(Integrations.prototype, 'integrate');
- });
+ Integrations.initialIntegrations = [];
- it('should create Integrations', () => {
- const integrations = new Integrations(dummyAgile);
+ jest.spyOn(Integrations.prototype, 'integrate');
+ jest.spyOn(Integrations, 'onRegisterInitialIntegration');
- expect(integrations.integrations.size).toBe(0);
+ jest.clearAllMocks();
});
- 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);
+ it('should create Integrations with the before specified initial Integrations (default config)', () => {
+ Integrations.initialIntegrations = [dummyIntegration1, 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(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',
+ });
+
+ describe('onRegisterInitialIntegration function tests', () => {
+ it('should register specified onRegisterInitialIntegration callback', () => {
+ // Nothing to testable
});
- dummyIntegration2 = new Integration({
- key: 'dummyIntegration2',
+ });
+
+ describe('addInitialIntegration function tests', () => {
+ const callback1 = jest.fn();
+ const callback2 = jest.fn();
+
+ 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
new file mode 100644
index 00000000..fc81e6e8
--- /dev/null
+++ b/packages/core/tests/unit/shared.test.ts
@@ -0,0 +1,212 @@
+import {
+ Agile,
+ Collection,
+ Computed,
+ shared,
+ State,
+ Storage,
+ createStorage,
+ createState,
+ createCollection,
+ createComputed,
+ assignSharedAgileInstance,
+} from '../../src';
+import { LogMock } from '../helper/logMock';
+
+jest.mock('../../src/storages/storage');
+jest.mock('../../src/collection');
+jest.mock('../../src/computed');
+
+// https://github.com/facebook/jest/issues/5023
+jest.mock('../../src/state', () => {
+ return {
+ State: jest.fn(),
+ };
+});
+
+describe('Shared Tests', () => {
+ let sharedAgileInstance: Agile;
+
+ beforeEach(() => {
+ 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', () => {
+ const StorageMock = Storage as jest.MockedClass;
+
+ beforeEach(() => {
+ StorageMock.mockClear();
+ });
+
+ it('should create Storage', () => {
+ const storageConfig = {
+ prefix: 'test',
+ methods: {
+ get: () => {
+ /* empty function */
+ },
+ set: () => {
+ /* empty function */
+ },
+ remove: () => {
+ /* empty function */
+ },
+ },
+ key: 'myTestStorage',
+ };
+
+ const storage = createStorage(storageConfig);
+
+ expect(storage).toBeInstanceOf(Storage);
+ expect(StorageMock).toHaveBeenCalledWith(storageConfig);
+ });
+ });
+
+ describe('createState function tests', () => {
+ const StateMock = State as jest.MockedClass;
+
+ it('should create State with the shared Agile Instance', () => {
+ const state = createState('testValue', {
+ key: 'myCoolState',
+ });
+
+ expect(state).toBeInstanceOf(State);
+ expect(StateMock).toHaveBeenCalledWith(sharedAgileInstance, 'testValue', {
+ key: 'myCoolState',
+ });
+ });
+
+ it('should create State with a specified Agile Instance', () => {
+ const agile = new Agile();
+
+ const state = createState('testValue', {
+ key: 'myCoolState',
+ agileInstance: agile,
+ });
+
+ expect(state).toBeInstanceOf(State);
+ expect(StateMock).toHaveBeenCalledWith(agile, 'testValue', {
+ key: 'myCoolState',
+ });
+ });
+ });
+
+ describe('createCollection function tests', () => {
+ const CollectionMock = Collection as jest.MockedClass;
+
+ beforeEach(() => {
+ CollectionMock.mockClear();
+ });
+
+ it('should create Collection with the shared Agile Instance', () => {
+ const collectionConfig = {
+ selectors: ['test', 'test1'],
+ groups: ['test2', 'test10'],
+ defaultGroupKey: 'frank',
+ key: 'myCoolCollection',
+ };
+
+ const collection = createCollection(collectionConfig);
+
+ expect(collection).toBeInstanceOf(Collection);
+ expect(CollectionMock).toHaveBeenCalledWith(
+ sharedAgileInstance,
+ collectionConfig
+ );
+ });
+
+ it('should create Collection with a specified Agile Instance', () => {
+ const agile = new Agile();
+ const collectionConfig = {
+ selectors: ['test', 'test1'],
+ groups: ['test2', 'test10'],
+ defaultGroupKey: 'frank',
+ key: 'myCoolCollection',
+ };
+
+ const collection = createCollection(collectionConfig, agile);
+
+ expect(collection).toBeInstanceOf(Collection);
+ expect(CollectionMock).toHaveBeenCalledWith(agile, collectionConfig);
+ });
+ });
+
+ describe('createComputed function tests', () => {
+ const ComputedMock = Computed as jest.MockedClass;
+ const computedFunction = () => {
+ // empty
+ };
+
+ beforeEach(() => {
+ ComputedMock.mockClear();
+ });
+
+ it('should create Computed with the shared Agile Instance (default config)', () => {
+ const response = createComputed(computedFunction, ['dummyDep' as any]);
+
+ expect(response).toBeInstanceOf(Computed);
+ expect(ComputedMock).toHaveBeenCalledWith(
+ sharedAgileInstance,
+ computedFunction,
+ {
+ computedDeps: ['dummyDep' as any],
+ }
+ );
+ });
+
+ it('should create Computed with the shared Agile Instance (specific config)', () => {
+ const computedConfig = {
+ key: 'jeff',
+ isPlaceholder: false,
+ computedDeps: ['dummyDep' as any],
+ autodetect: true,
+ };
+
+ const response = createComputed(computedFunction, computedConfig);
+
+ expect(response).toBeInstanceOf(Computed);
+ expect(ComputedMock).toHaveBeenCalledWith(
+ sharedAgileInstance,
+ computedFunction,
+ computedConfig
+ );
+ });
+
+ it('should create Computed with a specified Agile Instance (specific config)', () => {
+ const agile = new Agile();
+ const computedConfig = {
+ key: 'jeff',
+ isPlaceholder: false,
+ computedDeps: ['dummyDep' as any],
+ autodetect: true,
+ };
+
+ const response = createComputed(computedFunction, {
+ ...computedConfig,
+ ...{ agileInstance: agile },
+ });
+
+ expect(response).toBeInstanceOf(Computed);
+ expect(ComputedMock).toHaveBeenCalledWith(
+ agile,
+ computedFunction,
+ computedConfig
+ );
+ });
+ });
+});
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 db4bd7e3..96decb42 100644
--- a/packages/core/tests/unit/utils.test.ts
+++ b/packages/core/tests/unit/utils.test.ts
@@ -1,12 +1,11 @@
import {
- globalBind,
- getAgileInstance,
Agile,
State,
Observer,
Collection,
StateObserver,
GroupObserver,
+ assignSharedAgileInstance,
} from '../../src';
import * as Utils from '../../src/utils';
import { LogMock } from '../helper/logMock';
@@ -15,47 +14,68 @@ 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', () => {
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(getAgileInstance(dummyState)).toBe(dummyAgile);
+ 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(getAgileInstance(dummyCollection)).toBe(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(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);
- });
-
- 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 globally bound Agile Instance' +
+ 'if specified Instance contains no valid Agile Instance' +
+ 'and no shared Agile Instance is 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;
- const response = getAgileInstance('weiredInstance');
+ // Destroy shared Agile Instance
+ assignSharedAgileInstance(undefined as any);
+
+ const response = Utils.getAgileInstance('weiredInstance');
expect(response).toBeUndefined();
LogMock.hasLoggedCode('20:03:00', [], 'weiredInstance');
@@ -360,23 +380,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 +405,27 @@ 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", () => {
+ global.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", () => {
+ 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 7c475015..90397943 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(() => {
+ LogMock.mockLogs();
jest.clearAllMocks();
- mockConsole(['error', 'warn']);
});
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..b700a72f 100644
--- a/packages/event/tests/unit/event.observer.test.ts
+++ b/packages/event/tests/unit/event.observer.test.ts
@@ -1,17 +1,18 @@
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;
let dummyEvent: Event;
beforeEach(() => {
- jest.clearAllMocks();
- mockConsole(['error', 'warn']);
+ 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 f2bdd61f..bd369952 100644
--- a/packages/event/tests/unit/event.test.ts
+++ b/packages/event/tests/unit/event.test.ts
@@ -1,16 +1,17 @@
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 });
+
+ jest.clearAllMocks();
});
it('should create Event (default config)', () => {
diff --git a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts
index 9e397c06..becea846 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 a0bf8774..2ba63ec8 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.addInitialIntegration(reactIntegration);
export default reactIntegration;
diff --git a/packages/vue/src/vue.integration.ts b/packages/vue/src/vue.integration.ts
index cf2268b9..d6a367ec 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.addInitialIntegration(vueIntegration);
export default vueIntegration;