From 31d0c1339a24c73c5a89043f5c1e25fc596f8a91 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 27 Jun 2021 17:48:47 +0200 Subject: [PATCH 01/55] fixed resizing in boxes example --- examples/react/release/boxes/package.json | 6 ++---- .../boxes/src/components/EditProperties/index.tsx | 6 ++++-- .../Rectangle/components/RectangleInner.tsx | 7 ++++--- .../boxes/src/components/Rectangle/index.tsx | 4 ++-- examples/react/release/boxes/yarn.lock | 15 +-------------- packages/core/.size-limit.js | 2 +- packages/react/.size-limit.js | 6 ++++++ packages/react/package.json | 3 ++- 8 files changed, 22 insertions(+), 27 deletions(-) create mode 100644 packages/react/.size-limit.js diff --git a/examples/react/release/boxes/package.json b/examples/react/release/boxes/package.json index 44579fee..8090cf6b 100644 --- a/examples/react/release/boxes/package.json +++ b/examples/react/release/boxes/package.json @@ -3,10 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@agile-ts/api": "file:.yalc/@agile-ts/api", "@agile-ts/core": "file:.yalc/@agile-ts/core", - "@agile-ts/event": "file:.yalc/@agile-ts/event", - "@agile-ts/multieditor": "file:.yalc/@agile-ts/multieditor", "@agile-ts/proxytree": "file:.yalc/@agile-ts/proxytree", "@agile-ts/react": "file:.yalc/@agile-ts/react", "@chakra-ui/react": "^1.6.3", @@ -35,7 +32,8 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree & yarn install" + "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/proxytree & yarn install", + "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/proxytree" }, "eslintConfig": { "extends": [ diff --git a/examples/react/release/boxes/src/components/EditProperties/index.tsx b/examples/react/release/boxes/src/components/EditProperties/index.tsx index 62c3fd51..b1caa3cd 100644 --- a/examples/react/release/boxes/src/components/EditProperties/index.tsx +++ b/examples/react/release/boxes/src/components/EditProperties/index.tsx @@ -11,16 +11,17 @@ import _ from 'lodash'; import { ImageInfo, ImageInfoFallback } from './components/ImageInfo'; import { useProxy, useSelector } from '@agile-ts/react'; import core from '../../core'; +import { ElementImageInterface } from '../../core/entities/ui/ui.interfaces'; export const EditProperties = () => { const selectedElementId = useSelector( core.ui.SELECTED_ELEMENT, (value) => value?.id - ); + ) as number | string; const selectedElementImage = useSelector( core.ui.SELECTED_ELEMENT, (value) => value?.image - ); + ) as ElementImageInterface; // TODO useProxy doesn't work here as expected because the selected Elements // doesn't exist on the creation of the Subscription Container @@ -136,6 +137,7 @@ const Property = ({ }) => { const ELEMENT = core.ui.ELEMENTS.getItem(id); const element = useProxy(ELEMENT, { componentId: 'Property' }); + console.log('Element', ELEMENT); return ( = ( setIsLoading(true); core.ui.getImageDimensions(element.image.src).then((response) => { setIsLoading(false); - ELEMENT?.patch({ - style: { ...{ size: response }, ...ELEMENT?.value.style }, - }); + if (ELEMENT != null) { + ELEMENT.nextStateValue.style.size = response; + ELEMENT?.ingest(); + } }); } else { setIsLoading(false); diff --git a/examples/react/release/boxes/src/components/Rectangle/index.tsx b/examples/react/release/boxes/src/components/Rectangle/index.tsx index c4a34aee..5dee9586 100644 --- a/examples/react/release/boxes/src/components/Rectangle/index.tsx +++ b/examples/react/release/boxes/src/components/Rectangle/index.tsx @@ -3,7 +3,7 @@ import { Drag } from '../actionComponents/Drag'; import { Resize } from '../actionComponents/Resize'; import { RectangleContainer } from './components/RectangleContainer'; import { RectangleInner } from './components/RectangleInner'; -import { useAgile, useProxy } from '@agile-ts/react'; +import { useAgile } from '@agile-ts/react'; import core from '../../core'; import { SELECTED_ELEMENT } from '../../core/entities/ui/ui.controller'; import { ElementStyleInterface } from '../../core/entities/ui/ui.interfaces'; @@ -24,7 +24,7 @@ export const Rectangle: React.FC = (props) => { { componentId: 'Rectangle', } - ); + ) as string | number; if (element == null) return null; diff --git a/examples/react/release/boxes/yarn.lock b/examples/react/release/boxes/yarn.lock index bd82c231..e30b38a1 100644 --- a/examples/react/release/boxes/yarn.lock +++ b/examples/react/release/boxes/yarn.lock @@ -2,20 +2,12 @@ # yarn lockfile v1 -"@agile-ts/api@file:.yalc/@agile-ts/api": - version "0.0.18" - dependencies: - "@agile-ts/utils" "^0.0.4" - "@agile-ts/core@file:.yalc/@agile-ts/core": version "0.0.17" dependencies: "@agile-ts/logger" "^0.0.4" "@agile-ts/utils" "^0.0.4" -"@agile-ts/event@file:.yalc/@agile-ts/event": - version "0.0.7" - "@agile-ts/logger@^0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d" @@ -23,16 +15,11 @@ dependencies: "@agile-ts/utils" "^0.0.4" -"@agile-ts/multieditor@file:.yalc/@agile-ts/multieditor": - version "0.0.17" - -"@agile-ts/proxytree@^0.0.3", "@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree": +"@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree": version "0.0.3" "@agile-ts/react@file:.yalc/@agile-ts/react": version "0.0.18" - dependencies: - "@agile-ts/proxytree" "^0.0.3" "@agile-ts/utils@^0.0.4": version "0.0.4" diff --git a/packages/core/.size-limit.js b/packages/core/.size-limit.js index 1b8247f6..b3971b86 100644 --- a/packages/core/.size-limit.js +++ b/packages/core/.size-limit.js @@ -1,6 +1,6 @@ module.exports = [ { path: 'dist/*', - limit: '35 kB', + limit: '20 kB', }, ]; diff --git a/packages/react/.size-limit.js b/packages/react/.size-limit.js new file mode 100644 index 00000000..addba251 --- /dev/null +++ b/packages/react/.size-limit.js @@ -0,0 +1,6 @@ +module.exports = [ + { + path: 'dist/*', + limit: '2 kB', + }, +]; diff --git a/packages/react/package.json b/packages/react/package.json index 922ce4ec..376b08b2 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -35,7 +35,8 @@ "preview": "npm pack", "test": "jest", "test:coverage": "jest --coverage", - "lint": "eslint src/**/*" + "lint": "eslint src/**/*", + "size": "yarn run build && size-limit" }, "devDependencies": { "@agile-ts/core": "file:../core", From eed02b9abcaa444e6c161edb0e214480dd12e64e Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 28 Jun 2021 18:35:10 +0200 Subject: [PATCH 02/55] 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 03/55] 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 04/55] 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 05/55] 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 06/55] 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 07/55] 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 08/55] 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 09/55] 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 10/55] 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 11/55] 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 12/55] 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 13/55] 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 14/55] 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 15/55] 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 16/55] 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 17/55] 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 18/55] 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 19/55] 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 20/55] 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; From 3189eba4b09ebc635d4233b492f4c95118b36d55 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 14:57:09 +0200 Subject: [PATCH 21/55] setup benchmark project --- benchmark/.env | 1 + .../react/counter/bench/agilets.tsx | 16 + benchmark/benchmarks/react/counter/index.ts | 63 ++ benchmark/lodash.ts | 4 + benchmark/package.json | 52 ++ benchmark/public/index.html | 16 + benchmark/run.ts | 89 +++ benchmark/tsconfig.json | 22 + benchmark/yarn.lock | 586 ++++++++++++++++++ package.json | 4 +- 10 files changed, 851 insertions(+), 2 deletions(-) create mode 100644 benchmark/.env create mode 100644 benchmark/benchmarks/react/counter/bench/agilets.tsx create mode 100644 benchmark/benchmarks/react/counter/index.ts create mode 100644 benchmark/lodash.ts create mode 100644 benchmark/package.json create mode 100644 benchmark/public/index.html create mode 100644 benchmark/run.ts create mode 100644 benchmark/tsconfig.json create mode 100644 benchmark/yarn.lock diff --git a/benchmark/.env b/benchmark/.env new file mode 100644 index 00000000..21903adb --- /dev/null +++ b/benchmark/.env @@ -0,0 +1 @@ +MANUAL_BENCHMARK=false diff --git a/benchmark/benchmarks/react/counter/bench/agilets.tsx b/benchmark/benchmarks/react/counter/bench/agilets.tsx new file mode 100644 index 00000000..0761a07a --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/agilets.tsx @@ -0,0 +1,16 @@ +import { Agile, Logger } from '@agile-ts/core'; +import { useAgile } from '@agile-ts/react'; +import React from 'react'; +import ReactDom from 'react-dom'; + +const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); +const COUNT = AgileApp.createState(0); + +const App = () => { + const count = useAgile(COUNT); + return

COUNT.set((state) => state + 1)}>{count}

; +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts new file mode 100644 index 00000000..987cc3d9 --- /dev/null +++ b/benchmark/benchmarks/react/counter/index.ts @@ -0,0 +1,63 @@ +import Benchmark, { Suite, Options } from 'benchmark'; +import ReactDOM from 'react-dom'; + +// Files to run the Benchmark on +import agilets from './bench/agilets'; + +// @ts-ignore +// Benchmark.js requires an instance of itself globally +window.Benchmark = Benchmark; + +// Create new Benchmark test suite +const suite = new Suite('Count'); + +// Retrieve the Element to render the Benchmark in +const target = document.getElementById('bench')!; + +// Increment Element +let increment: HTMLHeadingElement; + +function configTest(renderElement: (target: HTMLElement) => void): Options { + return { + fn() { + increment.click(); + }, + onStart() { + // Render Benchmark Component in the target Element + renderElement(target); + + // Retrieve Increment Element + increment = target.querySelector('h1')!; + }, + onComplete() { + // Set 'output' in the Benchmark + (this as any).output = parseInt(target.innerText, 10); + + // Unmount Component + ReactDOM.unmountComponentAtNode(target); + target.innerHTML = ''; + }, + }; +} + +// Add Tests to Benchmark Suite +suite + .add('AgileTs', configTest(agilets)) + + // Add Listener + .on('start', function (this: any) { + console.log(`Starting ${this.name}`); + }) + .on('cycle', (event: any) => { + console.log(String(event.target)); + }) + .on('complete', function (this: any) { + console.log(`Fastest is ${this.filter('fastest').map('name')}`); + + // @ts-ignore + // Notify server to end the Benchmark + window.TEST.ended = true; + }) + + // Run Benchmark Suite + .run({ async: true }); diff --git a/benchmark/lodash.ts b/benchmark/lodash.ts new file mode 100644 index 00000000..104b3b1f --- /dev/null +++ b/benchmark/lodash.ts @@ -0,0 +1,4 @@ +import _ from 'lodash'; + +// Benchmark.js requires lodash globally +window._ = _; diff --git a/benchmark/package.json b/benchmark/package.json new file mode 100644 index 00000000..02877ecc --- /dev/null +++ b/benchmark/package.json @@ -0,0 +1,52 @@ +{ + "name": "benchmark", + "version": "0.1.0", + "private": true, + "author": "BennoDev", + "license": "MIT", + "homepage": "https://agile-ts.org/", + "description": "Benchmark Tests", + "scripts": { + "test": "node -r esbuild-register run.ts", + "test:counter": "yarn test ./benchmarks/react/counter", + "test:fields": "yarn test ./benchmarks/react/fields", + "install:local:agile": "yalc add @agile-ts/core @agile-ts/react", + "install:public:agile": "yarn add @agile-ts/core @agile-ts/react" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/agile-ts/agile.git" + }, + "dependencies": { + "benchmark": "^2.1.4", + "colorette": "^1.2.2", + "dotenv": "^10.0.0", + "esbuild": "^0.12.14", + "esbuild-register": "^2.6.0", + "lodash": "^4.17.21", + "playwright": "^1.12.3", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "typescript": "^4.3.5" + }, + "devDependencies": { + "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/react": "file:.yalc/@agile-ts/react", + "@reduxjs/toolkit": "^1.5.1", + "@types/benchmark": "^2.1.0", + "@types/node": "^16.0.0", + "@types/react": "^17.0.13", + "@types/react-dom": "^17.0.8", + "jotai": "^0.16.0", + "mobx": "^6.2.0", + "mobx-react": "^7.1.0", + "pullstate": "^1.22.1", + "react-redux": "^7.2.3", + "recoil": "^0.2.0", + "redux": "^4.0.5", + "valtio": "^1.0.3" + }, + "bugs": { + "url": "https://github.com/agile-ts/agile/issues" + } +} diff --git a/benchmark/public/index.html b/benchmark/public/index.html new file mode 100644 index 00000000..93fc27b9 --- /dev/null +++ b/benchmark/public/index.html @@ -0,0 +1,16 @@ + + + + + Benchmark + + + +
+ + + diff --git a/benchmark/run.ts b/benchmark/run.ts new file mode 100644 index 00000000..a43c6329 --- /dev/null +++ b/benchmark/run.ts @@ -0,0 +1,89 @@ +import dotenv from 'dotenv'; +import esbuild from 'esbuild'; +import playwright from 'playwright'; + +// Loads environment variables from the '.env' file +dotenv.config(); + +// https://nodejs.org/docs/latest/api/process.html#process_process_argv +// Extract entry from the executed command +// yarn run ./path/to/entry -> './path/to/entry' is extracted +const entry = process.argv.slice(2)[0]; +if (entry == null) { + throw new Error( + "No valid entry was provided! Valid entry example: 'yarn run ./benchmarks/react/counter'" + ); +} + +const startBenchmark = async () => { + // Bundle Benchmark Test and launch the server on which they are executed + const server = await esbuild.serve( + { + servedir: 'public', + port: 3000, + host: '127.0.0.1', // localhost + }, + { + inject: ['./lodash.ts'], // https://esbuild.github.io/api/#inject + entryPoints: [entry], // https://esbuild.github.io/api/#entry-points + outfile: './public/bundle.js', + target: 'es2015', + format: 'cjs', // https://esbuild.github.io/api/#format-commonjs + platform: 'browser', + minify: true, // https://esbuild.github.io/api/#minify + bundle: true, // https://esbuild.github.io/api/#bundle + sourcemap: 'external', // https://esbuild.github.io/api/#sourcemap// https://github.com/evanw/esbuild/issues/69 + } + ); + const serverUrl = `http://${server.host}:${server.port}`; + + console.log(`Server is running at port: ${server.port}`); + + // Launch Chrome as browser to run the benchmarks in + const browser = await playwright.chromium.launch(); + const context = await browser.newContext(); + const page = await context.newPage(); + + // Option to open and test the benchmarks in the browser manually + if (process.env.MANUAL_BENCHMARK === 'true') { + console.log( + `Open the Browser at '${serverUrl}' to run the tests manually.` + ); + + await server.wait; + } + + // Setup 'pageerror' listener to throw occurring errors in the local console + // https://playwright.dev/docs/api/class-page/#page-event-page-error + page.on('pageerror', (error) => { + throw error; + }); + + // Setup 'console' listener to transfer the browser logs into the local console + // https://playwright.dev/docs/api/class-page/#page-event-console + page.on('console', (...message) => { + console.log(...message); + }); + + // Open url the server is running on + await page.goto(serverUrl); + + // Wait for tests to be executed (indicator is when 'window.TESTS.ended' is set to true) + // https://playwright.dev/docs/api/class-frame#frame-wait-for-function + await page.waitForFunction( + // @ts-ignore + () => window.TEST?.ended, + undefined, + { + timeout: 0, + polling: 100, + } + ); + + // Close Browser and stop server + await browser.close(); + server.stop(); +}; + +// Execute the Benchmark +startBenchmark(); diff --git a/benchmark/tsconfig.json b/benchmark/tsconfig.json new file mode 100644 index 00000000..b36827f2 --- /dev/null +++ b/benchmark/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react" + } +} diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock new file mode 100644 index 00000000..a84f9339 --- /dev/null +++ b/benchmark/yarn.lock @@ -0,0 +1,586 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@agile-ts/core@file:.yalc/@agile-ts/core": + version "0.1.0" + dependencies: + "@agile-ts/logger" "^0.0.5" + "@agile-ts/utils" "^0.0.5" + +"@agile-ts/logger@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.5.tgz#bad39e1995a0c14e7f3f7c6e44c028f4d7a30f38" + integrity sha512-qBNUyPJGecOZOS9r8dyGF/VLBioEY5DYZn4Hoq+sEkpyvoi718c90i57B1M++I2MCCONVMGytG61Gs1sts73qw== + dependencies: + "@agile-ts/utils" "^0.0.5" + +"@agile-ts/react@file:.yalc/@agile-ts/react": + version "0.1.0" + +"@agile-ts/utils@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6" + integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA== + +"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d" + integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg== + dependencies: + regenerator-runtime "^0.13.4" + +"@reduxjs/toolkit@^1.5.1": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.0.tgz#0a17c6941c57341f8b31e982352b495ab69d5add" + integrity sha512-eGL50G+Vj5AG5uD0lineb6rRtbs96M8+hxbcwkHpZ8LQcmt0Bm33WyBSnj5AweLkjQ7ZP+KFRDHiLMznljRQ3A== + dependencies: + immer "^9.0.1" + redux "^4.1.0" + redux-thunk "^2.3.0" + reselect "^4.0.0" + +"@types/benchmark@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/benchmark/-/benchmark-2.1.0.tgz#157e2ef22311d3140fb33e82a938a1beb26e78e0" + integrity sha512-wxT2/LZn4z0NvSfZirxmBx686CU7EXp299KHkIk79acXpQtgeYHrslFzDacPGXifC0Pe3CEaLup07bgY1PnuQw== + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + +"@types/node@*", "@types/node@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f" + integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg== + +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/react-dom@^17.0.8": + version "17.0.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.8.tgz#3180de6d79bf53762001ad854e3ce49f36dd71fc" + integrity sha512-0ohAiJAx1DAUEcY9UopnfwCE9sSMDGnY/oXjWMax6g3RpzmTt2GMyMVAXcbn0mo8XAff0SbQJl2/SBU+hjSZ1A== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.16": + version "7.1.16" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21" + integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react@*", "@types/react@^17.0.13": + version "17.0.13" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.13.tgz#6b7c9a8f2868586ad87d941c02337c6888fb874f" + integrity sha512-D/G3PiuqTfE3IMNjLn/DCp6umjVCSvtZTPdtAFy5+Ved6CsdRvivfKeCzw79W4AatShtU4nGqgvOv5Gro534vQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" + integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== + +"@types/yauzl@^2.9.1": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" + integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== + dependencies: + "@types/node" "*" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +benchmark@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.4.tgz#09f3de31c916425d498cc2ee565a0ebf3c2a5629" + integrity sha1-CfPeMckWQl1JjMLuVloOvzwqVik= + dependencies: + lodash "^4.17.4" + platform "^1.3.3" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + +colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== + +commander@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +csstype@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" + integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== + +debug@4, debug@^4.1.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +esbuild-register@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/esbuild-register/-/esbuild-register-2.6.0.tgz#9f19a54c82be751dd87673d6a66d7b9e1cdd8498" + integrity sha512-2u4AtnCXP5nivtIxZryiZOUcEQkOzFS7UhAqibUEmaTAThJ48gDLYTBF/Fsz+5r0hbV1jrFE6PQvPDUrKZNt/Q== + dependencies: + esbuild "^0.12.8" + jsonc-parser "^3.0.0" + +esbuild@^0.12.14, esbuild@^0.12.8: + version "0.12.14" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.14.tgz#43157dbd0b36d939247d4eb4909a4886ac40f82e" + integrity sha512-z8p+6FGiplR7a3pPonXREbm+8IeXjBGvDpVidZmGB/AJMsJSfGCU+n7KOMCazA9AwvagadRWBhiKorC0w9WJvw== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +glob@^7.1.3: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.2.4: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +hamt_plus@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/hamt_plus/-/hamt_plus-1.0.2.tgz#e21c252968c7e33b20f6a1b094cd85787a265601" + integrity sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE= + +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +immer@^8.0.1: + version "8.0.4" + resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a" + integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ== + +immer@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.3.tgz#146e2ba8b84d4b1b15378143c2345559915097f4" + integrity sha512-mONgeNSMuyjIe0lkQPa9YhdmTv8P19IeHV0biYhcXhbd5dhdB9HSK93zBpyKjp6wersSUgT5QyU0skmejUVP2A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +jotai@^0.16.0: + version "0.16.11" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-0.16.11.tgz#efeaf0311513cf41d5c82b7009f8381d518cbc41" + integrity sha512-EPBeDSBc4FwbFRArRjcI6IsHrVkba771mSOFTN3M4r5rU3ZcZMDFLElZScZVr6w1kgrEXvaMm59VAyEB3QUEbA== + +jpeg-js@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" + integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsonc-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22" + integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA== + +lodash@^4.17.21, lodash@^4.17.4: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +mime@^2.4.6: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +mobx-react-lite@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz#331d7365a6b053378dfe9c087315b4e41c5df69f" + integrity sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g== + +mobx-react@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.2.0.tgz#241e925e963bb83a31d269f65f9f379e37ecbaeb" + integrity sha512-KHUjZ3HBmZlNnPd1M82jcdVsQRDlfym38zJhZEs33VxyVQTvL77hODCArq6+C1P1k/6erEeo2R7rpE7ZeOL7dg== + dependencies: + mobx-react-lite "^3.2.0" + +mobx@^6.2.0: + version "6.3.2" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.2.tgz#125590961f702a572c139ab69392bea416d2e51b" + integrity sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +platform@^1.3.3: + version "1.3.6" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== + +playwright@^1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.12.3.tgz#113afa2cba10fb56e9a5b307377343e32a155a99" + integrity sha512-eyhHvZV7dMAUltqjQsgJ9CjZM8dznzN1+rcfCI6W6lfQ7IlPvTFGLuKOCcI4ETbjfbxqaS5FKIkb1WDDzq2Nww== + dependencies: + commander "^6.1.0" + debug "^4.1.1" + extract-zip "^2.0.1" + https-proxy-agent "^5.0.0" + jpeg-js "^0.4.2" + mime "^2.4.6" + pngjs "^5.0.0" + progress "^2.0.3" + proper-lockfile "^4.1.1" + proxy-from-env "^1.1.0" + rimraf "^3.0.2" + stack-utils "^2.0.3" + ws "^7.4.6" + yazl "^2.5.1" + +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +proper-lockfile@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + +proxy-compare@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-2.0.0.tgz#36f41114a25fcf359037308d12529183a9dc182c" + integrity sha512-xhJF1+vPCnu93QYva3Weii5ho1AeX5dsR/P5O7pzy9QLxeOgMSQNC8zDo0bGg9vtn61Pu5Qn+5w/Y8OSU5k+8g== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pullstate@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/pullstate/-/pullstate-1.22.1.tgz#ffdde634e8c721907de8e6d37a85c6083137ee8a" + integrity sha512-Xu3umsGOG6qCQ4IWxKSEikQqdR7GDsTHQPE7wquzQENMRZbPeHURA9dZgH/9ktuhDh3D1qnIDI9PyPftabme0A== + dependencies: + fast-deep-equal "^3.1.3" + immer "^8.0.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-redux@^7.2.3: + version "7.2.4" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225" + integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA== + dependencies: + "@babel/runtime" "^7.12.1" + "@types/react-redux" "^7.1.16" + hoist-non-react-statics "^3.3.2" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.13.1" + +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +recoil@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.2.0.tgz#69344b5bec3129272560d8d9d6001ada3ee4d80c" + integrity sha512-VOJfYVQ3VgmfS7L5tV9QdOR+AJhvll8yGr1+3nJPCqADulImuScGZ2sJtejPps3zfTu/o98y5kO4lje8Tx6XHw== + dependencies: + hamt_plus "1.0.2" + +redux-thunk@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.0, redux@^4.0.5, redux@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4" + integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g== + dependencies: + "@babel/runtime" "^7.9.2" + +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +stack-utils@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" + +typescript@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" + integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== + +valtio@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.0.6.tgz#b316deea5537d254a141e2e5af1692d9eae2f60f" + integrity sha512-ylCis9IkcE7b92XjMb3ebdJgLvJEFJ2NjfuD01QNr98pVOhRa5WsW4LSykFgbO4W7ftrZtO8jN4svZL0XlD77w== + dependencies: + proxy-compare "2.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^7.4.6: + version "7.5.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.1.tgz#44fc000d87edb1d9c53e51fbc69a0ac1f6871d66" + integrity sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yazl@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" diff --git a/package.json b/package.json index b4ed3355..2e7e2bce 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "name": "agile", "private": true, "author": "BennoDev", "license": "MIT", @@ -73,6 +74,5 @@ }, "workspaces": [ "packages/*" - ], - "name": "agile" + ] } From 8daec3a25bb313cebc88b289e534523ae3ce90ab Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 15:15:04 +0200 Subject: [PATCH 22/55] added some more state manager to tests --- .../react/counter/bench/agilets.tsx | 4 +- .../benchmarks/react/counter/bench/jotai.tsx | 14 +++++++ .../benchmarks/react/counter/bench/mobx.tsx | 19 +++++++++ .../benchmarks/react/counter/bench/recoil.tsx | 22 ++++++++++ .../react/counter/bench/redux-toolkit.tsx | 40 ++++++++++++++++++ .../benchmarks/react/counter/bench/redux.tsx | 41 +++++++++++++++++++ .../benchmarks/react/counter/bench/valtio.tsx | 14 +++++++ .../react/counter/bench/zustand.tsx | 19 +++++++++ benchmark/benchmarks/react/counter/index.ts | 15 +++++++ benchmark/package.json | 4 +- benchmark/yarn.lock | 23 +++-------- 11 files changed, 193 insertions(+), 22 deletions(-) create mode 100644 benchmark/benchmarks/react/counter/bench/jotai.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/mobx.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/recoil.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/redux-toolkit.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/redux.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/valtio.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/zustand.tsx diff --git a/benchmark/benchmarks/react/counter/bench/agilets.tsx b/benchmark/benchmarks/react/counter/bench/agilets.tsx index 0761a07a..300ff431 100644 --- a/benchmark/benchmarks/react/counter/bench/agilets.tsx +++ b/benchmark/benchmarks/react/counter/bench/agilets.tsx @@ -1,7 +1,7 @@ -import { Agile, Logger } from '@agile-ts/core'; -import { useAgile } from '@agile-ts/react'; import React from 'react'; import ReactDom from 'react-dom'; +import { Agile, Logger } from '@agile-ts/core'; +import { useAgile } from '@agile-ts/react'; const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); const COUNT = AgileApp.createState(0); diff --git a/benchmark/benchmarks/react/counter/bench/jotai.tsx b/benchmark/benchmarks/react/counter/bench/jotai.tsx new file mode 100644 index 00000000..ca138586 --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/jotai.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { atom, useAtom } from 'jotai'; + +const countAtom = atom(0); + +const App = () => { + const [count, setCount] = useAtom(countAtom); + return

setCount((state) => state + 1)}>{count}

; +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/bench/mobx.tsx b/benchmark/benchmarks/react/counter/bench/mobx.tsx new file mode 100644 index 00000000..c597bf9a --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/mobx.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; + +const appState = observable({ + count: 0, + increment: action(function () { + appState.count += 1; + }), +}); + +const App = observer(() => { + return

{appState.count}

; +}); + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/bench/recoil.tsx b/benchmark/benchmarks/react/counter/bench/recoil.tsx new file mode 100644 index 00000000..d857e23d --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/recoil.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { atom, RecoilRoot, useRecoilState } from 'recoil'; + +const counterState = atom({ + key: 'counterState', + default: 0, +}); + +const App = () => { + const [count, setCount] = useRecoilState(counterState); + return

setCount((v) => v + 1)}>{count}

; +}; + +export default function (target: HTMLElement) { + ReactDom.render( + + + , + target + ); +} diff --git a/benchmark/benchmarks/react/counter/bench/redux-toolkit.tsx b/benchmark/benchmarks/react/counter/bench/redux-toolkit.tsx new file mode 100644 index 00000000..1fbbedc6 --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/redux-toolkit.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { configureStore, createSlice } from '@reduxjs/toolkit'; +import { Provider, useDispatch, useSelector } from 'react-redux'; + +const counterSlice = createSlice({ + name: 'counter', + initialState: { + count: 0, + }, + reducers: { + increment: (state) => { + state.count += 1; + }, + }, +}); + +const store = configureStore({ + reducer: { + counter: counterSlice.reducer, + }, +}); + +const App = () => { + const count = useSelector((state: any) => state.counter.count); + const dispatch = useDispatch(); + + return ( +

dispatch(counterSlice.actions.increment())}>{count}

+ ); +}; + +export default function (target: HTMLElement) { + ReactDom.render( + + + , + target + ); +} diff --git a/benchmark/benchmarks/react/counter/bench/redux.tsx b/benchmark/benchmarks/react/counter/bench/redux.tsx new file mode 100644 index 00000000..72b5c604 --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/redux.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { combineReducers, createStore } from 'redux'; +import { Provider, useDispatch, useSelector } from 'react-redux'; + +const increment = () => { + return { + type: 'INCREMENT', + }; +}; + +const counter = (state = 0, action: any) => { + switch (action.type) { + case 'INCREMENT': + return state + 1; + default: + return state; + } +}; + +const rootReducer = combineReducers({ + counter, +}); + +const store = createStore(rootReducer); + +const App = () => { + const count = useSelector((state: any) => state.counter); + const dispatch = useDispatch(); + + return

dispatch(increment())}>{count}

; +}; + +export default function (target: HTMLElement) { + ReactDom.render( + + + , + target + ); +} diff --git a/benchmark/benchmarks/react/counter/bench/valtio.tsx b/benchmark/benchmarks/react/counter/bench/valtio.tsx new file mode 100644 index 00000000..682a3211 --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/valtio.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { proxy, useSnapshot } from 'valtio'; + +const state = proxy({ count: 0 }); + +function App() { + const snapshot = useSnapshot(state, { sync: true }); + return

state.count++}>{snapshot.count}

; +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/bench/zustand.tsx b/benchmark/benchmarks/react/counter/bench/zustand.tsx new file mode 100644 index 00000000..9c9bd62d --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/zustand.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import create from 'zustand'; + +const useStore = create((set) => ({ + count: 0, + increment: () => set((state) => ({ count: state.count + 1 })), +})); + +const App = () => { + const count = useStore((state) => state.count); + const increment = useStore((state) => state.increment); + + return

increment()}>{count}

; +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index 987cc3d9..f0b4c3e7 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -3,6 +3,13 @@ import ReactDOM from 'react-dom'; // Files to run the Benchmark on import agilets from './bench/agilets'; +import jotai from './bench/jotai'; +import mobx from './bench/mobx'; +import recoil from './bench/recoil'; +import redux from './bench/redux'; +import reduxToolkit from './bench/redux-toolkit'; +import valtio from './bench/valtio'; +import zustand from './bench/zustand'; // @ts-ignore // Benchmark.js requires an instance of itself globally @@ -17,6 +24,7 @@ const target = document.getElementById('bench')!; // Increment Element let increment: HTMLHeadingElement; +// Configures a Benchmark test function configTest(renderElement: (target: HTMLElement) => void): Options { return { fn() { @@ -43,6 +51,13 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { // Add Tests to Benchmark Suite suite .add('AgileTs', configTest(agilets)) + .add('Jotai', configTest(jotai)) + .add('Mobx', configTest(mobx)) + .add('Recoil', configTest(recoil)) + .add('Redux', configTest(redux)) + .add('Redux-Toolkit', configTest(reduxToolkit)) + .add('Valtio', configTest(valtio)) + .add('Zustand', configTest(zustand)) // Add Listener .on('start', function (this: any) { diff --git a/benchmark/package.json b/benchmark/package.json index 02877ecc..ba4931b7 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -40,11 +40,11 @@ "jotai": "^0.16.0", "mobx": "^6.2.0", "mobx-react": "^7.1.0", - "pullstate": "^1.22.1", "react-redux": "^7.2.3", "recoil": "^0.2.0", "redux": "^4.0.5", - "valtio": "^1.0.3" + "valtio": "^1.0.3", + "zustand": "^3.5.5" }, "bugs": { "url": "https://github.com/agile-ts/agile/issues" diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index a84f9339..77b49473 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -202,11 +202,6 @@ extract-zip@^2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" -fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -263,11 +258,6 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -immer@^8.0.1: - version "8.0.4" - resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a" - integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ== - immer@^9.0.1: version "9.0.3" resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.3.tgz#146e2ba8b84d4b1b15378143c2345559915097f4" @@ -437,14 +427,6 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -pullstate@^1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/pullstate/-/pullstate-1.22.1.tgz#ffdde634e8c721907de8e6d37a85c6083137ee8a" - integrity sha512-Xu3umsGOG6qCQ4IWxKSEikQqdR7GDsTHQPE7wquzQENMRZbPeHURA9dZgH/9ktuhDh3D1qnIDI9PyPftabme0A== - dependencies: - fast-deep-equal "^3.1.3" - immer "^8.0.1" - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -584,3 +566,8 @@ yazl@^2.5.1: integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== dependencies: buffer-crc32 "~0.2.3" + +zustand@^3.5.5: + version "3.5.5" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.5.5.tgz#628458ad70621ddc2a17dbee49be963e5c0dccb5" + integrity sha512-iTiJoxzYFtiD7DhscgwK2P4Kft1JcZEI2U7mG8IxiOFM4KpBAiJZfFop3r/3wbCuyltXI6ph1Fx90e4j/S43XA== From 877ef4e5e467497b188aed52b6fe9b8a49f976cc Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 15:39:30 +0200 Subject: [PATCH 23/55] fixed some deps --- .../benchmarks/react/counter/bench/jotai.tsx | 9 ++++- .../react/counter/bench/zustand.tsx | 2 +- benchmark/package.json | 26 +++++++------- benchmark/yarn.lock | 36 ++++++++++--------- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/benchmark/benchmarks/react/counter/bench/jotai.tsx b/benchmark/benchmarks/react/counter/bench/jotai.tsx index ca138586..31568aab 100644 --- a/benchmark/benchmarks/react/counter/bench/jotai.tsx +++ b/benchmark/benchmarks/react/counter/bench/jotai.tsx @@ -6,7 +6,14 @@ const countAtom = atom(0); const App = () => { const [count, setCount] = useAtom(countAtom); - return

setCount((state) => state + 1)}>{count}

; + return ( +

{ + setCount((v) => v + 1); + }}> + {count} +

+ ); }; export default function (target: HTMLElement) { diff --git a/benchmark/benchmarks/react/counter/bench/zustand.tsx b/benchmark/benchmarks/react/counter/bench/zustand.tsx index 9c9bd62d..ac73a2b5 100644 --- a/benchmark/benchmarks/react/counter/bench/zustand.tsx +++ b/benchmark/benchmarks/react/counter/bench/zustand.tsx @@ -11,7 +11,7 @@ const App = () => { const count = useStore((state) => state.count); const increment = useStore((state) => state.increment); - return

increment()}>{count}

; + return

{count}

; }; export default function (target: HTMLElement) { diff --git a/benchmark/package.json b/benchmark/package.json index ba4931b7..1856eb70 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -18,33 +18,33 @@ "url": "git+https://github.com/agile-ts/agile.git" }, "dependencies": { + "@agile-ts/core": "^0.1.0", + "@agile-ts/react": "^0.1.0", + "@reduxjs/toolkit": "^1.6.0", "benchmark": "^2.1.4", "colorette": "^1.2.2", "dotenv": "^10.0.0", "esbuild": "^0.12.14", "esbuild-register": "^2.6.0", + "jotai": "^1.1.2", "lodash": "^4.17.21", + "mobx": "^6.3.2", + "mobx-react": "^7.2.0", "playwright": "^1.12.3", "react": "^17.0.2", "react-dom": "^17.0.2", - "typescript": "^4.3.5" + "react-redux": "^7.2.4", + "recoil": "^0.3.1", + "redux": "^4.1.0", + "typescript": "^4.3.5", + "valtio": "^1.0.6", + "zustand": "^3.5.5" }, "devDependencies": { - "@agile-ts/core": "file:.yalc/@agile-ts/core", - "@agile-ts/react": "file:.yalc/@agile-ts/react", - "@reduxjs/toolkit": "^1.5.1", "@types/benchmark": "^2.1.0", "@types/node": "^16.0.0", "@types/react": "^17.0.13", - "@types/react-dom": "^17.0.8", - "jotai": "^0.16.0", - "mobx": "^6.2.0", - "mobx-react": "^7.1.0", - "react-redux": "^7.2.3", - "recoil": "^0.2.0", - "redux": "^4.0.5", - "valtio": "^1.0.3", - "zustand": "^3.5.5" + "@types/react-dom": "^17.0.8" }, "bugs": { "url": "https://github.com/agile-ts/agile/issues" diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index 77b49473..415c710a 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -2,8 +2,10 @@ # yarn lockfile v1 -"@agile-ts/core@file:.yalc/@agile-ts/core": +"@agile-ts/core@^0.1.0": version "0.1.0" + resolved "https://registry.yarnpkg.com/@agile-ts/core/-/core-0.1.0.tgz#11f048f06288b78a0f5841b4b8ee62c0f9863f75" + integrity sha512-+SeukkYXVwv9WFnk7/P/NCqNHFdywQNnSxGdcHAh1jSFeofofcP+aqp5oaEYf/4A73qe1yDF6vr7wMz0q0Zz4w== dependencies: "@agile-ts/logger" "^0.0.5" "@agile-ts/utils" "^0.0.5" @@ -15,8 +17,10 @@ dependencies: "@agile-ts/utils" "^0.0.5" -"@agile-ts/react@file:.yalc/@agile-ts/react": +"@agile-ts/react@^0.1.0": version "0.1.0" + resolved "https://registry.yarnpkg.com/@agile-ts/react/-/react-0.1.0.tgz#338ef6c562eae473f1328fb6727b4ae2aa9b4fc4" + integrity sha512-CzQE69LBIT8Ynk0M570TTKhA1KCQoR/RM/sNWh72De88W1yOw+BUtff5APsJPv+3we04zy7dz9KwIYjR4VbQLA== "@agile-ts/utils@^0.0.5": version "0.0.5" @@ -30,7 +34,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@reduxjs/toolkit@^1.5.1": +"@reduxjs/toolkit@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.0.tgz#0a17c6941c57341f8b31e982352b495ab69d5add" integrity sha512-eGL50G+Vj5AG5uD0lineb6rRtbs96M8+hxbcwkHpZ8LQcmt0Bm33WyBSnj5AweLkjQ7ZP+KFRDHiLMznljRQ3A== @@ -276,10 +280,10 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -jotai@^0.16.0: - version "0.16.11" - resolved "https://registry.yarnpkg.com/jotai/-/jotai-0.16.11.tgz#efeaf0311513cf41d5c82b7009f8381d518cbc41" - integrity sha512-EPBeDSBc4FwbFRArRjcI6IsHrVkba771mSOFTN3M4r5rU3ZcZMDFLElZScZVr6w1kgrEXvaMm59VAyEB3QUEbA== +jotai@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.1.2.tgz#3f211e0c03c74e95ea6fd7a69c1d2b65731009bf" + integrity sha512-dni4wtgYGG+s9YbOJN7lcfrrhxiD6bH1SN00Pnl0F2htgOXmjxqkGlFzw02OK0Rw35wGNzBfDTJVtbGD9wHOhg== jpeg-js@^0.4.2: version "0.4.3" @@ -325,14 +329,14 @@ mobx-react-lite@^3.2.0: resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-3.2.0.tgz#331d7365a6b053378dfe9c087315b4e41c5df69f" integrity sha512-q5+UHIqYCOpBoFm/PElDuOhbcatvTllgRp3M1s+Hp5j0Z6XNgDbgqxawJ0ZAUEyKM8X1zs70PCuhAIzX1f4Q/g== -mobx-react@^7.1.0: +mobx-react@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-7.2.0.tgz#241e925e963bb83a31d269f65f9f379e37ecbaeb" integrity sha512-KHUjZ3HBmZlNnPd1M82jcdVsQRDlfym38zJhZEs33VxyVQTvL77hODCArq6+C1P1k/6erEeo2R7rpE7ZeOL7dg== dependencies: mobx-react-lite "^3.2.0" -mobx@^6.2.0: +mobx@^6.3.2: version "6.3.2" resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.2.tgz#125590961f702a572c139ab69392bea416d2e51b" integrity sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg== @@ -449,7 +453,7 @@ react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-redux@^7.2.3: +react-redux@^7.2.4: version "7.2.4" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225" integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA== @@ -469,10 +473,10 @@ react@^17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" -recoil@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.2.0.tgz#69344b5bec3129272560d8d9d6001ada3ee4d80c" - integrity sha512-VOJfYVQ3VgmfS7L5tV9QdOR+AJhvll8yGr1+3nJPCqADulImuScGZ2sJtejPps3zfTu/o98y5kO4lje8Tx6XHw== +recoil@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.3.1.tgz#40ef544160d19d76e25de8929d7e512eace13b90" + integrity sha512-KNA3DRqgxX4rRC8E7fc6uIw7BACmMPuraIYy+ejhE8tsw7w32CetMm8w7AMZa34wzanKKkev3vl3H7Z4s0QSiA== dependencies: hamt_plus "1.0.2" @@ -481,7 +485,7 @@ redux-thunk@^2.3.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== -redux@^4.0.0, redux@^4.0.5, redux@^4.1.0: +redux@^4.0.0, redux@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4" integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g== @@ -535,7 +539,7 @@ typescript@^4.3.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== -valtio@^1.0.3: +valtio@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/valtio/-/valtio-1.0.6.tgz#b316deea5537d254a141e2e5af1692d9eae2f60f" integrity sha512-ylCis9IkcE7b92XjMb3ebdJgLvJEFJ2NjfuD01QNr98pVOhRa5WsW4LSykFgbO4W7ftrZtO8jN4svZL0XlD77w== From 0a3a550c097960f4811318247d58de155fac5ab6 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 16:26:40 +0200 Subject: [PATCH 24/55] added basic readme to benchmarks --- benchmark/README.md | 52 +++++++++++++++++++++ benchmark/benchmarks/react/counter/index.ts | 21 +++++---- benchmark/run.ts | 13 +++--- 3 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 benchmark/README.md diff --git a/benchmark/README.md b/benchmark/README.md new file mode 100644 index 00000000..771c48de --- /dev/null +++ b/benchmark/README.md @@ -0,0 +1,52 @@ +# 🚀️ Benchmarks + +The `Benchmark Test Suites` are supposed to showcase where AgileTs is roughly located in terms of performance. +I know a counter doesn't really show real world app performance, +but it is better than nothing. + +## Counter Benchmark + +```ts +1. Zustand x 30,591 ops/sec ±1.15% (61 runs sampled) +2. Redux x 30,239 ops/sec ±1.64% (63 runs sampled) +3. Mobx x 29,032 ops/sec ±1.24% (64 runs sampled) +4. AgileTs x 28,327 ops/sec ±2.96% (60 runs sampled) +5. Redux-Toolkit x 22,808 ops/sec ±1.79% (65 runs sampled) +6. Jotai x 22,479 ops/sec ±5.79% (63 runs sampled) +7. Valtio x 20,784 ops/sec ±2.75% (63 runs sampled) +8. Recoil x 14,351 ops/sec ±1.55% (65 runs sampled) +``` + +## 🏃 Running Benchmarks + +The Benchmark tests run on top of the [`benchmark.js` library](https://github.com/bestiejs/benchmark.js/) +via [Playwright](https://github.com/microsoft/playwright) in the Chrome Browser. + +Before starting, make sure you are in the `/benchmark` directory. + +### 1️⃣ Install dependencies + +To prepare the dependencies, run: +```ts +yarn install +``` + +### 2️⃣ Run Benchmark Test Suite + +Execute the benchmark located in `./benchmarks/react/counter`. +```ts +yarn run test:counter +``` + +## ⭐️ Contribute + +Get a part of AgileTs and start contributing. We welcome any meaningful contribution. 😀 +To find out more about contributing, check out the [CONTRIBUTING.md](https://github.com/agile-ts/agile/blob/master/CONTRIBUTING.md). + + + Maintainability + + +## 🎉 Credits + +The Benchmark CLI is inspired by [`exome`](https://github.com/Marcisbee/exome). diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index f0b4c3e7..f7f573aa 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -15,40 +15,41 @@ import zustand from './bench/zustand'; // Benchmark.js requires an instance of itself globally window.Benchmark = Benchmark; -// Create new Benchmark test suite +// Create new Benchmark Test Suite const suite = new Suite('Count'); -// Retrieve the Element to render the Benchmark in +// Retrieve the Element to render the Benchmark Test Suite in const target = document.getElementById('bench')!; // Increment Element let increment: HTMLHeadingElement; -// Configures a Benchmark test +// Configures a single Benchmark Test function configTest(renderElement: (target: HTMLElement) => void): Options { return { fn() { + // Execute increment action increment.click(); }, onStart() { - // Render Benchmark Component in the target Element + // Render React Component in the target Element renderElement(target); - // Retrieve Increment Element + // Retrieve Element to execute the increment action on increment = target.querySelector('h1')!; }, onComplete() { - // Set 'output' in the Benchmark + // Set 'output' in the Benchmark itself to print it later (this as any).output = parseInt(target.innerText, 10); - // Unmount Component + // Unmount React Component ReactDOM.unmountComponentAtNode(target); target.innerHTML = ''; }, }; } -// Add Tests to Benchmark Suite +// Add Tests to the Benchmark Test Suite suite .add('AgileTs', configTest(agilets)) .add('Jotai', configTest(jotai)) @@ -70,9 +71,9 @@ suite console.log(`Fastest is ${this.filter('fastest').map('name')}`); // @ts-ignore - // Notify server to end the Benchmark + // Notify server that the Benchmark Test Suite has ended window.TEST.ended = true; }) - // Run Benchmark Suite + // Run Benchmark Test Suite .run({ async: true }); diff --git a/benchmark/run.ts b/benchmark/run.ts index a43c6329..7c233c47 100644 --- a/benchmark/run.ts +++ b/benchmark/run.ts @@ -6,7 +6,7 @@ import playwright from 'playwright'; dotenv.config(); // https://nodejs.org/docs/latest/api/process.html#process_process_argv -// Extract entry from the executed command +// Extract entry (at third parameter) from the executed command // yarn run ./path/to/entry -> './path/to/entry' is extracted const entry = process.argv.slice(2)[0]; if (entry == null) { @@ -16,7 +16,8 @@ if (entry == null) { } const startBenchmark = async () => { - // Bundle Benchmark Test and launch the server on which they are executed + // Bundle Benchmark Test Suite + // and launch the server on which the Test Suite is executed const server = await esbuild.serve( { servedir: 'public', @@ -39,12 +40,12 @@ const startBenchmark = async () => { console.log(`Server is running at port: ${server.port}`); - // Launch Chrome as browser to run the benchmarks in + // Launch Chrome as browser to run the Benchmark Test Suite in const browser = await playwright.chromium.launch(); const context = await browser.newContext(); const page = await context.newPage(); - // Option to open and test the benchmarks in the browser manually + // Option to open and test the Benchmark Test Suite in the browser manually if (process.env.MANUAL_BENCHMARK === 'true') { console.log( `Open the Browser at '${serverUrl}' to run the tests manually.` @@ -65,7 +66,7 @@ const startBenchmark = async () => { console.log(...message); }); - // Open url the server is running on + // Open the url the server is running on await page.goto(serverUrl); // Wait for tests to be executed (indicator is when 'window.TESTS.ended' is set to true) @@ -80,7 +81,7 @@ const startBenchmark = async () => { } ); - // Close Browser and stop server + // Close browser and stop server await browser.close(); server.stop(); }; From e7ec3de6a6998143dfb2e961a4f3a4478009022f Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 17:49:08 +0200 Subject: [PATCH 25/55] added hookstate --- .../benchmarks/react/counter/bench/hookstate.tsx | 14 ++++++++++++++ benchmark/benchmarks/react/counter/index.ts | 4 +++- benchmark/package.json | 1 + benchmark/yarn.lock | 5 +++++ 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 benchmark/benchmarks/react/counter/bench/hookstate.tsx diff --git a/benchmark/benchmarks/react/counter/bench/hookstate.tsx b/benchmark/benchmarks/react/counter/bench/hookstate.tsx new file mode 100644 index 00000000..e3f12268 --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/hookstate.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { createState, useHookstate } from '@hookstate/core'; + +const counter = createState(0); + +const App = () => { + const state = useHookstate(counter); + return

state.set((v) => v + 1)}>{state.get()}

; +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index f7f573aa..e62d7349 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -1,8 +1,9 @@ -import Benchmark, { Suite, Options } from 'benchmark'; import ReactDOM from 'react-dom'; +import Benchmark, { Suite, Options } from 'benchmark'; // Files to run the Benchmark on import agilets from './bench/agilets'; +import hookstate from './bench/hookstate'; import jotai from './bench/jotai'; import mobx from './bench/mobx'; import recoil from './bench/recoil'; @@ -52,6 +53,7 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { // Add Tests to the Benchmark Test Suite suite .add('AgileTs', configTest(agilets)) + // .add('Hookstate', configTest(hookstate)) // Commented out because it crashes the test (idk why) .add('Jotai', configTest(jotai)) .add('Mobx', configTest(mobx)) .add('Recoil', configTest(recoil)) diff --git a/benchmark/package.json b/benchmark/package.json index 1856eb70..cf332933 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -20,6 +20,7 @@ "dependencies": { "@agile-ts/core": "^0.1.0", "@agile-ts/react": "^0.1.0", + "@hookstate/core": "^3.0.8", "@reduxjs/toolkit": "^1.6.0", "benchmark": "^2.1.4", "colorette": "^1.2.2", diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index 415c710a..5c44baee 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -34,6 +34,11 @@ dependencies: regenerator-runtime "^0.13.4" +"@hookstate/core@^3.0.8": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-3.0.8.tgz#d6838153d6d43c2f35cfca475c31248192564e62" + integrity sha512-blQagGIVIbNoUiNCRrvaXqFmUe7WGMY35ok/LENfl2pcIsLBjkreYIZiaSFi83tkycwq7ZOmcQz/R1nvLKhH8w== + "@reduxjs/toolkit@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.0.tgz#0a17c6941c57341f8b31e982352b495ab69d5add" From 1c0ccf6e620c1bf1e27d37310e48c94c0f43c625 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 20:13:10 +0200 Subject: [PATCH 26/55] added fields benchmark --- benchmark/benchmarks/react/counter/index.ts | 2 +- .../react/fields/bench/agile/collection.tsx | 49 ++++++++++++ .../react/fields/bench/agile/nestedState.tsx | 48 +++++++++++ .../react/fields/bench/agile/state.tsx | 63 +++++++++++++++ .../react/fields/bench/hookstate.tsx | 43 ++++++++++ .../benchmarks/react/fields/bench/jotai.tsx | 41 ++++++++++ .../benchmarks/react/fields/bench/mobx.tsx | 45 +++++++++++ .../benchmarks/react/fields/bench/recoil.tsx | 49 ++++++++++++ .../benchmarks/react/fields/bench/redux.tsx | 68 ++++++++++++++++ .../benchmarks/react/fields/bench/valtio.tsx | 43 ++++++++++ benchmark/benchmarks/react/fields/index.ts | 80 +++++++++++++++++++ 11 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 benchmark/benchmarks/react/fields/bench/agile/collection.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/agile/state.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/hookstate.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/jotai.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/mobx.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/recoil.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/redux.tsx create mode 100644 benchmark/benchmarks/react/fields/bench/valtio.tsx create mode 100644 benchmark/benchmarks/react/fields/index.ts diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index e62d7349..a6d6489e 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -53,7 +53,7 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { // Add Tests to the Benchmark Test Suite suite .add('AgileTs', configTest(agilets)) - // .add('Hookstate', configTest(hookstate)) // Commented out because it crashes the test (idk why) + .add('Hookstate', configTest(hookstate)) .add('Jotai', configTest(jotai)) .add('Mobx', configTest(mobx)) .add('Recoil', configTest(recoil)) diff --git a/benchmark/benchmarks/react/fields/bench/agile/collection.tsx b/benchmark/benchmarks/react/fields/bench/agile/collection.tsx new file mode 100644 index 00000000..fe983c22 --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/agile/collection.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { Agile, Logger } from '@agile-ts/core'; +import { useAgile, useValue } from '@agile-ts/react'; + +const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); + +const FIELDS = AgileApp.createCollection({ + initialData: Array.from(Array(1000).keys()).map((i) => ({ + id: i, + name: `Field #${i + 1}`, + })), +}); + +function Field({ index }: { index: number | string }) { + const ITEM = FIELDS.getItem(index); + const item = useAgile(ITEM); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + ITEM?.patch({ name: e.target.value })} + /> +
+ ); +} + +function App() { + const fieldKeys = useValue(FIELDS); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fieldKeys.map((key, i) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx b/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx new file mode 100644 index 00000000..d5c38b3d --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { Agile, Logger } from '@agile-ts/core'; +import { useAgile } from '@agile-ts/react'; + +const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); + +const FIELDS = AgileApp.createState( + Array.from(Array(1000).keys()).map((i) => + AgileApp.createState(`Field #${i + 1}`) + ) +); + +function Field({ index }: { index: number }) { + const name = useAgile(FIELDS.value[index]); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + FIELDS.value[index].set(e.target.value); + }} + /> +
+ ); +} + +function App() { + const fields = useAgile(FIELDS); + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/fields/bench/agile/state.tsx b/benchmark/benchmarks/react/fields/bench/agile/state.tsx new file mode 100644 index 00000000..85115db4 --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/agile/state.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { Agile, Logger } from '@agile-ts/core'; +import { useProxy, useSelector } from '@agile-ts/react'; + +const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); + +const FIELDS = AgileApp.createState( + Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`) +); + +// With Selector +// function Field({ index }: { index: number }) { +// const name = useSelector(FIELDS, (value) => value[index]) as string; +// +// return ( +//
+// Last {``} render at: {new Date().toISOString()} +//   +// { +// FIELDS.nextStateValue[index] = e.target.value; +// FIELDS.ingest(); +// }} /> +//
+// ); +// } + +// With Proxy +function Field({ index }: { index: number }) { + const fields = useProxy(FIELDS); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + FIELDS.nextStateValue[index] = e.target.value; + FIELDS.ingest(); + }} + /> +
+ ); +} + +function App() { + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {FIELDS.value.map((field, index) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/fields/bench/hookstate.tsx b/benchmark/benchmarks/react/fields/bench/hookstate.tsx new file mode 100644 index 00000000..6278912c --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/hookstate.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { createState, useHookstate, State } from '@hookstate/core'; + +const fields = createState( + Array.from(Array.from(Array(1000).keys()).map((i) => `Field #${i + 1} value`)) +); + +function Field({ field }: { field: State }) { + const name = useHookstate(field); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + name.set(e.target.value); + }} + /> +
+ ); +} + +function App() { + const state = useHookstate(fields); + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {state.map((field, index) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/fields/bench/jotai.tsx b/benchmark/benchmarks/react/fields/bench/jotai.tsx new file mode 100644 index 00000000..c402d0dd --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/jotai.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { atom, useAtom } from 'jotai'; + +const fields = Array.from(Array(1000).keys()).map((i) => + atom(`Field #${i + 1}`) +); + +const fieldsStore = atom(fields); + +function Field({ index }: { index: number }) { + const [name, rename] = useAtom(fields[index]); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + rename(e.target.value)} /> +
+ ); +} + +function App() { + const [fields] = useAtom(fieldsStore); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/fields/bench/mobx.tsx b/benchmark/benchmarks/react/fields/bench/mobx.tsx new file mode 100644 index 00000000..bb685aea --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/mobx.tsx @@ -0,0 +1,45 @@ +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import * as ReactDom from 'react-dom'; + +const appState = observable({ + fields: Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`), + rename: action(function (value: string, index: number) { + // console.log(state) + appState.fields[index] = value; + }), +}); + +function Field({ index }: { index: number }) { + const field = appState.fields[index]; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + appState.rename(e.target.value, index)} + /> +
+ ); +} + +const App = observer(() => { + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {appState.fields.map((field, index) => ( + + ))} +
+ ); +}); + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/fields/bench/recoil.tsx b/benchmark/benchmarks/react/fields/bench/recoil.tsx new file mode 100644 index 00000000..7030a536 --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/recoil.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { atom, RecoilRoot, useRecoilState } from 'recoil'; + +const fields = Array.from(Array(1000).keys()).map((i) => + atom({ key: `field-${i}`, default: `Field #${i + 1}` }) +); + +const fieldsStore = atom({ + key: 'fieldsStore', + default: fields, +}); + +function Field({ index }: { index: number }) { + const [name, rename] = useRecoilState(fields[index]); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + rename(e.target.value)} /> +
+ ); +} + +function App() { + const [fields] = useRecoilState(fieldsStore); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render( + + + , + target + ); +} diff --git a/benchmark/benchmarks/react/fields/bench/redux.tsx b/benchmark/benchmarks/react/fields/bench/redux.tsx new file mode 100644 index 00000000..95a41fb2 --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/redux.tsx @@ -0,0 +1,68 @@ +import { configureStore, createSlice } from '@reduxjs/toolkit'; +import { combineReducers, createStore } from 'redux'; +import { Provider, useDispatch, useSelector } from 'react-redux'; +import * as React from 'react'; +import * as ReactDom from 'react-dom'; + +const fieldsSlice = createSlice({ + name: 'fields', + initialState: { + fields: Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`), + }, + reducers: { + rename: (state, action) => { + state.fields[action.payload.index] = action.payload.value; + }, + }, +}); + +const store = configureStore({ + reducer: { + fields: fieldsSlice.reducer, + }, +}); + +function Field({ index, field: name }: { index: number; field: string }) { + const dispatch = useDispatch(); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + + dispatch(fieldsSlice.actions.rename({ index, value: e.target.value })) + } + /> +
+ ); +} + +function App() { + const fields: string[] = useSelector( + (state: any) => state.fields.fields, + () => false + ); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render( + + + , + target + ); +} diff --git a/benchmark/benchmarks/react/fields/bench/valtio.tsx b/benchmark/benchmarks/react/fields/bench/valtio.tsx new file mode 100644 index 00000000..b356e66d --- /dev/null +++ b/benchmark/benchmarks/react/fields/bench/valtio.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { proxy, useSnapshot } from 'valtio'; + +const state = proxy({ + fields: Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`), +}); + +function Field({ index }: { index: number }) { + const { fields } = useSnapshot(state); + const name = fields[index]; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + (state.fields[index] = e.target.value)} + /> +
+ ); +} + +function App() { + const { fields } = useSnapshot(state, { sync: true }); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/fields/index.ts b/benchmark/benchmarks/react/fields/index.ts new file mode 100644 index 00000000..4bc6e4a9 --- /dev/null +++ b/benchmark/benchmarks/react/fields/index.ts @@ -0,0 +1,80 @@ +import Benchmark, { Suite, Options } from 'benchmark'; +import ReactDOM from 'react-dom'; + +// Files to run the Benchmark on +import agileCollection from './bench/agile/collection'; +import agileState from './bench/agile/state'; +import agileNestedState from './bench/agile/nestedState'; +import hookstate from './bench/hookstate'; +import jotai from './bench/jotai'; +import mobx from './bench/mobx'; +import recoil from './bench/recoil'; +import redux from './bench/redux'; +import valtio from './bench/valtio'; + +// @ts-ignore +// Benchmark.js requires an instance of itself globally +window.Benchmark = Benchmark; + +// Create new Benchmark Test Suite +const suite = new Suite('1000 Fields'); + +// Retrieve the Element to render the Benchmark Test Suite in +const target = document.getElementById('bench')!; + +// Configures a single Benchmark Test +function configTest(renderElement: (target: HTMLElement) => void): Options { + return { + fn() { + // Retrieve Input field to update + const fieldToUpdate = Math.floor(Math.random() * 1000); + const input = target.querySelectorAll('input')[fieldToUpdate]; + + // Update retrieved Input value + const evt = document.createEvent('HTMLEvents'); + evt.initEvent('input', true, true); + input.value = '' + Math.random(); + (input as any)._valueTracker.setValue(Math.random()); + input.dispatchEvent(evt); + }, + onStart() { + // Render React Component in the target Element + renderElement(target); + }, + onComplete() { + // Unmount React Component + ReactDOM.unmountComponentAtNode(target); + target.innerHTML = ''; + }, + }; +} + +// Add Tests to the Benchmark Test Suite +suite + // .add('Agile Collection', configTest(agileCollection)) + // .add('Agile State', configTest(agileState)) + .add('Agile nested State', configTest(agileNestedState)) + .add('Hookstate', configTest(hookstate)) + .add('Jotai', configTest(jotai)) + .add('Mobx', configTest(mobx)) + .add('Recoil', configTest(recoil)) + .add('Redux', configTest(redux)) + .add('Valtio', configTest(valtio)) + + // Add Listener + .on('start', function (this: any) { + console.log(`Starting ${this.name}`); + }) + .on('cycle', (event: any) => { + console.log(String(event.target)); + }) + .on('complete', function (this: any) { + console.log(`Fastest is ${this.filter('fastest').map('name')}`); + + // @ts-ignore + // Notify server that the Benchmark Test Suite has ended + window.TEST.ended = true; + }) + + // Run Benchmark Test Suite + .run({ async: true }); From d96452b5d94b067104e9b834ab524a8d8810c784 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 20:23:49 +0200 Subject: [PATCH 27/55] optimized agile nested State --- .../react/fields/bench/agile/nestedState.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx b/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx index d5c38b3d..5aab1dee 100644 --- a/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx +++ b/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactDom from 'react-dom'; -import { Agile, Logger } from '@agile-ts/core'; +import { Agile, Logger, State } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); @@ -11,8 +11,8 @@ const FIELDS = AgileApp.createState( ) ); -function Field({ index }: { index: number }) { - const name = useAgile(FIELDS.value[index]); +function Field({ field }: { field: State }) { + const name = useAgile(field); return (
@@ -21,7 +21,7 @@ function Field({ index }: { index: number }) { { - FIELDS.value[index].set(e.target.value); + field.set(e.target.value); }} />
@@ -37,7 +37,7 @@ function App() {
{fields.map((field, index) => ( - + ))} ); From cafe155f31c98f09f26ded69f5b8a6cd17091b88 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 3 Jul 2021 20:34:46 +0200 Subject: [PATCH 28/55] updated benchmark readme --- benchmark/README.md | 14 ++++++++++++++ benchmark/benchmarks/react/fields/bench/mobx.tsx | 4 ++-- benchmark/benchmarks/react/fields/bench/redux.tsx | 5 ++--- benchmark/benchmarks/react/fields/index.ts | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 771c48de..98663667 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -17,6 +17,20 @@ but it is better than nothing. 8. Recoil x 14,351 ops/sec ±1.55% (65 runs sampled) ``` +## 1000 Fields + +```ts +1. Agile nested State x 9,687 ops/sec ±2.46% (52 runs sampled) +2. Hookstate x 5,170 ops/sec ±5.77% (31 runs sampled) +3. Jotai x 4,578 ops/sec ±5.49% (57 runs sampled) +4. Recoil x 3,790 ops/sec ±3.74% (55 runs sampled) +5. Agile State x 1,480 ops/sec ±1.39% (12 runs sampled) +6. Agile Collection x 498 ops/sec ±1.81% (61 runs sampled) +7. Redux x 150 ops/sec ±1.72% (59 runs sampled) +8. Mobx x 143 ops/sec ±0.92% (57 runs sampled) +9. Valtio x 37.44 ops/sec ±5.17% (42 runs sampled) +``` + ## 🏃 Running Benchmarks The Benchmark tests run on top of the [`benchmark.js` library](https://github.com/bestiejs/benchmark.js/) diff --git a/benchmark/benchmarks/react/fields/bench/mobx.tsx b/benchmark/benchmarks/react/fields/bench/mobx.tsx index bb685aea..6dab7352 100644 --- a/benchmark/benchmarks/react/fields/bench/mobx.tsx +++ b/benchmark/benchmarks/react/fields/bench/mobx.tsx @@ -1,7 +1,7 @@ -import { action, observable } from 'mobx'; -import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDom from 'react-dom'; +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; const appState = observable({ fields: Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`), diff --git a/benchmark/benchmarks/react/fields/bench/redux.tsx b/benchmark/benchmarks/react/fields/bench/redux.tsx index 95a41fb2..c862271b 100644 --- a/benchmark/benchmarks/react/fields/bench/redux.tsx +++ b/benchmark/benchmarks/react/fields/bench/redux.tsx @@ -1,8 +1,7 @@ -import { configureStore, createSlice } from '@reduxjs/toolkit'; -import { combineReducers, createStore } from 'redux'; -import { Provider, useDispatch, useSelector } from 'react-redux'; import * as React from 'react'; import * as ReactDom from 'react-dom'; +import { configureStore, createSlice } from '@reduxjs/toolkit'; +import { Provider, useDispatch, useSelector } from 'react-redux'; const fieldsSlice = createSlice({ name: 'fields', diff --git a/benchmark/benchmarks/react/fields/index.ts b/benchmark/benchmarks/react/fields/index.ts index 4bc6e4a9..e9e75dd5 100644 --- a/benchmark/benchmarks/react/fields/index.ts +++ b/benchmark/benchmarks/react/fields/index.ts @@ -51,8 +51,8 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { // Add Tests to the Benchmark Test Suite suite - // .add('Agile Collection', configTest(agileCollection)) - // .add('Agile State', configTest(agileState)) + .add('Agile Collection', configTest(agileCollection)) + .add('Agile State', configTest(agileState)) .add('Agile nested State', configTest(agileNestedState)) .add('Hookstate', configTest(hookstate)) .add('Jotai', configTest(jotai)) From 5e409bb74784ecf77d9313c3cfb655c8a068651a Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 4 Jul 2021 07:08:09 +0200 Subject: [PATCH 29/55] added computed benchmark --- benchmark/README.md | 9 +++ .../computed/bench/agilets/autoTracking.tsx | 24 +++++++ .../computed/bench/agilets/hardCoded.tsx | 27 +++++++ .../benchmarks/react/computed/bench/jotai.tsx | 21 ++++++ .../react/computed/bench/recoil.tsx | 40 +++++++++++ benchmark/benchmarks/react/computed/index.ts | 71 +++++++++++++++++++ .../benchmarks/react/counter/bench/jotai.tsx | 9 +-- .../bench/{agile => agilets}/collection.tsx | 0 .../bench/{agile => agilets}/nestedState.tsx | 0 .../fields/bench/{agile => agilets}/state.tsx | 0 benchmark/benchmarks/react/fields/index.ts | 6 +- benchmark/package.json | 1 + benchmark/run.ts | 2 +- 13 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx create mode 100644 benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx create mode 100644 benchmark/benchmarks/react/computed/bench/jotai.tsx create mode 100644 benchmark/benchmarks/react/computed/bench/recoil.tsx create mode 100644 benchmark/benchmarks/react/computed/index.ts rename benchmark/benchmarks/react/fields/bench/{agile => agilets}/collection.tsx (100%) rename benchmark/benchmarks/react/fields/bench/{agile => agilets}/nestedState.tsx (100%) rename benchmark/benchmarks/react/fields/bench/{agile => agilets}/state.tsx (100%) diff --git a/benchmark/README.md b/benchmark/README.md index 98663667..35da136d 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -31,6 +31,15 @@ but it is better than nothing. 9. Valtio x 37.44 ops/sec ±5.17% (42 runs sampled) ``` +## Computed + +```ts +Agile Hard Coded x 23,201 ops/sec ±1.39% (64 runs sampled) +Agile Auto Tracking x 22,661 ops/sec ±3.31% (60 runs sampled) +Jotai x 18,489 ops/sec ±5.43% (62 runs sampled) +Recoil x 10,312 ops/sec ±2.57% (64 runs sampled) +``` + ## 🏃 Running Benchmarks The Benchmark tests run on top of the [`benchmark.js` library](https://github.com/bestiejs/benchmark.js/) diff --git a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx new file mode 100644 index 00000000..2017d339 --- /dev/null +++ b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { Agile, Logger } from '@agile-ts/core'; +import { useAgile } from '@agile-ts/react'; + +const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); +const COUNT = AgileApp.createState(0); +const COMPUTED_COUNT = AgileApp.createComputed(() => { + return COUNT.value * 5; +}); + +const App = () => { + const [count, computedCount] = useAgile([COUNT, COMPUTED_COUNT]); + return ( +
+

COUNT.set((state) => state + 1)}>{count}

+

{computedCount}

+
+ ); +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx new file mode 100644 index 00000000..62144e26 --- /dev/null +++ b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { Agile, Logger } from '@agile-ts/core'; +import { useAgile } from '@agile-ts/react'; + +const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); +const COUNT = AgileApp.createState(0); +const COMPUTED_COUNT = AgileApp.createComputed( + () => { + return COUNT.value * 5; + }, + { autodetect: false, computedDeps: [COUNT] } +); + +const App = () => { + const [count, computedCount] = useAgile([COUNT, COMPUTED_COUNT]); + return ( +
+

COUNT.set((state) => state + 1)}>{count}

+

{computedCount}

+
+ ); +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/computed/bench/jotai.tsx b/benchmark/benchmarks/react/computed/bench/jotai.tsx new file mode 100644 index 00000000..84518a6f --- /dev/null +++ b/benchmark/benchmarks/react/computed/bench/jotai.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { atom, useAtom } from 'jotai'; + +const countAtom = atom(0); +const computedCountAtom = atom((get) => get(countAtom) * 5); + +const App = () => { + const [count, setCount] = useAtom(countAtom); + const [computedCount] = useAtom(computedCountAtom); + return ( +
+

setCount((v) => v + 1)}>{count}

+

{computedCount}

+
+ ); +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/computed/bench/recoil.tsx b/benchmark/benchmarks/react/computed/bench/recoil.tsx new file mode 100644 index 00000000..a5195403 --- /dev/null +++ b/benchmark/benchmarks/react/computed/bench/recoil.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { + atom, + RecoilRoot, + useRecoilState, + selector, + useRecoilValue, +} from 'recoil'; + +const counterState = atom({ + key: 'counterState', + default: 0, +}); +const computedCounterState = selector({ + key: 'computedCounterState', + get: ({ get }) => { + return get(counterState) * 5; + }, +}); + +const App = () => { + const [count, setCount] = useRecoilState(counterState); + const computedCount = useRecoilValue(computedCounterState); + return ( +
+

setCount((v) => v + 1)}>{count}

+

{computedCount}

+
+ ); +}; + +export default function (target: HTMLElement) { + ReactDom.render( + + + , + target + ); +} diff --git a/benchmark/benchmarks/react/computed/index.ts b/benchmark/benchmarks/react/computed/index.ts new file mode 100644 index 00000000..2e89e69f --- /dev/null +++ b/benchmark/benchmarks/react/computed/index.ts @@ -0,0 +1,71 @@ +import ReactDOM from 'react-dom'; +import Benchmark, { Suite, Options } from 'benchmark'; + +// Files to run the Benchmark on +import agileAutoTracking from './bench/agilets/autoTracking'; +import agileHardCoded from './bench/agilets/hardCoded'; +import jotai from './bench/jotai'; +import recoil from './bench/recoil'; + +// @ts-ignore +// Benchmark.js requires an instance of itself globally +window.Benchmark = Benchmark; + +// Create new Benchmark Test Suite +const suite = new Suite('Count'); + +// Retrieve the Element to render the Benchmark Test Suite in +const target = document.getElementById('bench')!; + +// Increment Element +let increment: HTMLHeadingElement; + +// Configures a single Benchmark Test +function configTest(renderElement: (target: HTMLElement) => void): Options { + return { + fn() { + // Execute increment action + increment.click(); + }, + onStart() { + // Render React Component in the target Element + renderElement(target); + + // Retrieve Element to execute the increment action on + increment = target.querySelector('h1')!; + }, + onComplete() { + // Set 'output' in the Benchmark itself to print it later + (this as any).output = parseInt(target.innerText, 10); + + // Unmount React Component + ReactDOM.unmountComponentAtNode(target); + target.innerHTML = ''; + }, + }; +} + +// Add Tests to the Benchmark Test Suite +suite + .add('Agile Auto Tracking', configTest(agileAutoTracking)) + .add('Agile Hard Coded', configTest(agileAutoTracking)) + .add('Jotai', configTest(jotai)) + .add('Recoil', configTest(recoil)) + + // Add Listener + .on('start', function (this: any) { + console.log(`Starting ${this.name}`); + }) + .on('cycle', (event: any) => { + console.log(String(event.target)); + }) + .on('complete', function (this: any) { + console.log(`Fastest is ${this.filter('fastest').map('name')}`); + + // @ts-ignore + // Notify server that the Benchmark Test Suite has ended + window.TEST.ended = true; + }) + + // Run Benchmark Test Suite + .run({ async: true }); diff --git a/benchmark/benchmarks/react/counter/bench/jotai.tsx b/benchmark/benchmarks/react/counter/bench/jotai.tsx index 31568aab..cb256aba 100644 --- a/benchmark/benchmarks/react/counter/bench/jotai.tsx +++ b/benchmark/benchmarks/react/counter/bench/jotai.tsx @@ -6,14 +6,7 @@ const countAtom = atom(0); const App = () => { const [count, setCount] = useAtom(countAtom); - return ( -

{ - setCount((v) => v + 1); - }}> - {count} -

- ); + return

setCount((v) => v + 1)}>{count}

; }; export default function (target: HTMLElement) { diff --git a/benchmark/benchmarks/react/fields/bench/agile/collection.tsx b/benchmark/benchmarks/react/fields/bench/agilets/collection.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/agile/collection.tsx rename to benchmark/benchmarks/react/fields/bench/agilets/collection.tsx diff --git a/benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx b/benchmark/benchmarks/react/fields/bench/agilets/nestedState.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/agile/nestedState.tsx rename to benchmark/benchmarks/react/fields/bench/agilets/nestedState.tsx diff --git a/benchmark/benchmarks/react/fields/bench/agile/state.tsx b/benchmark/benchmarks/react/fields/bench/agilets/state.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/agile/state.tsx rename to benchmark/benchmarks/react/fields/bench/agilets/state.tsx diff --git a/benchmark/benchmarks/react/fields/index.ts b/benchmark/benchmarks/react/fields/index.ts index e9e75dd5..9a8d2160 100644 --- a/benchmark/benchmarks/react/fields/index.ts +++ b/benchmark/benchmarks/react/fields/index.ts @@ -2,9 +2,9 @@ import Benchmark, { Suite, Options } from 'benchmark'; import ReactDOM from 'react-dom'; // Files to run the Benchmark on -import agileCollection from './bench/agile/collection'; -import agileState from './bench/agile/state'; -import agileNestedState from './bench/agile/nestedState'; +import agileCollection from './bench/agilets/collection'; +import agileState from './bench/agilets/state'; +import agileNestedState from './bench/agilets/nestedState'; import hookstate from './bench/hookstate'; import jotai from './bench/jotai'; import mobx from './bench/mobx'; diff --git a/benchmark/package.json b/benchmark/package.json index cf332933..745fb4af 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -10,6 +10,7 @@ "test": "node -r esbuild-register run.ts", "test:counter": "yarn test ./benchmarks/react/counter", "test:fields": "yarn test ./benchmarks/react/fields", + "test:computed": "yarn test ./benchmarks/react/computed", "install:local:agile": "yalc add @agile-ts/core @agile-ts/react", "install:public:agile": "yarn add @agile-ts/core @agile-ts/react" }, diff --git a/benchmark/run.ts b/benchmark/run.ts index 7c233c47..a711778c 100644 --- a/benchmark/run.ts +++ b/benchmark/run.ts @@ -21,7 +21,7 @@ const startBenchmark = async () => { const server = await esbuild.serve( { servedir: 'public', - port: 3000, + port: 3003, host: '127.0.0.1', // localhost }, { From c934a77965d659f5e2dbf4866329b7cd9f4e14ab Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 4 Jul 2021 07:12:04 +0200 Subject: [PATCH 30/55] ran prettier --- examples/react/release/boxes/src/api.ts | 34 +++++++++---------- .../vue/develop/my-project/babel.config.js | 6 ++-- packages/core/src/collection/selector.ts | 5 +-- .../cra-template-agile-typescript/README.md | 2 +- 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/examples/react/release/boxes/src/api.ts b/examples/react/release/boxes/src/api.ts index 17d0a2a6..8e01fedc 100644 --- a/examples/react/release/boxes/src/api.ts +++ b/examples/react/release/boxes/src/api.ts @@ -1,26 +1,26 @@ -import queryString, {ParsedUrlQueryInput} from 'querystring' +import queryString, { ParsedUrlQueryInput } from 'querystring'; type RequestOptions = { - queryParams?: ParsedUrlQueryInput - method?: 'GET' | 'POST' - body?: object | string -} + queryParams?: ParsedUrlQueryInput; + method?: 'GET' | 'POST'; + body?: object | string; +}; export const apiUrl = (lambda: string, queryParams?: ParsedUrlQueryInput) => { - let url = `https://f10adraov8.execute-api.us-east-1.amazonaws.com/dev/${lambda}` - if (queryParams) url += '?' + queryString.stringify(queryParams) + let url = `https://f10adraov8.execute-api.us-east-1.amazonaws.com/dev/${lambda}`; + if (queryParams) url += '?' + queryString.stringify(queryParams); - return url -} + return url; +}; export const callApi = (lambda: string, options?: RequestOptions) => { - const {queryParams, body, method} = options || {} - const url = apiUrl(lambda, queryParams) + const { queryParams, body, method } = options || {}; + const url = apiUrl(lambda, queryParams); - let bodyString = body - if (typeof bodyString === 'object') { - bodyString = JSON.stringify(body) - } + let bodyString = body; + if (typeof bodyString === 'object') { + bodyString = JSON.stringify(body); + } - return fetch(url, {body: bodyString, method}).then((res) => res.json()) -} + return fetch(url, { body: bodyString, method }).then((res) => res.json()); +}; diff --git a/examples/vue/develop/my-project/babel.config.js b/examples/vue/develop/my-project/babel.config.js index e9558405..078c0056 100644 --- a/examples/vue/develop/my-project/babel.config.js +++ b/examples/vue/develop/my-project/babel.config.js @@ -1,5 +1,3 @@ module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] -} + presets: ['@vue/cli-plugin-babel/preset'], +}; diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 5e484e9d..937c5458 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -227,10 +227,7 @@ export class Selector< item.selectedBy.delete(this._key as any); item.removeSideEffect(Selector.rebuildSelectorSideEffectKey); item.removeSideEffect(Selector.rebuildItemSideEffectKey); - if ( - item.isPlaceholder && - this._itemKey != null - ) + if (item.isPlaceholder && this._itemKey != null) delete this.collection().data[this._itemKey]; } diff --git a/packages/cra-template-agile-typescript/README.md b/packages/cra-template-agile-typescript/README.md index 7ff1b678..4b973574 100644 --- a/packages/cra-template-agile-typescript/README.md +++ b/packages/cra-template-agile-typescript/README.md @@ -24,4 +24,4 @@ npx create-react-app my-app --template agile-typescript ## Credits -https://github.com/facebook/create-react-app/blob/master/packages/cra-template-typescript \ No newline at end of file +https://github.com/facebook/create-react-app/blob/master/packages/cra-template-typescript From 15e45434deaaa1441db368968f452882fed93cc3 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 4 Jul 2021 08:05:02 +0200 Subject: [PATCH 31/55] added toc to README --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index da0c7031..addb909e 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ MY_STATE.persist("storage-key"); ### 🤸‍ Flexible -- Works in nearly any UI-Framework (currently supported are React, React-Native and Vue). +- Works in nearly any UI-Framework (currently supported are [React](https://reactjs.org/), [React-Native](https://reactnative.dev/) and [Vue](https://vuejs.org/)). - Surly behaves with the workflow that suits you best. No need for _reducers_, _actions_, .. - Has **0** external dependencies. @@ -115,16 +115,19 @@ MY_STATE.persist("storage-key"); 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, +The advantage of keeping logic separate to UI-Components, +is that your code is more decoupled, portable, scalable, and above all, easily testable. +Learn more about ways to centralize your application logic with AgileTs +in our [Style Guides](https://agile-ts.org/docs/style-guide). + ### 🎯 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 Section](https://agile-ts.org/docs/examples/Introduction). +Learn the powerful tools of AgileTs in a short period 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 tutorials, +you can jump straight into the [Example Section](https://agile-ts.org/docs/examples/Introduction).
@@ -133,10 +136,10 @@ you can jump straight into our [Example Section](https://agile-ts.org/docs/examp
Installation -In order to 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 contains the State Management Logic of AgileTs - and therefore offers powerful classes such as the [`State Class`](https://agile-ts.org/docs/core/state). + and therefore provides powerful classes like the [`State Class`](https://agile-ts.org/docs/core/state). ``` npm install @agile-ts/core ``` @@ -158,9 +161,37 @@ In order to use AgileTs in a UI-Framework, we need to install two packages. 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. +to learn more about its functionalities and capabilities. If you have any further questions, -don't hesitate to join our [Community Discord](https://discord.gg/T9GzreAwPH). +feel free to join our [Community Discord](https://discord.gg/T9GzreAwPH). +We will be happy to help you. + +- Overview + - [Introduction](https://agile-ts.org/docs/introduction/) + - [Installation](https://agile-ts.org/docs/installation) + - [Style Guides](https://agile-ts.org/docs/style-guide) + - [Supported Frameworks](https://agile-ts.org/docs/frameworks) + - [Contributing](https://agile-ts.org/docs/contributing) +- Quick Start + - [React](https://agile-ts.org/docs/quick-start/react) + - [Vue](https://agile-ts.org/docs/quick-start/vue) + - [Angular](https://agile-ts.org/docs/quick-start/angular) +- Packages + - [core](https://agile-ts.org/docs/core) + - [Agile Instance](https://agile-ts.org/docs/core/agile-instance) + - [State](https://agile-ts.org/docs/core/state) + - [Collection](https://agile-ts.org/docs/core/collection) + - [Computed](https://agile-ts.org/docs/core/computed) + - [Storage](https://agile-ts.org/docs/core/storage) + - [Integration](https://agile-ts.org/docs/core/integration) + - [react](https://agile-ts.org/docs/react) + - [React Hooks](https://agile-ts.org/docs/react/hooks) + - [AgileHOC](https://agile-ts.org/docs/react/AgileHOC) +- Examples + - [React](https://agile-ts.org/docs/examples/react) + - [React-Native](https://agile-ts.org/docs/examples/react-native) + - [Vue](https://agile-ts.org/docs/examples/vue) +- [Typescript Interfaces](https://agile-ts.org/docs/interfaces)
@@ -203,4 +234,3 @@ 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). -For the API, we were mainly inspired by [Svelte](https://svelte.dev/). From 1d3710807eb0f4c1bc3944dd3abf264e78f5aed3 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 4 Jul 2021 18:03:19 +0200 Subject: [PATCH 32/55] updated 1000 fields benchmark --- .../bench/agilets/collection.tsx | 0 .../bench/agilets/nestedState.tsx | 0 .../{fields => 1000fields}/bench/agilets/state.tsx | 0 .../react/{fields => 1000fields}/bench/hookstate.tsx | 0 .../react/{fields => 1000fields}/bench/jotai.tsx | 8 ++++---- .../react/{fields => 1000fields}/bench/mobx.tsx | 0 .../react/{fields => 1000fields}/bench/recoil.tsx | 8 ++++---- .../react/{fields => 1000fields}/bench/redux.tsx | 0 .../react/{fields => 1000fields}/bench/valtio.tsx | 0 .../benchmarks/react/{fields => 1000fields}/index.ts | 12 +++++++++--- benchmark/benchmarks/react/counter/index.ts | 2 +- benchmark/package.json | 2 +- 12 files changed, 19 insertions(+), 13 deletions(-) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/agilets/collection.tsx (100%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/agilets/nestedState.tsx (100%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/agilets/state.tsx (100%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/hookstate.tsx (100%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/jotai.tsx (79%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/mobx.tsx (100%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/recoil.tsx (79%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/redux.tsx (100%) rename benchmark/benchmarks/react/{fields => 1000fields}/bench/valtio.tsx (100%) rename benchmark/benchmarks/react/{fields => 1000fields}/index.ts (88%) diff --git a/benchmark/benchmarks/react/fields/bench/agilets/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/agilets/collection.tsx rename to benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx diff --git a/benchmark/benchmarks/react/fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/agilets/nestedState.tsx rename to benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx diff --git a/benchmark/benchmarks/react/fields/bench/agilets/state.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/agilets/state.tsx rename to benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx diff --git a/benchmark/benchmarks/react/fields/bench/hookstate.tsx b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/hookstate.tsx rename to benchmark/benchmarks/react/1000fields/bench/hookstate.tsx diff --git a/benchmark/benchmarks/react/fields/bench/jotai.tsx b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx similarity index 79% rename from benchmark/benchmarks/react/fields/bench/jotai.tsx rename to benchmark/benchmarks/react/1000fields/bench/jotai.tsx index c402d0dd..8b8b4124 100644 --- a/benchmark/benchmarks/react/fields/bench/jotai.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactDom from 'react-dom'; -import { atom, useAtom } from 'jotai'; +import { atom, useAtom, Atom } from 'jotai'; const fields = Array.from(Array(1000).keys()).map((i) => atom(`Field #${i + 1}`) @@ -8,8 +8,8 @@ const fields = Array.from(Array(1000).keys()).map((i) => const fieldsStore = atom(fields); -function Field({ index }: { index: number }) { - const [name, rename] = useAtom(fields[index]); +function Field({ field }: { field: Atom }) { + const [name, rename] = useAtom(field); return (
@@ -30,7 +30,7 @@ function App() {

{fields.map((field, index) => ( - + ))} ); diff --git a/benchmark/benchmarks/react/fields/bench/mobx.tsx b/benchmark/benchmarks/react/1000fields/bench/mobx.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/mobx.tsx rename to benchmark/benchmarks/react/1000fields/bench/mobx.tsx diff --git a/benchmark/benchmarks/react/fields/bench/recoil.tsx b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx similarity index 79% rename from benchmark/benchmarks/react/fields/bench/recoil.tsx rename to benchmark/benchmarks/react/1000fields/bench/recoil.tsx index 7030a536..49f745bb 100644 --- a/benchmark/benchmarks/react/fields/bench/recoil.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import * as ReactDom from 'react-dom'; -import { atom, RecoilRoot, useRecoilState } from 'recoil'; +import { atom, RecoilRoot, RecoilState, useRecoilState } from 'recoil'; const fields = Array.from(Array(1000).keys()).map((i) => atom({ key: `field-${i}`, default: `Field #${i + 1}` }) @@ -11,8 +11,8 @@ const fieldsStore = atom({ default: fields, }); -function Field({ index }: { index: number }) { - const [name, rename] = useRecoilState(fields[index]); +function Field({ field }: { field: RecoilState }) { + const [name, rename] = useRecoilState(field); return (
@@ -33,7 +33,7 @@ function App() {

{fields.map((field, index) => ( - + ))} ); diff --git a/benchmark/benchmarks/react/fields/bench/redux.tsx b/benchmark/benchmarks/react/1000fields/bench/redux.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/redux.tsx rename to benchmark/benchmarks/react/1000fields/bench/redux.tsx diff --git a/benchmark/benchmarks/react/fields/bench/valtio.tsx b/benchmark/benchmarks/react/1000fields/bench/valtio.tsx similarity index 100% rename from benchmark/benchmarks/react/fields/bench/valtio.tsx rename to benchmark/benchmarks/react/1000fields/bench/valtio.tsx diff --git a/benchmark/benchmarks/react/fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts similarity index 88% rename from benchmark/benchmarks/react/fields/index.ts rename to benchmark/benchmarks/react/1000fields/index.ts index 9a8d2160..afb341db 100644 --- a/benchmark/benchmarks/react/fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -16,8 +16,11 @@ import valtio from './bench/valtio'; // Benchmark.js requires an instance of itself globally window.Benchmark = Benchmark; +// TODO figure out how to pass this count into the tests +const fieldsCount = 1000; + // Create new Benchmark Test Suite -const suite = new Suite('1000 Fields'); +const suite = new Suite(`${fieldsCount} Fields`); // Retrieve the Element to render the Benchmark Test Suite in const target = document.getElementById('bench')!; @@ -27,7 +30,7 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { return { fn() { // Retrieve Input field to update - const fieldToUpdate = Math.floor(Math.random() * 1000); + const fieldToUpdate = Math.floor(Math.random() * fieldsCount); const input = target.querySelectorAll('input')[fieldToUpdate]; // Update retrieved Input value @@ -66,7 +69,10 @@ suite console.log(`Starting ${this.name}`); }) .on('cycle', (event: any) => { - console.log(String(event.target)); + console.log( + String(event.target), + `renderCount: ${event.target.renderCount}` + ); }) .on('complete', function (this: any) { console.log(`Fastest is ${this.filter('fastest').map('name')}`); diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index a6d6489e..256f10b5 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -67,7 +67,7 @@ suite console.log(`Starting ${this.name}`); }) .on('cycle', (event: any) => { - console.log(String(event.target)); + console.log(String(event.target), `[Count: ${event.target.output}]`); }) .on('complete', function (this: any) { console.log(`Fastest is ${this.filter('fastest').map('name')}`); diff --git a/benchmark/package.json b/benchmark/package.json index 745fb4af..8d77ec4c 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -9,7 +9,7 @@ "scripts": { "test": "node -r esbuild-register run.ts", "test:counter": "yarn test ./benchmarks/react/counter", - "test:fields": "yarn test ./benchmarks/react/fields", + "test:fields": "yarn test ./benchmarks/react/1000fields", "test:computed": "yarn test ./benchmarks/react/computed", "install:local:agile": "yalc add @agile-ts/core @agile-ts/react", "install:public:agile": "yarn add @agile-ts/core @agile-ts/react" From 60de624b787a6c6a79b7f2b82feb09337f29be35 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 4 Jul 2021 18:25:53 +0200 Subject: [PATCH 33/55] added 1000 fields update count --- .../1000fields/bench/agilets/nestedState.tsx | 8 ++++++++ .../react/1000fields/bench/hookstate.tsx | 8 ++++++++ .../benchmarks/react/1000fields/bench/jotai.tsx | 15 ++++++++++++++- benchmark/benchmarks/react/1000fields/index.ts | 7 ++++++- benchmark/benchmarks/react/computed/index.ts | 14 ++++++++++++-- benchmark/package.json | 2 +- 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx index 5aab1dee..b0658b75 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx @@ -11,9 +11,13 @@ const FIELDS = AgileApp.createState( ) ); +let updatedFieldsCount = 0; + function Field({ field }: { field: State }) { const name = useAgile(field); + updatedFieldsCount++; + return (
Last {``} render at: {new Date().toISOString()} @@ -22,6 +26,9 @@ function Field({ field }: { field: State }) { value={name} onChange={(e) => { field.set(e.target.value); + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; }} />
@@ -39,6 +46,7 @@ function App() { {fields.map((field, index) => ( ))} +
); } diff --git a/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx index 6278912c..ac21a7f1 100644 --- a/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx @@ -6,9 +6,13 @@ const fields = createState( Array.from(Array.from(Array(1000).keys()).map((i) => `Field #${i + 1} value`)) ); +let updatedFieldsCount = 0; + function Field({ field }: { field: State }) { const name = useHookstate(field); + updatedFieldsCount++; + return (
Last {``} render at: {new Date().toISOString()} @@ -17,6 +21,9 @@ function Field({ field }: { field: State }) { value={name.get()} onChange={(e) => { name.set(e.target.value); + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; }} />
@@ -34,6 +41,7 @@ function App() { {state.map((field, index) => ( ))} +
); } diff --git a/benchmark/benchmarks/react/1000fields/bench/jotai.tsx b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx index 8b8b4124..39cedc3d 100644 --- a/benchmark/benchmarks/react/1000fields/bench/jotai.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx @@ -8,14 +8,26 @@ const fields = Array.from(Array(1000).keys()).map((i) => const fieldsStore = atom(fields); +let updatedFieldsCount = 0; + function Field({ field }: { field: Atom }) { const [name, rename] = useAtom(field); + updatedFieldsCount++; + return (
Last {``} render at: {new Date().toISOString()}   - rename(e.target.value)} /> + { + rename(e.target.value); + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + }} + />
); } @@ -32,6 +44,7 @@ function App() { {fields.map((field, index) => ( ))} +
); } diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index afb341db..29de00c0 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -45,6 +45,11 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { renderElement(target); }, onComplete() { + (this as any).updatedFieldsCount = parseInt( + (document.getElementById('updatedFieldsCount') as any)?.innerText, + 10 + ); + // Unmount React Component ReactDOM.unmountComponentAtNode(target); target.innerHTML = ''; @@ -71,7 +76,7 @@ suite .on('cycle', (event: any) => { console.log( String(event.target), - `renderCount: ${event.target.renderCount}` + `[updatedFieldsCount: ${event.target.updatedFieldsCount}]` ); }) .on('complete', function (this: any) { diff --git a/benchmark/benchmarks/react/computed/index.ts b/benchmark/benchmarks/react/computed/index.ts index 2e89e69f..959d17cb 100644 --- a/benchmark/benchmarks/react/computed/index.ts +++ b/benchmark/benchmarks/react/computed/index.ts @@ -36,7 +36,14 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { }, onComplete() { // Set 'output' in the Benchmark itself to print it later - (this as any).output = parseInt(target.innerText, 10); + (this as any).output = parseInt( + (target.querySelector('h1') as any)?.innerText, + 10 + ); + (this as any).computedOutput = parseInt( + (target.querySelector('p') as any)?.innerText, + 10 + ); // Unmount React Component ReactDOM.unmountComponentAtNode(target); @@ -57,7 +64,10 @@ suite console.log(`Starting ${this.name}`); }) .on('cycle', (event: any) => { - console.log(String(event.target)); + console.log( + String(event.target), + `[Count: ${event.target.output}, ComputedCount: ${event.target.computedOutput}]` + ); }) .on('complete', function (this: any) { console.log(`Fastest is ${this.filter('fastest').map('name')}`); diff --git a/benchmark/package.json b/benchmark/package.json index 8d77ec4c..98a1fbec 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -9,7 +9,7 @@ "scripts": { "test": "node -r esbuild-register run.ts", "test:counter": "yarn test ./benchmarks/react/counter", - "test:fields": "yarn test ./benchmarks/react/1000fields", + "test:1000fields": "yarn test ./benchmarks/react/1000fields", "test:computed": "yarn test ./benchmarks/react/computed", "install:local:agile": "yalc add @agile-ts/core @agile-ts/react", "install:public:agile": "yarn add @agile-ts/core @agile-ts/react" From a4d440f64301c3b1d8af2af8b86fa02a15242b17 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 5 Jul 2021 07:37:42 +0200 Subject: [PATCH 34/55] added nanostores --- .../1000fields/bench/agilets/collection.tsx | 4 +- .../react/1000fields/bench/agilets/state.tsx | 4 +- .../react/1000fields/bench/hookstate.tsx | 4 +- .../react/1000fields/bench/jotai.tsx | 4 +- .../react/1000fields/bench/mobx.tsx | 4 +- .../react/1000fields/bench/nanostores.tsx | 59 +++++++++++++++++++ .../react/1000fields/bench/recoil.tsx | 4 +- .../react/1000fields/bench/redux.tsx | 4 +- .../react/1000fields/bench/valtio.tsx | 4 +- .../benchmarks/react/1000fields/index.ts | 2 + .../computed/bench/agilets/autoTracking.tsx | 15 ++++- .../computed/bench/agilets/hardCoded.tsx | 15 ++++- .../benchmarks/react/computed/bench/jotai.tsx | 14 ++++- .../react/computed/bench/nanostores.tsx | 36 +++++++++++ .../react/computed/bench/recoil.tsx | 27 ++++++--- benchmark/benchmarks/react/computed/index.ts | 4 +- .../react/counter/bench/nanostores.tsx | 19 ++++++ benchmark/benchmarks/react/counter/index.ts | 2 + benchmark/package.json | 1 + benchmark/yarn.lock | 5 ++ 20 files changed, 196 insertions(+), 35 deletions(-) create mode 100644 benchmark/benchmarks/react/1000fields/bench/nanostores.tsx create mode 100644 benchmark/benchmarks/react/computed/bench/nanostores.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/nanostores.tsx diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx index fe983c22..64630278 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { Agile, Logger } from '@agile-ts/core'; import { useAgile, useValue } from '@agile-ts/react'; diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx index 85115db4..5304f93b 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { Agile, Logger } from '@agile-ts/core'; import { useProxy, useSelector } from '@agile-ts/react'; diff --git a/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx index ac21a7f1..20e217d4 100644 --- a/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { createState, useHookstate, State } from '@hookstate/core'; const fields = createState( diff --git a/benchmark/benchmarks/react/1000fields/bench/jotai.tsx b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx index 39cedc3d..78e86f5d 100644 --- a/benchmark/benchmarks/react/1000fields/bench/jotai.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { atom, useAtom, Atom } from 'jotai'; const fields = Array.from(Array(1000).keys()).map((i) => diff --git a/benchmark/benchmarks/react/1000fields/bench/mobx.tsx b/benchmark/benchmarks/react/1000fields/bench/mobx.tsx index 6dab7352..fa2645b0 100644 --- a/benchmark/benchmarks/react/1000fields/bench/mobx.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/mobx.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx b/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx new file mode 100644 index 00000000..c159f140 --- /dev/null +++ b/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { createStore, WritableStore } from 'nanostores'; +import { useStore } from 'nanostores/react'; + +const fieldsStore = createStore[]>(() => { + const fields = Array.from(Array(1000).keys()).map((i) => { + const fieldStore = createStore(() => { + fieldsStore.set(`Field #${i + 1}` as any); + }); + return fieldStore; + }); + + fieldsStore.set(fields); +}); + +let updatedFieldsCount = 0; + +function Field({ field }: { field: WritableStore }) { + const name = useStore(field); + + updatedFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + field.set(e.target.value); + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + }} + /> +
+ ); +} + +function App() { + const fields = useStore(fieldsStore); + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
+ ); +} + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/1000fields/bench/recoil.tsx b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx index 49f745bb..04a8a426 100644 --- a/benchmark/benchmarks/react/1000fields/bench/recoil.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { atom, RecoilRoot, RecoilState, useRecoilState } from 'recoil'; const fields = Array.from(Array(1000).keys()).map((i) => diff --git a/benchmark/benchmarks/react/1000fields/bench/redux.tsx b/benchmark/benchmarks/react/1000fields/bench/redux.tsx index c862271b..e8cb41aa 100644 --- a/benchmark/benchmarks/react/1000fields/bench/redux.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/redux.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { configureStore, createSlice } from '@reduxjs/toolkit'; import { Provider, useDispatch, useSelector } from 'react-redux'; diff --git a/benchmark/benchmarks/react/1000fields/bench/valtio.tsx b/benchmark/benchmarks/react/1000fields/bench/valtio.tsx index b356e66d..cf075bb8 100644 --- a/benchmark/benchmarks/react/1000fields/bench/valtio.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/valtio.tsx @@ -1,5 +1,5 @@ -import * as React from 'react'; -import * as ReactDom from 'react-dom'; +import React from 'react'; +import ReactDom from 'react-dom'; import { proxy, useSnapshot } from 'valtio'; const state = proxy({ diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index 29de00c0..eb089491 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -8,6 +8,7 @@ import agileNestedState from './bench/agilets/nestedState'; import hookstate from './bench/hookstate'; import jotai from './bench/jotai'; import mobx from './bench/mobx'; +import nanostores from './bench/nanostores'; import recoil from './bench/recoil'; import redux from './bench/redux'; import valtio from './bench/valtio'; @@ -65,6 +66,7 @@ suite .add('Hookstate', configTest(hookstate)) .add('Jotai', configTest(jotai)) .add('Mobx', configTest(mobx)) + .add('Nano Stores', configTest(nanostores)) .add('Recoil', configTest(recoil)) .add('Redux', configTest(redux)) .add('Valtio', configTest(valtio)) diff --git a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx index 2017d339..5f2be0f8 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx @@ -9,12 +9,21 @@ const COMPUTED_COUNT = AgileApp.createComputed(() => { return COUNT.value * 5; }); +const CountView = () => { + const count = useAgile(COUNT); + return

COUNT.set((state) => state + 1)}>{count}

; +}; + +const ComputedCountView = () => { + const computedCount = useAgile(COMPUTED_COUNT); + return

{computedCount}

; +}; + const App = () => { - const [count, computedCount] = useAgile([COUNT, COMPUTED_COUNT]); return (
-

COUNT.set((state) => state + 1)}>{count}

-

{computedCount}

+ +
); }; diff --git a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx index 62144e26..45c05d38 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx @@ -12,12 +12,21 @@ const COMPUTED_COUNT = AgileApp.createComputed( { autodetect: false, computedDeps: [COUNT] } ); +const CountView = () => { + const count = useAgile(COUNT); + return

COUNT.set((state) => state + 1)}>{count}

; +}; + +const ComputedCountView = () => { + const computedCount = useAgile(COMPUTED_COUNT); + return

{computedCount}

; +}; + const App = () => { - const [count, computedCount] = useAgile([COUNT, COMPUTED_COUNT]); return (
-

COUNT.set((state) => state + 1)}>{count}

-

{computedCount}

+ +
); }; diff --git a/benchmark/benchmarks/react/computed/bench/jotai.tsx b/benchmark/benchmarks/react/computed/bench/jotai.tsx index 84518a6f..6de63920 100644 --- a/benchmark/benchmarks/react/computed/bench/jotai.tsx +++ b/benchmark/benchmarks/react/computed/bench/jotai.tsx @@ -5,13 +5,21 @@ import { atom, useAtom } from 'jotai'; const countAtom = atom(0); const computedCountAtom = atom((get) => get(countAtom) * 5); -const App = () => { +const CountView = () => { const [count, setCount] = useAtom(countAtom); + return

setCount((v) => v + 1)}>{count}

; +}; + +const ComputedCountView = () => { const [computedCount] = useAtom(computedCountAtom); + return

{computedCount}

; +}; + +const App = () => { return (
-

setCount((v) => v + 1)}>{count}

-

{computedCount}

+ +
); }; diff --git a/benchmark/benchmarks/react/computed/bench/nanostores.tsx b/benchmark/benchmarks/react/computed/bench/nanostores.tsx new file mode 100644 index 00000000..5ac7c0d2 --- /dev/null +++ b/benchmark/benchmarks/react/computed/bench/nanostores.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { createDerived, createStore, getValue } from 'nanostores'; +import { useStore } from 'nanostores/react'; + +const countStore = createStore(() => { + countStore.set(0); +}); +const computedStore = createDerived(countStore, (count) => { + return count * 5; +}); + +const CountView = () => { + const count = useStore(countStore); + return ( +

countStore.set(getValue(countStore) + 1)}>{count}

+ ); +}; + +const ComputedCountView = () => { + const computedCount = useStore(computedStore); + return

{computedCount}

; +}; + +const App = () => { + return ( +
+ + +
+ ); +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/computed/bench/recoil.tsx b/benchmark/benchmarks/react/computed/bench/recoil.tsx index a5195403..8b275a0e 100644 --- a/benchmark/benchmarks/react/computed/bench/recoil.tsx +++ b/benchmark/benchmarks/react/computed/bench/recoil.tsx @@ -7,25 +7,34 @@ import { selector, useRecoilValue, } from 'recoil'; +import { useAtom } from 'jotai'; -const counterState = atom({ - key: 'counterState', +const countState = atom({ + key: 'countState', default: 0, }); -const computedCounterState = selector({ - key: 'computedCounterState', +const computedCountState = selector({ + key: 'computedCountState', get: ({ get }) => { - return get(counterState) * 5; + return get(countState) * 5; }, }); +const CountView = () => { + const [count, setCount] = useRecoilState(countState); + return

setCount((v) => v + 1)}>{count}

; +}; + +const ComputedCountView = () => { + const computedCount = useRecoilValue(computedCountState); + return

{computedCount}

; +}; + const App = () => { - const [count, setCount] = useRecoilState(counterState); - const computedCount = useRecoilValue(computedCounterState); return (
-

setCount((v) => v + 1)}>{count}

-

{computedCount}

+ +
); }; diff --git a/benchmark/benchmarks/react/computed/index.ts b/benchmark/benchmarks/react/computed/index.ts index 959d17cb..3c089e8a 100644 --- a/benchmark/benchmarks/react/computed/index.ts +++ b/benchmark/benchmarks/react/computed/index.ts @@ -5,6 +5,7 @@ import Benchmark, { Suite, Options } from 'benchmark'; import agileAutoTracking from './bench/agilets/autoTracking'; import agileHardCoded from './bench/agilets/hardCoded'; import jotai from './bench/jotai'; +import nanostores from './bench/nanostores'; import recoil from './bench/recoil'; // @ts-ignore @@ -55,8 +56,9 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { // Add Tests to the Benchmark Test Suite suite .add('Agile Auto Tracking', configTest(agileAutoTracking)) - .add('Agile Hard Coded', configTest(agileAutoTracking)) + .add('Agile Hard Coded', configTest(agileHardCoded)) .add('Jotai', configTest(jotai)) + .add('Nano Stores', configTest(nanostores)) .add('Recoil', configTest(recoil)) // Add Listener diff --git a/benchmark/benchmarks/react/counter/bench/nanostores.tsx b/benchmark/benchmarks/react/counter/bench/nanostores.tsx new file mode 100644 index 00000000..9b377c7b --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/nanostores.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { createStore, getValue } from 'nanostores'; +import { useStore } from 'nanostores/react'; + +const countStore = createStore(() => { + countStore.set(0); +}); + +const App = () => { + const count = useStore(countStore); + return ( +

countStore.set(getValue(countStore) + 1)}>{count}

+ ); +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index 256f10b5..79ce6c87 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -6,6 +6,7 @@ import agilets from './bench/agilets'; import hookstate from './bench/hookstate'; import jotai from './bench/jotai'; import mobx from './bench/mobx'; +import nanostores from './bench/nanostores'; import recoil from './bench/recoil'; import redux from './bench/redux'; import reduxToolkit from './bench/redux-toolkit'; @@ -56,6 +57,7 @@ suite .add('Hookstate', configTest(hookstate)) .add('Jotai', configTest(jotai)) .add('Mobx', configTest(mobx)) + .add('Nano Stores', configTest(nanostores)) .add('Recoil', configTest(recoil)) .add('Redux', configTest(redux)) .add('Redux-Toolkit', configTest(reduxToolkit)) diff --git a/benchmark/package.json b/benchmark/package.json index 98a1fbec..adaaa954 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -32,6 +32,7 @@ "lodash": "^4.17.21", "mobx": "^6.3.2", "mobx-react": "^7.2.0", + "nanostores": "^0.3.3", "playwright": "^1.12.3", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index 5c44baee..8a02f9bb 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -351,6 +351,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +nanostores@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/nanostores/-/nanostores-0.3.3.tgz#3f6a858d3e2700c70c7942301380550f66a8e9ee" + integrity sha512-+MemxV/HzzTPJQCvzEmwfIFMAIcDEiod37A5F1ERyKQqtm6hbEfuNokfmJfecYM2gjunlPdPHA5lQ9cwHHSYNg== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" From 3e2d14ff6216b43dd170d3f88321d5530d89f26f Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 5 Jul 2021 08:18:23 +0200 Subject: [PATCH 35/55] updated fields benchmark --- benchmark/README.md | 56 +++++++-- .../1000fields/bench/agilets/collection.tsx | 92 ++++++++------ .../1000fields/bench/agilets/nestedState.tsx | 96 +++++++------- .../react/1000fields/bench/agilets/state.tsx | 118 ++++++++++-------- .../react/1000fields/bench/hookstate.tsx | 90 +++++++------ .../react/1000fields/bench/jotai.tsx | 92 +++++++------- .../react/1000fields/bench/mobx.tsx | 88 +++++++------ .../react/1000fields/bench/nanostores.tsx | 98 ++++++++------- .../react/1000fields/bench/recoil.tsx | 85 ++++++++----- .../react/1000fields/bench/redux.tsx | 112 ++++++++++------- .../react/1000fields/bench/valtio.tsx | 82 +++++++----- .../benchmarks/react/1000fields/index.ts | 15 ++- 12 files changed, 606 insertions(+), 418 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 35da136d..0fc0e137 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -20,15 +20,53 @@ but it is better than nothing. ## 1000 Fields ```ts -1. Agile nested State x 9,687 ops/sec ±2.46% (52 runs sampled) -2. Hookstate x 5,170 ops/sec ±5.77% (31 runs sampled) -3. Jotai x 4,578 ops/sec ±5.49% (57 runs sampled) -4. Recoil x 3,790 ops/sec ±3.74% (55 runs sampled) -5. Agile State x 1,480 ops/sec ±1.39% (12 runs sampled) -6. Agile Collection x 498 ops/sec ±1.81% (61 runs sampled) -7. Redux x 150 ops/sec ±1.72% (59 runs sampled) -8. Mobx x 143 ops/sec ±0.92% (57 runs sampled) -9. Valtio x 37.44 ops/sec ±5.17% (42 runs sampled) +// 1 Field +Agile Collection x 13,729 ops/sec ±3.42% (60 runs sampled) [updatedFieldsCount: 76468, renderFieldsCount: 73] +Agile State x 19,008 ops/sec ±1.87% (66 runs sampled) [updatedFieldsCount: 103559, renderFieldsCount: 72] +Agile nested State x 21,119 ops/sec ±1.45% (64 runs sampled) [updatedFieldsCount: 116226, renderFieldsCount: 72] +Hookstate x 20,026 ops/sec ±0.68% (64 runs sampled) [updatedFieldsCount: 112513, renderFieldsCount: 112513] +Jotai x 16,372 ops/sec ±3.34% (63 runs sampled) [updatedFieldsCount: 90275, renderFieldsCount: 90275] +Mobx x 15,892 ops/sec ±3.42% (60 runs sampled) [updatedFieldsCount: 82400, renderFieldsCount: 82400] +Nano Stores x 21,455 ops/sec ±1.00% (66 runs sampled) [updatedFieldsCount: 114136, renderFieldsCount: 114136] +Recoil x 11,504 ops/sec ±3.44% (63 runs sampled) [updatedFieldsCount: 61553, renderFieldsCount: 61554] +Redux x 13,070 ops/sec ±2.73% (62 runs sampled) [updatedFieldsCount: 69239, renderFieldsCount: 69240] +Valtio x 9,962 ops/sec ±2.60% (60 runs sampled) [updatedFieldsCount: 54290, renderFieldsCount: 108579] + +// 10 Fields +Agile Collection x 10,651 ops/sec ±4.14% (58 runs sampled) [updatedFieldsCount: 56668, renderFieldsCount: 582] +Agile State x 16,175 ops/sec ±1.55% (65 runs sampled) [updatedFieldsCount: 87481, renderFieldsCount: 80] +Agile nested State x 20,703 ops/sec ±1.27% (65 runs sampled) [updatedFieldsCount: 113946, renderFieldsCount: 712] +Hookstate x 18,733 ops/sec ±3.14% (59 runs sampled) [updatedFieldsCount: 105792, renderFieldsCount: 105801] +Jotai x 15,602 ops/sec ±3.65% (61 runs sampled) [updatedFieldsCount: 85977, renderFieldsCount: 85986] +Mobx x 9,283 ops/sec ±3.16% (52 runs sampled) [updatedFieldsCount: 50806, renderFieldsCount: 508060] +Nano Stores x 20,125 ops/sec ±1.62% (62 runs sampled) [updatedFieldsCount: 108704, renderFieldsCount: 108713] +Recoil x 11,103 ops/sec ±4.50% (61 runs sampled) [updatedFieldsCount: 62920, renderFieldsCount: 62939] +Redux x 8,728 ops/sec ±1.61% (64 runs sampled) [updatedFieldsCount: 50794, renderFieldsCount: 507950] +Valtio x 3,557 ops/sec ±2.96% (23 runs sampled) [updatedFieldsCount: 22473, renderFieldsCount: 449450] + +// 100 Fields +Agile Collection x 3,897 ops/sec ±3.01% (25 runs sampled) [updatedFieldsCount: 24427, renderFieldsCount: 2502] +Agile State x 8,355 ops/sec ±0.85% (67 runs sampled) [updatedFieldsCount: 46249, renderFieldsCount: 173] +Agile nested State x 18,641 ops/sec ±1.17% (63 runs sampled) [updatedFieldsCount: 98669, renderFieldsCount: 6802] +Hookstate x 14,865 ops/sec ±2.51% (61 runs sampled) [updatedFieldsCount: 81616, renderFieldsCount: 81715] +Jotai x 12,676 ops/sec ±3.09% (61 runs sampled) [updatedFieldsCount: 65930, renderFieldsCount: 66029] +Mobx x 1,812 ops/sec ±1.49% (63 runs sampled) [updatedFieldsCount: 9639, renderFieldsCount: 963900] +Nano Stores x 16,283 ops/sec ±1.39% (62 runs sampled) [updatedFieldsCount: 84772, renderFieldsCount: 84871] +Recoil x 9,418 ops/sec ±2.94% (62 runs sampled) [updatedFieldsCount: 52425, renderFieldsCount: 52624] +Redux x 1,896 ops/sec ±1.74% (62 runs sampled) [updatedFieldsCount: 10133, renderFieldsCount: 1013400] +Valtio x 472 ops/sec ±2.97% (61 runs sampled) [updatedFieldsCount: 2494, renderFieldsCount: 498700] + +// 1000 Fields +Agile Collection x 503 ops/sec ±2.23% (62 runs sampled) [updatedFieldsCount: 2616, renderFieldsCount: 3520] +Agile State x 1,437 ops/sec ±1.48% (59 runs sampled) [updatedFieldsCount: 7569, renderFieldsCount: 1061] +Agile nested State x 9,411 ops/sec ±1.54% (56 runs sampled) [updatedFieldsCount: 46693, renderFieldsCount: 33243] +Hookstate x 4,539 ops/sec ±3.61% (27 runs sampled) [updatedFieldsCount: 26381, renderFieldsCount: 27380] +Jotai x 4,014 ops/sec ±5.35% (53 runs sampled) [updatedFieldsCount: 20390, renderFieldsCount: 21389] +Mobx x 151 ops/sec ±0.75% (59 runs sampled) [updatedFieldsCount: 786, renderFieldsCount: 786000] +Nano Stores x 5,511 ops/sec ±6.27% (32 runs sampled) [updatedFieldsCount: 31266, renderFieldsCount: 32265] +Recoil x 3,562 ops/sec ±3.16% (58 runs sampled) [updatedFieldsCount: 18503, renderFieldsCount: 20502] +Redux x 165 ops/sec ±1.40% (57 runs sampled) [updatedFieldsCount: 858, renderFieldsCount: 859000] +Valtio x 38.76 ops/sec ±5.50% (42 runs sampled) [updatedFieldsCount: 215, renderFieldsCount: 429000] ``` ## Computed diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx index 64630278..25a78022 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx @@ -3,47 +3,65 @@ import ReactDom from 'react-dom'; import { Agile, Logger } from '@agile-ts/core'; import { useAgile, useValue } from '@agile-ts/react'; -const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); - -const FIELDS = AgileApp.createCollection({ - initialData: Array.from(Array(1000).keys()).map((i) => ({ - id: i, - name: `Field #${i + 1}`, - })), -}); - -function Field({ index }: { index: number | string }) { - const ITEM = FIELDS.getItem(index); - const item = useAgile(ITEM); - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - ITEM?.patch({ name: e.target.value })} - /> -
- ); -} +export default function (target: HTMLElement, fieldsCount: number) { + const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); + + const FIELDS = AgileApp.createCollection({ + initialData: Array.from(Array(fieldsCount).keys()).map((i) => ({ + id: i, + name: `Field #${i + 1}`, + })), + }); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; -function App() { - const fieldKeys = useValue(FIELDS); + function Field({ index }: { index: number | string }) { + const ITEM = FIELDS.getItem(index); + const item = useAgile(ITEM); - return ( -
+ renderFieldsCount++; + + return (
- Last {``} render at: {new Date().toISOString()} + Last {``} render at: {new Date().toISOString()} +   + { + ITEM?.patch({ name: e.target.value }); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + />
-
- {fieldKeys.map((key, i) => ( - - ))} -
- ); -} + ); + } + + function App() { + const fieldKeys = useValue(FIELDS); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fieldKeys.map((key, i) => ( + + ))} +
+
+
+ ); + } -export default function (target: HTMLElement) { ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx index b0658b75..34375fdc 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx @@ -3,54 +3,62 @@ import * as ReactDom from 'react-dom'; import { Agile, Logger, State } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; -const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); - -const FIELDS = AgileApp.createState( - Array.from(Array(1000).keys()).map((i) => - AgileApp.createState(`Field #${i + 1}`) - ) -); - -let updatedFieldsCount = 0; - -function Field({ field }: { field: State }) { - const name = useAgile(field); - - updatedFieldsCount++; - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - { - field.set(e.target.value); - (document.getElementById( - 'updatedFieldsCount' - ) as any).innerText = updatedFieldsCount; - }} - /> -
+export default function (target: HTMLElement, fieldsCount: number) { + const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); + + const FIELDS = AgileApp.createState( + Array.from(Array(fieldsCount).keys()).map((i) => + AgileApp.createState(`Field #${i + 1}`) + ) ); -} -function App() { - const fields = useAgile(FIELDS); - return ( -
+ let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ field }: { field: State }) { + const name = useAgile(field); + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + field.set(e.target.value); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } + + function App() { + const fields = useAgile(FIELDS); + return (
- Last {``} render at: {new Date().toISOString()} +
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
-
- {fields.map((field, index) => ( - - ))} -
-
- ); -} + ); + } -export default function (target: HTMLElement) { ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx index 5304f93b..19a99ddb 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx @@ -3,61 +3,77 @@ import ReactDom from 'react-dom'; import { Agile, Logger } from '@agile-ts/core'; import { useProxy, useSelector } from '@agile-ts/react'; -const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); - -const FIELDS = AgileApp.createState( - Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`) -); - -// With Selector -// function Field({ index }: { index: number }) { -// const name = useSelector(FIELDS, (value) => value[index]) as string; -// -// return ( -//
-// Last {``} render at: {new Date().toISOString()} -//   -// { -// FIELDS.nextStateValue[index] = e.target.value; -// FIELDS.ingest(); -// }} /> -//
-// ); -// } - -// With Proxy -function Field({ index }: { index: number }) { - const fields = useProxy(FIELDS); - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - { - FIELDS.nextStateValue[index] = e.target.value; - FIELDS.ingest(); - }} - /> -
+export default function (target: HTMLElement, fieldsCount: number) { + const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); + + const FIELDS = AgileApp.createState( + Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`) ); -} -function App() { - return ( -
+ let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + // With Selector + // function Field({ index }: { index: number }) { + // const name = useSelector(FIELDS, (value) => value[index]) as string; + // + // return ( + //
+ // Last {``} render at: {new Date().toISOString()} + //   + // { + // FIELDS.nextStateValue[index] = e.target.value; + // FIELDS.ingest(); + // }} /> + //
+ // ); + // } + + // With Proxy + function Field({ index }: { index: number }) { + const fields = useProxy(FIELDS); + + renderFieldsCount++; + + return (
- Last {``} render at: {new Date().toISOString()} + Last {``} render at: {new Date().toISOString()} +   + { + FIELDS.nextStateValue[index] = e.target.value; + FIELDS.ingest(); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + />
-
- {FIELDS.value.map((field, index) => ( - - ))} -
- ); -} + ); + } + + function App() { + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {FIELDS.value.map((field, index) => ( + + ))} +
+
+
+ ); + } -export default function (target: HTMLElement) { ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx index 20e217d4..a64009fb 100644 --- a/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/hookstate.tsx @@ -2,50 +2,60 @@ import React from 'react'; import ReactDom from 'react-dom'; import { createState, useHookstate, State } from '@hookstate/core'; -const fields = createState( - Array.from(Array.from(Array(1000).keys()).map((i) => `Field #${i + 1} value`)) -); - -let updatedFieldsCount = 0; - -function Field({ field }: { field: State }) { - const name = useHookstate(field); - - updatedFieldsCount++; - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - { - name.set(e.target.value); - (document.getElementById( - 'updatedFieldsCount' - ) as any).innerText = updatedFieldsCount; - }} - /> -
+export default function (target: HTMLElement, fieldsCount: number) { + const fields = createState( + Array.from( + Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1} value`) + ) ); -} -function App() { - const state = useHookstate(fields); - return ( -
+ let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ field }: { field: State }) { + const name = useHookstate(field); + + renderFieldsCount++; + + return (
- Last {``} render at: {new Date().toISOString()} + Last {``} render at: {new Date().toISOString()} +   + { + name.set(e.target.value); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + />
-
- {state.map((field, index) => ( - - ))} -
-
- ); -} + ); + } + + function App() { + const state = useHookstate(fields); + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {state.map((field, index) => ( + + ))} +
+
+
+ ); + } -export default function (target: HTMLElement) { ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/bench/jotai.tsx b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx index 78e86f5d..3823e908 100644 --- a/benchmark/benchmarks/react/1000fields/bench/jotai.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/jotai.tsx @@ -2,53 +2,61 @@ import React from 'react'; import ReactDom from 'react-dom'; import { atom, useAtom, Atom } from 'jotai'; -const fields = Array.from(Array(1000).keys()).map((i) => - atom(`Field #${i + 1}`) -); - -const fieldsStore = atom(fields); - -let updatedFieldsCount = 0; - -function Field({ field }: { field: Atom }) { - const [name, rename] = useAtom(field); - - updatedFieldsCount++; - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - { - rename(e.target.value); - (document.getElementById( - 'updatedFieldsCount' - ) as any).innerText = updatedFieldsCount; - }} - /> -
+export default function (target: HTMLElement, fieldsCount: number) { + const fields = Array.from(Array(fieldsCount).keys()).map((i) => + atom(`Field #${i + 1}`) ); -} -function App() { - const [fields] = useAtom(fieldsStore); + const fieldsStore = atom(fields); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ field }: { field: Atom }) { + const [name, rename] = useAtom(field); - return ( -
+ renderFieldsCount++; + + return (
- Last {``} render at: {new Date().toISOString()} + Last {``} render at: {new Date().toISOString()} +   + { + rename(e.target.value); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + />
-
- {fields.map((field, index) => ( - - ))} -
-
- ); -} + ); + } + + function App() { + const [fields] = useAtom(fieldsStore); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
+
+ ); + } -export default function (target: HTMLElement) { ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/bench/mobx.tsx b/benchmark/benchmarks/react/1000fields/bench/mobx.tsx index fa2645b0..d8b05198 100644 --- a/benchmark/benchmarks/react/1000fields/bench/mobx.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/mobx.tsx @@ -3,43 +3,61 @@ import ReactDom from 'react-dom'; import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; -const appState = observable({ - fields: Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`), - rename: action(function (value: string, index: number) { - // console.log(state) - appState.fields[index] = value; - }), -}); - -function Field({ index }: { index: number }) { - const field = appState.fields[index]; - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - appState.rename(e.target.value, index)} - /> -
- ); -} +export default function (target: HTMLElement, fieldsCount: number) { + const appState = observable({ + fields: Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`), + rename: action(function (value: string, index: number) { + // console.log(state) + appState.fields[index] = value; + }), + }); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ index }: { index: number }) { + const field = appState.fields[index]; + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + appState.rename(e.target.value, index); + + updatedFieldsCount++; -const App = observer(() => { - return ( -
+ (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } + + const App = observer(() => { + return (
- Last {``} render at: {new Date().toISOString()} +
+ Last {``} render at: {new Date().toISOString()} +
+
+ {appState.fields.map((field, index) => ( + + ))} +
+
-
- {appState.fields.map((field, index) => ( - - ))} -
- ); -}); - -export default function (target: HTMLElement) { + ); + }); + ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx b/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx index c159f140..4fa38246 100644 --- a/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/nanostores.tsx @@ -3,57 +3,65 @@ import ReactDom from 'react-dom'; import { createStore, WritableStore } from 'nanostores'; import { useStore } from 'nanostores/react'; -const fieldsStore = createStore[]>(() => { - const fields = Array.from(Array(1000).keys()).map((i) => { - const fieldStore = createStore(() => { - fieldsStore.set(`Field #${i + 1}` as any); +export default function (target: HTMLElement, fieldsCount: number) { + const fieldsStore = createStore[]>(() => { + const fields = Array.from(Array(fieldsCount).keys()).map((i) => { + const fieldStore = createStore(() => { + fieldsStore.set(`Field #${i + 1}` as any); + }); + return fieldStore; }); - return fieldStore; + + fieldsStore.set(fields); }); - fieldsStore.set(fields); -}); - -let updatedFieldsCount = 0; - -function Field({ field }: { field: WritableStore }) { - const name = useStore(field); - - updatedFieldsCount++; - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - { - field.set(e.target.value); - (document.getElementById( - 'updatedFieldsCount' - ) as any).innerText = updatedFieldsCount; - }} - /> -
- ); -} + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ field }: { field: WritableStore }) { + const name = useStore(field); + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + field.set(e.target.value); + + updatedFieldsCount++; -function App() { - const fields = useStore(fieldsStore); - return ( -
+ (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } + + function App() { + const fields = useStore(fieldsStore); + return (
- Last {``} render at: {new Date().toISOString()} +
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
-
- {fields.map((field, index) => ( - - ))} -
-
- ); -} + ); + } -export default function (target: HTMLElement) { ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/bench/recoil.tsx b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx index 04a8a426..c6e194ab 100644 --- a/benchmark/benchmarks/react/1000fields/bench/recoil.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/recoil.tsx @@ -2,44 +2,65 @@ import React from 'react'; import ReactDom from 'react-dom'; import { atom, RecoilRoot, RecoilState, useRecoilState } from 'recoil'; -const fields = Array.from(Array(1000).keys()).map((i) => - atom({ key: `field-${i}`, default: `Field #${i + 1}` }) -); - -const fieldsStore = atom({ - key: 'fieldsStore', - default: fields, -}); - -function Field({ field }: { field: RecoilState }) { - const [name, rename] = useRecoilState(field); - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - rename(e.target.value)} /> -
+export default function (target: HTMLElement, fieldsCount: number) { + const fields = Array.from(Array(fieldsCount).keys()).map((i) => + atom({ key: `field-${i}`, default: `Field #${i + 1}` }) ); -} -function App() { - const [fields] = useRecoilState(fieldsStore); + const fieldsStore = atom({ + key: 'fieldsStore', + default: fields, + }); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ field }: { field: RecoilState }) { + const [name, rename] = useRecoilState(field); + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + rename(e.target.value); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } + + function App() { + const [fields] = useRecoilState(fieldsStore); - return ( -
+ return (
- Last {``} render at: {new Date().toISOString()} +
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
-
- {fields.map((field, index) => ( - - ))} -
- ); -} + ); + } -export default function (target: HTMLElement) { ReactDom.render( diff --git a/benchmark/benchmarks/react/1000fields/bench/redux.tsx b/benchmark/benchmarks/react/1000fields/bench/redux.tsx index e8cb41aa..ac134f70 100644 --- a/benchmark/benchmarks/react/1000fields/bench/redux.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/redux.tsx @@ -3,61 +3,81 @@ import ReactDom from 'react-dom'; import { configureStore, createSlice } from '@reduxjs/toolkit'; import { Provider, useDispatch, useSelector } from 'react-redux'; -const fieldsSlice = createSlice({ - name: 'fields', - initialState: { - fields: Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`), - }, - reducers: { - rename: (state, action) => { - state.fields[action.payload.index] = action.payload.value; +export default function (target: HTMLElement, fieldsCount: number) { + const fieldsSlice = createSlice({ + name: 'fields', + initialState: { + fields: Array.from(Array(fieldsCount).keys()).map( + (i) => `Field #${i + 1}` + ), }, - }, -}); + reducers: { + rename: (state, action) => { + state.fields[action.payload.index] = action.payload.value; + }, + }, + }); -const store = configureStore({ - reducer: { - fields: fieldsSlice.reducer, - }, -}); + const store = configureStore({ + reducer: { + fields: fieldsSlice.reducer, + }, + }); -function Field({ index, field: name }: { index: number; field: string }) { - const dispatch = useDispatch(); + let updatedFieldsCount = 0; + let renderFieldsCount = 0; - return ( -
- Last {``} render at: {new Date().toISOString()} -   - - dispatch(fieldsSlice.actions.rename({ index, value: e.target.value })) - } - /> -
- ); -} + function Field({ index, field: name }: { index: number; field: string }) { + const dispatch = useDispatch(); -function App() { - const fields: string[] = useSelector( - (state: any) => state.fields.fields, - () => false - ); + renderFieldsCount++; - return ( -
+ return (
- Last {``} render at: {new Date().toISOString()} + Last {``} render at: {new Date().toISOString()} +   + { + dispatch( + fieldsSlice.actions.rename({ index, value: e.target.value }) + ); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + />
-
- {fields.map((field, index) => ( - - ))} -
- ); -} + ); + } + + function App() { + const fields: string[] = useSelector( + (state: any) => state.fields.fields, + () => false + ); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
+
+ ); + } -export default function (target: HTMLElement) { ReactDom.render( diff --git a/benchmark/benchmarks/react/1000fields/bench/valtio.tsx b/benchmark/benchmarks/react/1000fields/bench/valtio.tsx index cf075bb8..b27698b0 100644 --- a/benchmark/benchmarks/react/1000fields/bench/valtio.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/valtio.tsx @@ -2,42 +2,60 @@ import React from 'react'; import ReactDom from 'react-dom'; import { proxy, useSnapshot } from 'valtio'; -const state = proxy({ - fields: Array.from(Array(1000).keys()).map((i) => `Field #${i + 1}`), -}); - -function Field({ index }: { index: number }) { - const { fields } = useSnapshot(state); - const name = fields[index]; - - return ( -
- Last {``} render at: {new Date().toISOString()} -   - (state.fields[index] = e.target.value)} - /> -
- ); -} +export default function (target: HTMLElement, fieldsCount: number) { + const state = proxy({ + fields: Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`), + }); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ index }: { index: number }) { + const { fields } = useSnapshot(state); + const name = fields[index]; + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + state.fields[index] = e.target.value; + + updatedFieldsCount++; -function App() { - const { fields } = useSnapshot(state, { sync: true }); + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } - return ( -
+ function App() { + const { fields } = useSnapshot(state, { sync: true }); + + return (
- Last {``} render at: {new Date().toISOString()} +
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
-
- {fields.map((field, index) => ( - - ))} -
- ); -} + ); + } -export default function (target: HTMLElement) { ReactDom.render(, target); } diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index eb089491..e5bd27c3 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -17,8 +17,7 @@ import valtio from './bench/valtio'; // Benchmark.js requires an instance of itself globally window.Benchmark = Benchmark; -// TODO figure out how to pass this count into the tests -const fieldsCount = 1000; +const fieldsCount = 1; // Create new Benchmark Test Suite const suite = new Suite(`${fieldsCount} Fields`); @@ -27,7 +26,9 @@ const suite = new Suite(`${fieldsCount} Fields`); const target = document.getElementById('bench')!; // Configures a single Benchmark Test -function configTest(renderElement: (target: HTMLElement) => void): Options { +function configTest( + renderElement: (target: HTMLElement, fieldsCount: number) => void +): Options { return { fn() { // Retrieve Input field to update @@ -43,13 +44,17 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { }, onStart() { // Render React Component in the target Element - renderElement(target); + renderElement(target, fieldsCount); }, onComplete() { (this as any).updatedFieldsCount = parseInt( (document.getElementById('updatedFieldsCount') as any)?.innerText, 10 ); + (this as any).renderFieldsCount = parseInt( + (document.getElementById('renderFieldsCount') as any)?.innerText, + 10 + ); // Unmount React Component ReactDOM.unmountComponentAtNode(target); @@ -78,7 +83,7 @@ suite .on('cycle', (event: any) => { console.log( String(event.target), - `[updatedFieldsCount: ${event.target.updatedFieldsCount}]` + `[updatedFieldsCount: ${event.target.updatedFieldsCount}, renderFieldsCount: ${event.target.renderFieldsCount}]` ); }) .on('complete', function (this: any) { From 92d51523427c236e42d73eb367813e99efb4e349 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 5 Jul 2021 18:01:13 +0200 Subject: [PATCH 36/55] added bucket option --- benchmark/README.md | 3 +++ .../1000fields/bench/agilets/nestedState.tsx | 4 ++- .../react/counter/bench/agilets.tsx | 4 ++- packages/core/src/agile.ts | 20 ++++++++++++++ packages/core/src/runtime/index.ts | 10 ++++--- packages/core/tests/unit/agile.test.ts | 3 +++ .../core/tests/unit/runtime/runtime.test.ts | 27 ++++++++++++++++--- 7 files changed, 61 insertions(+), 10 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 0fc0e137..c509afd3 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -4,6 +4,9 @@ The `Benchmark Test Suites` are supposed to showcase where AgileTs is roughly lo I know a counter doesn't really show real world app performance, but it is better than nothing. +### What do the results from benchmark js mean? +https://stackoverflow.com/questions/28524653/what-do-the-results-from-benchmark-js-mean + ## Counter Benchmark ```ts diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx index 34375fdc..3f292f7c 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx @@ -4,7 +4,9 @@ import { Agile, Logger, State } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; export default function (target: HTMLElement, fieldsCount: number) { - const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); + const AgileApp = new Agile({ + logConfig: { level: Logger.level.ERROR }, + }); const FIELDS = AgileApp.createState( Array.from(Array(fieldsCount).keys()).map((i) => diff --git a/benchmark/benchmarks/react/counter/bench/agilets.tsx b/benchmark/benchmarks/react/counter/bench/agilets.tsx index 300ff431..05fc1f4a 100644 --- a/benchmark/benchmarks/react/counter/bench/agilets.tsx +++ b/benchmark/benchmarks/react/counter/bench/agilets.tsx @@ -3,7 +3,9 @@ import ReactDom from 'react-dom'; import { Agile, Logger } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; -const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); +const AgileApp = new Agile({ + logConfig: { level: Logger.level.ERROR }, +}); const COUNT = AgileApp.createState(0); const App = () => { diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 9791ae40..fec41bf5 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -89,9 +89,11 @@ export class Agile { logConfig: {}, bindGlobal: false, autoIntegrate: true, + bucket: true, }); this.config = { waitForMount: config.waitForMount as any, + bucket: config.bucket as any, }; this.key = config.key; this.integrations = new Integrations(this, { @@ -368,6 +370,15 @@ export interface CreateAgileConfigInterface * @default undefined */ key?: AgileKey; + /** + * Whether to put render events into "The bucket" of the browser, + * where all events are first put in wait for the UI thread + * to be done with whatever it's doing. + * + * [Learn more](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay) + * @default true + */ + bucket?: boolean; } export interface AgileConfigInterface { @@ -377,4 +388,13 @@ export interface AgileConfigInterface { * @default true */ waitForMount: boolean; + /** + * Whether to put render events into "The bucket" of the browser, + * where all events are first put in wait for the UI thread + * to be done with whatever it's doing. + * + * [Learn more](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay) + * @default true + */ + bucket: boolean; } diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index f5578699..7dec2575 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -123,10 +123,12 @@ export class Runtime { } else { this.isPerformingJobs = false; if (this.jobsToRerender.length > 0) { - // https://stackoverflow.com/questions/9083594/call-settimeout-without-delay - setTimeout(() => { - this.updateSubscribers(); - }); + if (this.agileInstance().config.bucket) { + // https://stackoverflow.com/questions/9083594/call-settimeout-without-delay + setTimeout(() => { + this.updateSubscribers(); + }); + } else this.updateSubscribers(); } } } diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 02ae2ef7..136702ae 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -88,6 +88,7 @@ describe('Agile Tests', () => { expect(agile.config).toStrictEqual({ waitForMount: true, + bucket: true, }); expect(agile.key).toBeUndefined(); expect(IntegrationsMock).toHaveBeenCalledWith(agile, { @@ -111,6 +112,7 @@ describe('Agile Tests', () => { it('should instantiate Agile (specific config)', () => { const agile = new Agile({ waitForMount: false, + bucket: false, localStorage: false, logConfig: { level: Logger.level.DEBUG, @@ -125,6 +127,7 @@ describe('Agile Tests', () => { expect(agile.config).toStrictEqual({ waitForMount: false, + bucket: false, }); expect(agile.key).toBe('jeff'); expect(IntegrationsMock).toHaveBeenCalledWith(agile, { diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index f72b5736..c75e3893 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -9,6 +9,7 @@ import { } from '../../../src'; import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../helper/logMock'; +import waitForExpect from 'wait-for-expect'; describe('Runtime Tests', () => { let dummyAgile: Agile; @@ -111,8 +112,10 @@ describe('Runtime Tests', () => { it( "should perform specified Job and all remaining Jobs in the 'jobQueue' " + - "and call 'updateSubscribers' if at least one performed Job needs to rerender", + "and call 'updateSubscribers' in a setTimeout " + + 'if at least one performed Job needs to rerender (config.bucket = true)', async () => { + runtime.agileInstance().config.bucket = true; runtime.jobQueue.push(dummyJob2); runtime.jobQueue.push(dummyJob3); @@ -129,8 +132,23 @@ describe('Runtime Tests', () => { expect(runtime.jobQueue).toStrictEqual([]); expect(runtime.jobsToRerender).toStrictEqual([dummyJob1, dummyJob2]); - // Sleep 5ms because updateSubscribers is called in a timeout - await new Promise((resolve) => setTimeout(resolve, 5)); + // Because 'updateSubscribers' is called in a timeout + await waitForExpect(() => { + expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1); + }); + } + ); + + it( + "should perform specified Job and all remaining Jobs in the 'jobQueue' " + + "and call 'updateSubscribers' " + + 'if at least one performed Job needs to rerender (config.bucket = false)', + async () => { + runtime.agileInstance().config.bucket = false; + runtime.jobQueue.push(dummyJob2); + runtime.jobQueue.push(dummyJob3); + + runtime.perform(dummyJob1); expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1); } @@ -151,7 +169,8 @@ describe('Runtime Tests', () => { it( "should perform specified Job and all remaining Jobs in the 'jobQueue' " + - "and shouldn't call 'updateSubscribes' if no performed Job needs to rerender", + "and shouldn't call 'updateSubscribes' " + + 'if no performed Job needs to rerender', async () => { dummyJob1.rerender = false; runtime.jobQueue.push(dummyJob3); From bca1ed7168ff65976a18154e0d3cbb5f909d78fe Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 7 Jul 2021 12:09:45 +0200 Subject: [PATCH 37/55] added pulsejs benchmark --- README.md | 6 +- .../1000fields/bench/agilets/collection.tsx | 6 +- .../1000fields/bench/agilets/nestedState.tsx | 10 +-- .../react/1000fields/bench/agilets/state.tsx | 27 ++------ .../1000fields/bench/pulsejs/collection.tsx | 63 +++++++++++++++++++ .../1000fields/bench/pulsejs/nestedState.tsx | 60 ++++++++++++++++++ .../react/1000fields/bench/pulsejs/state.tsx | 60 ++++++++++++++++++ .../benchmarks/react/1000fields/index.ts | 8 ++- .../computed/bench/agilets/autoTracking.tsx | 7 +-- .../computed/bench/agilets/hardCoded.tsx | 7 +-- .../react/counter/bench/agilets.tsx | 9 +-- .../react/counter/bench/pulsejs.tsx | 15 +++++ benchmark/benchmarks/react/counter/index.ts | 2 + benchmark/package.json | 6 +- benchmark/yarn.lock | 18 ++++-- packages/core/src/agile.ts | 6 +- packages/core/src/shared.ts | 2 +- 17 files changed, 248 insertions(+), 64 deletions(-) create mode 100644 benchmark/benchmarks/react/1000fields/bench/pulsejs/collection.tsx create mode 100644 benchmark/benchmarks/react/1000fields/bench/pulsejs/nestedState.tsx create mode 100644 benchmark/benchmarks/react/1000fields/bench/pulsejs/state.tsx create mode 100644 benchmark/benchmarks/react/counter/bench/pulsejs.tsx diff --git a/README.md b/README.md index addb909e..d054994f 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ It's only one click away. Just select your preferred Framework below. - [Vue](https://codesandbox.io/s/agilets-first-state-i5xxs) - Angular (coming soon) -More examples can be found in the [Example Section](https://agile-ts.org/docs/examples). +More examples can be found in the [Example section](https://agile-ts.org/docs/examples).
@@ -119,7 +119,7 @@ The advantage of keeping logic separate to UI-Components, is that your code is more decoupled, portable, scalable, and above all, easily testable. -Learn more about ways to centralize your application logic with AgileTs +You can learn more about ways to centralize your application logic with AgileTs in our [Style Guides](https://agile-ts.org/docs/style-guide). ### 🎯 Easy to Use @@ -127,7 +127,7 @@ in our [Style Guides](https://agile-ts.org/docs/style-guide). Learn the powerful tools of AgileTs in a short period 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 tutorials, -you can jump straight into the [Example Section](https://agile-ts.org/docs/examples/Introduction). +you can jump straight into the [Example section](https://agile-ts.org/docs/examples/Introduction).
diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx index 25a78022..f30fcef4 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx @@ -1,12 +1,10 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { Agile, Logger } from '@agile-ts/core'; +import { createCollection } from '@agile-ts/core'; import { useAgile, useValue } from '@agile-ts/react'; export default function (target: HTMLElement, fieldsCount: number) { - const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); - - const FIELDS = AgileApp.createCollection({ + const FIELDS = createCollection({ initialData: Array.from(Array(fieldsCount).keys()).map((i) => ({ id: i, name: `Field #${i + 1}`, diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx index 3f292f7c..88fe5ba6 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx @@ -1,16 +1,12 @@ import * as React from 'react'; import * as ReactDom from 'react-dom'; -import { Agile, Logger, State } from '@agile-ts/core'; +import { createState, State } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; export default function (target: HTMLElement, fieldsCount: number) { - const AgileApp = new Agile({ - logConfig: { level: Logger.level.ERROR }, - }); - - const FIELDS = AgileApp.createState( + const FIELDS = createState( Array.from(Array(fieldsCount).keys()).map((i) => - AgileApp.createState(`Field #${i + 1}`) + createState(`Field #${i + 1}`) ) ); diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx index 19a99ddb..08069b33 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx @@ -1,37 +1,18 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { Agile, Logger } from '@agile-ts/core'; -import { useProxy, useSelector } from '@agile-ts/react'; +import { createState } from '@agile-ts/core'; +import { useAgile } from '@agile-ts/react'; export default function (target: HTMLElement, fieldsCount: number) { - const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); - - const FIELDS = AgileApp.createState( + const FIELDS = createState( Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`) ); let updatedFieldsCount = 0; let renderFieldsCount = 0; - // With Selector - // function Field({ index }: { index: number }) { - // const name = useSelector(FIELDS, (value) => value[index]) as string; - // - // return ( - //
- // Last {``} render at: {new Date().toISOString()} - //   - // { - // FIELDS.nextStateValue[index] = e.target.value; - // FIELDS.ingest(); - // }} /> - //
- // ); - // } - - // With Proxy function Field({ index }: { index: number }) { - const fields = useProxy(FIELDS); + const fields = useAgile(FIELDS); renderFieldsCount++; diff --git a/benchmark/benchmarks/react/1000fields/bench/pulsejs/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/pulsejs/collection.tsx new file mode 100644 index 00000000..078a7ab9 --- /dev/null +++ b/benchmark/benchmarks/react/1000fields/bench/pulsejs/collection.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { collection, usePulse } from '@pulsejs/react'; + +export default function (target: HTMLElement, fieldsCount: number) { + const FIELDS = collection<{ id: number; name: string }>(); + FIELDS.collect( + Array.from(Array(fieldsCount).keys()).map((i) => ({ + id: i, + name: `Field #${i + 1}`, + })) + ); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ index }: { index: number | string }) { + const ITEM = FIELDS.getData(index); + const item = usePulse(ITEM); + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + ITEM?.patch({ name: e.target.value }); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } + + function App() { + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {Object.keys(FIELDS.data).map((key, i) => ( + + ))} +
+
+
+ ); + } + + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/1000fields/bench/pulsejs/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/pulsejs/nestedState.tsx new file mode 100644 index 00000000..d09f86f5 --- /dev/null +++ b/benchmark/benchmarks/react/1000fields/bench/pulsejs/nestedState.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { state, State } from '@pulsejs/core'; +import { usePulse } from '@pulsejs/react'; + +export default function (target: HTMLElement, fieldsCount: number) { + const FIELDS = state( + Array.from(Array(fieldsCount).keys()).map((i) => state(`Field #${i + 1}`)) + ); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ field }: { field: State }) { + const name = usePulse(field); + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + field.set(e.target.value); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } + + function App() { + const fields = usePulse(FIELDS); + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fields.map((field, index) => ( + + ))} +
+
+
+ ); + } + + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/1000fields/bench/pulsejs/state.tsx b/benchmark/benchmarks/react/1000fields/bench/pulsejs/state.tsx new file mode 100644 index 00000000..11e55fe7 --- /dev/null +++ b/benchmark/benchmarks/react/1000fields/bench/pulsejs/state.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { state } from '@pulsejs/core'; +import { usePulse } from '@pulsejs/react'; + +export default function (target: HTMLElement, fieldsCount: number) { + const FIELDS = state( + Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`) + ); + + let updatedFieldsCount = 0; + let renderFieldsCount = 0; + + function Field({ index }: { index: number }) { + const fields = usePulse(FIELDS); + + renderFieldsCount++; + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + FIELDS.nextState[index] = e.target.value; + FIELDS.set(); + + updatedFieldsCount++; + + (document.getElementById( + 'updatedFieldsCount' + ) as any).innerText = updatedFieldsCount; + (document.getElementById( + 'renderFieldsCount' + ) as any).innerText = renderFieldsCount; + }} + /> +
+ ); + } + + function App() { + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {FIELDS.value.map((field, index) => ( + + ))} +
+
+
+ ); + } + + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index e5bd27c3..bb42d83e 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -5,6 +5,9 @@ import ReactDOM from 'react-dom'; import agileCollection from './bench/agilets/collection'; import agileState from './bench/agilets/state'; import agileNestedState from './bench/agilets/nestedState'; +import pulseCollection from './bench/pulsejs/collection'; +import pulseState from './bench/pulsejs/state'; +import pulseNestedState from './bench/pulsejs/nestedState'; import hookstate from './bench/hookstate'; import jotai from './bench/jotai'; import mobx from './bench/mobx'; @@ -17,7 +20,7 @@ import valtio from './bench/valtio'; // Benchmark.js requires an instance of itself globally window.Benchmark = Benchmark; -const fieldsCount = 1; +const fieldsCount = 1000; // Create new Benchmark Test Suite const suite = new Suite(`${fieldsCount} Fields`); @@ -68,6 +71,9 @@ suite .add('Agile Collection', configTest(agileCollection)) .add('Agile State', configTest(agileState)) .add('Agile nested State', configTest(agileNestedState)) + .add('Pulse Collection', configTest(pulseCollection)) + .add('Pulse State', configTest(pulseState)) + .add('Pulse nested State', configTest(pulseNestedState)) .add('Hookstate', configTest(hookstate)) .add('Jotai', configTest(jotai)) .add('Mobx', configTest(mobx)) diff --git a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx index 5f2be0f8..df85d176 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx @@ -1,11 +1,10 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { Agile, Logger } from '@agile-ts/core'; +import { createComputed, createState } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; -const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); -const COUNT = AgileApp.createState(0); -const COMPUTED_COUNT = AgileApp.createComputed(() => { +const COUNT = createState(0); +const COMPUTED_COUNT = createComputed(() => { return COUNT.value * 5; }); diff --git a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx index 45c05d38..ac782b55 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx @@ -1,11 +1,10 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { Agile, Logger } from '@agile-ts/core'; +import { createComputed, createState } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; -const AgileApp = new Agile({ logConfig: { level: Logger.level.ERROR } }); -const COUNT = AgileApp.createState(0); -const COMPUTED_COUNT = AgileApp.createComputed( +const COUNT = createState(0); +const COMPUTED_COUNT = createComputed( () => { return COUNT.value * 5; }, diff --git a/benchmark/benchmarks/react/counter/bench/agilets.tsx b/benchmark/benchmarks/react/counter/bench/agilets.tsx index 05fc1f4a..e4e8ddb1 100644 --- a/benchmark/benchmarks/react/counter/bench/agilets.tsx +++ b/benchmark/benchmarks/react/counter/bench/agilets.tsx @@ -1,15 +1,12 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { Agile, Logger } from '@agile-ts/core'; +import { createState } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; -const AgileApp = new Agile({ - logConfig: { level: Logger.level.ERROR }, -}); -const COUNT = AgileApp.createState(0); +const COUNT = createState(0); const App = () => { - const count = useAgile(COUNT); + const count = useAgile(COUNT, undefined); return

COUNT.set((state) => state + 1)}>{count}

; }; diff --git a/benchmark/benchmarks/react/counter/bench/pulsejs.tsx b/benchmark/benchmarks/react/counter/bench/pulsejs.tsx new file mode 100644 index 00000000..f34e2d38 --- /dev/null +++ b/benchmark/benchmarks/react/counter/bench/pulsejs.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { state } from '@pulsejs/core'; +import { usePulse } from '@pulsejs/react'; + +const COUNT = state(0); + +const App = () => { + const count = usePulse(COUNT); + return

COUNT.set((state) => state + 1)}>{count}

; +}; + +export default function (target: HTMLElement) { + ReactDom.render(, target); +} diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index 79ce6c87..946030fa 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -7,6 +7,7 @@ import hookstate from './bench/hookstate'; import jotai from './bench/jotai'; import mobx from './bench/mobx'; import nanostores from './bench/nanostores'; +import pulsejs from './bench/pulsejs'; import recoil from './bench/recoil'; import redux from './bench/redux'; import reduxToolkit from './bench/redux-toolkit'; @@ -58,6 +59,7 @@ suite .add('Jotai', configTest(jotai)) .add('Mobx', configTest(mobx)) .add('Nano Stores', configTest(nanostores)) + .add('PulseJs', configTest(pulsejs)) .add('Recoil', configTest(recoil)) .add('Redux', configTest(redux)) .add('Redux-Toolkit', configTest(reduxToolkit)) diff --git a/benchmark/package.json b/benchmark/package.json index adaaa954..5217433f 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -19,9 +19,11 @@ "url": "git+https://github.com/agile-ts/agile.git" }, "dependencies": { - "@agile-ts/core": "^0.1.0", - "@agile-ts/react": "^0.1.0", + "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/react": "file:.yalc/@agile-ts/react", "@hookstate/core": "^3.0.8", + "@pulsejs/core": "^4.0.0-beta.3", + "@pulsejs/react": "^4.0.0-beta.3", "@reduxjs/toolkit": "^1.6.0", "benchmark": "^2.1.4", "colorette": "^1.2.2", diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index 8a02f9bb..9c8ac2c3 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -2,10 +2,8 @@ # yarn lockfile v1 -"@agile-ts/core@^0.1.0": +"@agile-ts/core@file:.yalc/@agile-ts/core": version "0.1.0" - resolved "https://registry.yarnpkg.com/@agile-ts/core/-/core-0.1.0.tgz#11f048f06288b78a0f5841b4b8ee62c0f9863f75" - integrity sha512-+SeukkYXVwv9WFnk7/P/NCqNHFdywQNnSxGdcHAh1jSFeofofcP+aqp5oaEYf/4A73qe1yDF6vr7wMz0q0Zz4w== dependencies: "@agile-ts/logger" "^0.0.5" "@agile-ts/utils" "^0.0.5" @@ -17,10 +15,8 @@ dependencies: "@agile-ts/utils" "^0.0.5" -"@agile-ts/react@^0.1.0": +"@agile-ts/react@file:.yalc/@agile-ts/react": version "0.1.0" - resolved "https://registry.yarnpkg.com/@agile-ts/react/-/react-0.1.0.tgz#338ef6c562eae473f1328fb6727b4ae2aa9b4fc4" - integrity sha512-CzQE69LBIT8Ynk0M570TTKhA1KCQoR/RM/sNWh72De88W1yOw+BUtff5APsJPv+3we04zy7dz9KwIYjR4VbQLA== "@agile-ts/utils@^0.0.5": version "0.0.5" @@ -39,6 +35,16 @@ resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-3.0.8.tgz#d6838153d6d43c2f35cfca475c31248192564e62" integrity sha512-blQagGIVIbNoUiNCRrvaXqFmUe7WGMY35ok/LENfl2pcIsLBjkreYIZiaSFi83tkycwq7ZOmcQz/R1nvLKhH8w== +"@pulsejs/core@^4.0.0-beta.3": + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/@pulsejs/core/-/core-4.0.0-beta.3.tgz#a19c983093dbc516ee3dcda4dee92917c77fd4cc" + integrity sha512-nYSckFFTPt8/8wZpEaFqKsSc1xd+eT7t3TSB3acy6yEQjbkQ1T2CL5tyNGL4tzT3Bher6LHkipDy0tMi5PsPUg== + +"@pulsejs/react@^4.0.0-beta.3": + version "4.0.0-beta.3" + resolved "https://registry.yarnpkg.com/@pulsejs/react/-/react-4.0.0-beta.3.tgz#d25bf1e969b8749d0f098f6e2ac066364f8c1fe1" + integrity sha512-QuO/UoBTL+5ObCTje3nvrMqrifPujWtbHpVMJMnWnZGrL88hiSivaWGvqzaU0BsIE3R6WQHZTJNDvBR8sXFIEQ== + "@reduxjs/toolkit@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.0.tgz#0a17c6941c57341f8b31e982352b495ab69d5add" diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index fec41bf5..4e6444b4 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -50,7 +50,7 @@ export class Agile { static logger = new Logger({ prefix: 'Agile', active: true, - level: Logger.level.WARN, + level: Logger.level.ERROR, }); // Identifier used to bind an Agile Instance globally @@ -375,7 +375,7 @@ export interface CreateAgileConfigInterface * where all events are first put in wait for the UI thread * to be done with whatever it's doing. * - * [Learn more](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay) + * [Learn more..](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay) * @default true */ bucket?: boolean; @@ -393,7 +393,7 @@ export interface AgileConfigInterface { * where all events are first put in wait for the UI thread * to be done with whatever it's doing. * - * [Learn more](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay) + * [Learn more..](https://stackoverflow.com/questions/9083594/call-settimeout-without-delay) * @default true */ bucket: boolean; diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index d433eaab..b3d91305 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -23,7 +23,7 @@ import { */ let sharedAgileInstance = new Agile({ key: 'shared', - logConfig: { prefix: 'Agile', level: Logger.level.WARN, active: true }, + logConfig: { prefix: 'Agile', level: Logger.level.ERROR, active: true }, localStorage: !runsOnServer(), }); export { sharedAgileInstance as shared }; From 19de4bbba8c8a213a05fd3555a6ba790bad46c41 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 7 Jul 2021 20:12:36 +0200 Subject: [PATCH 38/55] optimized equals and copy utils --- packages/core/src/logCodeManager.ts | 7 ++++--- packages/utils/src/index.ts | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index d90deb35..79e8e8d0 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -180,9 +180,8 @@ function getLog>( ): string { let result = logCodeMessages[logCode] ?? `'${logCode}' is a unknown logCode!`; - for (const i in replacers) { - // https://stackoverflow.com/questions/41438656/why-do-i-get-cannot-read-property-tostring-of-undefined - result = result.split('${' + i + '}').join(replacers[i] + ''); + for (let i = 0; i < replacers.length; i++) { + result = result.replace('${' + i + '}', replacers[i]); } return result; @@ -203,6 +202,8 @@ function log>( replacers: any[] = [], ...data: any[] ): void { + if (!Agile.logger.isActive) return; + const codes = logCode.split(':'); if (codes.length === 3) Agile.logger[logCodeTypes[codes[1]]](getLog(logCode, replacers), ...data); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index e9104543..46b16b86 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -8,19 +8,15 @@ * @param value - Array/Object that gets copied */ export function copy(value: T): T { - // Extra checking '!value' because 'typeof null === object' - if (!value) return value; - // Ignore everything that is no object or array - const valConstructorName = Object.getPrototypeOf(value).constructor.name; - if (!['object', 'array'].includes(valConstructorName.toLowerCase())) - return value; + // (Extra checking 'value == null' because 'typeof null === object') + if (value == null || typeof value !== 'object') return value; let temp; const newObject: any = Array.isArray(value) ? [] : {}; for (const property in value) { temp = value[property]; - newObject[property] = typeof temp === 'object' ? copy(temp) : temp; + newObject[property] = copy(temp); } return newObject as T; } @@ -223,7 +219,14 @@ export function flatMerge( * @param value2 - Second Value */ export function equal(value1: any, value2: any): boolean { - return value1 === value2 || JSON.stringify(value1) === JSON.stringify(value2); + return ( + value1 === value2 || + // Checking if 'value1' and 'value2' is typeof object before + // using the JSON.stringify comparison to optimize the performance + (typeof value1 === 'object' && + typeof value2 === 'object' && + JSON.stringify(value1) === JSON.stringify(value2)) + ); } //========================================================================================================= From 7a6e1a2db35ba174e8cf85da0b883cda0d759b3b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 8 Jul 2021 08:30:19 +0200 Subject: [PATCH 39/55] made logger optional --- .../functional-component-ts/package.json | 7 +- .../functional-component-ts/src/core/index.ts | 5 +- .../develop/functional-component-ts/yarn.lock | 40 +- packages/core/package.json | 9 +- packages/core/src/agile.ts | 48 +- packages/core/src/internal.ts | 1 - packages/core/src/logCodeManager.ts | 77 ++- packages/core/src/runtime/index.ts | 17 +- .../runtime/subscription/sub.controller.ts | 45 +- packages/core/src/shared.ts | 2 - packages/core/src/storages/storage.ts | 47 +- packages/event/src/event.ts | 5 +- packages/event/src/hooks/useEvent.ts | 6 +- packages/logger/src/index.ts | 505 +----------------- packages/logger/src/logger.ts | 491 +++++++++++++++++ packages/react/src/hocs/AgileHOC.ts | 6 +- packages/react/src/hooks/useAgile.ts | 5 +- yarn.lock | 12 +- 18 files changed, 700 insertions(+), 628 deletions(-) create mode 100644 packages/logger/src/logger.ts diff --git a/examples/react/develop/functional-component-ts/package.json b/examples/react/develop/functional-component-ts/package.json index 08b5324c..11686b80 100644 --- a/examples/react/develop/functional-component-ts/package.json +++ b/examples/react/develop/functional-component-ts/package.json @@ -5,10 +5,11 @@ "dependencies": { "@agile-ts/api": "file:.yalc/@agile-ts/api", "@agile-ts/core": "file:.yalc/@agile-ts/core", - "@agile-ts/multieditor": "file:.yalc/@agile-ts/multieditor", - "@agile-ts/react": "file:.yalc/@agile-ts/react", "@agile-ts/event": "file:.yalc/@agile-ts/event", + "@agile-ts/logger": "file:.yalc/@agile-ts/logger", + "@agile-ts/multieditor": "file:.yalc/@agile-ts/multieditor", "@agile-ts/proxytree": "file:.yalc/@agile-ts/proxytree", + "@agile-ts/react": "file:.yalc/@agile-ts/react", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "^5.2.0", @@ -30,7 +31,7 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree & yarn install" + "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree @agile-ts/logger & yarn install" }, "eslintConfig": { "extends": "react-app" diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index cf448f46..e4314dd1 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -1,12 +1,13 @@ -import { Agile, clone, Item, Logger } from '@agile-ts/core'; +import { Agile, clone, Item } from '@agile-ts/core'; import Event from '@agile-ts/event'; +import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger'; export const myStorage: any = {}; export const App = new Agile({ - logConfig: { level: Logger.level.DEBUG }, localStorage: true, }); +assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); // Register custom second Storage App.registerStorage( diff --git a/examples/react/develop/functional-component-ts/yarn.lock b/examples/react/develop/functional-component-ts/yarn.lock index 21b151e8..b44f5283 100644 --- a/examples/react/develop/functional-component-ts/yarn.lock +++ b/examples/react/develop/functional-component-ts/yarn.lock @@ -3,46 +3,36 @@ "@agile-ts/api@file:.yalc/@agile-ts/api": - version "0.0.18" + version "0.0.19" dependencies: - "@agile-ts/utils" "^0.0.4" + "@agile-ts/utils" "^0.0.5" "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.0.17" + version "0.1.0" dependencies: - "@agile-ts/logger" "^0.0.4" - "@agile-ts/utils" "^0.0.4" + "@agile-ts/utils" "^0.0.5" "@agile-ts/event@file:.yalc/@agile-ts/event": - version "0.0.7" + version "0.0.8" -"@agile-ts/logger@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d" - integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw== +"@agile-ts/logger@file:.yalc/@agile-ts/logger": + version "0.0.5" dependencies: - "@agile-ts/utils" "^0.0.4" + "@agile-ts/utils" "^0.0.5" "@agile-ts/multieditor@file:.yalc/@agile-ts/multieditor": - version "0.0.17" - -"@agile-ts/proxytree@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@agile-ts/proxytree/-/proxytree-0.0.3.tgz#e3dacab123a311f2f0d4a0369793fe90fdab7569" - integrity sha512-auO6trCo7ivLJYuLjxrnK4xuUTangVPTq8UuOMTlGbJFjmb8PLEkaXuRoVGSzv9jsT2FeS7KsP7Fs+yvv0WPdg== + version "0.0.18" "@agile-ts/proxytree@file:.yalc/@agile-ts/proxytree": - version "0.0.3" + version "0.0.4" "@agile-ts/react@file:.yalc/@agile-ts/react": - version "0.0.18" - dependencies: - "@agile-ts/proxytree" "^0.0.3" + version "0.1.0" -"@agile-ts/utils@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697" - integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ== +"@agile-ts/utils@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6" + integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA== "@babel/code-frame@7.8.3": version "7.8.3" diff --git a/packages/core/package.json b/packages/core/package.json index 1463bafd..4d10af6e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,9 +44,16 @@ "@agile-ts/utils": "file:../utils" }, "dependencies": { - "@agile-ts/utils": "^0.0.5", + "@agile-ts/utils": "^0.0.5" + }, + "peerDependencies": { "@agile-ts/logger": "^0.0.5" }, + "peerDependenciesMeta": { + "@agile-ts/logger": { + "optional": true + } + }, "publishConfig": { "access": "public" }, diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 4e6444b4..985a0554 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -14,8 +14,6 @@ import { CreateStorageConfigInterface, RegisterConfigInterface, defineConfig, - Logger, - CreateLoggerConfigInterface, StateConfigInterface, flatMerge, LogCodeManager, @@ -45,14 +43,6 @@ export class Agile { // Integrations (UI-Frameworks) that are integrated into the Agile Instance public integrations: Integrations; - // 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.ERROR, - }); - // Identifier used to bind an Agile Instance globally static globalKey = '__agile__'; @@ -84,7 +74,7 @@ export class Agile { */ constructor(config: CreateAgileConfigInterface = {}) { config = defineConfig(config, { - localStorage: true, + localStorage: false, waitForMount: true, logConfig: {}, bindGlobal: false, @@ -105,10 +95,7 @@ export class Agile { localStorage: config.localStorage, }); - // Assign customized Logger config to the static Logger - this.configureLogger(config.logConfig); - - LogCodeManager.log('10:00:00', [], this, Agile.logger); + LogCodeManager.log('10:00:00', [], this); // Create a global instance of the Agile Instance. // Why? 'getAgileInstance()' returns the global Agile Instance @@ -119,24 +106,6 @@ export class Agile { } } - /** - * 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. * @@ -337,17 +306,6 @@ export type AgileKey = string | number; export interface CreateAgileConfigInterface extends IntegrationsConfigInterface { - /** - * Configures the logging behaviour of AgileTs. - * @default { - prefix: 'Agile', - active: true, - level: Logger.level.WARN, - canUseCustomStyles: true, - allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], - } - */ - logConfig?: CreateLoggerConfigInterface; /** * Whether the Subscription Container shouldn't be ready * until the UI-Component it represents has been mounted. @@ -356,7 +314,7 @@ export interface CreateAgileConfigInterface waitForMount?: boolean; /** * Whether the Local Storage should be registered as a Agile Storage by default. - * @default true + * @default false */ localStorage?: boolean; /** diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index ad6631d3..b9356568 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -9,7 +9,6 @@ export * from './utils'; export * from '@agile-ts/utils'; // Logger -export * from '@agile-ts/logger'; export * from './logCodeManager'; // Agile diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 79e8e8d0..242338b2 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -1,4 +1,10 @@ -import { Agile } from './agile'; +// TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work +export let loggerPackage: any = null; +try { + loggerPackage = require('@agile-ts/logger'); +} catch (e) { + // empty catch block +} // The Log Code Manager keeps track // and manages all important Logs of AgileTs. @@ -202,11 +208,70 @@ function log>( replacers: any[] = [], ...data: any[] ): void { - if (!Agile.logger.isActive) return; + console.debug( + 'log()', + LogCodeManager.logger, + LogCodeManager.logger?.isActive, + logCode, + replacers + ); // TODO REMOVE + if (!LogCodeManager.logger?.isActive) return; + const logType = logCodeTypes[`${logCode.charAt(3)}${logCode.charAt(4)}`]; + console.debug('LogType:', logType); // TODO REMOVE + if (typeof logType !== 'string') return; + + // Handle logging without Logger + if (LogCodeManager.logger == null) { + if (logType === 'error' || logType === 'warn') + console[logType](getLog(logCode, replacers)); + return; + } + + // Handle logging with Logger + LogCodeManager.logger[logType](getLog(logCode, replacers), ...data); +} + +/** + * Logs the log message according to the specified log code + * with the Agile Logger if the provided tags are active. + * + * @internal + * @param tags - Tags to be active to log the logCode. + * @param logCode - Log code of the message to be returned. + * @param replacers - Instances that replace these '${x}' placeholders based on the index + * For example: 'replacers[0]' replaces '${0}', 'replacers[1]' replaces '${1}', .. + * @param data - Data to be attached to the end of the log message. + */ +function logIfTags>( + tags: string[], + logCode: T, + replacers: any[] = [], + ...data: any[] +): void { + console.debug( + 'logIfTags()', + LogCodeManager.logger, + LogCodeManager.logger?.isActive, + tags, + logCode, + replacers + ); // TODO REMOVE + if (!LogCodeManager.logger?.isActive) return; + const logType = logCodeTypes[`${logCode.charAt(3)}${logCode.charAt(4)}`]; + console.debug('LogType:', logType); // TODO REMOVE + if (typeof logType !== 'string') return; + + // Handle logging with Logger + if (LogCodeManager.logger == null) { + if (logType === 'error' || logType === 'warn') + console[logType](getLog(logCode, replacers)); + return; + } - const codes = logCode.split(':'); - if (codes.length === 3) - Agile.logger[logCodeTypes[codes[1]]](getLog(logCode, replacers), ...data); + // Handle logging without Logger + LogCodeManager.logger.if + .tag(tags) + [logType](getLog(logCode, replacers), ...data); } /** @@ -220,6 +285,8 @@ export const LogCodeManager = { log, logCodeLogTypes: logCodeTypes, logCodeMessages: logCodeMessages, + logger: loggerPackage?.sharedAgileLogger ?? null, + logIfTags, }; export type LogCodesArrayType = { diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 7dec2575..bf3bd786 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -71,9 +71,7 @@ export class Runtime { // Add specified Job to the queue this.jobQueue.push(job); - Agile.logger.if - .tag(['runtime']) - .info(LogCodeManager.getLog('16:01:00', [job._key]), job); + LogCodeManager.logIfTags(['runtime'], '16:01:00', [job._key], job); // Run first Job from the queue if (config.perform) { @@ -110,9 +108,7 @@ export class Runtime { if (job.rerender) this.jobsToRerender.push(job); this.currentJob = null; - Agile.logger.if - .tag(['runtime']) - .info(LogCodeManager.getLog('16:01:01', [job._key]), job); + LogCodeManager.logIfTags(['runtime'], '16:01:01', [job._key], job); // Perform Jobs as long as Jobs are left in the queue. // If no Job is left start updating (re-rendering) Subscription Container (UI-Components) @@ -262,9 +258,12 @@ export class Runtime { subscriptionContainer.updatedSubscribers.clear(); }); - Agile.logger.if - .tag(['runtime']) - .info(LogCodeManager.getLog('16:01:02'), subscriptionsToUpdate); + LogCodeManager.logIfTags( + ['runtime'], + '16:01:02', + [], + subscriptionsToUpdate + ); } /** diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index d738d5b5..51d71831 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -165,9 +165,12 @@ export class SubController { if (subscriptionInstance instanceof CallbackSubscriptionContainer) { unsub(subscriptionInstance); this.callbackSubs.delete(subscriptionInstance); - Agile.logger.if - .tag(['runtime', 'subscription']) - .info(LogCodeManager.getLog('15:01:00'), subscriptionInstance); + LogCodeManager.logIfTags( + ['subscription'], + '15:01:00', + [], + subscriptionInstance + ); return; } @@ -175,9 +178,12 @@ export class SubController { if (subscriptionInstance instanceof ComponentSubscriptionContainer) { unsub(subscriptionInstance); this.componentSubs.delete(subscriptionInstance); - Agile.logger.if - .tag(['runtime', 'subscription']) - .info(LogCodeManager.getLog('15:01:01'), subscriptionInstance); + LogCodeManager.logIfTags( + ['subscription'], + '15:01:01', + [], + subscriptionInstance + ); return; } @@ -191,9 +197,12 @@ export class SubController { (subContainer) => { unsub(subContainer as ComponentSubscriptionContainer); this.componentSubs.delete(subContainer); - Agile.logger.if - .tag(['runtime', 'subscription']) - .info(LogCodeManager.getLog('15:01:01'), subscriptionInstance); + LogCodeManager.logIfTags( + ['subscription'], + '15:01:01', + [], + subscriptionInstance + ); } ); return; @@ -241,9 +250,12 @@ export class SubController { componentSubscriptionContainer, ]; - Agile.logger.if - .tag(['runtime', 'subscription']) - .info(LogCodeManager.getLog('15:01:02'), componentSubscriptionContainer); + LogCodeManager.logIfTags( + ['subscription'], + '15:01:02', + [], + componentSubscriptionContainer + ); return componentSubscriptionContainer; } @@ -270,9 +282,12 @@ export class SubController { this.callbackSubs.add(callbackSubscriptionContainer); callbackSubscriptionContainer.ready = true; - Agile.logger.if - .tag(['runtime', 'subscription']) - .info(LogCodeManager.getLog('15:01:03'), callbackSubscriptionContainer); + LogCodeManager.logIfTags( + ['subscription'], + '15:01:03', + [], + callbackSubscriptionContainer + ); return callbackSubscriptionContainer; } diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index b3d91305..6214af06 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -10,7 +10,6 @@ import { defineConfig, DependableAgileInstancesType, flatMerge, - Logger, removeProperties, runsOnServer, State, @@ -23,7 +22,6 @@ import { */ let sharedAgileInstance = new Agile({ key: 'shared', - logConfig: { prefix: 'Agile', level: Logger.level.ERROR, active: true }, localStorage: !runsOnServer(), }); export { sharedAgileInstance as shared }; diff --git a/packages/core/src/storages/storage.ts b/packages/core/src/storages/storage.ts index 894a2a6d..4bc912fb 100644 --- a/packages/core/src/storages/storage.ts +++ b/packages/core/src/storages/storage.ts @@ -3,7 +3,6 @@ import { defineConfig, isAsyncFunction, isFunction, - Agile, LogCodeManager, } from '../internal'; @@ -89,12 +88,12 @@ export class Storage { const res = this.methods.get(this.getStorageKey(key)); const _res = isJsonString(res) ? JSON.parse(res) : res; - Agile.logger.if - .tag(['storage']) - .info( - LogCodeManager.getLog('13:01:00', [this.key, this.getStorageKey(key)]), - _res - ); + LogCodeManager.logIfTags( + ['storage'], + '13:01:00', + [this.key, this.getStorageKey(key)], + _res + ); return _res; } @@ -122,15 +121,12 @@ export class Storage { .then((res: any) => { const _res = isJsonString(res) ? JSON.parse(res) : res; - Agile.logger.if - .tag(['storage']) - .info( - LogCodeManager.getLog('13:01:00', [ - this.key, - this.getStorageKey(key), - ]), - _res - ); + LogCodeManager.logIfTags( + ['storage'], + '13:01:00', + [this.key, this.getStorageKey(key)], + _res + ); resolve(_res); }) @@ -149,12 +145,10 @@ export class Storage { public set(key: StorageItemKey, value: any): void { if (!this.ready || !this.methods.set) return; - Agile.logger.if - .tag(['storage']) - .info( - LogCodeManager.getLog('13:01:01', [this.key, this.getStorageKey(key)]), - value - ); + LogCodeManager.logIfTags(['storage'], '13:01:01', [ + this.key, + this.getStorageKey(key), + ]); this.methods.set(this.getStorageKey(key), JSON.stringify(value)); } @@ -169,11 +163,10 @@ export class Storage { public remove(key: StorageItemKey): void { if (!this.ready || !this.methods.remove) return; - Agile.logger.if - .tag(['storage']) - .info( - LogCodeManager.getLog('13:01:02', [this.key, this.getStorageKey(key)]) - ); + LogCodeManager.logIfTags(['storage'], '13:01:02', [ + this.key, + this.getStorageKey(key), + ]); this.methods.remove(this.getStorageKey(key)); } diff --git a/packages/event/src/event.ts b/packages/event/src/event.ts index 3e4a1c55..f27bbb81 100644 --- a/packages/event/src/event.ts +++ b/packages/event/src/event.ts @@ -3,6 +3,7 @@ import { defineConfig, generateId, isFunction, + LogCodeManager, Observer, } from '@agile-ts/core'; import { EventObserver, EventJob } from './internal'; @@ -122,7 +123,7 @@ export class Event { // Check if Callback is a Function if (!isFunction(_callback)) { - Agile.logger.error( + LogCodeManager.logger?.error( 'A Event Callback Function has to be typeof Function!' ); return this; @@ -130,7 +131,7 @@ export class Event { // Check if Callback Function already exists if (this.callbacks[key]) { - Agile.logger.error( + LogCodeManager.logger?.error( `Event Callback Function with the key/name '${key}' already exists!` ); return this; diff --git a/packages/event/src/hooks/useEvent.ts b/packages/event/src/hooks/useEvent.ts index 905d6413..67917785 100644 --- a/packages/event/src/hooks/useEvent.ts +++ b/packages/event/src/hooks/useEvent.ts @@ -2,6 +2,7 @@ import React from 'react'; import { Agile, getAgileInstance, + LogCodeManager, SubscriptionContainerKeyType, } from '@agile-ts/core'; import { Event, EventCallbackFunction } from '../internal'; @@ -22,7 +23,10 @@ export function useEvent>( // Get Agile Instance if (!agileInstance) agileInstance = getAgileInstance(event); if (!agileInstance || !agileInstance.subController) { - Agile.logger.error('Failed to subscribe Component with deps', event); + LogCodeManager.logger?.error( + 'Failed to subscribe Component with deps', + event + ); return; } diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index e3a2a889..32527293 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,491 +1,30 @@ -import { - defineConfig, - generateId, - includesArray, - isFunction, - isValidObject, -} from '@agile-ts/utils'; +import { CreateLoggerConfigInterface, Logger } from './logger'; +import { defineConfig } from '@agile-ts/utils'; -export class Logger { - public key?: LoggerKey; - - public isActive: boolean; - public config: LoggerConfigInterface; - public allowedTags: string[] = []; - public loggerCategories: { [key: string]: LoggerCategoryInterface } = {}; // Holds all registered Logger Categories - public watchers: { - [key: string]: LoggerWatcherConfigInterface; - } = {}; - - /** - * @public - * Logger - Handy Class for handling console.logs - */ - constructor(config: LoggerConfig = {}) { - let _config = typeof config === 'function' ? config(this) : config; - _config = defineConfig(_config, { - prefix: '', - allowedTags: [], - canUseCustomStyles: true, - active: true, - level: 0, - timestamp: false, - }); - this.isActive = _config.active as any; - this.allowedTags = _config.allowedTags as any; - this.config = { - timestamp: _config.timestamp as any, - prefix: _config.prefix as any, - canUseCustomStyles: _config.canUseCustomStyles as any, - level: _config.level as any, - }; - this.addDefaultLoggerCategories(); - } - - /** - * @public - * Adds Conditions to Logs - */ - public get if() { - return { - tag: (tags: string[]) => this.tag(tags), - }; - } - - /** - * @public - * Default Levels of Logger - */ - static get level() { - return { - TRACE: 1, - DEBUG: 2, - LOG: 5, - TABLE: 5, - INFO: 10, - SUCCESS: 15, - WARN: 20, - ERROR: 50, - }; - } - - //========================================================================================================= - // Add Default Logger Categories - //========================================================================================================= - /** - * @internal - * Adds Default Logger Categories - */ - private addDefaultLoggerCategories() { - this.createLoggerCategory({ - key: 'log', - level: Logger.level.LOG, - }); - this.createLoggerCategory({ - key: 'debug', - customStyle: 'color: #656565;', - prefix: 'Debug', - level: Logger.level.DEBUG, - }); - this.createLoggerCategory({ - key: 'info', - customStyle: 'color: #6c69a0;', - prefix: 'Info', - level: Logger.level.INFO, - }); - this.createLoggerCategory({ - key: 'success', - customStyle: 'color: #00b300;', - prefix: 'Success', - level: Logger.level.SUCCESS, - }); - this.createLoggerCategory({ - key: 'warn', - prefix: 'Warn', - level: Logger.level.WARN, - }); - this.createLoggerCategory({ - key: 'error', - prefix: 'Error', - level: Logger.level.ERROR, - }); - this.createLoggerCategory({ - key: 'trace', - prefix: 'Trace', - level: Logger.level.TRACE, - }); - this.createLoggerCategory({ - key: 'table', - level: Logger.level.TABLE, - }); - } - - //========================================================================================================= - // Tag - //========================================================================================================= - /** - * @internal - * Only executes following 'command' if all given tags are included in allowedTags - * @param tags - Tags - */ - private tag(tags: string[]) { - if (includesArray(this.allowedTags, tags)) { - return { - log: (...data: any[]) => this.log(...data), - debug: (...data: any[]) => this.debug(...data), - info: (...data: any[]) => this.info(...data), - success: (...data: any[]) => this.success(...data), - warn: (...data: any[]) => this.warn(...data), - error: (...data: any[]) => this.error(...data), - trace: (...data: any[]) => this.trace(...data), - table: (...data: any[]) => this.table(...data), - }; - } - return { - log: () => { - /* do nothing */ - }, - debug: () => { - /* do nothing */ - }, - info: () => { - /* do nothing */ - }, - success: () => { - /* do nothing */ - }, - warn: () => { - /* do nothing */ - }, - error: () => { - /* do nothing */ - }, - trace: () => { - /* do nothing */ - }, - table: () => { - /* do nothing */ - }, - }; - } - - public log(...data: any[]) { - this.invokeConsole(data, 'log', 'log'); - } - - public debug(...data: any[]) { - this.invokeConsole( - data, - 'debug', - typeof console.debug !== 'undefined' ? 'debug' : 'log' - ); - } - - public info(...data: any[]) { - this.invokeConsole( - data, - 'info', - typeof console.info !== 'undefined' ? 'info' : 'log' - ); - } - - public success(...data: any[]) { - this.invokeConsole(data, 'success', 'log'); - } - - public warn(...data: any[]) { - this.invokeConsole( - data, - 'warn', - typeof console.warn !== 'undefined' ? 'warn' : 'log' - ); - } - - public error(...data: any[]) { - this.invokeConsole( - data, - 'error', - typeof console.error !== 'undefined' ? 'error' : 'log' - ); - } - - public trace(...data: any[]) { - this.invokeConsole( - data, - 'trace', - typeof console.trace !== 'undefined' ? 'trace' : 'log' - ); - } - - public table(...data: any[]) { - this.invokeConsole( - data, - 'table', - typeof console.table !== 'undefined' ? 'table' : 'log' - ); - } - - public custom(loggerCategory: string, ...data: any[]) { - this.invokeConsole(data, loggerCategory, 'log'); - } - - //========================================================================================================= - // Invoke Console - //========================================================================================================= - /** - * @internal - * Logs data in Console - * @param data - Data - * @param loggerCategoryKey - Key/Name of Logger Category - * @param consoleLogType - console[consoleLogProperty] - */ - private invokeConsole( - data: any[], - loggerCategoryKey: LoggerCategoryKey, - consoleLogType: ConsoleLogType - ) { - const loggerCategory = this.getLoggerCategory(loggerCategoryKey); - - // Check if Logger Category is allowed - if (!this.isActive || loggerCategory.level < this.config.level) return; - - // Build Prefix of Log - const buildPrefix = (): string => { - let prefix = ''; - if (this.config.timestamp) - prefix = prefix.concat(`[${Date.now().toString()}] `); - if (this.config.prefix) prefix = prefix.concat(this.config.prefix); - if (loggerCategory.prefix) - prefix = prefix.concat(' ' + loggerCategory.prefix); - if (this.config.prefix || loggerCategory.prefix) - prefix = prefix.concat(':'); - return prefix; - }; - - // Add built Prefix - if (typeof data[0] === 'string') - data[0] = buildPrefix().concat(' ').concat(data[0]); - else data.unshift(buildPrefix()); - - // Call Watcher Callbacks - for (const key in this.watchers) { - const watcher = this.watchers[key]; - if (loggerCategory.level >= (watcher.level || 0)) { - watcher.callback(loggerCategory, data); - } - } - - // Init Custom Style - if (this.config.canUseCustomStyles && loggerCategory.customStyle) { - const newLogs: any[] = []; - let hasStyledString = false; // NOTE: Only one style can be used for one String block! - for (const log of data) { - if (!hasStyledString && typeof log === 'string') { - newLogs.push(`%c${log}`); - newLogs.push(loggerCategory.customStyle); - hasStyledString = true; - } else { - newLogs.push(log); - } - } - data = newLogs; - } - - // Handle Console Table Log - if (consoleLogType === 'table') { - if (typeof data[0] === 'string') { - console.log(data[0]); - console.table(data.filter((d) => typeof d !== 'string' && 'number')); - } - return; - } - - // Normal Log - console[consoleLogType](...data); - } - - //========================================================================================================= - // Create Logger Category - //========================================================================================================= - /** - * @public - * Creates new Logger Category - * @param loggerCategory - Logger Category - */ - public createLoggerCategory(loggerCategory: LoggerCategoryInterface) { - loggerCategory = defineConfig(loggerCategory, { - prefix: '', - level: 0, - }); - this.loggerCategories[loggerCategory.key] = loggerCategory; - } - - //========================================================================================================= - // Get Logger Category - //========================================================================================================= - /** - * @public - * Get Logger Category - * @param key - Key/Name of Logger Category - */ - public getLoggerCategory(key: LoggerCategoryKey) { - return this.loggerCategories[key]; - } - - //========================================================================================================= - // Watch - //========================================================================================================= - /** - * @public - * Watches Logger and detects Logs - * @param config - Config - * @return Key of Watcher Function - */ - public watch(config: LoggerWatcherConfigInterface): string; - /** - * @public - * Watches Logger and detects Logs - * @param key - Key of Watcher Function - * @param config - Config - */ - public watch(key: string, config: LoggerWatcherConfigInterface): this; - public watch( - keyOrConfig: string | LoggerWatcherConfigInterface, - config?: LoggerWatcherConfigInterface - ): this | string { - const generateKey = isValidObject(keyOrConfig); - let _config: LoggerWatcherConfigInterface; - let key: string; - - if (generateKey) { - key = generateId(); - _config = keyOrConfig as LoggerWatcherConfigInterface; - } else { - key = keyOrConfig as string; - _config = config as LoggerWatcherConfigInterface; - } - - _config = defineConfig(_config, { - level: 0, - }); - - // Check if Callback is a Function - if (!isFunction(_config.callback)) { - console.error( - 'Agile: A Watcher Callback Function has to be an function!' - ); - return this; - } - - // Check if Callback Function already exists - if (this.watchers[key]) { - console.error( - `Agile: Watcher Callback Function with the key/name ${key} already exists!` - ); - return this; - } - - this.watchers[key] = _config; - return generateKey ? key : this; - } - - //========================================================================================================= - // Remove Watcher - //========================================================================================================= - /** - * @public - * Removes Watcher at given Key - * @param key - Key of Watcher that gets removed - */ - public removeWatcher(key: string): this { - delete this.watchers[key]; - return this; - } - - //========================================================================================================= - // Set Level - //========================================================================================================= - /** - * @public - * Assigns new Level to Logger - * NOTE: Default Levels can be found in 'Logger.level.x' - * @param level - Level - */ - public setLevel(level: number): this { - this.config.level = level; - return this; - } -} - -export type LoggerCategoryKey = string | number; -export type LoggerKey = string | number; +export * from './logger'; +export default Logger; /** - * @param key - Key/Name of Logger Category - * @param customStyle - Css Styles that get applied to the Logs - * @param prefix - Prefix that gets written before each Log of this Category - * @param level - Until which Level this Logger Category gets logged + * Shared Agile Logger. */ -export interface LoggerCategoryInterface { - key: LoggerCategoryKey; - customStyle?: string; - prefix?: string; - level: number; -} - -/** - * @param prefix - Prefix that gets written before each log of this Logger - * @param canUseCustomStyles - If custom Styles can be applied to the Logs - * @param level - Handles which Logger Categories can be Logged - * @param timestamp - Timestamp that ges written before each log of this Logger - */ -export interface LoggerConfigInterface { - prefix: string; - canUseCustomStyles: boolean; - level: number; - timestamp: boolean; -} - -/** - * @param prefix - Prefix that gets written before each log of this Logger - * @param allowedTags - Only Logs that, contains the allowed Tags or have no Tag get logged - * @param canUseCustomStyles - If custom Styles can be applied to the Logs - * @param active - If Logger is active - * @param level - Handles which Logger Categories can be Logged - * @param timestamp - Timestamp that ges written before each log of this Logger - */ -export interface CreateLoggerConfigInterface { - prefix?: string; - allowedTags?: LoggerKey[]; - canUseCustomStyles?: boolean; - active?: boolean; - level?: number; - timestamp?: boolean; -} - -export type LoggerConfig = - | CreateLoggerConfigInterface - | ((logger: Logger) => CreateLoggerConfigInterface); - -export type ConsoleLogType = - | 'log' - | 'warn' - | 'error' - | 'trace' - | 'table' - | 'info' - | 'debug'; - -export type LoggerWatcherCallback = ( - loggerCategory: LoggerCategoryInterface, - data: any[] -) => void; +export let sharedAgileLogger = new Logger(); +assignSharedAgileLoggerConfig(); /** - * @param callback - Callback Function that gets called if something gets Logged - * @param level - At which level the watcher is called + * Assigns the specified configuration object to the shared Agile Logger. + * + * @param config - Configuration object */ -export interface LoggerWatcherConfigInterface { - callback: LoggerWatcherCallback; - level?: number; +// https://stackoverflow.com/questions/32558514/javascript-es6-export-const-vs-export-let +export function assignSharedAgileLoggerConfig( + config: CreateLoggerConfigInterface = {} +): void { + config = defineConfig(config, { + prefix: 'Agile', + active: true, + level: Logger.level.SUCCESS, + canUseCustomStyles: true, + allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], + }); + sharedAgileLogger = new Logger(config); } diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts new file mode 100644 index 00000000..12c96a21 --- /dev/null +++ b/packages/logger/src/logger.ts @@ -0,0 +1,491 @@ +import { + defineConfig, + generateId, + includesArray, + isFunction, + isValidObject, +} from '@agile-ts/utils'; + +export class Logger { + public key?: LoggerKey; + + public isActive: boolean; + public config: LoggerConfigInterface; + public allowedTags: string[] = []; + public loggerCategories: { [key: string]: LoggerCategoryInterface } = {}; // Holds all registered Logger Categories + public watchers: { + [key: string]: LoggerWatcherConfigInterface; + } = {}; + + /** + * @public + * Logger - Handy Class for handling console.logs + */ + constructor(config: LoggerConfig = {}) { + let _config = typeof config === 'function' ? config(this) : config; + _config = defineConfig(_config, { + prefix: '', + allowedTags: [], + canUseCustomStyles: true, + active: true, + level: 0, + timestamp: false, + }); + this.isActive = _config.active as any; + this.allowedTags = _config.allowedTags as any; + this.config = { + timestamp: _config.timestamp as any, + prefix: _config.prefix as any, + canUseCustomStyles: _config.canUseCustomStyles as any, + level: _config.level as any, + }; + this.addDefaultLoggerCategories(); + } + + /** + * @public + * Adds Conditions to Logs + */ + public get if() { + return { + tag: (tags: string[]) => this.tag(tags), + }; + } + + /** + * @public + * Default Levels of Logger + */ + static get level() { + return { + TRACE: 1, + DEBUG: 2, + LOG: 5, + TABLE: 5, + INFO: 10, + SUCCESS: 15, + WARN: 20, + ERROR: 50, + }; + } + + //========================================================================================================= + // Add Default Logger Categories + //========================================================================================================= + /** + * @internal + * Adds Default Logger Categories + */ + private addDefaultLoggerCategories() { + this.createLoggerCategory({ + key: 'log', + level: Logger.level.LOG, + }); + this.createLoggerCategory({ + key: 'debug', + customStyle: 'color: #656565;', + prefix: 'Debug', + level: Logger.level.DEBUG, + }); + this.createLoggerCategory({ + key: 'info', + customStyle: 'color: #6c69a0;', + prefix: 'Info', + level: Logger.level.INFO, + }); + this.createLoggerCategory({ + key: 'success', + customStyle: 'color: #00b300;', + prefix: 'Success', + level: Logger.level.SUCCESS, + }); + this.createLoggerCategory({ + key: 'warn', + prefix: 'Warn', + level: Logger.level.WARN, + }); + this.createLoggerCategory({ + key: 'error', + prefix: 'Error', + level: Logger.level.ERROR, + }); + this.createLoggerCategory({ + key: 'trace', + prefix: 'Trace', + level: Logger.level.TRACE, + }); + this.createLoggerCategory({ + key: 'table', + level: Logger.level.TABLE, + }); + } + + //========================================================================================================= + // Tag + //========================================================================================================= + /** + * @internal + * Only executes following 'command' if all given tags are included in allowedTags + * @param tags - Tags + */ + private tag(tags: string[]) { + if (includesArray(this.allowedTags, tags)) { + return { + log: (...data: any[]) => this.log(...data), + debug: (...data: any[]) => this.debug(...data), + info: (...data: any[]) => this.info(...data), + success: (...data: any[]) => this.success(...data), + warn: (...data: any[]) => this.warn(...data), + error: (...data: any[]) => this.error(...data), + trace: (...data: any[]) => this.trace(...data), + table: (...data: any[]) => this.table(...data), + }; + } + return { + log: () => { + /* do nothing */ + }, + debug: () => { + /* do nothing */ + }, + info: () => { + /* do nothing */ + }, + success: () => { + /* do nothing */ + }, + warn: () => { + /* do nothing */ + }, + error: () => { + /* do nothing */ + }, + trace: () => { + /* do nothing */ + }, + table: () => { + /* do nothing */ + }, + }; + } + + public log(...data: any[]) { + this.invokeConsole(data, 'log', 'log'); + } + + public debug(...data: any[]) { + this.invokeConsole( + data, + 'debug', + typeof console.debug !== 'undefined' ? 'debug' : 'log' + ); + } + + public info(...data: any[]) { + this.invokeConsole( + data, + 'info', + typeof console.info !== 'undefined' ? 'info' : 'log' + ); + } + + public success(...data: any[]) { + this.invokeConsole(data, 'success', 'log'); + } + + public warn(...data: any[]) { + this.invokeConsole( + data, + 'warn', + typeof console.warn !== 'undefined' ? 'warn' : 'log' + ); + } + + public error(...data: any[]) { + this.invokeConsole( + data, + 'error', + typeof console.error !== 'undefined' ? 'error' : 'log' + ); + } + + public trace(...data: any[]) { + this.invokeConsole( + data, + 'trace', + typeof console.trace !== 'undefined' ? 'trace' : 'log' + ); + } + + public table(...data: any[]) { + this.invokeConsole( + data, + 'table', + typeof console.table !== 'undefined' ? 'table' : 'log' + ); + } + + public custom(loggerCategory: string, ...data: any[]) { + this.invokeConsole(data, loggerCategory, 'log'); + } + + //========================================================================================================= + // Invoke Console + //========================================================================================================= + /** + * @internal + * Logs data in Console + * @param data - Data + * @param loggerCategoryKey - Key/Name of Logger Category + * @param consoleLogType - console[consoleLogProperty] + */ + private invokeConsole( + data: any[], + loggerCategoryKey: LoggerCategoryKey, + consoleLogType: ConsoleLogType + ) { + const loggerCategory = this.getLoggerCategory(loggerCategoryKey); + + // Check if Logger Category is allowed + if (!this.isActive || loggerCategory.level < this.config.level) return; + + // Build Prefix of Log + const buildPrefix = (): string => { + let prefix = ''; + if (this.config.timestamp) + prefix = prefix.concat(`[${Date.now().toString()}] `); + if (this.config.prefix) prefix = prefix.concat(this.config.prefix); + if (loggerCategory.prefix) + prefix = prefix.concat(' ' + loggerCategory.prefix); + if (this.config.prefix || loggerCategory.prefix) + prefix = prefix.concat(':'); + return prefix; + }; + + // Add built Prefix + if (typeof data[0] === 'string') + data[0] = buildPrefix().concat(' ').concat(data[0]); + else data.unshift(buildPrefix()); + + // Call Watcher Callbacks + for (const key in this.watchers) { + const watcher = this.watchers[key]; + if (loggerCategory.level >= (watcher.level || 0)) { + watcher.callback(loggerCategory, data); + } + } + + // Init Custom Style + if (this.config.canUseCustomStyles && loggerCategory.customStyle) { + const newLogs: any[] = []; + let hasStyledString = false; // NOTE: Only one style can be used for one String block! + for (const log of data) { + if (!hasStyledString && typeof log === 'string') { + newLogs.push(`%c${log}`); + newLogs.push(loggerCategory.customStyle); + hasStyledString = true; + } else { + newLogs.push(log); + } + } + data = newLogs; + } + + // Handle Console Table Log + if (consoleLogType === 'table') { + if (typeof data[0] === 'string') { + console.log(data[0]); + console.table(data.filter((d) => typeof d !== 'string' && 'number')); + } + return; + } + + // Normal Log + console[consoleLogType](...data); + } + + //========================================================================================================= + // Create Logger Category + //========================================================================================================= + /** + * @public + * Creates new Logger Category + * @param loggerCategory - Logger Category + */ + public createLoggerCategory(loggerCategory: LoggerCategoryInterface) { + loggerCategory = defineConfig(loggerCategory, { + prefix: '', + level: 0, + }); + this.loggerCategories[loggerCategory.key] = loggerCategory; + } + + //========================================================================================================= + // Get Logger Category + //========================================================================================================= + /** + * @public + * Get Logger Category + * @param key - Key/Name of Logger Category + */ + public getLoggerCategory(key: LoggerCategoryKey) { + return this.loggerCategories[key]; + } + + //========================================================================================================= + // Watch + //========================================================================================================= + /** + * @public + * Watches Logger and detects Logs + * @param config - Config + * @return Key of Watcher Function + */ + public watch(config: LoggerWatcherConfigInterface): string; + /** + * @public + * Watches Logger and detects Logs + * @param key - Key of Watcher Function + * @param config - Config + */ + public watch(key: string, config: LoggerWatcherConfigInterface): this; + public watch( + keyOrConfig: string | LoggerWatcherConfigInterface, + config?: LoggerWatcherConfigInterface + ): this | string { + const generateKey = isValidObject(keyOrConfig); + let _config: LoggerWatcherConfigInterface; + let key: string; + + if (generateKey) { + key = generateId(); + _config = keyOrConfig as LoggerWatcherConfigInterface; + } else { + key = keyOrConfig as string; + _config = config as LoggerWatcherConfigInterface; + } + + _config = defineConfig(_config, { + level: 0, + }); + + // Check if Callback is a Function + if (!isFunction(_config.callback)) { + console.error( + 'Agile: A Watcher Callback Function has to be an function!' + ); + return this; + } + + // Check if Callback Function already exists + if (this.watchers[key] != null) { + console.error( + `Agile: Watcher Callback Function with the key/name ${key} already exists!` + ); + return this; + } + + this.watchers[key] = _config; + return generateKey ? key : this; + } + + //========================================================================================================= + // Remove Watcher + //========================================================================================================= + /** + * @public + * Removes Watcher at given Key + * @param key - Key of Watcher that gets removed + */ + public removeWatcher(key: string): this { + delete this.watchers[key]; + return this; + } + + //========================================================================================================= + // Set Level + //========================================================================================================= + /** + * @public + * Assigns new Level to Logger + * NOTE: Default Levels can be found in 'Logger.level.x' + * @param level - Level + */ + public setLevel(level: number): this { + this.config.level = level; + return this; + } +} + +export type LoggerCategoryKey = string | number; +export type LoggerKey = string | number; + +/** + * @param key - Key/Name of Logger Category + * @param customStyle - Css Styles that get applied to the Logs + * @param prefix - Prefix that gets written before each Log of this Category + * @param level - Until which Level this Logger Category gets logged + */ +export interface LoggerCategoryInterface { + key: LoggerCategoryKey; + customStyle?: string; + prefix?: string; + level: number; +} + +/** + * @param prefix - Prefix that gets written before each log of this Logger + * @param canUseCustomStyles - If custom Styles can be applied to the Logs + * @param level - Handles which Logger Categories can be Logged + * @param timestamp - Timestamp that ges written before each log of this Logger + */ +export interface LoggerConfigInterface { + prefix: string; + canUseCustomStyles: boolean; + level: number; + timestamp: boolean; +} + +/** + * @param prefix - Prefix that gets written before each log of this Logger + * @param allowedTags - Only Logs that, contains the allowed Tags or have no Tag get logged + * @param canUseCustomStyles - If custom Styles can be applied to the Logs + * @param active - If Logger is active + * @param level - Handles which Logger Categories can be Logged + * @param timestamp - Timestamp that ges written before each log of this Logger + */ +export interface CreateLoggerConfigInterface { + prefix?: string; + allowedTags?: LoggerKey[]; + canUseCustomStyles?: boolean; + active?: boolean; + level?: number; + timestamp?: boolean; +} + +export type LoggerConfig = + | CreateLoggerConfigInterface + | ((logger: Logger) => CreateLoggerConfigInterface); + +export type ConsoleLogType = + | 'log' + | 'warn' + | 'error' + | 'trace' + | 'table' + | 'info' + | 'debug'; + +export type LoggerWatcherCallback = ( + loggerCategory: LoggerCategoryInterface, + data: any[] +) => void; + +/** + * @param callback - Callback Function that gets called if something gets Logged + * @param level - At which level the watcher is called + */ +export interface LoggerWatcherConfigInterface { + callback: LoggerWatcherCallback; + level?: number; +} diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts index 0a4261f2..9bceaaad 100644 --- a/packages/react/src/hocs/AgileHOC.ts +++ b/packages/react/src/hocs/AgileHOC.ts @@ -10,6 +10,7 @@ import { flatMerge, extractRelevantObservers, normalizeArray, + LogCodeManager, } from '@agile-ts/core'; /** @@ -57,7 +58,10 @@ export function AgileHOC( } } if (!agileInstance || !agileInstance.subController) { - Agile.logger.error('Failed to subscribe Component with deps', deps); + LogCodeManager.logger.error( + 'Failed to subscribe Component with deps', + deps + ); return reactComponent; } diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index dead032f..ef2810a9 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -14,9 +14,10 @@ import { extractRelevantObservers, SelectorWeakMapType, SelectorMethodType, + LogCodeManager, + normalizeArray, } from '@agile-ts/core'; import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; -import { normalizeArray } from '@agile-ts/utils'; import { AgileOutputHookArrayType, AgileOutputHookType } from './useOutput'; // TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work @@ -142,7 +143,7 @@ export function useAgile< // Try to extract Agile Instance from the specified Instance/s if (!agileInstance) agileInstance = getAgileInstance(observers[0]); if (!agileInstance || !agileInstance.subController) { - Agile.logger.error( + LogCodeManager.logger?.error( 'Failed to subscribe Component with deps because of missing valid Agile Instance.', deps ); diff --git a/yarn.lock b/yarn.lock index f1bbca79..d3795c28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,13 +3,17 @@ "@agile-ts/core@file:packages/core": - version "0.0.17" + version "0.1.0" dependencies: - "@agile-ts/logger" "^0.0.4" - "@agile-ts/utils" "^0.0.4" + "@agile-ts/utils" "^0.0.5" + +"@agile-ts/logger@file:packages/logger": + version "0.0.5" + dependencies: + "@agile-ts/utils" "^0.0.5" "@agile-ts/proxytree@file:packages/proxytree": - version "0.0.3" + version "0.0.4" "@akryum/winattr@^3.0.0": version "3.0.0" From cefc63b3a0d605b082ad28f6dc509d3b40aaad6f Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 8 Jul 2021 12:13:02 +0200 Subject: [PATCH 40/55] fixed logger export --- benchmark/package.json | 4 +-- packages/core/src/logCodeManager.ts | 50 ++++++++++------------------ packages/event/src/event.ts | 4 +-- packages/event/src/hooks/useEvent.ts | 2 +- packages/logger/src/index.ts | 29 ++++++++-------- packages/react/src/hocs/AgileHOC.ts | 2 +- packages/react/src/hooks/useAgile.ts | 4 +-- 7 files changed, 42 insertions(+), 53 deletions(-) diff --git a/benchmark/package.json b/benchmark/package.json index 5217433f..75a3e8bb 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -11,8 +11,8 @@ "test:counter": "yarn test ./benchmarks/react/counter", "test:1000fields": "yarn test ./benchmarks/react/1000fields", "test:computed": "yarn test ./benchmarks/react/computed", - "install:local:agile": "yalc add @agile-ts/core @agile-ts/react", - "install:public:agile": "yarn add @agile-ts/core @agile-ts/react" + "install:local:agile": "yalc add @agile-ts/core @agile-ts/react & yarn install", + "install:public:agile": "yarn add @agile-ts/core @agile-ts/react & yarn install" }, "repository": { "type": "git", diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 242338b2..bed130bc 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -208,27 +208,20 @@ function log>( replacers: any[] = [], ...data: any[] ): void { - console.debug( - 'log()', - LogCodeManager.logger, - LogCodeManager.logger?.isActive, - logCode, - replacers - ); // TODO REMOVE - if (!LogCodeManager.logger?.isActive) return; - const logType = logCodeTypes[`${logCode.charAt(3)}${logCode.charAt(4)}`]; - console.debug('LogType:', logType); // TODO REMOVE + const logger = LogCodeManager.getLogger(); + if (!logger?.isActive) return; + const logType = logCodeTypes[logCode.substr(3, 2)]; if (typeof logType !== 'string') return; // Handle logging without Logger - if (LogCodeManager.logger == null) { + if (logger == null) { if (logType === 'error' || logType === 'warn') console[logType](getLog(logCode, replacers)); return; } // Handle logging with Logger - LogCodeManager.logger[logType](getLog(logCode, replacers), ...data); + logger[logType](getLog(logCode, replacers), ...data); } /** @@ -248,30 +241,19 @@ function logIfTags>( replacers: any[] = [], ...data: any[] ): void { - console.debug( - 'logIfTags()', - LogCodeManager.logger, - LogCodeManager.logger?.isActive, - tags, - logCode, - replacers - ); // TODO REMOVE - if (!LogCodeManager.logger?.isActive) return; - const logType = logCodeTypes[`${logCode.charAt(3)}${logCode.charAt(4)}`]; - console.debug('LogType:', logType); // TODO REMOVE + const logger = LogCodeManager.getLogger(); + if (!logger?.isActive) return; + const logType = logCodeTypes[logCode.substr(3, 2)]; if (typeof logType !== 'string') return; - // Handle logging with Logger - if (LogCodeManager.logger == null) { - if (logType === 'error' || logType === 'warn') - console[logType](getLog(logCode, replacers)); + // Handle logging without Logger + if (logger == null) { + // Log nothing since if a log has a tag it is probably not so important return; } - // Handle logging without Logger - LogCodeManager.logger.if - .tag(tags) - [logType](getLog(logCode, replacers), ...data); + // Handle logging with Logger + logger.if.tag(tags)[logType](getLog(logCode, replacers), ...data); } /** @@ -285,7 +267,11 @@ export const LogCodeManager = { log, logCodeLogTypes: logCodeTypes, logCodeMessages: logCodeMessages, - logger: loggerPackage?.sharedAgileLogger ?? null, + // Not doing 'logger: loggerPackage?.sharedAgileLogger' + // because only by calling a function (now 'getLogger()') the 'sharedLogger' is refetched + getLogger: () => { + return loggerPackage?.sharedAgileLogger ?? null; + }, logIfTags, }; diff --git a/packages/event/src/event.ts b/packages/event/src/event.ts index f27bbb81..415f7d4f 100644 --- a/packages/event/src/event.ts +++ b/packages/event/src/event.ts @@ -123,7 +123,7 @@ export class Event { // Check if Callback is a Function if (!isFunction(_callback)) { - LogCodeManager.logger?.error( + LogCodeManager.getLogger()?.error( 'A Event Callback Function has to be typeof Function!' ); return this; @@ -131,7 +131,7 @@ export class Event { // Check if Callback Function already exists if (this.callbacks[key]) { - LogCodeManager.logger?.error( + LogCodeManager.getLogger()?.error( `Event Callback Function with the key/name '${key}' already exists!` ); return this; diff --git a/packages/event/src/hooks/useEvent.ts b/packages/event/src/hooks/useEvent.ts index 67917785..5a1d3168 100644 --- a/packages/event/src/hooks/useEvent.ts +++ b/packages/event/src/hooks/useEvent.ts @@ -23,7 +23,7 @@ export function useEvent>( // Get Agile Instance if (!agileInstance) agileInstance = getAgileInstance(event); if (!agileInstance || !agileInstance.subController) { - LogCodeManager.logger?.error( + LogCodeManager.getLogger()?.error( 'Failed to subscribe Component with deps', event ); diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 32527293..00aa3e3e 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,14 +1,18 @@ import { CreateLoggerConfigInterface, Logger } from './logger'; import { defineConfig } from '@agile-ts/utils'; -export * from './logger'; -export default Logger; +const defaultLogConfig = { + prefix: 'Agile', + active: true, + level: Logger.level.WARN, + canUseCustomStyles: true, + allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], +}; /** * Shared Agile Logger. */ -export let sharedAgileLogger = new Logger(); -assignSharedAgileLoggerConfig(); +let sharedAgileLogger = new Logger(defaultLogConfig); /** * Assigns the specified configuration object to the shared Agile Logger. @@ -16,15 +20,14 @@ assignSharedAgileLoggerConfig(); * @param config - Configuration object */ // https://stackoverflow.com/questions/32558514/javascript-es6-export-const-vs-export-let -export function assignSharedAgileLoggerConfig( +function assignSharedAgileLoggerConfig( config: CreateLoggerConfigInterface = {} -): void { - config = defineConfig(config, { - prefix: 'Agile', - active: true, - level: Logger.level.SUCCESS, - canUseCustomStyles: true, - allowedTags: ['runtime', 'storage', 'subscription', 'multieditor'], - }); +): Logger { + config = defineConfig(config, defaultLogConfig); sharedAgileLogger = new Logger(config); + return sharedAgileLogger; } + +export { sharedAgileLogger, assignSharedAgileLoggerConfig }; +export * from './logger'; +export default Logger; diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts index 9bceaaad..53793a7e 100644 --- a/packages/react/src/hocs/AgileHOC.ts +++ b/packages/react/src/hocs/AgileHOC.ts @@ -58,7 +58,7 @@ export function AgileHOC( } } if (!agileInstance || !agileInstance.subController) { - LogCodeManager.logger.error( + LogCodeManager.getLogger().error( 'Failed to subscribe Component with deps', deps ); diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index ef2810a9..5d2775c4 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -143,7 +143,7 @@ export function useAgile< // Try to extract Agile Instance from the specified Instance/s if (!agileInstance) agileInstance = getAgileInstance(observers[0]); if (!agileInstance || !agileInstance.subController) { - LogCodeManager.logger?.error( + LogCodeManager.getLogger()?.error( 'Failed to subscribe Component with deps because of missing valid Agile Instance.', deps ); @@ -192,7 +192,7 @@ export function useAgile< proxyWeakMap, waitForMount: false, componentId: config.componentId, - selectorWeakMap: selectorWeakMap, + selectorWeakMap, } ); From 9b4e30e69c8776d5ff1701a6b1c792b6c145b296 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 8 Jul 2021 18:25:31 +0200 Subject: [PATCH 41/55] fixed logger dependents --- .../1000fields/bench/agilets/collection.tsx | 4 ++- .../1000fields/bench/agilets/nestedState.tsx | 6 ++-- .../react/1000fields/bench/agilets/state.tsx | 4 ++- .../react/counter/bench/agilets.tsx | 3 +- .../container/SubscriptionContainer.ts | 2 -- packages/event/README.md | 5 ++-- packages/event/src/shared.ts | 30 +++++++++++++++++++ packages/multieditor/src/multieditor.ts | 13 ++++---- packages/multieditor/src/validator/index.ts | 7 +++-- packages/react/src/hooks/useAgile.ts | 6 ++-- 10 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 packages/event/src/shared.ts diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx index f30fcef4..7269c0a0 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/collection.tsx @@ -1,8 +1,10 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createCollection } from '@agile-ts/core'; +import { createCollection, LogCodeManager } from '@agile-ts/core'; import { useAgile, useValue } from '@agile-ts/react'; +LogCodeManager.getLogger().isActive = false; + export default function (target: HTMLElement, fieldsCount: number) { const FIELDS = createCollection({ initialData: Array.from(Array(fieldsCount).keys()).map((i) => ({ diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx index 88fe5ba6..a1fc5b92 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx @@ -1,8 +1,10 @@ import * as React from 'react'; import * as ReactDom from 'react-dom'; -import { createState, State } from '@agile-ts/core'; +import { createState, LogCodeManager, State } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; +LogCodeManager.getLogger().isActive = false; + export default function (target: HTMLElement, fieldsCount: number) { const FIELDS = createState( Array.from(Array(fieldsCount).keys()).map((i) => @@ -25,7 +27,7 @@ export default function (target: HTMLElement, fieldsCount: number) { { - field.set(e.target.value); + // field.set(e.target.value); updatedFieldsCount++; diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx index 08069b33..93f9798a 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/state.tsx @@ -1,8 +1,10 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createState } from '@agile-ts/core'; +import { createState, LogCodeManager } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; +LogCodeManager.getLogger().isActive = false; + export default function (target: HTMLElement, fieldsCount: number) { const FIELDS = createState( Array.from(Array(fieldsCount).keys()).map((i) => `Field #${i + 1}`) diff --git a/benchmark/benchmarks/react/counter/bench/agilets.tsx b/benchmark/benchmarks/react/counter/bench/agilets.tsx index e4e8ddb1..309b2f2a 100644 --- a/benchmark/benchmarks/react/counter/bench/agilets.tsx +++ b/benchmark/benchmarks/react/counter/bench/agilets.tsx @@ -1,8 +1,9 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createState } from '@agile-ts/core'; +import { createState, LogCodeManager } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; +LogCodeManager.getLogger().isActive = false; const COUNT = createState(0); const App = () => { diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 4c0dcb17..1ef0c89d 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -111,8 +111,6 @@ export class SubscriptionContainer { config: SubscriptionContainerConfigInterface = {} ) { config = defineConfig(config, { - proxyWeakMap: new WeakMap(), - selectorWeakMap: new WeakMap(), key: generateId(), }); diff --git a/packages/event/README.md b/packages/event/README.md index 18503089..5b52052c 100644 --- a/packages/event/README.md +++ b/packages/event/README.md @@ -16,8 +16,7 @@ ## ⏰ Short Example ```ts -const App = new Agile(); -const MY_EVENT = new Event(App); +const MY_EVENT = createEvent(); MY_EVENT.on((data) => {console.log("hello there " + data.name)}); // Print 'hello there jeff' if Event gets triggered MY_EVENT.trigger({name: "jeff"}); // Trigger Event ``` @@ -39,4 +38,4 @@ _Other Versions aren't supported anymore_ ## 📄 Documentation -The Agile Event Docs are located [here](https://agile-ts.org/docs/) \ No newline at end of file +The Agile Event Docs are located [here](https://agile-ts.org/docs/) diff --git a/packages/event/src/shared.ts b/packages/event/src/shared.ts new file mode 100644 index 00000000..e898f3d5 --- /dev/null +++ b/packages/event/src/shared.ts @@ -0,0 +1,30 @@ +import { Agile, defineConfig, removeProperties, shared } from '@agile-ts/core'; +import { + Event, + CreateEventConfigInterface, + DefaultEventPayload, +} from './internal'; + +export function createEvent( + config: CreateEventConfigInterfaceWithAgile = {} +): Event { + config = defineConfig(config, { + agileInstance: shared, + }); + return new Event( + config.agileInstance as any, + removeProperties(config, ['agileInstance']) + ); +} + +export interface CreateAgileSubInstanceInterface { + /** + * Instance of Agile the Instance belongs to. + * @default Agile.shared + */ + agileInstance?: Agile; +} + +export interface CreateEventConfigInterfaceWithAgile + extends CreateEventConfigInterface, + CreateAgileSubInstanceInterface {} diff --git a/packages/multieditor/src/multieditor.ts b/packages/multieditor/src/multieditor.ts index 5a08d585..91b6a88c 100644 --- a/packages/multieditor/src/multieditor.ts +++ b/packages/multieditor/src/multieditor.ts @@ -4,6 +4,7 @@ import { copy, defineConfig, getAgileInstance, + LogCodeManager, Observer, } from '@agile-ts/core'; import { @@ -52,7 +53,7 @@ export class MultiEditor< ) { if (!agileInstance) agileInstance = getAgileInstance(null); if (!agileInstance) - Agile.logger.error( + LogCodeManager.getLogger()?.error( 'No Global agileInstance found! Please pass an agileInstance into the MultiEditor!' ); this.agileInstance = () => agileInstance as any; @@ -247,9 +248,9 @@ export class MultiEditor< this.submitted = true; // Logging - Agile.logger.if - .tag(['multieditor']) - .info(`Submit MultiEditor '${this.key}'`, this.isValid); + // Agile.logger.if + // .tag(['multieditor']) + // .info(`Submit MultiEditor '${this.key}'`, this.isValid); // Check if Editor is Valid if (!this.isValid) return false; @@ -354,7 +355,9 @@ export class MultiEditor< */ public getItemById(key: ItemKey): Item | undefined { if (!Object.prototype.hasOwnProperty.call(this.data, key)) { - Agile.logger.error(`Editor Item '${key}' does not exists!`); + LogCodeManager.getLogger()?.error( + `Editor Item '${key}' does not exists!` + ); return undefined; } return this.data[key]; diff --git a/packages/multieditor/src/validator/index.ts b/packages/multieditor/src/validator/index.ts index 0e3625f9..8f0de479 100644 --- a/packages/multieditor/src/validator/index.ts +++ b/packages/multieditor/src/validator/index.ts @@ -4,6 +4,7 @@ import { defineConfig, generateId, isFunction, + LogCodeManager, } from '@agile-ts/core'; import { DataObject, @@ -125,13 +126,15 @@ export class Validator { // Check if Validation Method is a Function if (!isFunction(_method)) { - Agile.logger.error('A Validation Method has to be a function!'); + LogCodeManager.getLogger()?.error( + 'A Validation Method has to be a function!' + ); return this; } // Check if Validation Method already exists if (this.validationMethods[key]) { - Agile.logger.error( + LogCodeManager.getLogger()?.error( `Validation Method with the key/name '${key}' already exists!` ); return this; diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index 5d2775c4..e167f2f6 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -161,8 +161,9 @@ export function useAgile< // Building the Path WeakMap in the 'useIsomorphicLayoutEffect' // because the 'useIsomorphicLayoutEffect' is called after the rerender. // -> All used paths in the UI-Component were successfully tracked. - const proxyWeakMap: ProxyWeakMapType = new WeakMap(); + let proxyWeakMap: ProxyWeakMapType | undefined = undefined; if (config.proxyBased && proxyPackage != null) { + proxyWeakMap = new WeakMap(); for (const observer of observers) { const proxyTree = proxyTreeWeakMap.get(observer); if (proxyTree != null) { @@ -174,8 +175,9 @@ export function useAgile< } // Build Selector WeakMap based on the specified selector method - const selectorWeakMap: SelectorWeakMapType = new WeakMap(); + let selectorWeakMap: SelectorWeakMapType | undefined = undefined; if (config.selector != null) { + selectorWeakMap = new WeakMap(); for (const observer of observers) { selectorWeakMap.set(observer, { methods: [config.selector] }); } From 9417760c1b0ca1fcb93941bc2d8a9640e231eab8 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 8 Jul 2021 18:51:14 +0200 Subject: [PATCH 42/55] fixed some tests --- .../1000fields/bench/agilets/nestedState.tsx | 2 +- packages/core/src/shared.ts | 2 +- packages/core/tests/unit/agile.test.ts | 46 ++--------------- packages/event/src/internal.ts | 4 ++ packages/event/src/shared.ts | 15 +++--- packages/event/tests/unit/shared.test.ts | 51 +++++++++++++++++++ packages/react/src/hocs/AgileHOC.ts | 2 +- packages/utils/tests/unit/utils.test.ts | 4 +- 8 files changed, 69 insertions(+), 57 deletions(-) create mode 100644 packages/event/tests/unit/shared.test.ts diff --git a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx index a1fc5b92..377b6b01 100644 --- a/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx +++ b/benchmark/benchmarks/react/1000fields/bench/agilets/nestedState.tsx @@ -27,7 +27,7 @@ export default function (target: HTMLElement, fieldsCount: number) { { - // field.set(e.target.value); + field.set(e.target.value); updatedFieldsCount++; diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index 6214af06..a3ac9461 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -184,7 +184,7 @@ export function createComputed( export interface CreateAgileSubInstanceInterface { /** * Instance of Agile the Instance belongs to. - * @default Agile.shared + * @default sharedAgileInstance */ agileInstance?: Agile; } diff --git a/packages/core/tests/unit/agile.test.ts b/packages/core/tests/unit/agile.test.ts index 136702ae..2d0dc29e 100644 --- a/packages/core/tests/unit/agile.test.ts +++ b/packages/core/tests/unit/agile.test.ts @@ -7,7 +7,6 @@ import { Storage, Computed, Collection, - Logger, Storages, } from '../../src'; import testIntegration from '../helper/test.integration'; @@ -77,7 +76,6 @@ describe('Agile Tests', () => { // Reset globalThis globalThis[Agile.globalKey] = undefined; - jest.spyOn(Agile.prototype, 'configureLogger'); jest.spyOn(Agile.prototype, 'integrate'); jest.clearAllMocks(); @@ -100,10 +98,9 @@ describe('Agile Tests', () => { expect(SubControllerMock).toHaveBeenCalledWith(agile); expect(agile.subController).toBeInstanceOf(SubController); expect(StoragesMock).toHaveBeenCalledWith(agile, { - localStorage: true, + localStorage: false, }); expect(agile.storages).toBeInstanceOf(Storages); - expect(agile.configureLogger).toHaveBeenCalledWith({}); // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBeUndefined(); @@ -113,13 +110,7 @@ describe('Agile Tests', () => { const agile = new Agile({ waitForMount: false, bucket: false, - localStorage: false, - logConfig: { - level: Logger.level.DEBUG, - active: false, - prefix: 'Jeff', - timestamp: true, - }, + localStorage: true, bindGlobal: true, key: 'jeff', autoIntegrate: false, @@ -139,15 +130,9 @@ describe('Agile Tests', () => { expect(SubControllerMock).toHaveBeenCalledWith(agile); expect(agile.subController).toBeInstanceOf(SubController); expect(StoragesMock).toHaveBeenCalledWith(agile, { - localStorage: false, + localStorage: true, }); expect(agile.storages).toBeInstanceOf(Storages); - expect(agile.configureLogger).toHaveBeenCalledWith({ - active: false, - level: Logger.level.DEBUG, - prefix: 'Jeff', - timestamp: true, - }); // Check if Agile Instance got bound globally expect(globalThis[Agile.globalKey]).toBe(agile); @@ -182,31 +167,6 @@ describe('Agile Tests', () => { 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', () => { - 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(() => { jest.spyOn(Shared, 'createStorage'); diff --git a/packages/event/src/internal.ts b/packages/event/src/internal.ts index e63a1ffb..695b0fbb 100644 --- a/packages/event/src/internal.ts +++ b/packages/event/src/internal.ts @@ -4,6 +4,10 @@ // !! All internal Agile Editor modules must be imported from here!! +// Event export * from './event.job'; export * from './event.observer'; export * from './event'; + +// Shared +export * from './shared'; diff --git a/packages/event/src/shared.ts b/packages/event/src/shared.ts index e898f3d5..4aa11e38 100644 --- a/packages/event/src/shared.ts +++ b/packages/event/src/shared.ts @@ -1,4 +1,9 @@ -import { Agile, defineConfig, removeProperties, shared } from '@agile-ts/core'; +import { + CreateAgileSubInstanceInterface, + defineConfig, + removeProperties, + shared, +} from '@agile-ts/core'; import { Event, CreateEventConfigInterface, @@ -17,14 +22,6 @@ export function createEvent( ); } -export interface CreateAgileSubInstanceInterface { - /** - * Instance of Agile the Instance belongs to. - * @default Agile.shared - */ - agileInstance?: Agile; -} - export interface CreateEventConfigInterfaceWithAgile extends CreateEventConfigInterface, CreateAgileSubInstanceInterface {} diff --git a/packages/event/tests/unit/shared.test.ts b/packages/event/tests/unit/shared.test.ts new file mode 100644 index 00000000..fd2687a3 --- /dev/null +++ b/packages/event/tests/unit/shared.test.ts @@ -0,0 +1,51 @@ +import { Agile, assignSharedAgileInstance } from '@agile-ts/core'; +import { Event, createEvent } from '../../src'; +import { LogMock } from '../../../core/tests/helper/logMock'; + +jest.mock('../../src/event'); + +describe('Shared Tests', () => { + let sharedAgileInstance: Agile; + + beforeEach(() => { + LogMock.mockLogs(); + + sharedAgileInstance = new Agile(); + assignSharedAgileInstance(sharedAgileInstance); + + jest.clearAllMocks(); + }); + + describe('createEvent function tests', () => { + const EventMock = Event as jest.MockedClass; + + it('should create Event with the shared Agile Instance', () => { + const event = createEvent({ + key: 'myCoolState', + delay: 10, + }); + + expect(event).toBeInstanceOf(Event); + expect(EventMock).toHaveBeenCalledWith(sharedAgileInstance, { + key: 'myCoolState', + delay: 10, + }); + }); + + it('should create Event with a specified Agile Instance', () => { + const agile = new Agile(); + + const event = createEvent({ + key: 'myCoolState', + delay: 10, + agileInstance: agile, + }); + + expect(event).toBeInstanceOf(Event); + expect(EventMock).toHaveBeenCalledWith(agile, { + key: 'myCoolState', + delay: 10, + }); + }); + }); +}); diff --git a/packages/react/src/hocs/AgileHOC.ts b/packages/react/src/hocs/AgileHOC.ts index 53793a7e..4832162c 100644 --- a/packages/react/src/hocs/AgileHOC.ts +++ b/packages/react/src/hocs/AgileHOC.ts @@ -58,7 +58,7 @@ export function AgileHOC( } } if (!agileInstance || !agileInstance.subController) { - LogCodeManager.getLogger().error( + LogCodeManager.getLogger()?.error( 'Failed to subscribe Component with deps', deps ); diff --git a/packages/utils/tests/unit/utils.test.ts b/packages/utils/tests/unit/utils.test.ts index 8206d311..06b984c3 100644 --- a/packages/utils/tests/unit/utils.test.ts +++ b/packages/utils/tests/unit/utils.test.ts @@ -15,12 +15,12 @@ import { createArrayFromObject, removeProperties, } from '../../src'; -import mockConsole from 'jest-mock-console'; +import { LogMock } from '../../../core/tests/helper/logMock'; describe('Utils Tests', () => { beforeEach(() => { + LogMock.mockLogs(); jest.clearAllMocks(); - mockConsole(['error', 'warn']); }); describe('copy function tests', () => { From f5c611ad56b03aa57267715025e89cce6348c15c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 8 Jul 2021 19:57:51 +0200 Subject: [PATCH 43/55] fixed tests --- packages/event/tests/unit/shared.test.ts | 8 ++++---- packages/utils/src/index.ts | 11 +++++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/event/tests/unit/shared.test.ts b/packages/event/tests/unit/shared.test.ts index fd2687a3..7b5719a0 100644 --- a/packages/event/tests/unit/shared.test.ts +++ b/packages/event/tests/unit/shared.test.ts @@ -21,13 +21,13 @@ describe('Shared Tests', () => { it('should create Event with the shared Agile Instance', () => { const event = createEvent({ - key: 'myCoolState', + key: 'myCoolEvent', delay: 10, }); expect(event).toBeInstanceOf(Event); expect(EventMock).toHaveBeenCalledWith(sharedAgileInstance, { - key: 'myCoolState', + key: 'myCoolEvent', delay: 10, }); }); @@ -36,14 +36,14 @@ describe('Shared Tests', () => { const agile = new Agile(); const event = createEvent({ - key: 'myCoolState', + key: 'myCoolEvent', delay: 10, agileInstance: agile, }); expect(event).toBeInstanceOf(Event); expect(EventMock).toHaveBeenCalledWith(agile, { - key: 'myCoolState', + key: 'myCoolEvent', delay: 10, }); }); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 46b16b86..9c7b0fe4 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -8,10 +8,17 @@ * @param value - Array/Object that gets copied */ export function copy(value: T): T { - // Ignore everything that is no object or array - // (Extra checking 'value == null' because 'typeof null === object') + // Extra checking 'value == null' because 'typeof null === object' if (value == null || typeof value !== 'object') return value; + // Ignore everything that is no object or array but has the type of an object (e.g. classes) + const valConstructorName = Object.getPrototypeOf(value).constructor.name; + if ( + valConstructorName.toLowerCase() !== 'object' && + valConstructorName.toLowerCase() !== 'array' + ) + return value; + let temp; const newObject: any = Array.isArray(value) ? [] : {}; for (const property in value) { From 6780e97f6dfc065ec848d85503ef0ed87072aa3c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 9 Jul 2021 06:41:10 +0200 Subject: [PATCH 44/55] added registered storage log --- .../react/develop/functional-component-ts/src/core/index.ts | 2 +- packages/core/src/logCodeManager.ts | 1 + packages/core/src/storages/index.ts | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/react/develop/functional-component-ts/src/core/index.ts b/examples/react/develop/functional-component-ts/src/core/index.ts index e4314dd1..c98da0af 100644 --- a/examples/react/develop/functional-component-ts/src/core/index.ts +++ b/examples/react/develop/functional-component-ts/src/core/index.ts @@ -4,10 +4,10 @@ import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger'; export const myStorage: any = {}; +assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); export const App = new Agile({ localStorage: true, }); -assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); // Register custom second Storage App.registerStorage( diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index bed130bc..23f4ca51 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -65,6 +65,7 @@ const logCodeMessages = { "The Storage with the key/name '${1}' doesn't exists!`", // Storage + '13:00:00': "Registered new Storage '${0}'.", '13:01:00': "GET value at key '${1}' from Storage '${0}'.", '13:01:01': "SET value at key '${1}' in Storage '${0}'.", '13:01:02': "REMOVE value at key '${1}' from Storage '${0}'.", diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index 552a96b7..b917ca39 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -115,6 +115,8 @@ export class Storages { } }); + LogCodeManager.log('13:00:00', [storage.key], storage); + return true; } From 98521366d363970545ef4cca8fec73212d4adb83 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 9 Jul 2021 14:36:41 +0200 Subject: [PATCH 45/55] fixed stupid typo --- benchmark/package.json | 4 ++-- .../functional-component-ts/package.json | 3 ++- examples/vue/develop/my-project/package.json | 4 +++- examples/vue/develop/my-project/src/core.js | 19 ++++++++------- examples/vue/develop/my-project/yarn.lock | 23 ++++++++----------- packages/core/src/integrations/index.ts | 20 ++++++++++------ packages/core/src/logCodeManager.ts | 4 ++-- packages/core/src/runtime/index.ts | 16 +++++++++---- .../src/hooks/useIsomorphicLayoutEffect.ts | 2 +- 9 files changed, 56 insertions(+), 39 deletions(-) diff --git a/benchmark/package.json b/benchmark/package.json index 75a3e8bb..9e71cdbc 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -11,8 +11,8 @@ "test:counter": "yarn test ./benchmarks/react/counter", "test:1000fields": "yarn test ./benchmarks/react/1000fields", "test:computed": "yarn test ./benchmarks/react/computed", - "install:local:agile": "yalc add @agile-ts/core @agile-ts/react & yarn install", - "install:public:agile": "yarn add @agile-ts/core @agile-ts/react & yarn install" + "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react & yarn install", + "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react & yarn install" }, "repository": { "type": "git", diff --git a/examples/react/develop/functional-component-ts/package.json b/examples/react/develop/functional-component-ts/package.json index 11686b80..4972a343 100644 --- a/examples/react/develop/functional-component-ts/package.json +++ b/examples/react/develop/functional-component-ts/package.json @@ -31,7 +31,8 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "install:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree @agile-ts/logger & yarn install" + "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree @agile-ts/logger & yarn install", + "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/api @agile-ts/multieditor @agile-ts/event @agile-ts/proxytree @agile-ts/logger & yarn install" }, "eslintConfig": { "extends": "react-app" diff --git a/examples/vue/develop/my-project/package.json b/examples/vue/develop/my-project/package.json index 47b1f72d..ce01a6b2 100644 --- a/examples/vue/develop/my-project/package.json +++ b/examples/vue/develop/my-project/package.json @@ -6,10 +6,12 @@ "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint", - "install:agile": "yalc add @agile-ts/core @agile-ts/vue & yarn install" + "install:dev:agile": "yalc add @agile-ts/core @agile-ts/vue @agile-ts/logger & yarn install", + "install:prod:agile": "yarn add @agile-ts/core @agile-ts/vue @agile-ts/logger & yarn install" }, "dependencies": { "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/logger": "file:.yalc/@agile-ts/logger", "@agile-ts/vue": "file:.yalc/@agile-ts/vue", "core-js": "^3.6.5", "vue": "^2.6.11" diff --git a/examples/vue/develop/my-project/src/core.js b/examples/vue/develop/my-project/src/core.js index a8999d9f..0512ec9b 100644 --- a/examples/vue/develop/my-project/src/core.js +++ b/examples/vue/develop/my-project/src/core.js @@ -1,19 +1,22 @@ -import { Agile, globalBind, Logger } from '@agile-ts/core'; -import vueIntegration from '@agile-ts/vue'; +import { Agile, assignSharedAgileInstance, globalBind } from '@agile-ts/core'; +import { Logger, assignSharedAgileLoggerConfig } from '@agile-ts/logger'; +import '@agile-ts/vue'; + +assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); // Create Agile Instance -export const App = new Agile({ - logConfig: { level: Logger.level.DEBUG }, -}).integrate(vueIntegration); +export const App = new Agile({ localStorage: true }); +assignSharedAgileInstance(App); // console.debug('hi'); // Doesn't work here idk why // Create State export const MY_STATE = App.createState('World', { key: 'my-state', -}).computeValue((v) => { - return `Hello ${v}`; -}); +}) + .computeValue((v) => { + return `Hello ${v}`; + }); export const MY_COMPUTED = App.createComputed( async () => { diff --git a/examples/vue/develop/my-project/yarn.lock b/examples/vue/develop/my-project/yarn.lock index 04c336c0..5997fe57 100644 --- a/examples/vue/develop/my-project/yarn.lock +++ b/examples/vue/develop/my-project/yarn.lock @@ -3,25 +3,22 @@ "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.0.17" + version "0.1.0" dependencies: - "@agile-ts/logger" "^0.0.4" - "@agile-ts/utils" "^0.0.4" + "@agile-ts/utils" "^0.0.5" -"@agile-ts/logger@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.4.tgz#7f4d82ef8f03b13089af0878c360575c43f0962d" - integrity sha512-qm0obAKqJMaPKM+c76gktRXyw3OL1v39AnhMZ0FBGwJqHWU+fLRkCzlQwjaROCr3F1XP01Lc/Ls3efF0WzyEPw== +"@agile-ts/logger@file:.yalc/@agile-ts/logger": + version "0.0.5" dependencies: - "@agile-ts/utils" "^0.0.4" + "@agile-ts/utils" "^0.0.5" -"@agile-ts/utils@^0.0.4": - version "0.0.4" - resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.4.tgz#66e9536e561796489a37155da6b74ce2dc482697" - integrity sha512-GiZyTYmCm4j2N57oDjeMuPpfQdgn9clb0Cxpfuwi2Bq5T/KPXlaROLsVGwHLjwwT+NX7xxr5qNJH8pZTnHnYRQ== +"@agile-ts/utils@^0.0.5": + version "0.0.5" + resolved "https://registry.yarnpkg.com/@agile-ts/utils/-/utils-0.0.5.tgz#23cc83e60eb6b15734247fac1d77f1fd629ffdb6" + integrity sha512-R86X9MjMty14eoQ4djulZSdHf9mIF9dPcj4g+SABqdA6AqbewS0/BQGNGR5p6gXhqc4+mT8rzkutywdPnMUNfA== "@agile-ts/vue@file:.yalc/@agile-ts/vue": - version "0.0.5" + version "0.1.0" "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13": version "7.12.13" diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 1efb0805..40e558e6 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -42,6 +42,9 @@ export class Integrations { callback: (integration: Integration) => void ): void { onRegisterInitialIntegrationCallbacks.push(callback); + Integrations.initialIntegrations.forEach((integration) => { + callback(integration); + }); } /** @@ -60,11 +63,6 @@ export class Integrations { 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); @@ -82,7 +80,11 @@ export class Integrations { public async integrate(integration: Integration): Promise { // Check if Integration is valid if (integration._key == null) { - LogCodeManager.log('18:03:00', [integration._key], integration); + LogCodeManager.log( + '18:03:00', + [integration._key, this.agileInstance().key], + integration + ); return false; } @@ -95,7 +97,11 @@ export class Integrations { this.integrations.add(integration); integration.integrated = true; - LogCodeManager.log('18:00:00', [integration._key], integration); + LogCodeManager.log( + '18:00:00', + [integration._key, this.agileInstance().key], + integration + ); return true; } diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index 23f4ca51..e78f3cb1 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -103,10 +103,10 @@ const logCodeMessages = { "The 'perform()' method isn't set in Observer but need to be set! Observer is no stand alone class.", // Integrations - '18:00:00': "Integrated '${0}' into AgileTs", + '18:00:00': "Integrated '${0}' into AgileTs '${1}'", '18:02:00': "Can't call the 'update()' method on a not ready Integration '${0}'!", - '18:03:00': "Failed to integrate Framework '${0}'!", + '18:03:00': "Failed to integrate Framework '${0}' into AgileTs '${1}'!", // Computed '19:03:00': diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index bf3bd786..72cd0d3c 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -28,6 +28,8 @@ export class Runtime { // Whether the `jobQueue` is currently being actively processed public isPerformingJobs = false; + private bucketTimeout: NodeJS.Timeout | null = null; + /** * The Runtime queues and executes incoming Observer-based Jobs * to prevent [race conditions](https://en.wikipedia.org/wiki/Race_condition#:~:text=A%20race%20condition%20or%20race,the%20possible%20behaviors%20is%20undesirable.) @@ -120,10 +122,16 @@ export class Runtime { this.isPerformingJobs = false; if (this.jobsToRerender.length > 0) { if (this.agileInstance().config.bucket) { - // https://stackoverflow.com/questions/9083594/call-settimeout-without-delay - setTimeout(() => { - this.updateSubscribers(); - }); + // Check if an bucket timeout is active, if so don't call a new one, + // since if the active timeout is called it will also proceed Jobs + // that were not added before the call + if (this.bucketTimeout == null) { + // https://stackoverflow.com/questions/9083594/call-settimeout-without-delay + this.bucketTimeout = setTimeout(() => { + this.bucketTimeout = null; + this.updateSubscribers(); + }); + } } else this.updateSubscribers(); } } diff --git a/packages/react/src/hooks/useIsomorphicLayoutEffect.ts b/packages/react/src/hooks/useIsomorphicLayoutEffect.ts index becea846..c76c0393 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 eaa4eafc1aa0c432aa0a74462563bb6824a9ef8e Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 9 Jul 2021 17:21:38 +0200 Subject: [PATCH 46/55] added deps array to useAgile --- .../computed/bench/agilets/autoTracking.tsx | 3 ++- .../computed/bench/agilets/hardCoded.tsx | 3 ++- examples/react/release/boxes/package.json | 5 ++-- .../src/components/EditProperties/index.tsx | 4 +-- examples/react/release/boxes/src/core/app.ts | 10 ++++---- examples/react/release/boxes/yarn.lock | 25 ++++++++----------- packages/react/src/hooks/useAgile.ts | 12 ++++++++- 7 files changed, 36 insertions(+), 26 deletions(-) diff --git a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx index df85d176..0b596405 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/autoTracking.tsx @@ -1,8 +1,9 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createComputed, createState } from '@agile-ts/core'; +import { createComputed, createState, LogCodeManager } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; +LogCodeManager.getLogger().isActive = false; const COUNT = createState(0); const COMPUTED_COUNT = createComputed(() => { return COUNT.value * 5; diff --git a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx index ac782b55..59cc57a8 100644 --- a/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx +++ b/benchmark/benchmarks/react/computed/bench/agilets/hardCoded.tsx @@ -1,8 +1,9 @@ import React from 'react'; import ReactDom from 'react-dom'; -import { createComputed, createState } from '@agile-ts/core'; +import { createComputed, createState, LogCodeManager } from '@agile-ts/core'; import { useAgile } from '@agile-ts/react'; +LogCodeManager.getLogger().isActive = false; const COUNT = createState(0); const COMPUTED_COUNT = createComputed( () => { diff --git a/examples/react/release/boxes/package.json b/examples/react/release/boxes/package.json index 8090cf6b..8fdd52f6 100644 --- a/examples/react/release/boxes/package.json +++ b/examples/react/release/boxes/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/logger": "file:.yalc/@agile-ts/logger", "@agile-ts/proxytree": "file:.yalc/@agile-ts/proxytree", "@agile-ts/react": "file:.yalc/@agile-ts/react", "@chakra-ui/react": "^1.6.3", @@ -32,8 +33,8 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/proxytree & yarn install", - "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/proxytree" + "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react @agile-ts/proxytree @agile-ts/logger & yarn install", + "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/proxytree @agile-ts/logger & yarn install" }, "eslintConfig": { "extends": [ diff --git a/examples/react/release/boxes/src/components/EditProperties/index.tsx b/examples/react/release/boxes/src/components/EditProperties/index.tsx index b1caa3cd..3729f759 100644 --- a/examples/react/release/boxes/src/components/EditProperties/index.tsx +++ b/examples/react/release/boxes/src/components/EditProperties/index.tsx @@ -136,8 +136,8 @@ const Property = ({ id: number | string; }) => { const ELEMENT = core.ui.ELEMENTS.getItem(id); - const element = useProxy(ELEMENT, { componentId: 'Property' }); - console.log('Element', ELEMENT); + const element = useProxy(ELEMENT, { componentId: 'Property', deps: [id] }); + return ( { agileInstance?.subController.unsubscribe(subscriptionContainer); }; - }, []); + }, config.deps); return getReturnValue(depsArray); } @@ -261,4 +262,13 @@ export interface AgileHookConfigInterface { * @default undefined */ observerType?: string; + /** + * Dependencies that determine, in addition to unmounting and remounting the React-Component, + * when the specified Agile Sub Instances should be re-subscribed to the React-Component. + * + * [Github issue](https://github.com/agile-ts/agile/issues/170) + * + * @default [] + */ + deps?: any[]; } From 19c474a948ed9a096ab58d3110ae686bb4354db9 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 9 Jul 2021 18:23:09 +0200 Subject: [PATCH 47/55] tried to fix resizing in boxes example --- .../actionComponents/Resize/components/Handle.tsx | 6 ++++-- .../components/actionComponents/Resize/index.tsx | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx b/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx index dbe0a795..3b86fe96 100644 --- a/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx +++ b/examples/react/release/boxes/src/components/actionComponents/Resize/components/Handle.tsx @@ -10,13 +10,14 @@ type Position = { bottom?: number | string; }; -interface HandlePropsInterface { +export interface HandlePropsInterface { placement: ResizeHandle; visible: boolean; + innerRef?: any; } export const Handle: React.FC = (props) => { - const { placement, visible } = props; + const { placement, visible, innerRef } = props; const size = 10; const position: Position = {}; @@ -34,6 +35,7 @@ export const Handle: React.FC = (props) => { return ( = (props) => { lockAspectRatio, } = props; + // https://github.com/react-grid-layout/react-resizable#custom-react-component + const WrappedHandle = React.forwardRef((props: HandlePropsInterface, ref) => ( + + )); + return ( { + console.debug('onResize', size); + let topDiff = 0; if (handle.includes('n')) { topDiff = size.height - newSize.height; @@ -58,9 +65,7 @@ export const Resize: React.FC = (props) => { }} resizeHandles={handlePlacements} handle={(placement) => ( -
- -
+ )} lockAspectRatio={lockAspectRatio}>
{children}
From 16e7d4bbe49fe98f47be2f02d464dc74450af628 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 9 Jul 2021 19:50:48 +0200 Subject: [PATCH 48/55] fixed tests --- .../actionComponents/Resize/index.tsx | 2 -- packages/core/src/integrations/index.ts | 8 ++--- packages/core/src/logCodeManager.ts | 5 ++-- packages/core/src/runtime/index.ts | 3 +- .../unit/integrations/integrations.test.ts | 28 ++++++++++++++--- .../core/tests/unit/runtime/runtime.test.ts | 30 ++++++++++++++++--- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/examples/react/release/boxes/src/components/actionComponents/Resize/index.tsx b/examples/react/release/boxes/src/components/actionComponents/Resize/index.tsx index 360d96ac..b8e1d2bd 100644 --- a/examples/react/release/boxes/src/components/actionComponents/Resize/index.tsx +++ b/examples/react/release/boxes/src/components/actionComponents/Resize/index.tsx @@ -40,8 +40,6 @@ export const Resize: React.FC = (props) => { width={size.width} height={size.height} onResize={(_, { size: newSize, handle }) => { - console.debug('onResize', size); - let topDiff = 0; if (handle.includes('n')) { topDiff = size.height - newSize.height; diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 40e558e6..442d3814 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -12,14 +12,14 @@ export class Integrations { public integrations: Set = new Set(); // External added Integrations - // that are to integrate into each created Agile Instance + // that are to integrate into not yet existing Agile Instances static initialIntegrations: Integration[] = []; /** - * Adds an external Integration to be registered in each Agile Instance created. + * Registers the specified Integration in each existing or not-yet created Agile Instance. * * @public - * @param integration - Integration to be registered in each Agile Instance created. + * @param integration - Integration to be registered in each Agile Instance. */ static addInitialIntegration(integration: Integration): void { if (integration instanceof Integration) { @@ -36,7 +36,7 @@ export class Integrations { * Fires on each external added Integration. * * @public - * @param callback - Callback to be fired when an Integration was added externally. + * @param callback - Callback to be fired when an Integration was externally added. */ static onRegisterInitialIntegration( callback: (integration: Integration) => void diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index e78f3cb1..f7a6788b 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -187,6 +187,7 @@ function getLog>( ): string { let result = logCodeMessages[logCode] ?? `'${logCode}' is a unknown logCode!`; + // Replace '${x}' with the specified replacer instances for (let i = 0; i < replacers.length; i++) { result = result.replace('${' + i + '}', replacers[i]); } @@ -196,7 +197,7 @@ function getLog>( /** * Logs the log message according to the specified log code - * with the Agile Logger. + * with the Agile Logger if installed or the normal console. * * @internal * @param logCode - Log code of the message to be returned. @@ -227,7 +228,7 @@ function log>( /** * Logs the log message according to the specified log code - * with the Agile Logger if the provided tags are active. + * with the Agile Logger if installed and the provided tags are active. * * @internal * @param tags - Tags to be active to log the logCode. diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 72cd0d3c..d29c7dc5 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -28,7 +28,8 @@ export class Runtime { // Whether the `jobQueue` is currently being actively processed public isPerformingJobs = false; - private bucketTimeout: NodeJS.Timeout | null = null; + // Current 'bucket' timeout 'scheduled' for updating the Subscribers (UI-Components) + public bucketTimeout: NodeJS.Timeout | null = null; /** * The Runtime queues and executes incoming Observer-based Jobs diff --git a/packages/core/tests/unit/integrations/integrations.test.ts b/packages/core/tests/unit/integrations/integrations.test.ts index b8550a22..a7598d61 100644 --- a/packages/core/tests/unit/integrations/integrations.test.ts +++ b/packages/core/tests/unit/integrations/integrations.test.ts @@ -38,11 +38,12 @@ describe('Integrations Tests', () => { expect(Integrations.onRegisterInitialIntegration).toHaveBeenCalledWith( expect.any(Function) ); + expect(integrations.integrate).toHaveBeenCalledTimes(2); expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration1); expect(integrations.integrate).toHaveBeenCalledWith(dummyIntegration2); }); - it('should create Integrations without the before specified initial Integrations (specific config)', () => { + it('should create Integrations without the before specified initial Integrations (autoIntegrate = false)', () => { Integrations.initialIntegrations = [dummyIntegration1, dummyIntegration2]; const integrations = new Integrations(dummyAgile, { autoIntegrate: false }); @@ -61,9 +62,27 @@ describe('Integrations Tests', () => { }); describe('onRegisterInitialIntegration function tests', () => { - it('should register specified onRegisterInitialIntegration callback', () => { - // Nothing to testable + let callback; + beforeEach(() => { + callback = jest.fn(); }); + + it( + 'should register specified onRegisterInitialIntegration callback ' + + 'and call it for each tracked initial Integrations', + () => { + Integrations.initialIntegrations = [ + dummyIntegration1, + dummyIntegration2, + ]; + + Integrations.onRegisterInitialIntegration(callback); + + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledWith(dummyIntegration1); + expect(callback).toHaveBeenCalledWith(dummyIntegration2); + } + ); }); describe('addInitialIntegration function tests', () => { @@ -74,6 +93,7 @@ describe('Integrations Tests', () => { Integrations.onRegisterInitialIntegration(callback1); Integrations.onRegisterInitialIntegration(callback2); }); + it( 'should add valid Integration to the initialIntegrations array ' + 'and fire the onRegisterInitialIntegration callbacks', @@ -149,7 +169,7 @@ describe('Integrations Tests', () => { LogMock.hasLoggedCode( '18:03:00', - [dummyIntegration1._key], + [dummyIntegration1._key, dummyAgile.key], dummyIntegration1 ); }); diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index c75e3893..5bcc3014 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -31,6 +31,7 @@ describe('Runtime Tests', () => { expect(runtime.jobsToRerender).toStrictEqual([]); expect(Array.from(runtime.notReadyJobsToRerender)).toStrictEqual([]); expect(runtime.isPerformingJobs).toBeFalsy(); + expect(runtime.bucketTimeout).toBeNull(); }); describe('Runtime Function Tests', () => { @@ -112,10 +113,11 @@ describe('Runtime Tests', () => { it( "should perform specified Job and all remaining Jobs in the 'jobQueue' " + - "and call 'updateSubscribers' in a setTimeout " + + "and call 'updateSubscribers' in a setTimeout (bucket) " + 'if at least one performed Job needs to rerender (config.bucket = true)', async () => { runtime.agileInstance().config.bucket = true; + runtime.bucketTimeout = null; runtime.jobQueue.push(dummyJob2); runtime.jobQueue.push(dummyJob3); @@ -131,10 +133,12 @@ describe('Runtime Tests', () => { expect(runtime.isPerformingJobs).toBeFalsy(); // because Jobs were performed expect(runtime.jobQueue).toStrictEqual([]); expect(runtime.jobsToRerender).toStrictEqual([dummyJob1, dummyJob2]); + expect(runtime.bucketTimeout).not.toBeNull(); // Because 'updateSubscribers' is called in a timeout await waitForExpect(() => { expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1); + expect(runtime.bucketTimeout).toBeNull(); }); } ); @@ -145,15 +149,35 @@ describe('Runtime Tests', () => { 'if at least one performed Job needs to rerender (config.bucket = false)', async () => { runtime.agileInstance().config.bucket = false; + runtime.bucketTimeout = null; runtime.jobQueue.push(dummyJob2); runtime.jobQueue.push(dummyJob3); runtime.perform(dummyJob1); + expect(runtime.bucketTimeout).toBeNull(); + expect(runtime.updateSubscribers).toHaveBeenCalledTimes(1); } ); + it( + "should perform specified Job and all remaining Jobs in the 'jobQueue' " + + "and shouldn't call 'updateSubscribers' although at least one performed Job needs to rerender" + + 'if a bucket timeout is already active (config.bucket = true)', + async () => { + runtime.agileInstance().config.bucket = true; + runtime.bucketTimeout = 'notNull' as any; + runtime.jobQueue.push(dummyJob2); + runtime.jobQueue.push(dummyJob3); + + runtime.perform(dummyJob1); + + expect(runtime.bucketTimeout).toBe('notNull'); + expect(runtime.updateSubscribers).not.toHaveBeenCalled(); + } + ); + it('should perform specified Job and ingest its dependents into the runtime', async () => { dummyJob1.observer.dependents.add(dummyObserver2); dummyJob1.observer.dependents.add(dummyObserver1); @@ -185,9 +209,7 @@ describe('Runtime Tests', () => { expect(runtime.isPerformingJobs).toBeFalsy(); // because Jobs were performed expect(runtime.jobQueue).toStrictEqual([]); expect(runtime.jobsToRerender).toStrictEqual([]); - - // Sleep 5ms because updateSubscribers is called in a timeout - await new Promise((resolve) => setTimeout(resolve, 5)); + expect(runtime.bucketTimeout).toBeNull(); expect(runtime.updateSubscribers).not.toHaveBeenCalled(); } From 91fae50b910d5b6542e05fd54383f06c929c01b2 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 10 Jul 2021 08:25:59 +0200 Subject: [PATCH 49/55] added benchmark leaderboard --- benchmark/benchmarkManager.ts | 69 +++++++++++++++++++++ benchmark/benchmarks/react/counter/index.ts | 17 ++++- benchmark/package.json | 1 + benchmark/run.ts | 25 +++++++- benchmark/yarn.lock | 47 +++++++++++--- packages/utils/src/index.ts | 9 ++- 6 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 benchmark/benchmarkManager.ts diff --git a/benchmark/benchmarkManager.ts b/benchmark/benchmarkManager.ts new file mode 100644 index 00000000..3c144d9a --- /dev/null +++ b/benchmark/benchmarkManager.ts @@ -0,0 +1,69 @@ +export interface CycleResultInterface { + name: string; + opsInSec: number; + failRate: number; + ranSampleCount: number; + ui: any; +} + +export function getCycleResult(event: any): CycleResultInterface { + return { + name: event.target.name, + opsInSec: Math.round(event.target.hz), + failRate: event.target.stats.rme.toFixed(2), + ranSampleCount: event.target.stats.sample.length, + ui: { + count: event.target.output, + }, + }; +} + +export function startBenchmarkLog(testSuiteName: string): void { + console.log(`{white Starting Benchmark "{cyan.bold ${testSuiteName}}"..}`); +} + +export function cycleLog(cycleResult: CycleResultInterface): void { + console.log( + `{gray ..Proceeded {green.bold ${cycleResult.name}} - {yellow ${cycleResult.opsInSec} ops/sec}}` + ); +} + +export function endBenchmarkLog( + testSuiteName: string, + results: CycleResultInterface[], + fastest: string[] +): void { + console.log(`{white ..End Benchmark "{cyan.bold ${testSuiteName}}"..}\n\n`); + + results.sort((a, b) => { + if (a.opsInSec < b.opsInSec) return 1; + return -1; + }); + + let resultString = ''; + for (let i = 0; i < results.length; i++) { + const cycleResult = results[i]; + + // Build Cycle Result Log + const cycleString = `{bold.bgGreen ${ + i + 1 + }.} {bold.blue ${cycleResult.name + .padEnd(15, '.') + .replace(/(\.+)$/, '{red $1}')}}{yellow ${ + cycleResult.opsInSec + } ops/se} ±${cycleResult.failRate} (${ + cycleResult.ranSampleCount + } runs sampled)`; + + resultString += `${cycleString}${i < results.length - 1 ? '\n' : ''}`; + } + + // Build Leaderboard Header + console.log('{bgYellow.white.bold Leaderboard:}\n'); + + // Print Leaderboard + console.log(resultString); + + // Print fastest + console.log(`\n{bold Fastest is {bold.green ${fastest}}}\n`); +} diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index 946030fa..447bca69 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -13,6 +13,13 @@ import redux from './bench/redux'; import reduxToolkit from './bench/redux-toolkit'; import valtio from './bench/valtio'; import zustand from './bench/zustand'; +import { + cycleLog, + CycleResultInterface, + endBenchmarkLog, + getCycleResult, + startBenchmarkLog, +} from '../../../benchmarkManager'; // @ts-ignore // Benchmark.js requires an instance of itself globally @@ -52,6 +59,8 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { }; } +const results: CycleResultInterface[] = []; + // Add Tests to the Benchmark Test Suite suite .add('AgileTs', configTest(agilets)) @@ -68,13 +77,15 @@ suite // Add Listener .on('start', function (this: any) { - console.log(`Starting ${this.name}`); + startBenchmarkLog(this.name); }) .on('cycle', (event: any) => { - console.log(String(event.target), `[Count: ${event.target.output}]`); + const cycleResult = getCycleResult(event); + cycleLog(cycleResult); + results.push(cycleResult); }) .on('complete', function (this: any) { - console.log(`Fastest is ${this.filter('fastest').map('name')}`); + endBenchmarkLog(this.name, results, this.filter('fastest').map('name')); // @ts-ignore // Notify server that the Benchmark Test Suite has ended diff --git a/benchmark/package.json b/benchmark/package.json index 9e71cdbc..5d7a4db7 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -26,6 +26,7 @@ "@pulsejs/react": "^4.0.0-beta.3", "@reduxjs/toolkit": "^1.6.0", "benchmark": "^2.1.4", + "chalk": "^4.1.1", "colorette": "^1.2.2", "dotenv": "^10.0.0", "esbuild": "^0.12.14", diff --git a/benchmark/run.ts b/benchmark/run.ts index a711778c..88207646 100644 --- a/benchmark/run.ts +++ b/benchmark/run.ts @@ -1,6 +1,7 @@ import dotenv from 'dotenv'; import esbuild from 'esbuild'; import playwright from 'playwright'; +import chalk from 'chalk'; // Loads environment variables from the '.env' file dotenv.config(); @@ -16,6 +17,8 @@ if (entry == null) { } const startBenchmark = async () => { + console.log(chalk.blue('Starting the benchmark server..')); + // Bundle Benchmark Test Suite // and launch the server on which the Test Suite is executed const server = await esbuild.serve( @@ -38,7 +41,11 @@ const startBenchmark = async () => { ); const serverUrl = `http://${server.host}:${server.port}`; - console.log(`Server is running at port: ${server.port}`); + console.log( + chalk.cyan( + `Server is running at port: ${chalk.magenta.bold(server.port)}\n` + ) + ); // Launch Chrome as browser to run the Benchmark Test Suite in const browser = await playwright.chromium.launch(); @@ -48,7 +55,9 @@ const startBenchmark = async () => { // Option to open and test the Benchmark Test Suite in the browser manually if (process.env.MANUAL_BENCHMARK === 'true') { console.log( - `Open the Browser at '${serverUrl}' to run the tests manually.` + `${chalk.blue('[i]')} ${chalk.gray( + `Benchmark is running at ${chalk.blue.bold(serverUrl)}` + )}` ); await server.wait; @@ -63,7 +72,17 @@ const startBenchmark = async () => { // Setup 'console' listener to transfer the browser logs into the local console // https://playwright.dev/docs/api/class-page/#page-event-console page.on('console', (...message) => { - console.log(...message); + const stringMessages = message.map((m) => m.text()); + const colorMessage = stringMessages[0]; + stringMessages.shift(); // Remove 'colorMessage' (first argument) from 'stringMessages' array + + // Parse color message to work in chalck + // https://stackoverflow.com/questions/56526522/gulp-chalk-pass-string-template-through-method + const parsedColorMessage = [colorMessage]; + // @ts-ignore + parsedColorMessage.raw = [colorMessage]; + + console.log(chalk(parsedColorMessage), ...stringMessages); }); // Open the url the server is running on diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index 9c8ac2c3..16d56ebc 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -4,14 +4,6 @@ "@agile-ts/core@file:.yalc/@agile-ts/core": version "0.1.0" - dependencies: - "@agile-ts/logger" "^0.0.5" - "@agile-ts/utils" "^0.0.5" - -"@agile-ts/logger@^0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@agile-ts/logger/-/logger-0.0.5.tgz#bad39e1995a0c14e7f3f7c6e44c028f4d7a30f38" - integrity sha512-qBNUyPJGecOZOS9r8dyGF/VLBioEY5DYZn4Hoq+sEkpyvoi718c90i57B1M++I2MCCONVMGytG61Gs1sts73qw== dependencies: "@agile-ts/utils" "^0.0.5" @@ -123,6 +115,13 @@ agent-base@6: dependencies: debug "4" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -149,6 +148,26 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +chalk@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" @@ -258,6 +277,11 @@ hamt_plus@1.0.2: resolved "https://registry.yarnpkg.com/hamt_plus/-/hamt_plus-1.0.2.tgz#e21c252968c7e33b20f6a1b094cd85787a265601" integrity sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -550,6 +574,13 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + typescript@^4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 9c7b0fe4..4cf3a2f1 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -12,11 +12,10 @@ export function copy(value: T): T { if (value == null || typeof value !== 'object') return value; // Ignore everything that is no object or array but has the type of an object (e.g. classes) - const valConstructorName = Object.getPrototypeOf(value).constructor.name; - if ( - valConstructorName.toLowerCase() !== 'object' && - valConstructorName.toLowerCase() !== 'array' - ) + const valConstructorName = Object.getPrototypeOf( + value + ).constructor.name.toLowerCase(); + if (valConstructorName !== 'object' && valConstructorName !== 'array') return value; let temp; From b9224893f4fd40268188d04004d86774a7fd54d6 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 10 Jul 2021 09:45:43 +0200 Subject: [PATCH 50/55] updated benchmark readme --- benchmark/README.md | 125 ++++++++++-------- benchmark/benchmarkManager.ts | 16 ++- .../benchmarks/react/1000fields/index.ts | 23 +++- benchmark/benchmarks/react/computed/index.ts | 21 ++- benchmark/benchmarks/react/counter/index.ts | 2 +- benchmark/run.ts | 12 +- 6 files changed, 125 insertions(+), 74 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index c509afd3..3e5ae740 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -10,75 +10,96 @@ https://stackoverflow.com/questions/28524653/what-do-the-results-from-benchmark- ## Counter Benchmark ```ts -1. Zustand x 30,591 ops/sec ±1.15% (61 runs sampled) -2. Redux x 30,239 ops/sec ±1.64% (63 runs sampled) -3. Mobx x 29,032 ops/sec ±1.24% (64 runs sampled) -4. AgileTs x 28,327 ops/sec ±2.96% (60 runs sampled) -5. Redux-Toolkit x 22,808 ops/sec ±1.79% (65 runs sampled) -6. Jotai x 22,479 ops/sec ±5.79% (63 runs sampled) -7. Valtio x 20,784 ops/sec ±2.75% (63 runs sampled) -8. Recoil x 14,351 ops/sec ±1.55% (65 runs sampled) +1. PulseJs.............41599 ops/se ±1.04 (61 runs sampled) +2. AgileTs.............40847 ops/se ±2.55 (61 runs sampled) +3. Nano Stores.........32107 ops/se ±1.27 (64 runs sampled) +4. Zustand.............29314 ops/se ±1.40 (62 runs sampled) +5. Redux...............29111 ops/se ±1.29 (66 runs sampled) +6. Hookstate...........28380 ops/se ±4.79 (61 runs sampled) +7. Mobx................27800 ops/se ±2.89 (64 runs sampled) +8. Jotai...............22926 ops/se ±4.17 (64 runs sampled) +9. Redux-Toolkit.......22157 ops/se ±2.26 (65 runs sampled) +10. Valtio..............20401 ops/se ±1.30 (63 runs sampled) +11. Recoil..............13943 ops/se ±5.10 (59 runs sampled) + +Fastest is PulseJs + ``` ## 1000 Fields ```ts // 1 Field -Agile Collection x 13,729 ops/sec ±3.42% (60 runs sampled) [updatedFieldsCount: 76468, renderFieldsCount: 73] -Agile State x 19,008 ops/sec ±1.87% (66 runs sampled) [updatedFieldsCount: 103559, renderFieldsCount: 72] -Agile nested State x 21,119 ops/sec ±1.45% (64 runs sampled) [updatedFieldsCount: 116226, renderFieldsCount: 72] -Hookstate x 20,026 ops/sec ±0.68% (64 runs sampled) [updatedFieldsCount: 112513, renderFieldsCount: 112513] -Jotai x 16,372 ops/sec ±3.34% (63 runs sampled) [updatedFieldsCount: 90275, renderFieldsCount: 90275] -Mobx x 15,892 ops/sec ±3.42% (60 runs sampled) [updatedFieldsCount: 82400, renderFieldsCount: 82400] -Nano Stores x 21,455 ops/sec ±1.00% (66 runs sampled) [updatedFieldsCount: 114136, renderFieldsCount: 114136] -Recoil x 11,504 ops/sec ±3.44% (63 runs sampled) [updatedFieldsCount: 61553, renderFieldsCount: 61554] -Redux x 13,070 ops/sec ±2.73% (62 runs sampled) [updatedFieldsCount: 69239, renderFieldsCount: 69240] -Valtio x 9,962 ops/sec ±2.60% (60 runs sampled) [updatedFieldsCount: 54290, renderFieldsCount: 108579] +1. Agile nested State..27992 ops/se ±1.73 (64 runs sampled) +2. Pulse Collection....25547 ops/se ±1.04 (64 runs sampled) +3. Agile State.........23962 ops/se ±2.16 (64 runs sampled) +4. Nano Stores.........20662 ops/se ±1.76 (65 runs sampled) +5. Hookstate...........19430 ops/se ±1.81 (61 runs sampled) +6. Agile Collection....18491 ops/se ±2.13 (65 runs sampled) +7. Jotai...............16029 ops/se ±3.39 (62 runs sampled) +8. Mobx................15631 ops/se ±3.42 (61 runs sampled) +9. Redux...............12698 ops/se ±2.86 (61 runs sampled) +10. Recoil..............11183 ops/se ±3.73 (61 runs sampled) +11. Valtio..............9728 ops/se ±2.81 (62 runs sampled) + +Fastest is Agile nested State // 10 Fields -Agile Collection x 10,651 ops/sec ±4.14% (58 runs sampled) [updatedFieldsCount: 56668, renderFieldsCount: 582] -Agile State x 16,175 ops/sec ±1.55% (65 runs sampled) [updatedFieldsCount: 87481, renderFieldsCount: 80] -Agile nested State x 20,703 ops/sec ±1.27% (65 runs sampled) [updatedFieldsCount: 113946, renderFieldsCount: 712] -Hookstate x 18,733 ops/sec ±3.14% (59 runs sampled) [updatedFieldsCount: 105792, renderFieldsCount: 105801] -Jotai x 15,602 ops/sec ±3.65% (61 runs sampled) [updatedFieldsCount: 85977, renderFieldsCount: 85986] -Mobx x 9,283 ops/sec ±3.16% (52 runs sampled) [updatedFieldsCount: 50806, renderFieldsCount: 508060] -Nano Stores x 20,125 ops/sec ±1.62% (62 runs sampled) [updatedFieldsCount: 108704, renderFieldsCount: 108713] -Recoil x 11,103 ops/sec ±4.50% (61 runs sampled) [updatedFieldsCount: 62920, renderFieldsCount: 62939] -Redux x 8,728 ops/sec ±1.61% (64 runs sampled) [updatedFieldsCount: 50794, renderFieldsCount: 507950] -Valtio x 3,557 ops/sec ±2.96% (23 runs sampled) [updatedFieldsCount: 22473, renderFieldsCount: 449450] +1. Agile nested State..27658 ops/se ±1.99 (64 runs sampled) +2. Pulse Collection....24839 ops/se ±1.31 (65 runs sampled) +3. Agile State.........19853 ops/se ±2.15 (64 runs sampled) +4. Nano Stores.........19479 ops/se ±2.12 (60 runs sampled) +5. Hookstate...........18104 ops/se ±3.37 (60 runs sampled) +6. Jotai...............15472 ops/se ±2.45 (62 runs sampled) +7. Agile Collection....13352 ops/se ±3.67 (61 runs sampled) +8. Recoil..............10522 ops/se ±3.79 (58 runs sampled) +9. Mobx................9477 ops/se ±1.94 (62 runs sampled) +10. Redux...............8434 ops/se ±2.67 (47 runs sampled) +11. Valtio..............3532 ops/se ±2.27 (23 runs sampled) + +Fastest is Agile nested State // 100 Fields -Agile Collection x 3,897 ops/sec ±3.01% (25 runs sampled) [updatedFieldsCount: 24427, renderFieldsCount: 2502] -Agile State x 8,355 ops/sec ±0.85% (67 runs sampled) [updatedFieldsCount: 46249, renderFieldsCount: 173] -Agile nested State x 18,641 ops/sec ±1.17% (63 runs sampled) [updatedFieldsCount: 98669, renderFieldsCount: 6802] -Hookstate x 14,865 ops/sec ±2.51% (61 runs sampled) [updatedFieldsCount: 81616, renderFieldsCount: 81715] -Jotai x 12,676 ops/sec ±3.09% (61 runs sampled) [updatedFieldsCount: 65930, renderFieldsCount: 66029] -Mobx x 1,812 ops/sec ±1.49% (63 runs sampled) [updatedFieldsCount: 9639, renderFieldsCount: 963900] -Nano Stores x 16,283 ops/sec ±1.39% (62 runs sampled) [updatedFieldsCount: 84772, renderFieldsCount: 84871] -Recoil x 9,418 ops/sec ±2.94% (62 runs sampled) [updatedFieldsCount: 52425, renderFieldsCount: 52624] -Redux x 1,896 ops/sec ±1.74% (62 runs sampled) [updatedFieldsCount: 10133, renderFieldsCount: 1013400] -Valtio x 472 ops/sec ±2.97% (61 runs sampled) [updatedFieldsCount: 2494, renderFieldsCount: 498700] +1. Agile nested State..24124 ops/se ±1.05 (65 runs sampled) +2. Pulse Collection....21912 ops/se ±1.35 (66 runs sampled) +3. Nano Stores.........15638 ops/se ±1.63 (62 runs sampled) +4. Hookstate...........13986 ops/se ±2.28 (59 runs sampled) +5. Jotai...............12167 ops/se ±2.78 (63 runs sampled) +6. Agile State.........9175 ops/se ±1.56 (51 runs sampled) +7. Recoil..............8717 ops/se ±3.51 (49 runs sampled) +8. Agile Collection....4177 ops/se ±1.64 (61 runs sampled) +9. Redux...............1763 ops/se ±1.06 (63 runs sampled) +10. Mobx................1699 ops/se ±1.82 (62 runs sampled) +11. Valtio..............432 ops/se ±2.18 (60 runs sampled) + +Fastest is Agile nested State // 1000 Fields -Agile Collection x 503 ops/sec ±2.23% (62 runs sampled) [updatedFieldsCount: 2616, renderFieldsCount: 3520] -Agile State x 1,437 ops/sec ±1.48% (59 runs sampled) [updatedFieldsCount: 7569, renderFieldsCount: 1061] -Agile nested State x 9,411 ops/sec ±1.54% (56 runs sampled) [updatedFieldsCount: 46693, renderFieldsCount: 33243] -Hookstate x 4,539 ops/sec ±3.61% (27 runs sampled) [updatedFieldsCount: 26381, renderFieldsCount: 27380] -Jotai x 4,014 ops/sec ±5.35% (53 runs sampled) [updatedFieldsCount: 20390, renderFieldsCount: 21389] -Mobx x 151 ops/sec ±0.75% (59 runs sampled) [updatedFieldsCount: 786, renderFieldsCount: 786000] -Nano Stores x 5,511 ops/sec ±6.27% (32 runs sampled) [updatedFieldsCount: 31266, renderFieldsCount: 32265] -Recoil x 3,562 ops/sec ±3.16% (58 runs sampled) [updatedFieldsCount: 18503, renderFieldsCount: 20502] -Redux x 165 ops/sec ±1.40% (57 runs sampled) [updatedFieldsCount: 858, renderFieldsCount: 859000] -Valtio x 38.76 ops/sec ±5.50% (42 runs sampled) [updatedFieldsCount: 215, renderFieldsCount: 429000] +1. Agile nested State..10756 ops/se ±1.43 (58 runs sampled) +2. Pulse Collection....9774 ops/se ±2.39 (58 runs sampled) +3. Hookstate...........4737 ops/se ±4.33 (58 runs sampled) +4. Nano Stores.........4638 ops/se ±6.40 (28 runs sampled) +5. Jotai...............3352 ops/se ±4.17 (53 runs sampled) +6. Recoil..............3139 ops/se ±4.69 (54 runs sampled) +7. Agile State.........1389 ops/se ±1.52 (57 runs sampled) +8. Agile Collection....500 ops/se ±1.89 (61 runs sampled) +9. Redux...............154 ops/se ±1.48 (57 runs sampled) +10. Mobx................144 ops/se ±1.06 (55 runs sampled) +11. Valtio..............37 ops/se ±4.26 (40 runs sampled) + +Fastest is Agile nested State ``` ## Computed ```ts -Agile Hard Coded x 23,201 ops/sec ±1.39% (64 runs sampled) -Agile Auto Tracking x 22,661 ops/sec ±3.31% (60 runs sampled) -Jotai x 18,489 ops/sec ±5.43% (62 runs sampled) -Recoil x 10,312 ops/sec ±2.57% (64 runs sampled) +1. Agile Hard Coded....32079 ops/se ±1.51 (62 runs sampled) +2. Agile Auto Tracking.30974 ops/se ±2.21 (64 runs sampled) +3. Nano Stores.........28821 ops/se ±1.49 (64 runs sampled) +4. Jotai...............18922 ops/se ±2.12 (61 runs sampled) +5. Recoil..............10103 ops/se ±2.47 (64 runs sampled) + +Fastest is Agile Hard Coded ``` ## 🏃 Running Benchmarks diff --git a/benchmark/benchmarkManager.ts b/benchmark/benchmarkManager.ts index 3c144d9a..73cd210f 100644 --- a/benchmark/benchmarkManager.ts +++ b/benchmark/benchmarkManager.ts @@ -19,12 +19,16 @@ export function getCycleResult(event: any): CycleResultInterface { } export function startBenchmarkLog(testSuiteName: string): void { - console.log(`{white Starting Benchmark "{cyan.bold ${testSuiteName}}"..}`); + console.log(`{white Starting Benchmark "{magenta.bold ${testSuiteName}}"..}`); } -export function cycleLog(cycleResult: CycleResultInterface): void { +export function cycleLog( + cycleResult: CycleResultInterface, + ...addition: any[] +): void { console.log( - `{gray ..Proceeded {green.bold ${cycleResult.name}} - {yellow ${cycleResult.opsInSec} ops/sec}}` + `{gray ..Proceeded {green.bold ${cycleResult.name}} - {yellow ${cycleResult.opsInSec} ops/sec}}`, + ...addition ); } @@ -33,7 +37,9 @@ export function endBenchmarkLog( results: CycleResultInterface[], fastest: string[] ): void { - console.log(`{white ..End Benchmark "{cyan.bold ${testSuiteName}}"..}\n\n`); + console.log( + `{white ..End Benchmark "{magenta.bold ${testSuiteName}}"..}\n\n` + ); results.sort((a, b) => { if (a.opsInSec < b.opsInSec) return 1; @@ -48,7 +54,7 @@ export function endBenchmarkLog( const cycleString = `{bold.bgGreen ${ i + 1 }.} {bold.blue ${cycleResult.name - .padEnd(15, '.') + .padEnd(20, '.') .replace(/(\.+)$/, '{red $1}')}}{yellow ${ cycleResult.opsInSec } ops/se} ±${cycleResult.failRate} (${ diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index bb42d83e..8dc19a90 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -15,6 +15,13 @@ import nanostores from './bench/nanostores'; import recoil from './bench/recoil'; import redux from './bench/redux'; import valtio from './bench/valtio'; +import { + cycleLog, + CycleResultInterface, + endBenchmarkLog, + getCycleResult, + startBenchmarkLog, +} from '../../../benchmarkManager'; // @ts-ignore // Benchmark.js requires an instance of itself globally @@ -66,14 +73,16 @@ function configTest( }; } +const results: CycleResultInterface[] = []; + // Add Tests to the Benchmark Test Suite suite .add('Agile Collection', configTest(agileCollection)) .add('Agile State', configTest(agileState)) .add('Agile nested State', configTest(agileNestedState)) .add('Pulse Collection', configTest(pulseCollection)) - .add('Pulse State', configTest(pulseState)) - .add('Pulse nested State', configTest(pulseNestedState)) + // .add('Pulse State', configTest(pulseState)) + // .add('Pulse nested State', configTest(pulseNestedState)) .add('Hookstate', configTest(hookstate)) .add('Jotai', configTest(jotai)) .add('Mobx', configTest(mobx)) @@ -84,16 +93,18 @@ suite // Add Listener .on('start', function (this: any) { - console.log(`Starting ${this.name}`); + startBenchmarkLog(this.name); }) .on('cycle', (event: any) => { - console.log( - String(event.target), + const cycleResult = getCycleResult(event); + cycleLog( + cycleResult, `[updatedFieldsCount: ${event.target.updatedFieldsCount}, renderFieldsCount: ${event.target.renderFieldsCount}]` ); + results.push(cycleResult); }) .on('complete', function (this: any) { - console.log(`Fastest is ${this.filter('fastest').map('name')}`); + endBenchmarkLog(this.name, results, this.filter('fastest').map('name')); // @ts-ignore // Notify server that the Benchmark Test Suite has ended diff --git a/benchmark/benchmarks/react/computed/index.ts b/benchmark/benchmarks/react/computed/index.ts index 3c089e8a..6bbb0e0a 100644 --- a/benchmark/benchmarks/react/computed/index.ts +++ b/benchmark/benchmarks/react/computed/index.ts @@ -7,13 +7,20 @@ import agileHardCoded from './bench/agilets/hardCoded'; import jotai from './bench/jotai'; import nanostores from './bench/nanostores'; import recoil from './bench/recoil'; +import { + cycleLog, + CycleResultInterface, + endBenchmarkLog, + getCycleResult, + startBenchmarkLog, +} from '../../../benchmarkManager'; // @ts-ignore // Benchmark.js requires an instance of itself globally window.Benchmark = Benchmark; // Create new Benchmark Test Suite -const suite = new Suite('Count'); +const suite = new Suite('Computed'); // Retrieve the Element to render the Benchmark Test Suite in const target = document.getElementById('bench')!; @@ -53,6 +60,8 @@ function configTest(renderElement: (target: HTMLElement) => void): Options { }; } +const results: CycleResultInterface[] = []; + // Add Tests to the Benchmark Test Suite suite .add('Agile Auto Tracking', configTest(agileAutoTracking)) @@ -63,16 +72,18 @@ suite // Add Listener .on('start', function (this: any) { - console.log(`Starting ${this.name}`); + startBenchmarkLog(this.name); }) .on('cycle', (event: any) => { - console.log( - String(event.target), + const cycleResult = getCycleResult(event); + cycleLog( + cycleResult, `[Count: ${event.target.output}, ComputedCount: ${event.target.computedOutput}]` ); + results.push(cycleResult); }) .on('complete', function (this: any) { - console.log(`Fastest is ${this.filter('fastest').map('name')}`); + endBenchmarkLog(this.name, results, this.filter('fastest').map('name')); // @ts-ignore // Notify server that the Benchmark Test Suite has ended diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index 447bca69..0878dbd7 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -81,7 +81,7 @@ suite }) .on('cycle', (event: any) => { const cycleResult = getCycleResult(event); - cycleLog(cycleResult); + cycleLog(cycleResult, `[Count: ${event.target.output}]`); results.push(cycleResult); }) .on('complete', function (this: any) { diff --git a/benchmark/run.ts b/benchmark/run.ts index 88207646..2bd03642 100644 --- a/benchmark/run.ts +++ b/benchmark/run.ts @@ -17,7 +17,7 @@ if (entry == null) { } const startBenchmark = async () => { - console.log(chalk.blue('Starting the benchmark server..')); + console.log(chalk.blue('Starting the benchmark server..\n')); // Bundle Benchmark Test Suite // and launch the server on which the Test Suite is executed @@ -42,9 +42,9 @@ const startBenchmark = async () => { const serverUrl = `http://${server.host}:${server.port}`; console.log( - chalk.cyan( - `Server is running at port: ${chalk.magenta.bold(server.port)}\n` - ) + `${chalk.blue('[i]')} ${chalk.gray( + `Server is running at port: ${chalk.blueBright.bold(server.port)}` + )}` ); // Launch Chrome as browser to run the Benchmark Test Suite in @@ -56,13 +56,15 @@ const startBenchmark = async () => { if (process.env.MANUAL_BENCHMARK === 'true') { console.log( `${chalk.blue('[i]')} ${chalk.gray( - `Benchmark is running at ${chalk.blue.bold(serverUrl)}` + `Benchmark is running at ${chalk.blueBright.bold(serverUrl)}` )}` ); await server.wait; } + console.log('\n'); + // Setup 'pageerror' listener to throw occurring errors in the local console // https://playwright.dev/docs/api/class-page/#page-event-page-error page.on('pageerror', (error) => { From 721040382df7ce8a12abb59e7d5b048edaac2cad Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 10 Jul 2021 12:49:09 +0200 Subject: [PATCH 51/55] replaced defined config with a basic merge because defineConfig is slow --- benchmark/README.md | 27 ++++--- packages/core/src/agile.ts | 7 +- .../src/collection/collection.persistent.ts | 8 +- .../src/collection/group/group.observer.ts | 6 +- packages/core/src/collection/group/index.ts | 13 ++-- packages/core/src/collection/index.ts | 73 +++++++++++-------- packages/core/src/collection/item.ts | 13 ++-- packages/core/src/collection/selector.ts | 11 +-- packages/core/src/computed/index.ts | 22 +++--- packages/core/src/integrations/index.ts | 7 +- packages/core/src/runtime/index.ts | 22 ++++-- packages/core/src/runtime/observer.ts | 11 +-- packages/core/src/runtime/runtime.job.ts | 7 +- .../container/SubscriptionContainer.ts | 12 +-- .../runtime/subscription/sub.controller.ts | 6 +- packages/core/src/state/index.ts | 28 ++++--- packages/core/src/state/state.observer.ts | 6 +- packages/core/src/state/state.persistent.ts | 8 +- packages/core/src/state/state.runtime.job.ts | 6 +- packages/core/src/storages/index.ts | 8 +- packages/core/src/storages/persistent.ts | 15 ++-- packages/core/src/storages/storage.ts | 6 +- 22 files changed, 172 insertions(+), 150 deletions(-) diff --git a/benchmark/README.md b/benchmark/README.md index 3e5ae740..ce2e9db2 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -10,20 +10,19 @@ https://stackoverflow.com/questions/28524653/what-do-the-results-from-benchmark- ## Counter Benchmark ```ts -1. PulseJs.............41599 ops/se ±1.04 (61 runs sampled) -2. AgileTs.............40847 ops/se ±2.55 (61 runs sampled) -3. Nano Stores.........32107 ops/se ±1.27 (64 runs sampled) -4. Zustand.............29314 ops/se ±1.40 (62 runs sampled) -5. Redux...............29111 ops/se ±1.29 (66 runs sampled) -6. Hookstate...........28380 ops/se ±4.79 (61 runs sampled) -7. Mobx................27800 ops/se ±2.89 (64 runs sampled) -8. Jotai...............22926 ops/se ±4.17 (64 runs sampled) -9. Redux-Toolkit.......22157 ops/se ±2.26 (65 runs sampled) -10. Valtio..............20401 ops/se ±1.30 (63 runs sampled) -11. Recoil..............13943 ops/se ±5.10 (59 runs sampled) - -Fastest is PulseJs - +1. AgileTs.............43028 ops/se ±2.45 (63 runs sampled) +2. PulseJs.............41086 ops/se ±2.60 (63 runs sampled) +3. Nano Stores.........31933 ops/se ±1.27 (63 runs sampled) +4. Zustand.............29329 ops/se ±1.30 (62 runs sampled) +5. Redux...............28845 ops/se ±2.47 (61 runs sampled) +6. Hookstate...........27555 ops/se ±5.00 (59 runs sampled) +7. Mobx................27427 ops/se ±3.69 (62 runs sampled) +8. Redux-Toolkit.......22191 ops/se ±1.06 (65 runs sampled) +9. Jotai...............22157 ops/se ±4.10 (63 runs sampled) +10. Valtio..............21089 ops/se ±0.77 (63 runs sampled) +11. Recoil..............13926 ops/se ±2.12 (62 runs sampled) + +Fastest is AgileTs ``` ## 1000 Fields diff --git a/packages/core/src/agile.ts b/packages/core/src/agile.ts index 985a0554..85c03544 100644 --- a/packages/core/src/agile.ts +++ b/packages/core/src/agile.ts @@ -13,7 +13,6 @@ import { Storages, CreateStorageConfigInterface, RegisterConfigInterface, - defineConfig, StateConfigInterface, flatMerge, LogCodeManager, @@ -73,14 +72,14 @@ export class Agile { * @param config - Configuration object */ constructor(config: CreateAgileConfigInterface = {}) { - config = defineConfig(config, { + config = { localStorage: false, waitForMount: true, - logConfig: {}, bindGlobal: false, autoIntegrate: true, bucket: true, - }); + ...config, + }; this.config = { waitForMount: config.waitForMount as any, bucket: config.bucket as any, diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 86b049a3..7e8f5b70 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -3,7 +3,6 @@ import { CollectionKey, CreatePersistentConfigInterface, DefaultItem, - defineConfig, Group, GroupKey, ItemKey, @@ -37,11 +36,12 @@ export class CollectionPersistent< super(collection.agileInstance(), { instantiate: false, }); - config = defineConfig(config, { + config = { instantiate: true, storageKeys: [], - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ...config, + }; this.collection = () => collection; this.instantiatePersistent({ key: config.key, diff --git a/packages/core/src/collection/group/group.observer.ts b/packages/core/src/collection/group/group.observer.ts index d298f653..c9da7654 100644 --- a/packages/core/src/collection/group/group.observer.ts +++ b/packages/core/src/collection/group/group.observer.ts @@ -3,7 +3,6 @@ import { Group, CreateObserverConfigInterface, copy, - defineConfig, equal, generateId, RuntimeJob, @@ -68,7 +67,7 @@ export class GroupObserver extends Observer { config: GroupIngestConfigInterface = {} ): void { const group = this.group(); - config = defineConfig(config, { + config = { perform: true, background: false, sideEffects: { @@ -77,7 +76,8 @@ export class GroupObserver extends Observer { }, force: false, maxTriesToUpdate: 3, - }); + ...config, + }; // Force overwriting the Group value if it is a placeholder. // After assigning a value to the Group, the Group is supposed to be no placeholder anymore. diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index af25f737..03f1d413 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -3,7 +3,6 @@ import { Collection, DefaultItem, ItemKey, - defineConfig, normalizeArray, Item, copy, @@ -184,10 +183,11 @@ export class Group< const notExistingItemKeysInCollection: Array = []; const existingItemKeys: Array = []; let newGroupValue = copy(this.nextStateValue); - config = defineConfig(config, { + config = { method: 'push', overwrite: false, - }); + ...config, + }; // Add itemKeys to Group _itemKeys.forEach((itemKey) => { @@ -304,12 +304,13 @@ export class Group< key = keyOrConfig as PersistentKey; } - _config = defineConfig(_config, { + _config = { loadValue: true, followCollectionPersistKeyPattern: true, storageKeys: [], - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ..._config, + }; // Create storageItemKey based on Collection key/name identifier if (_config.followCollectionPersistKeyPattern) { diff --git a/packages/core/src/collection/index.ts b/packages/core/src/collection/index.ts index 32272973..14d707cb 100644 --- a/packages/core/src/collection/index.ts +++ b/packages/core/src/collection/index.ts @@ -7,7 +7,6 @@ import { SelectorKey, StorageKey, GroupConfigInterface, - defineConfig, isValidObject, normalizeArray, copy, @@ -72,12 +71,13 @@ export class Collection< constructor(agileInstance: Agile, config: CollectionConfig = {}) { this.agileInstance = () => agileInstance; let _config = typeof config === 'function' ? config(this) : config; - _config = defineConfig(_config, { + _config = { primaryKey: 'id', groups: {}, selectors: {}, defaultGroupKey: 'default', - }); + ..._config, + }; this._key = _config.key; this.config = { defaultGroupKey: _config.defaultGroupKey as any, @@ -304,12 +304,13 @@ export class Collection< const _groupKeys = normalizeArray(groupKeys); const defaultGroupKey = this.config.defaultGroupKey; const primaryKey = this.config.primaryKey; - config = defineConfig(config, { + config = { method: 'push', background: false, patch: false, select: false, - }); + ...config, + }; // Add default groupKey, since all Items are added to the default Group if (!_groupKeys.includes(defaultGroupKey)) _groupKeys.push(defaultGroupKey); @@ -373,10 +374,11 @@ export class Collection< ): Item | undefined { const item = this.getItem(itemKey, { notExisting: true }); const primaryKey = this.config.primaryKey; - config = defineConfig(config, { + config = { patch: true, background: false, - }); + ...config, + }; // Check if the given conditions are suitable for a update action if (item == null) { @@ -405,9 +407,10 @@ export class Collection< let patchConfig: { addNewProperties?: boolean } = typeof config.patch === 'object' ? config.patch : {}; - patchConfig = defineConfig(patchConfig, { + patchConfig = { addNewProperties: true, - }); + ...patchConfig, + }; item.patch(changes as any, { background: config.background, @@ -495,9 +498,10 @@ export class Collection< groupKey: GroupKey | undefined | null, config: HasConfigInterface = {} ): Group | undefined { - config = defineConfig(config, { + config = { notExisting: false, - }); + ...config, + }; // Retrieve Group const group = groupKey ? this.groups[groupKey] : undefined; @@ -668,9 +672,10 @@ export class Collection< selectorKey: SelectorKey | undefined | null, config: HasConfigInterface = {} ): Selector | undefined { - config = defineConfig(config, { + config = { notExisting: false, - }); + ...config, + }; // Get Selector const selector = selectorKey ? this.selectors[selectorKey] : undefined; @@ -775,9 +780,10 @@ export class Collection< itemKey: ItemKey | undefined | null, config: HasConfigInterface = {} ): Item | undefined { - config = defineConfig(config, { + config = { notExisting: false, - }); + ...config, + }; // Get Item const item = itemKey != null ? this.data[itemKey] : undefined; @@ -879,9 +885,10 @@ export class Collection< * @param config - Configuration object */ public getAllItems(config: HasConfigInterface = {}): Array> { - config = defineConfig(config, { + config = { notExisting: false, - }); + ...config, + }; const defaultGroup = this.getDefaultGroup(); let items: Array> = []; @@ -962,11 +969,12 @@ export class Collection< key = keyOrConfig as StorageKey; } - _config = defineConfig(_config, { + _config = { loadValue: true, storageKeys: [], - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ..._config, + }; // Check if Collection is already persisted if (this.persistent != null && this.isPersisted) return this; @@ -1105,9 +1113,10 @@ export class Collection< config: UpdateItemKeyConfigInterface = {} ): boolean { const item = this.getItem(oldItemKey, { notExisting: true }); - config = defineConfig(config, { + config = { background: false, - }); + ...config, + }; if (item == null || oldItemKey === newItemKey) return false; @@ -1277,10 +1286,11 @@ export class Collection< itemKeys: ItemKey | Array, config: RemoveItemsConfigInterface = {} ): this { - config = defineConfig(config, { + config = { notExisting: false, removeSelector: false, - }); + ...config, + }; const _itemKeys = normalizeArray(itemKeys); _itemKeys.forEach((itemKey) => { @@ -1339,10 +1349,11 @@ export class Collection< data: DataType, config: AssignDataConfigInterface = {} ): boolean { - config = defineConfig(config, { + config = { patch: false, background: false, - }); + ...config, + }; const _data = copy(data); // Copy data object to get rid of reference const primaryKey = this.config.primaryKey; @@ -1397,10 +1408,11 @@ export class Collection< item: Item, config: AssignItemConfigInterface = {} ): boolean { - config = defineConfig(config, { + config = { overwrite: false, background: false, - }); + ...config, + }; const primaryKey = this.config.primaryKey; let itemKey = item._value[primaryKey]; let increaseCollectionSize = true; @@ -1457,13 +1469,14 @@ export class Collection< itemKey: ItemKey, config: RebuildGroupsThatIncludeItemKeyConfigInterface = {} ): void { - config = defineConfig(config, { + config = { background: false, sideEffects: { enabled: true, exclude: [], }, - }); + ...config, + }; // Rebuild Groups that include itemKey for (const groupKey in this.groups) { diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index 5375b143..7797e042 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -3,7 +3,6 @@ import { Collection, StateKey, StateRuntimeJobConfigInterface, - defineConfig, SelectorKey, PersistentKey, isValidObject, @@ -63,7 +62,7 @@ export class Item extends State< config: StateRuntimeJobConfigInterface = {} ): this { super.setKey(value); - config = defineConfig(config, { + config = { sideEffects: { enabled: true, exclude: [], @@ -72,7 +71,8 @@ export class Item extends State< force: false, storage: true, overwrite: false, - }); + ...config, + }; if (value == null) return this; // Update 'rebuildGroupsThatIncludeItemKey' side effect to the new itemKey @@ -129,12 +129,13 @@ export class Item extends State< key = keyOrConfig as PersistentKey; } - _config = defineConfig(_config, { + _config = { loadValue: true, followCollectionPersistKeyPattern: true, storageKeys: [], - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ..._config, + }; // Create storageItemKey based on Collection key/name identifier if (_config.followCollectionPersistKeyPattern) { diff --git a/packages/core/src/collection/selector.ts b/packages/core/src/collection/selector.ts index 937c5458..7937dd66 100644 --- a/packages/core/src/collection/selector.ts +++ b/packages/core/src/collection/selector.ts @@ -1,7 +1,6 @@ import { Collection, DefaultItem, - defineConfig, Item, ItemKey, State, @@ -41,9 +40,10 @@ export class Selector< itemKey: ItemKey | null, config: SelectorConfigInterface = {} ) { - config = defineConfig(config, { + config = { isPlaceholder: false, - }); + ...config, + }; super(collection.agileInstance(), null, config); this.collection = () => collection; this._item = null; @@ -116,7 +116,7 @@ export class Selector< itemKey: ItemKey | null, config: StateRuntimeJobConfigInterface = {} ): this { - config = defineConfig(config, { + config = { background: false, sideEffects: { enabled: true, @@ -125,7 +125,8 @@ export class Selector< force: false, overwrite: this._item?.isPlaceholder ?? false, storage: true, - }); + ...config, + }; // Don't select Item if Collection is not correctly instantiated yet // (because only after a successful instantiation the Collection diff --git a/packages/core/src/computed/index.ts b/packages/core/src/computed/index.ts index b166deb1..efc19b5e 100644 --- a/packages/core/src/computed/index.ts +++ b/packages/core/src/computed/index.ts @@ -1,12 +1,10 @@ import { State, Agile, - defineConfig, Observer, StateConfigInterface, ComputedTracker, Collection, - extractObservers, StateIngestConfigInterface, removeProperties, LogCodeManager, @@ -52,10 +50,11 @@ export class Computed extends State< key: config.key, dependents: config.dependents, }); - config = defineConfig(config, { + config = { computedDeps: [], autodetect: !isAsyncFunction(computeFunction), - }); + ...config, + }; this.agileInstance = () => agileInstance; this.computeFunction = computeFunction; this.config = { @@ -87,9 +86,10 @@ export class Computed extends State< * @param config - Configuration object */ public recompute(config: RecomputeConfigInterface = {}): this { - config = defineConfig(config, { + config = { autodetect: false, - }); + ...config, + }; this.compute({ autodetect: config.autodetect }).then((result) => { this.observers['value'].ingestValue( result, @@ -120,9 +120,10 @@ export class Computed extends State< deps: Array = [], config: RecomputeConfigInterface = {} ): this { - config = defineConfig(config, { + config = { autodetect: this.config.autodetect, - }); + ...config, + }; // Make this Observer no longer depend on the old dep Observers this.deps.forEach((observer) => { @@ -160,9 +161,10 @@ export class Computed extends State< public async compute( config: ComputeConfigInterface = {} ): Promise { - config = defineConfig(config, { + config = { autodetect: this.config.autodetect, - }); + ...config, + }; // Start auto tracking of Observers on which the computeFunction might depend if (config.autodetect) ComputedTracker.track(); diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts index 442d3814..a7421b18 100644 --- a/packages/core/src/integrations/index.ts +++ b/packages/core/src/integrations/index.ts @@ -1,4 +1,4 @@ -import { Agile, defineConfig, Integration, LogCodeManager } from '../internal'; +import { Agile, Integration, LogCodeManager } from '../internal'; const onRegisterInitialIntegrationCallbacks: (( integration: Integration @@ -57,9 +57,10 @@ export class Integrations { * @param config - Configuration object */ constructor(agileInstance: Agile, config: IntegrationsConfigInterface = {}) { - config = defineConfig(config, { + config = { autoIntegrate: true, - }); + ...config, + }; this.agileInstance = () => agileInstance; if (config.autoIntegrate) { diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index d29c7dc5..f5222a3b 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -4,7 +4,6 @@ import { RuntimeJob, CallbackSubscriptionContainer, ComponentSubscriptionContainer, - defineConfig, notEqual, LogCodeManager, } from '../internal'; @@ -67,9 +66,10 @@ export class Runtime { * @param config - Configuration object */ public ingest(job: RuntimeJob, config: IngestConfigInterface = {}): void { - config = defineConfig(config, { + config = { perform: !this.isPerformingJobs, - }); + ...config, + }; // Add specified Job to the queue this.jobQueue.push(job); @@ -181,9 +181,13 @@ export class Runtime { public extractToUpdateSubscriptionContainer( jobs: Array ): Array { + // https://medium.com/@bretcameron/how-to-make-your-code-faster-using-javascript-sets-b432457a4a77 const subscriptionsToUpdate = new Set(); - jobs.forEach((job) => { + // Using for loop for performance optimization + // https://stackoverflow.com/questions/43821759/why-array-foreach-is-slower-than-for-loop-in-javascript + for (let i = 0; i < jobs.length; i++) { + const job = jobs[i]; job.subscriptionContainersToUpdate.forEach((subscriptionContainer) => { let updateSubscriptionContainer = true; @@ -235,7 +239,7 @@ export class Runtime { job.subscriptionContainersToUpdate.delete(subscriptionContainer); }); - }); + } return Array.from(subscriptionsToUpdate); } @@ -252,7 +256,11 @@ export class Runtime { public updateSubscriptionContainer( subscriptionsToUpdate: Array ): void { - subscriptionsToUpdate.forEach((subscriptionContainer) => { + // Using for loop for performance optimization + // https://stackoverflow.com/questions/43821759/why-array-foreach-is-slower-than-for-loop-in-javascript + for (let i = 0; i < subscriptionsToUpdate.length; i++) { + const subscriptionContainer = subscriptionsToUpdate[i]; + // Call 'callback function' if Callback based Subscription if (subscriptionContainer instanceof CallbackSubscriptionContainer) subscriptionContainer.callback(); @@ -265,7 +273,7 @@ export class Runtime { ); subscriptionContainer.updatedSubscribers.clear(); - }); + } LogCodeManager.logIfTags( ['runtime'], diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index bce9e5fe..4897a059 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -3,7 +3,6 @@ import { StateKey, RuntimeJob, SubscriptionContainer, - defineConfig, IngestConfigInterface, CreateRuntimeJobConfigInterface, LogCodeManager, @@ -60,10 +59,11 @@ export class Observer { agileInstance: Agile, config: CreateObserverConfigInterface = {} ) { - config = defineConfig(config, { + config = { dependents: [], subs: [], - }); + ...config, + }; this.agileInstance = () => agileInstance; this._key = config.key; this.value = config.value; @@ -104,7 +104,7 @@ export class Observer { * @param config - Configuration object */ public ingest(config: ObserverIngestConfigInterface = {}): void { - config = defineConfig(config, { + config = { perform: true, background: false, sideEffects: { @@ -112,7 +112,8 @@ export class Observer { exclude: [], }, force: false, - }); + ...config, + }; // Create Runtime-Job const job = new RuntimeJob(this, { diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts index d3eea3f2..84fbb5f1 100644 --- a/packages/core/src/runtime/runtime.job.ts +++ b/packages/core/src/runtime/runtime.job.ts @@ -1,4 +1,4 @@ -import { Observer, defineConfig, SubscriptionContainer } from '../internal'; +import { Observer, SubscriptionContainer } from '../internal'; export class RuntimeJob { public config: RuntimeJobConfigInterface; @@ -32,7 +32,7 @@ export class RuntimeJob { observer: ObserverType, config: CreateRuntimeJobConfigInterface = {} ) { - config = defineConfig(config, { + config = { background: false, sideEffects: { enabled: true, @@ -40,7 +40,8 @@ export class RuntimeJob { }, force: false, maxTriesToUpdate: 3, - }); + ...config, + }; this.config = { background: config.background, force: config.force, diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 1ef0c89d..34aca13e 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -1,9 +1,4 @@ -import { - defineConfig, - generateId, - isValidObject, - Observer, -} from '../../../internal'; +import { generateId, isValidObject, Observer } from '../../../internal'; export class SubscriptionContainer { /** @@ -110,9 +105,10 @@ export class SubscriptionContainer { subs: Array | { [key: string]: Observer }, config: SubscriptionContainerConfigInterface = {} ) { - config = defineConfig(config, { + config = { key: generateId(), - }); + ...config, + }; this.subscribers = new Set(); this.key = config.key; diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 51d71831..3f6d22ea 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -6,7 +6,6 @@ import { CallbackSubscriptionContainer, isFunction, SubscriptionContainerConfigInterface, - defineConfig, removeProperties, LogCodeManager, } from '../../internal'; @@ -113,9 +112,10 @@ export class SubController { subscriptionContainer: SubscriptionContainer; props: { [key: string]: Observer['value'] }; } { - config = defineConfig(config, { + config = { waitForMount: this.agileInstance().config.waitForMount, - }); + ...config, + }; // Create Subscription Container based on specified 'integrationInstance' const subscriptionContainer = isFunction(integrationInstance) diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index b70a3a70..6c3cf779 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -2,7 +2,6 @@ import { Agile, StorageKey, copy, - defineConfig, flatMerge, isValidObject, StateObserver, @@ -84,10 +83,11 @@ export class State { initialValue: ValueType, config: StateConfigInterface = {} ) { - config = defineConfig(config, { + config = { dependents: [], isPlaceholder: false, - }); + ...config, + }; this.agileInstance = () => agileInstance; this._key = config.key; this.observers['value'] = new StateObserver(this, { @@ -196,9 +196,10 @@ export class State { value: ValueType | ((value: ValueType) => ValueType), config: StateIngestConfigInterface = {} ): this { - config = defineConfig(config, { + config = { force: false, - }); + ...config, + }; const _value = isFunction(value) ? (value as any)(copy(this._value)) : value; @@ -299,9 +300,10 @@ export class State { targetWithChanges: Object, config: PatchConfigInterface = {} ): this { - config = defineConfig(config, { + config = { addNewProperties: true, - }); + ...config, + }; // Check if the given conditions are suitable for a patch action if (!isValidObject(this.nextStateValue, true)) { @@ -467,11 +469,12 @@ export class State { key = keyOrConfig as PersistentKey; } - _config = defineConfig(_config, { + _config = { loadValue: true, storageKeys: [], - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ..._config, + }; // Check if State is already persisted if (this.persistent != null && this.isPersisted) return this; @@ -697,9 +700,10 @@ export class State { callback: SideEffectFunctionType, config: AddSideEffectConfigInterface = {} ): this { - config = defineConfig(config, { + config = { weight: 10, - }); + ...config, + }; if (!isFunction(callback)) { LogCodeManager.log('00:03:01', ['Side Effect Callback', 'function']); return this; diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index 85598d79..2b874757 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -3,7 +3,6 @@ import { State, Computed, copy, - defineConfig, equal, notEqual, isFunction, @@ -81,7 +80,7 @@ export class StateObserver extends Observer { config: StateIngestConfigInterface = {} ): void { const state = this.state(); - config = defineConfig(config, { + config = { perform: true, background: false, sideEffects: { @@ -92,7 +91,8 @@ export class StateObserver extends Observer { storage: true, overwrite: false, maxTriesToUpdate: 3, - }); + ...config, + }; // Force overwriting the State value if it is a placeholder. // After assigning a value to the State, the State is supposed to be no placeholder anymore. diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 03029ff0..4b544388 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -1,6 +1,5 @@ import { CreatePersistentConfigInterface, - defineConfig, Persistent, PersistentKey, State, @@ -26,11 +25,12 @@ export class StatePersistent extends Persistent { super(state.agileInstance(), { instantiate: false, }); - config = defineConfig(config, { + config = { instantiate: true, storageKeys: [], - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ...config, + }; this.state = () => state; this.instantiatePersistent({ key: config.key, diff --git a/packages/core/src/state/state.runtime.job.ts b/packages/core/src/state/state.runtime.job.ts index 16e1628a..3faf958d 100644 --- a/packages/core/src/state/state.runtime.job.ts +++ b/packages/core/src/state/state.runtime.job.ts @@ -1,5 +1,4 @@ import { - defineConfig, RuntimeJob, RuntimeJobConfigInterface, RuntimeJobKey, @@ -25,7 +24,7 @@ export class StateRuntimeJob extends RuntimeJob { config: CreateStateRuntimeJobConfigInterface = {} ) { super(observer, config); - config = defineConfig(config, { + config = { background: false, sideEffects: { enabled: true, @@ -35,7 +34,8 @@ export class StateRuntimeJob extends RuntimeJob { storage: true, overwrite: false, maxTriesToUpdate: 3, - }); + ...config, + }; this.config = { background: config.background, diff --git a/packages/core/src/storages/index.ts b/packages/core/src/storages/index.ts index b917ca39..75eddab5 100644 --- a/packages/core/src/storages/index.ts +++ b/packages/core/src/storages/index.ts @@ -1,7 +1,6 @@ import { Agile, Storage, - defineConfig, Persistent, StorageKey, StorageItemKey, @@ -34,10 +33,11 @@ export class Storages { config: CreateStoragesConfigInterface = {} ) { this.agileInstance = () => agileInstance; - config = defineConfig(config, { + config = { localStorage: false, - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ...config, + }; this.config = { defaultStorageKey: config.defaultStorageKey as any }; if (config.localStorage) this.instantiateLocalStorage(); } diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index a335d982..abeaab17 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -1,10 +1,4 @@ -import { - Agile, - copy, - defineConfig, - LogCodeManager, - StorageKey, -} from '../internal'; +import { Agile, copy, LogCodeManager, StorageKey } from '../internal'; export class Persistent { // Agile Instance the Persistent belongs to @@ -44,11 +38,12 @@ export class Persistent { ) { this.agileInstance = () => agileInstance; this._key = Persistent.placeHolderKey; - config = defineConfig(config, { + config = { instantiate: true, storageKeys: [], - defaultStorageKey: null, - }); + defaultStorageKey: null as any, + ...config, + }; this.agileInstance().storages.persistentInstances.add(this); this.config = { defaultStorageKey: config.defaultStorageKey as any }; diff --git a/packages/core/src/storages/storage.ts b/packages/core/src/storages/storage.ts index 4bc912fb..01100e4e 100644 --- a/packages/core/src/storages/storage.ts +++ b/packages/core/src/storages/storage.ts @@ -1,6 +1,5 @@ import { isJsonString, - defineConfig, isAsyncFunction, isFunction, LogCodeManager, @@ -27,10 +26,11 @@ export class Storage { * @param config - Configuration object */ constructor(config: CreateStorageConfigInterface) { - config = defineConfig(config, { + config = { prefix: 'agile', async: false, - }); + ...config, + }; this.key = config.key; this.methods = config.methods; this.config = { From 9556767d24e686e10bcb84e15dee62cbc3244cf1 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 10 Jul 2021 15:17:17 +0200 Subject: [PATCH 52/55] removed defineConfig --- benchmark/benchmarkManager.ts | 6 +-- packages/api/src/index.ts | 4 +- packages/core/src/shared.ts | 11 ++-- packages/event/src/event.ts | 6 +-- packages/event/src/shared.ts | 6 +-- packages/logger/src/index.ts | 3 +- packages/logger/src/logger.ts | 17 +++--- packages/multieditor/src/item.ts | 11 ++-- packages/multieditor/src/multieditor.ts | 21 ++++---- .../multieditor/src/status/status.observer.ts | 6 +-- packages/multieditor/src/validator/index.ts | 14 ++--- packages/react/src/hooks/useAgile.ts | 8 +-- packages/utils/src/index.ts | 38 +++---------- packages/utils/tests/unit/utils.test.ts | 53 ------------------- 14 files changed, 60 insertions(+), 144 deletions(-) diff --git a/benchmark/benchmarkManager.ts b/benchmark/benchmarkManager.ts index 73cd210f..dd20e5a1 100644 --- a/benchmark/benchmarkManager.ts +++ b/benchmark/benchmarkManager.ts @@ -37,9 +37,7 @@ export function endBenchmarkLog( results: CycleResultInterface[], fastest: string[] ): void { - console.log( - `{white ..End Benchmark "{magenta.bold ${testSuiteName}}"..}\n\n` - ); + console.log(`{white ..End Benchmark "{magenta.bold ${testSuiteName}}"}\n\n`); results.sort((a, b) => { if (a.opsInSec < b.opsInSec) return 1; @@ -57,7 +55,7 @@ export function endBenchmarkLog( .padEnd(20, '.') .replace(/(\.+)$/, '{red $1}')}}{yellow ${ cycleResult.opsInSec - } ops/se} ±${cycleResult.failRate} (${ + } ops/se} {gray ±${cycleResult.failRate}%} (${ cycleResult.ranSampleCount } runs sampled)`; diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 9c7880ec..e8de6610 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,4 +1,4 @@ -import { clone, copy, defineConfig, isValidObject } from '@agile-ts/utils'; +import { clone, copy, isValidObject } from '@agile-ts/utils'; export default class API { public config: ApiConfig; @@ -113,7 +113,7 @@ export default class API { // Configure request Options const config = copy(this.config); - config.options = defineConfig(options, config.options || {}); + config.options = { ...config.options, ...options }; config.options.method = method; if (!config.options.headers) config.options.headers = {}; diff --git a/packages/core/src/shared.ts b/packages/core/src/shared.ts index a3ac9461..79d792d3 100644 --- a/packages/core/src/shared.ts +++ b/packages/core/src/shared.ts @@ -7,7 +7,6 @@ import { CreateComputedConfigInterface, CreateStorageConfigInterface, DefaultItem, - defineConfig, DependableAgileInstancesType, flatMerge, removeProperties, @@ -74,9 +73,10 @@ export function createState( initialValue: ValueType, config: CreateStateConfigInterfaceWithAgile = {} ): State { - config = defineConfig(config, { + config = { agileInstance: sharedAgileInstance, - }); + ...config, + }; return new State( config.agileInstance as any, initialValue, @@ -170,9 +170,10 @@ export function createComputed( if (configOrDeps) _config = configOrDeps; } - _config = defineConfig(_config, { + _config = { agileInstance: sharedAgileInstance, - }); + ..._config, + }; return new Computed( _config.agileInstance as any, diff --git a/packages/event/src/event.ts b/packages/event/src/event.ts index 415f7d4f..8291b1a6 100644 --- a/packages/event/src/event.ts +++ b/packages/event/src/event.ts @@ -1,6 +1,5 @@ import { Agile, - defineConfig, generateId, isFunction, LogCodeManager, @@ -34,14 +33,15 @@ export class Event { */ constructor(agileInstance: Agile, config: CreateEventConfigInterface = {}) { this.agileInstance = () => agileInstance; - config = defineConfig(config, { + config = { enabled: true, rerender: false, maxUses: undefined, delay: undefined, overlap: false, dependents: [], - }); + ...config, + }; this._key = config.key; this.observer = new EventObserver(this, { key: config.key, diff --git a/packages/event/src/shared.ts b/packages/event/src/shared.ts index 4aa11e38..7aadc8a8 100644 --- a/packages/event/src/shared.ts +++ b/packages/event/src/shared.ts @@ -1,6 +1,5 @@ import { CreateAgileSubInstanceInterface, - defineConfig, removeProperties, shared, } from '@agile-ts/core'; @@ -13,9 +12,10 @@ import { export function createEvent( config: CreateEventConfigInterfaceWithAgile = {} ): Event { - config = defineConfig(config, { + config = { agileInstance: shared, - }); + ...config, + }; return new Event( config.agileInstance as any, removeProperties(config, ['agileInstance']) diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 00aa3e3e..2012864f 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,5 +1,4 @@ import { CreateLoggerConfigInterface, Logger } from './logger'; -import { defineConfig } from '@agile-ts/utils'; const defaultLogConfig = { prefix: 'Agile', @@ -23,7 +22,7 @@ let sharedAgileLogger = new Logger(defaultLogConfig); function assignSharedAgileLoggerConfig( config: CreateLoggerConfigInterface = {} ): Logger { - config = defineConfig(config, defaultLogConfig); + config = { ...defaultLogConfig, ...config }; sharedAgileLogger = new Logger(config); return sharedAgileLogger; } diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts index 12c96a21..b6756030 100644 --- a/packages/logger/src/logger.ts +++ b/packages/logger/src/logger.ts @@ -1,5 +1,4 @@ import { - defineConfig, generateId, includesArray, isFunction, @@ -23,14 +22,15 @@ export class Logger { */ constructor(config: LoggerConfig = {}) { let _config = typeof config === 'function' ? config(this) : config; - _config = defineConfig(_config, { + _config = { prefix: '', allowedTags: [], canUseCustomStyles: true, active: true, level: 0, timestamp: false, - }); + ..._config, + }; this.isActive = _config.active as any; this.allowedTags = _config.allowedTags as any; this.config = { @@ -313,10 +313,12 @@ export class Logger { * @param loggerCategory - Logger Category */ public createLoggerCategory(loggerCategory: LoggerCategoryInterface) { - loggerCategory = defineConfig(loggerCategory, { + loggerCategory = { prefix: '', + // @ts-ignore level: 0, - }); + ...loggerCategory, + }; this.loggerCategories[loggerCategory.key] = loggerCategory; } @@ -365,9 +367,10 @@ export class Logger { _config = config as LoggerWatcherConfigInterface; } - _config = defineConfig(_config, { + _config = { level: 0, - }); + ..._config, + }; // Check if Callback is a Function if (!isFunction(_config.callback)) { diff --git a/packages/multieditor/src/item.ts b/packages/multieditor/src/item.ts index 738eece2..5d1458aa 100644 --- a/packages/multieditor/src/item.ts +++ b/packages/multieditor/src/item.ts @@ -1,8 +1,4 @@ -import { - defineConfig, - State, - StateRuntimeJobConfigInterface, -} from '@agile-ts/core'; +import { State, StateRuntimeJobConfigInterface } from '@agile-ts/core'; import { MultiEditor, Validator, Status, ItemKey } from './internal'; export class Item extends State { @@ -31,9 +27,10 @@ export class Item extends State { super(editor.agileInstance(), data, { key: key, }); - config = defineConfig(config, { + config = { canBeEdited: true, - }); + ...config, + }; this.editor = () => editor; this.validator = editor.getValidator(key); this.config = config; diff --git a/packages/multieditor/src/multieditor.ts b/packages/multieditor/src/multieditor.ts index 91b6a88c..440e2549 100644 --- a/packages/multieditor/src/multieditor.ts +++ b/packages/multieditor/src/multieditor.ts @@ -2,7 +2,6 @@ import { Agile, ComputeValueMethod, copy, - defineConfig, getAgileInstance, LogCodeManager, Observer, @@ -58,14 +57,15 @@ export class MultiEditor< ); this.agileInstance = () => agileInstance as any; let _config = typeof config === 'function' ? config(this) : config; - _config = defineConfig(_config, { + _config = { fixedProperties: [], editableProperties: Object.keys(_config.data), validateMethods: {}, computeMethods: {}, reValidateMode: 'onSubmit', validate: 'editable', - }); + ..._config, + }; this._key = _config?.key; this.onSubmit = _config.onSubmit as any; this.fixedProperties = _config.fixedProperties as any; @@ -167,9 +167,10 @@ export class MultiEditor< ): this { const item = this.getItemById(key); if (!item) return this; - config = defineConfig(config, { + config = { background: true, - }); + ...config, + }; // Apply changes to Item item.set(value, config); @@ -194,10 +195,11 @@ export class MultiEditor< ): this { const item = this.getItemById(key); if (!item) return this; - config = defineConfig(config, { + config = { background: false, reset: true, - }); + ...config, + }; // Update initial Value item.initialStateValue = copy(value); @@ -228,10 +230,11 @@ export class MultiEditor< config: SubmitConfigInterface = {} ): Promise { const preparedData: DataObject = {}; - config = defineConfig(config, { + config = { assignToInitial: true, onSubmitConfig: undefined, - }); + ...config, + }; // Assign Statuses to Items for (const key in this.data) { diff --git a/packages/multieditor/src/status/status.observer.ts b/packages/multieditor/src/status/status.observer.ts index 4805ac20..dd39089b 100644 --- a/packages/multieditor/src/status/status.observer.ts +++ b/packages/multieditor/src/status/status.observer.ts @@ -2,7 +2,6 @@ import { Status, StatusInterface } from '../internal'; import { Agile, copy, - defineConfig, equal, IngestConfigInterface, Observer, @@ -45,7 +44,7 @@ export class StatusObserver extends Observer { * @param config - Config */ public assign(config: StatusIngestConfigInterface = {}): void { - config = defineConfig(config, { + config = { perform: true, background: false, sideEffects: { @@ -53,7 +52,8 @@ export class StatusObserver extends Observer { exclude: [], }, force: false, - }); + ...config, + }; // Set Next Status Value this.nextValue = copy(this.status().nextValue); diff --git a/packages/multieditor/src/validator/index.ts b/packages/multieditor/src/validator/index.ts index 8f0de479..bcc9fe7d 100644 --- a/packages/multieditor/src/validator/index.ts +++ b/packages/multieditor/src/validator/index.ts @@ -1,11 +1,4 @@ -import { - Agile, - copy, - defineConfig, - generateId, - isFunction, - LogCodeManager, -} from '@agile-ts/core'; +import { copy, generateId, isFunction, LogCodeManager } from '@agile-ts/core'; import { DataObject, MultiEditor, @@ -25,9 +18,10 @@ export class Validator { * @param config - Config */ constructor(config: ValidatorConfigInterface = {}) { - this.config = defineConfig(config, { + this.config = { prefix: 'default', - }); + ...config, + }; this._key = this.config.key; } diff --git a/packages/react/src/hooks/useAgile.ts b/packages/react/src/hooks/useAgile.ts index ba532d8f..97fb30d0 100644 --- a/packages/react/src/hooks/useAgile.ts +++ b/packages/react/src/hooks/useAgile.ts @@ -6,7 +6,6 @@ import { Observer, State, SubscriptionContainerKeyType, - defineConfig, isValidObject, generateId, ProxyWeakMapType, @@ -67,14 +66,15 @@ export function useAgile< deps: X | Y, config: AgileHookConfigInterface = {} ): AgileOutputHookArrayType | AgileOutputHookType { - config = defineConfig(config, { + config = { key: generateId(), proxyBased: false, - agileInstance: null, + agileInstance: null as any, componentId: undefined, observerType: undefined, deps: [], - }); + ...config, + }; const depsArray = extractRelevantObservers( normalizeArray(deps), config.observerType diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 4cf3a2f1..736c82f2 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -88,9 +88,10 @@ export function normalizeArray( items?: DataType | Array, config: { createUndefinedArray?: boolean } = {} ): Array { - config = defineConfig(config, { + config = { createUndefinedArray: false, // If it should return [] or [undefined] if the passed Item is undefined - }); + ...config, + }; if (items == null && !config.createUndefinedArray) return []; return Array.isArray(items) ? items : [items as DataType]; } @@ -142,34 +143,6 @@ export function isJsonString(value: any): boolean { return true; } -//========================================================================================================= -// Define Config -//========================================================================================================= -/** - * @internal - * Merges default values/properties into config object - * @param config - Config object that receives default values - * @param defaults - Default values object that gets merged into config object - * @param overwriteUndefinedProperties - If undefined Properties in config gets overwritten by the default value - */ -export function defineConfig( - config: ConfigInterface, - defaults: Object, - overwriteUndefinedProperties?: boolean -): ConfigInterface { - if (overwriteUndefinedProperties === undefined) - overwriteUndefinedProperties = true; - - if (overwriteUndefinedProperties) { - const finalConfig = { ...defaults, ...config }; - for (const key in finalConfig) - if (finalConfig[key] === undefined) finalConfig[key] = defaults[key]; - return finalConfig; - } - - return { ...defaults, ...config }; -} - //========================================================================================================= // Flat Merge //========================================================================================================= @@ -194,9 +167,10 @@ export function flatMerge( changes: Object, config: FlatMergeConfigInterface = {} ): DataType { - config = defineConfig(config, { + config = { addNewProperties: true, - }); + ...config, + }; // Copy Source to avoid References const _source = copy(source); diff --git a/packages/utils/tests/unit/utils.test.ts b/packages/utils/tests/unit/utils.test.ts index 06b984c3..cefdff3c 100644 --- a/packages/utils/tests/unit/utils.test.ts +++ b/packages/utils/tests/unit/utils.test.ts @@ -1,7 +1,6 @@ import { clone, copy, - defineConfig, equal, flatMerge, generateId, @@ -259,58 +258,6 @@ describe('Utils Tests', () => { }); }); - describe('defineConfig function tests', () => { - it('should merge defaults into config and overwrite undefined properties (default config)', () => { - const config = { - allowLogging: true, - loops: 10, - isHuman: undefined, - }; - expect( - defineConfig(config, { - allowLogging: false, - loops: 15, - isHuman: true, - isRobot: false, - name: 'jeff', - }) - ).toStrictEqual({ - allowLogging: true, - loops: 10, - isHuman: true, - isRobot: false, - name: 'jeff', - }); - }); - - it("should merge defaults into config and shouldn't overwrite undefined properties (overwriteUndefinedProperties = false)", () => { - const config = { - allowLogging: true, - loops: 10, - isHuman: undefined, - }; - expect( - defineConfig( - config, - { - allowLogging: false, - loops: 15, - isHuman: true, - isRobot: false, - name: 'jeff', - }, - false - ) - ).toStrictEqual({ - allowLogging: true, - loops: 10, - isHuman: undefined, - isRobot: false, - name: 'jeff', - }); - }); - }); - describe('flatMerge function tests', () => { it('should merge Changes Object into Source Object', () => { const source = { From 53ae1435a607d4a11487b4498f3ecaf11121a33b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 10 Jul 2021 17:05:01 +0200 Subject: [PATCH 53/55] fixed typo --- packages/core/src/collection/collection.persistent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 7e8f5b70..0289620b 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -145,7 +145,7 @@ export class CollectionPersistent< // that it was loaded completely and exists at all dummyItem?.persist(itemStorageKey, { loadValue: false, - defaultStorageKey: this.config.defaultStorageKey || undefined, + defaultStorageKey: this.config.defaultStorageKey as any, storageKeys: this.storageKeys, followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); From d053c4e7dcfd59c835fffa8bfbaa8e8086bedc86 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 10 Jul 2021 17:48:44 +0200 Subject: [PATCH 54/55] added defineConfig benchmark --- .../benchmarks/react/1000fields/index.ts | 16 ++-- benchmark/benchmarks/react/computed/index.ts | 14 +-- benchmark/benchmarks/react/counter/index.ts | 34 ++++---- .../defineConfig/bench/referencer.ts | 16 ++++ .../typescript/defineConfig/bench/spreader.ts | 17 ++++ .../typescript/defineConfig/index.ts | 85 +++++++++++++++++++ benchmark/package.json | 1 + 7 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 benchmark/benchmarks/typescript/defineConfig/bench/referencer.ts create mode 100644 benchmark/benchmarks/typescript/defineConfig/bench/spreader.ts create mode 100644 benchmark/benchmarks/typescript/defineConfig/index.ts diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index 8dc19a90..9b251c1d 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -1,5 +1,12 @@ -import Benchmark, { Suite, Options } from 'benchmark'; import ReactDOM from 'react-dom'; +import Benchmark, { Suite, Options } from 'benchmark'; +import { + cycleLog, + CycleResultInterface, + endBenchmarkLog, + getCycleResult, + startBenchmarkLog, +} from '../../../benchmarkManager'; // Files to run the Benchmark on import agileCollection from './bench/agilets/collection'; @@ -15,13 +22,6 @@ import nanostores from './bench/nanostores'; import recoil from './bench/recoil'; import redux from './bench/redux'; import valtio from './bench/valtio'; -import { - cycleLog, - CycleResultInterface, - endBenchmarkLog, - getCycleResult, - startBenchmarkLog, -} from '../../../benchmarkManager'; // @ts-ignore // Benchmark.js requires an instance of itself globally diff --git a/benchmark/benchmarks/react/computed/index.ts b/benchmark/benchmarks/react/computed/index.ts index 6bbb0e0a..e70b9c70 100644 --- a/benchmark/benchmarks/react/computed/index.ts +++ b/benchmark/benchmarks/react/computed/index.ts @@ -1,12 +1,5 @@ import ReactDOM from 'react-dom'; import Benchmark, { Suite, Options } from 'benchmark'; - -// Files to run the Benchmark on -import agileAutoTracking from './bench/agilets/autoTracking'; -import agileHardCoded from './bench/agilets/hardCoded'; -import jotai from './bench/jotai'; -import nanostores from './bench/nanostores'; -import recoil from './bench/recoil'; import { cycleLog, CycleResultInterface, @@ -15,6 +8,13 @@ import { startBenchmarkLog, } from '../../../benchmarkManager'; +// Files to run the Benchmark on +import agileAutoTracking from './bench/agilets/autoTracking'; +import agileHardCoded from './bench/agilets/hardCoded'; +import jotai from './bench/jotai'; +import nanostores from './bench/nanostores'; +import recoil from './bench/recoil'; + // @ts-ignore // Benchmark.js requires an instance of itself globally window.Benchmark = Benchmark; diff --git a/benchmark/benchmarks/react/counter/index.ts b/benchmark/benchmarks/react/counter/index.ts index 0878dbd7..067a85de 100644 --- a/benchmark/benchmarks/react/counter/index.ts +++ b/benchmark/benchmarks/react/counter/index.ts @@ -1,5 +1,12 @@ import ReactDOM from 'react-dom'; import Benchmark, { Suite, Options } from 'benchmark'; +import { + cycleLog, + CycleResultInterface, + endBenchmarkLog, + getCycleResult, + startBenchmarkLog, +} from '../../../benchmarkManager'; // Files to run the Benchmark on import agilets from './bench/agilets'; @@ -13,13 +20,6 @@ import redux from './bench/redux'; import reduxToolkit from './bench/redux-toolkit'; import valtio from './bench/valtio'; import zustand from './bench/zustand'; -import { - cycleLog, - CycleResultInterface, - endBenchmarkLog, - getCycleResult, - startBenchmarkLog, -} from '../../../benchmarkManager'; // @ts-ignore // Benchmark.js requires an instance of itself globally @@ -64,16 +64,16 @@ const results: CycleResultInterface[] = []; // Add Tests to the Benchmark Test Suite suite .add('AgileTs', configTest(agilets)) - .add('Hookstate', configTest(hookstate)) - .add('Jotai', configTest(jotai)) - .add('Mobx', configTest(mobx)) - .add('Nano Stores', configTest(nanostores)) - .add('PulseJs', configTest(pulsejs)) - .add('Recoil', configTest(recoil)) - .add('Redux', configTest(redux)) - .add('Redux-Toolkit', configTest(reduxToolkit)) - .add('Valtio', configTest(valtio)) - .add('Zustand', configTest(zustand)) + // .add('Hookstate', configTest(hookstate)) + // .add('Jotai', configTest(jotai)) + // .add('Mobx', configTest(mobx)) + // .add('Nano Stores', configTest(nanostores)) + // .add('PulseJs', configTest(pulsejs)) + // .add('Recoil', configTest(recoil)) + // .add('Redux', configTest(redux)) + // .add('Redux-Toolkit', configTest(reduxToolkit)) + // .add('Valtio', configTest(valtio)) + // .add('Zustand', configTest(zustand)) // Add Listener .on('start', function (this: any) { diff --git a/benchmark/benchmarks/typescript/defineConfig/bench/referencer.ts b/benchmark/benchmarks/typescript/defineConfig/bench/referencer.ts new file mode 100644 index 00000000..ba6f36b6 --- /dev/null +++ b/benchmark/benchmarks/typescript/defineConfig/bench/referencer.ts @@ -0,0 +1,16 @@ +export function defineConfig( + config: any, + defaults: any, + overwriteUndefinedProperties?: boolean +): void { + if (overwriteUndefinedProperties === undefined) + overwriteUndefinedProperties = true; + + for (const defaultKey in defaults) { + if ( + !Object.prototype.hasOwnProperty.call(config, defaultKey) || + (overwriteUndefinedProperties && config[defaultKey] === undefined) + ) + config[defaultKey] = defaults[defaultKey]; + } +} diff --git a/benchmark/benchmarks/typescript/defineConfig/bench/spreader.ts b/benchmark/benchmarks/typescript/defineConfig/bench/spreader.ts new file mode 100644 index 00000000..7fe3c013 --- /dev/null +++ b/benchmark/benchmarks/typescript/defineConfig/bench/spreader.ts @@ -0,0 +1,17 @@ +export function defineConfig( + config: any, + defaults: any, + overwriteUndefinedProperties?: boolean +): any { + if (overwriteUndefinedProperties === undefined) + overwriteUndefinedProperties = true; + + if (overwriteUndefinedProperties) { + const finalConfig = { ...defaults, ...config }; + for (const key in finalConfig) + if (finalConfig[key] === undefined) finalConfig[key] = defaults[key]; + return finalConfig; + } + + return { ...defaults, ...config }; +} diff --git a/benchmark/benchmarks/typescript/defineConfig/index.ts b/benchmark/benchmarks/typescript/defineConfig/index.ts new file mode 100644 index 00000000..f38b2034 --- /dev/null +++ b/benchmark/benchmarks/typescript/defineConfig/index.ts @@ -0,0 +1,85 @@ +import Benchmark, { Suite } from 'benchmark'; +import { + cycleLog, + CycleResultInterface, + endBenchmarkLog, + getCycleResult, + startBenchmarkLog, +} from '../../../benchmarkManager'; + +// Files to run the Benchmark on +import * as referencer from './bench/referencer'; +import * as spreader from './bench/spreader'; + +interface ConfigInterface { + x1?: boolean; + x2?: string; + x3?: number; + x4?: boolean; + x5?: string; +} + +const defaultConfig: ConfigInterface = { x1: true, x2: undefined }; + +// @ts-ignore +// Benchmark.js requires an instance of itself globally +window.Benchmark = Benchmark; + +// Create new Benchmark Test Suite +const suite = new Suite('define config'); + +const results: CycleResultInterface[] = []; + +// Add Tests to the Benchmark Test Suite +suite + .add('Primitiver', function () { + let config = defaultConfig; + config = { + x1: false, + x2: 'jeff', + x3: 10, + x4: false, + x5: 'hans', + ...config, + }; + }) + .add('Referencer', function () { + const config = defaultConfig; + referencer.defineConfig(config, { + x1: false, + x2: 'jeff', + x3: 10, + x4: false, + x5: 'hans', + }); + }) + .add('Spreader', function () { + let config = defaultConfig; + config = spreader.defineConfig(config, { + x1: false, + x2: 'jeff', + x3: 10, + x4: false, + x5: 'hans', + }); + }) + + // Add Listener + .on('start', function (this: any) { + startBenchmarkLog(this.name); + }) + .on('cycle', (event: any) => { + const cycleResult = getCycleResult(event); + cycleLog(cycleResult); + results.push(cycleResult); + }) + .on('complete', function (this: any) { + endBenchmarkLog(this.name, results, this.filter('fastest').map('name')); + + // @ts-ignore + // Notify server that the Benchmark Test Suite has ended + window.TEST.ended = true; + }) + + // Run Benchmark Test Suite + .run({ async: true }); diff --git a/benchmark/package.json b/benchmark/package.json index 5d7a4db7..d7cf2247 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -11,6 +11,7 @@ "test:counter": "yarn test ./benchmarks/react/counter", "test:1000fields": "yarn test ./benchmarks/react/1000fields", "test:computed": "yarn test ./benchmarks/react/computed", + "test:defineConfig": "yarn test ./benchmarks/typescript/defineConfig", "install:dev:agile": "yalc add @agile-ts/core @agile-ts/react & yarn install", "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react & yarn install" }, From cef61b67a7e44f49276a4a9acb3edb4679d5e8c4 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 10 Jul 2021 20:23:53 +0200 Subject: [PATCH 55/55] bumped version --- .changeset/serious-terms-perform.md | 27 +++++++++++++++++++++++++++ packages/react/README.md | 3 +-- packages/vue/README.md | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 .changeset/serious-terms-perform.md diff --git a/.changeset/serious-terms-perform.md b/.changeset/serious-terms-perform.md new file mode 100644 index 00000000..afb114d6 --- /dev/null +++ b/.changeset/serious-terms-perform.md @@ -0,0 +1,27 @@ +--- +'@agile-ts/api': patch +'@agile-ts/core': patch +'@agile-ts/event': patch +'@agile-ts/logger': patch +'@agile-ts/multieditor': patch +'@agile-ts/proxytree': patch +'@agile-ts/react': patch +'@agile-ts/utils': patch +'@agile-ts/vue': patch +--- + +#### :rocket: New Feature +* `react` + * [#171](https://github.com/agile-ts/agile/pull/171) Add deps array to useAgile() hook ([@bennodev19](https://github.com/bennodev19)) +* `core`, `event`, `react`, `vue` + * [#166](https://github.com/agile-ts/agile/pull/166) Shared Agile Instance ([@bennodev19](https://github.com/bennodev19)) + +#### :nail_care: Polish +* `api`, `core`, `event`, `logger`, `multieditor`, `react`, `utils` + * [#168](https://github.com/agile-ts/agile/pull/168) Performance optimization ([@bennodev19](https://github.com/bennodev19)) +* `core`, `event`, `react`, `vue` + * [#166](https://github.com/agile-ts/agile/pull/166) Shared Agile Instance ([@bennodev19](https://github.com/bennodev19)) + +#### Committers: 1 +- BennoDev ([@bennodev19](https://github.com/bennodev19)) + diff --git a/packages/react/README.md b/packages/react/README.md index e02ab0d9..fa074c38 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -75,8 +75,7 @@ Therefore, we have created a table that shows which versions fit together withou | @agile-ts/react | @agile-ts/core | NPM Version | Supported React versions | Supports hook based components | | ---------------- | ----------------------- | ------------------------ | -------------------------|---------------------------------- | -| v0.0.15+ | v0.0.16+ | v6+ | 16.8+ | Yes | -| v0.0.7 - v0.0.14 | v0.0.7 - v0.0.15 | v6+ | 16.8+ | Yes | +| v0.1.1+ | v0.1.1+ | v6+ | 16.8+ | Yes | _Older Versions aren't supported anymore_ diff --git a/packages/vue/README.md b/packages/vue/README.md index a82cbad0..f446a9d7 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -43,7 +43,7 @@ Therefore, we have created a table that shows which versions fit together withou | @agile-ts/vue | @agile-ts/core | NPM Version | Supported Vue versions | | ---------------- | ----------------------- | ------------------------ | -------------------------| -| v0.0.01+ | v0.0.16+ | v6+ | 2.x (3.x not tested) | +| v0.1.1+ | v0.1.1+ | v6+ | 2.x (3.x not tested) | _Older Versions aren't supported anymore_