From 0c2e76a50ebba0ab34dc8e532296ddc8ffada4f3 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sat, 4 Sep 2021 19:39:39 +0200 Subject: [PATCH 01/15] fixed typos --- .../benchmarks/react/1000fields/index.ts | 16 ++-- packages/core/src/collection/collection.ts | 42 ++++++---- packages/core/src/collection/group/index.ts | 80 ++++++++++++++++++- 3 files changed, 111 insertions(+), 27 deletions(-) diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index 9c1385be..9b318872 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -80,16 +80,16 @@ suite .add('Agile Collection', configTest(agileCollection)) .add('Agile State', configTest(agileState)) .add('Agile nested State', configTest(agileNestedState)) - .add('Pulse Collection', configTest(pulseCollection)) + // .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)) - .add('Nano Stores', configTest(nanostores)) - .add('Recoil', configTest(recoil)) - .add('Redux', configTest(redux)) - .add('Valtio', configTest(valtio)) + // .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)) // Add Listener .on('start', function (this: any) { diff --git a/packages/core/src/collection/collection.ts b/packages/core/src/collection/collection.ts index 31b8a107..2e541dad 100644 --- a/packages/core/src/collection/collection.ts +++ b/packages/core/src/collection/collection.ts @@ -1,26 +1,27 @@ import { Agile, - Item, - Group, - GroupKey, - Selector, - SelectorKey, - StorageKey, - GroupConfigInterface, - isValidObject, - normalizeArray, - copy, CollectionPersistent, - GroupAddConfigInterface, ComputedTracker, + copy, + defineConfig, generateId, - SideEffectConfigInterface, - SelectorConfigInterface, - removeProperties, + Group, + GroupAddConfigInterface, + GroupConfigInterface, + GroupKey, isFunction, + isValidObject, + Item, LogCodeManager, + normalizeArray, PatchOptionConfigInterface, - defineConfig, + removeProperties, + Selector, + SelectorConfigInterface, + SelectorKey, + SideEffectConfigInterface, + StorageKey, + TrackedChangeMethod, } from '../internal'; export class Collection< @@ -1473,12 +1474,19 @@ export class Collection< // Rebuild Groups that include itemKey for (const groupKey in this.groups) { const group = this.getGroup(groupKey); - if (group?.has(itemKey)) { + if (group != null && group.has(itemKey)) { // Not necessary because a sideEffect of ingesting the Group // into the runtime is to rebuilt itself // group.rebuild(); - group?.rebuild({ + // TODO + group.trackChange({ + key: itemKey, + index: group.nextStateValue.findIndex((ik) => itemKey === ik), + method: TrackedChangeMethod.UPDATE, + }); + + group.rebuild({ background: config?.background, sideEffects: config?.sideEffects, storage: false, diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index dc339159..f3bdca9a 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -39,6 +39,13 @@ export class Group< // Keeps track of all Item identifiers for Items that couldn't be found in the Collection notFoundItemKeys: Array = []; + // Keeps track of all changes made between rebuilds (add, remove, update) + // Why not rebuilding the Group directly in the add(), remove() method? + // Because rebuilding the Group is a side effect of the Group. + // A rebuild should always happen whenever the Group mutates. + // (-> Simplicity and keeping the current structure to not rewrite all tests) + trackedChanges: TrackedChangeInterface[] = []; + /** * An extension of the State Class that categorizes and preserves the ordering of structured data. * It allows us to cluster together data from a Collection as an array of Item keys. @@ -130,12 +137,15 @@ export class Group< */ public remove( itemKeys: ItemKey | ItemKey[], - config: StateIngestConfigInterface = {} + config: GroupRemoveConfigInterface = {} ): this { const _itemKeys = normalizeArray(itemKeys); const notExistingItemKeysInCollection: Array = []; const notExistingItemKeys: Array = []; let newGroupValue = copy(this.nextStateValue); + defineConfig(config, { + softRebuild: true, + }); // Remove itemKeys from Group _itemKeys.forEach((itemKey) => { @@ -146,6 +156,14 @@ export class Group< return; } + if (config.softRebuild) { + this.trackChange({ + index: newGroupValue.findIndex((ik) => ik === itemKey), + method: TrackedChangeMethod.REMOVE, + key: itemKey, + }); + } + // Check if itemKey exists in Collection if (!this.collection().getItem(itemKey)) notExistingItemKeysInCollection.push(itemKey); @@ -187,17 +205,33 @@ export class Group< defineConfig(config, { method: 'push', overwrite: false, + softRebuild: true, }); // Add itemKeys to Group _itemKeys.forEach((itemKey) => { + const exists = newGroupValue.includes(itemKey); + // Check if itemKey exists in Collection if (!this.collection().getItem(itemKey)) notExistingItemKeysInCollection.push(itemKey); + // Track changes to soft rebuild the Group when rebuilding the Group + if (config.softRebuild) { + this.trackChange({ + method: exists ? TrackedChangeMethod.UPDATE : TrackedChangeMethod.ADD, + key: itemKey, + index: exists + ? newGroupValue.findIndex((ik) => ik === itemKey) + : config.method === 'push' + ? newGroupValue.length - 1 + : 0, + }); + } + // Remove itemKey temporary from newGroupValue // if it should be overwritten and already exists in the newGroupValue - if (newGroupValue.includes(itemKey)) { + if (exists) { if (config.overwrite) { newGroupValue = newGroupValue.filter((key) => key !== itemKey); } else { @@ -373,6 +407,14 @@ export class Group< return this; } + + /** + * TODO + * @param change + */ + public trackChange(change: TrackedChangeInterface) { + this.trackedChanges.push(change); + } } export type GroupKey = string | number; @@ -400,6 +442,19 @@ export interface GroupAddConfigInterface extends StateIngestConfigInterface { * @default false */ overwrite?: boolean; + /** + * TODO + * @default true + */ + softRebuild?: boolean; +} + +export interface GroupRemoveConfigInterface extends StateIngestConfigInterface { + /** + * TODO + * @default true + */ + softRebuild?: boolean; } export interface GroupConfigInterface { @@ -425,3 +480,24 @@ export interface GroupPersistConfigInterface */ followCollectionPersistKeyPattern?: boolean; } + +export enum TrackedChangeMethod { + ADD, + REMOVE, + UPDATE, +} + +export interface TrackedChangeInterface { + /** + * TODO + */ + method: TrackedChangeMethod; + /** + * TODO + */ + key: ItemKey; + /** + * TODO + */ + index: number; +} From c894d24e5dd5755c4a1ef671ec63b7604c0e2cde Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 6 Sep 2021 20:33:30 +0200 Subject: [PATCH 02/15] added basic soft rebuild --- packages/core/src/collection/group/index.ts | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index f3bdca9a..f5b458b7 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -384,6 +384,35 @@ export class Group< // contains the Items which are essential for a proper rebuild) if (!this.collection().isInstantiated) return this; + // Soft rebuild the Collection (-> rebuild only parts of the Collection) + if (this.trackedChanges.length > 0) { + this.trackedChanges.forEach((change) => { + const item = this.collection().getItem(change.key); + switch (change.method) { + case TrackedChangeMethod.ADD: + if (item != null) { + this._value.splice(change.index, 0, change.key); + this._output.splice(change.index, 0, copy(item._value)); + } + break; + case TrackedChangeMethod.UPDATE: + if (item != null) { + this._output.splice(change.index, 0, copy(item._value)); + } + break; + case TrackedChangeMethod.REMOVE: + this._value.splice(change.index, 1); + this._output.splice(change.index, 1); + break; + default: + } + }); + this.trackedChanges = []; + return this; + } + + // Rebuild the whole Collection + // Fetch Items from Collection this._value.forEach((itemKey) => { const item = this.collection().getItem(itemKey); From 91221f4948cc075f28e42e53089698abe2f21b5c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Tue, 7 Sep 2021 20:29:59 +0200 Subject: [PATCH 03/15] fixed typos --- packages/core/src/collection/collection.ts | 5 ----- .../src/collection/group/group.observer.ts | 16 ++++++-------- packages/core/src/collection/group/index.ts | 21 ++++++++++++++----- .../collection/group/group.observer.test.ts | 10 ++++----- .../tests/unit/collection/group/group.test.ts | 8 +++---- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/core/src/collection/collection.ts b/packages/core/src/collection/collection.ts index 2e541dad..bf713291 100644 --- a/packages/core/src/collection/collection.ts +++ b/packages/core/src/collection/collection.ts @@ -1475,11 +1475,6 @@ export class Collection< for (const groupKey in this.groups) { const group = this.getGroup(groupKey); if (group != null && group.has(itemKey)) { - // Not necessary because a sideEffect of ingesting the Group - // into the runtime is to rebuilt itself - // group.rebuild(); - - // TODO group.trackChange({ key: itemKey, index: group.nextStateValue.findIndex((ik) => itemKey === ik), diff --git a/packages/core/src/collection/group/group.observer.ts b/packages/core/src/collection/group/group.observer.ts index 4c0e860c..872bfcf0 100644 --- a/packages/core/src/collection/group/group.observer.ts +++ b/packages/core/src/collection/group/group.observer.ts @@ -45,14 +45,14 @@ export class GroupObserver extends Observer { * into the runtime wrapped into a Runtime-Job * where it is executed accordingly. * - * During the execution the runtime applies the rebuilt `nextGroupOutput` to the Group, + * During the execution the runtime applies the current Group `output` to the Group, * updates its dependents and re-renders the UI-Components it is subscribed to. * * @internal * @param config - Configuration object */ public ingest(config: GroupIngestConfigInterface = {}): void { - this.group().rebuild(config); + this.ingestOutput(this.group()._output, config); } /** @@ -63,11 +63,11 @@ export class GroupObserver extends Observer { * updates its dependents and re-renders the UI-Components it is subscribed to. * * @internal - * @param newGroupItems - New Group Items to be applied to the Group. + * @param newGroupOutput - New Group Output to be applied to the Group. * @param config - Configuration object. */ - public ingestItems( - newGroupItems: Item[], + public ingestOutput( + newGroupOutput: DataType[], config: GroupIngestConfigInterface = {} ): void { const group = this.group(); @@ -89,11 +89,7 @@ export class GroupObserver extends Observer { } // Assign next Group output to Observer - this.nextGroupOutput = copy( - newGroupItems.map((item) => { - return item._value; - }) - ); + this.nextGroupOutput = copy(newGroupOutput); // Check if current Group output and to assign Group output are equal if (equal(group._output, this.nextGroupOutput) && !config.force) return; diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index f5b458b7..19623438 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -376,9 +376,6 @@ export class Group< * @param config - Configuration object */ public rebuild(config: StateIngestConfigInterface = {}): this { - const notFoundItemKeys: Array = []; // Item keys that couldn't be found in the Collection - const groupItems: Array> = []; - // Don't rebuild Group if Collection isn't correctly instantiated yet // (because only after a successful instantiation the Collection // contains the Items which are essential for a proper rebuild) @@ -386,13 +383,16 @@ export class Group< // Soft rebuild the Collection (-> rebuild only parts of the Collection) if (this.trackedChanges.length > 0) { + let ingestGroupValue = false; this.trackedChanges.forEach((change) => { const item = this.collection().getItem(change.key); + switch (change.method) { case TrackedChangeMethod.ADD: if (item != null) { this._value.splice(change.index, 0, change.key); this._output.splice(change.index, 0, copy(item._value)); + ingestGroupValue = true; } break; case TrackedChangeMethod.UPDATE: @@ -403,15 +403,21 @@ export class Group< case TrackedChangeMethod.REMOVE: this._value.splice(change.index, 1); this._output.splice(change.index, 1); + ingestGroupValue = true; break; default: } }); this.trackedChanges = []; + this.observers['output'].ingest(config); + if (ingestGroupValue) this.observers['value'].ingest(config); return this; } - // Rebuild the whole Collection + // Hard rebuild the whole Collection + + const notFoundItemKeys: Array = []; // Item keys that couldn't be found in the Collection + const groupItems: Array> = []; // Fetch Items from Collection this._value.forEach((itemKey) => { @@ -432,7 +438,12 @@ export class Group< this.notFoundItemKeys = notFoundItemKeys; // Ingest rebuilt Group output into the Runtime - this.observers['output'].ingestItems(groupItems, config); + this.observers['output'].ingestOutput( + groupItems.map((item) => { + return item._value; + }), + config + ); return this; } 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 488147b1..d5b6a783 100644 --- a/packages/core/tests/unit/collection/group/group.observer.test.ts +++ b/packages/core/tests/unit/collection/group/group.observer.test.ts @@ -158,7 +158,7 @@ describe('GroupObserver Tests', () => { }); }); - groupObserver.ingestItems([dummyItem1, dummyItem2]); + groupObserver.ingestOutput([dummyItem1, dummyItem2]); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, @@ -190,7 +190,7 @@ describe('GroupObserver Tests', () => { }); }); - groupObserver.ingestItems([dummyItem1, dummyItem2], { + groupObserver.ingestOutput([dummyItem1, dummyItem2], { perform: false, force: true, sideEffects: { @@ -219,7 +219,7 @@ describe('GroupObserver Tests', () => { () => { dummyGroup._output = [dummyItem1._value, dummyItem2._value]; - groupObserver.ingestItems([dummyItem1, dummyItem2]); + groupObserver.ingestOutput([dummyItem1, dummyItem2]); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, @@ -249,7 +249,7 @@ describe('GroupObserver Tests', () => { }); }); - groupObserver.ingestItems([dummyItem1, dummyItem2], { force: true }); + groupObserver.ingestOutput([dummyItem1, dummyItem2], { force: true }); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, @@ -281,7 +281,7 @@ describe('GroupObserver Tests', () => { }); dummyGroup.isPlaceholder = true; - groupObserver.ingestItems([dummyItem1, dummyItem2]); + groupObserver.ingestOutput([dummyItem1, dummyItem2]); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 17574a02..45ac8d30 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -512,7 +512,7 @@ describe('Group Tests', () => { describe('rebuild function tests', () => { beforeEach(() => { group._value = ['dummyItem1Key', 'dummyItem3Key', 'dummyItem2Key']; - group.observers['output'].ingestItems = jest.fn(); + group.observers['output'].ingestOutput = jest.fn(); }); it('should ingest the built Group output and set notFoundItemKeys to the not found Item Keys (default config)', () => { @@ -520,7 +520,7 @@ describe('Group Tests', () => { expect(group.notFoundItemKeys).toStrictEqual(['dummyItem3Key']); expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' - expect(group.observers['output'].ingestItems).toHaveBeenCalledWith( + expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( [dummyItem1, dummyItem2], {} ); @@ -537,7 +537,7 @@ describe('Group Tests', () => { expect(group.notFoundItemKeys).toStrictEqual(['dummyItem3Key']); expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' - expect(group.observers['output'].ingestItems).toHaveBeenCalledWith( + expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( [dummyItem1, dummyItem2], { storage: true, overwrite: true, background: false } ); @@ -556,7 +556,7 @@ describe('Group Tests', () => { expect(group.notFoundItemKeys).toStrictEqual([]); expect(group._output).toStrictEqual([]); - expect(group.observers['output'].ingestItems).not.toHaveBeenCalled(); + expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); LogMock.hasNotLogged('warn'); }); }); From 5aad7379591fb908a812f91a1a11d4e6d7ccedaa Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 8 Sep 2021 08:35:10 +0200 Subject: [PATCH 04/15] fixed typos --- benchmark/package.json | 1 + .../functional-component-ts/src/App.tsx | 2 +- .../src/collection/group/group.observer.ts | 5 ++-- packages/core/src/collection/group/index.ts | 28 +++++++++---------- packages/core/src/logCodeManager.ts | 4 ++- packages/core/src/state/state.observer.ts | 1 - 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/benchmark/package.json b/benchmark/package.json index 52dcd5bf..137ac46f 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/logger": "file:.yalc/@agile-ts/logger", "@agile-ts/react": "file:.yalc/@agile-ts/react", "@hookstate/core": "^3.0.8", "@pulsejs/core": "^4.0.0-beta.3", diff --git a/examples/react/develop/functional-component-ts/src/App.tsx b/examples/react/develop/functional-component-ts/src/App.tsx index 7c0849b3..2321584a 100644 --- a/examples/react/develop/functional-component-ts/src/App.tsx +++ b/examples/react/develop/functional-component-ts/src/App.tsx @@ -1,7 +1,6 @@ import React, { useEffect } from 'react'; import './App.css'; import { useAgile, useWatcher, useProxy, useSelector } from '@agile-ts/react'; -import { useEvent } from '@agile-ts/event/dist/react'; import { COUNTUP, externalCreatedItem, @@ -15,6 +14,7 @@ import { } from './core'; import { generateId } from '@agile-ts/utils'; import { globalBind } from '@agile-ts/core'; +import { useEvent } from '@agile-ts/event'; let rerenderCount = 0; let rerenderCountInCountupView = 0; diff --git a/packages/core/src/collection/group/group.observer.ts b/packages/core/src/collection/group/group.observer.ts index 872bfcf0..32fa71bd 100644 --- a/packages/core/src/collection/group/group.observer.ts +++ b/packages/core/src/collection/group/group.observer.ts @@ -45,14 +45,14 @@ export class GroupObserver extends Observer { * into the runtime wrapped into a Runtime-Job * where it is executed accordingly. * - * During the execution the runtime applies the current Group `output` to the Group, + * During the execution the runtime applies the `nextGroupOutput` to the Group, * updates its dependents and re-renders the UI-Components it is subscribed to. * * @internal * @param config - Configuration object */ public ingest(config: GroupIngestConfigInterface = {}): void { - this.ingestOutput(this.group()._output, config); + this.ingestOutput(this.group().nextGroupOutput, config); } /** @@ -126,6 +126,7 @@ export class GroupObserver extends Observer { // Assign new Group output group._output = copy(observer.nextGroupOutput); + group.nextGroupOutput = copy(observer.nextGroupOutput); // Assign new public output to the Observer (output used by the Integrations) job.observer.previousValue = copy(job.observer.value); diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index 19623438..f39eaef9 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -30,21 +30,23 @@ export class Group< static rebuildGroupSideEffectKey = 'rebuildGroup'; // Item values represented by the Group - _output: Array = []; + public _output: Array = []; + // Next output of the Group (which can be used for dynamic Group updates) + public nextGroupOutput: Array = []; // Manages dependencies to other States and subscriptions of UI-Components. // It also serves as an interface to the runtime. public observers: GroupObservers = {} as any; // Keeps track of all Item identifiers for Items that couldn't be found in the Collection - notFoundItemKeys: Array = []; + public notFoundItemKeys: Array = []; // Keeps track of all changes made between rebuilds (add, remove, update) // Why not rebuilding the Group directly in the add(), remove() method? // Because rebuilding the Group is a side effect of the Group. // A rebuild should always happen whenever the Group mutates. // (-> Simplicity and keeping the current structure to not rewrite all tests) - trackedChanges: TrackedChangeInterface[] = []; + public trackedChanges: TrackedChangeInterface[] = []; /** * An extension of the State Class that categorizes and preserves the ordering of structured data. @@ -143,7 +145,7 @@ export class Group< const notExistingItemKeysInCollection: Array = []; const notExistingItemKeys: Array = []; let newGroupValue = copy(this.nextStateValue); - defineConfig(config, { + config = defineConfig(config, { softRebuild: true, }); @@ -202,7 +204,7 @@ export class Group< const notExistingItemKeysInCollection: Array = []; const existingItemKeys: Array = []; let newGroupValue = copy(this.nextStateValue); - defineConfig(config, { + config = defineConfig(config, { method: 'push', overwrite: false, softRebuild: true, @@ -217,7 +219,7 @@ export class Group< notExistingItemKeysInCollection.push(itemKey); // Track changes to soft rebuild the Group when rebuilding the Group - if (config.softRebuild) { + if (config.softRebuild && (!exists || (exists && config.overwrite))) { this.trackChange({ method: exists ? TrackedChangeMethod.UPDATE : TrackedChangeMethod.ADD, key: itemKey, @@ -383,34 +385,30 @@ export class Group< // Soft rebuild the Collection (-> rebuild only parts of the Collection) if (this.trackedChanges.length > 0) { - let ingestGroupValue = false; this.trackedChanges.forEach((change) => { const item = this.collection().getItem(change.key); switch (change.method) { case TrackedChangeMethod.ADD: if (item != null) { - this._value.splice(change.index, 0, change.key); - this._output.splice(change.index, 0, copy(item._value)); - ingestGroupValue = true; + // this._value.splice(change.index, 0, change.key); // Already updated in 'add' method + this.nextGroupOutput.splice(change.index, 0, copy(item._value)); } break; case TrackedChangeMethod.UPDATE: if (item != null) { - this._output.splice(change.index, 0, copy(item._value)); + this.nextGroupOutput.splice(change.index, 0, copy(item._value)); } break; case TrackedChangeMethod.REMOVE: - this._value.splice(change.index, 1); - this._output.splice(change.index, 1); - ingestGroupValue = true; + // this._value.splice(change.index, 1); // Already updated in 'remove' method + this.nextGroupOutput.splice(change.index, 1); break; default: } }); this.trackedChanges = []; this.observers['output'].ingest(config); - if (ingestGroupValue) this.observers['value'].ingest(config); return this; } diff --git a/packages/core/src/logCodeManager.ts b/packages/core/src/logCodeManager.ts index fb5aaffc..5c1afbcf 100644 --- a/packages/core/src/logCodeManager.ts +++ b/packages/core/src/logCodeManager.ts @@ -305,7 +305,9 @@ if (process.env.NODE_ENV !== 'production') { log, logCodeLogTypes: logCodeTypes, logCodeMessages: logCodeMessages, - getLogger: loggerPackage.getLogger, + getLogger: () => { + return loggerPackage?.getLogger() ?? null; + }, logIfTags, setAllowLogging, }; diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index f2ef181c..6994237c 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -15,7 +15,6 @@ import { ObserverKey, defineConfig, } from '../internal'; -import type { EnhancedState } from '../internal'; export class StateObserver extends Observer { // State the Observer belongs to From 1211f0bb400c46fae941cfada88d05696b546efe Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Wed, 8 Sep 2021 08:58:48 +0200 Subject: [PATCH 05/15] fixed typos --- packages/core/src/collection/group/group.observer.ts | 1 - packages/core/src/collection/group/index.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/collection/group/group.observer.ts b/packages/core/src/collection/group/group.observer.ts index 32fa71bd..0029ecd1 100644 --- a/packages/core/src/collection/group/group.observer.ts +++ b/packages/core/src/collection/group/group.observer.ts @@ -6,7 +6,6 @@ import { equal, generateId, RuntimeJob, - Item, IngestConfigInterface, CreateRuntimeJobConfigInterface, defineConfig, diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index f39eaef9..ece977e4 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -397,7 +397,7 @@ export class Group< break; case TrackedChangeMethod.UPDATE: if (item != null) { - this.nextGroupOutput.splice(change.index, 0, copy(item._value)); + this.nextGroupOutput[change.index] = copy(item._value); } break; case TrackedChangeMethod.REMOVE: From 1da3b28689d0df5ee2c071f8f62dcbe2aeadfc7e Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 9 Sep 2021 07:18:29 +0200 Subject: [PATCH 06/15] added fields example --- examples/react/develop/fields/.env | 1 + examples/react/develop/fields/.gitignore | 23 ++++++ examples/react/develop/fields/README.md | 70 ++++++++++++++++++ examples/react/develop/fields/package.json | 38 ++++++++++ .../react/develop/fields/public/favicon.ico | Bin 0 -> 3870 bytes .../react/develop/fields/public/index.html | 43 +++++++++++ .../react/develop/fields/public/logo192.png | Bin 0 -> 5347 bytes .../react/develop/fields/public/logo512.png | Bin 0 -> 9664 bytes .../react/develop/fields/public/manifest.json | 25 +++++++ .../react/develop/fields/public/robots.txt | 3 + examples/react/develop/fields/src/App.js | 60 +++++++++++++++ examples/react/develop/fields/src/index.js | 10 +++ 12 files changed, 273 insertions(+) create mode 100644 examples/react/develop/fields/.env create mode 100644 examples/react/develop/fields/.gitignore create mode 100644 examples/react/develop/fields/README.md create mode 100644 examples/react/develop/fields/package.json create mode 100644 examples/react/develop/fields/public/favicon.ico create mode 100644 examples/react/develop/fields/public/index.html create mode 100644 examples/react/develop/fields/public/logo192.png create mode 100644 examples/react/develop/fields/public/logo512.png create mode 100644 examples/react/develop/fields/public/manifest.json create mode 100644 examples/react/develop/fields/public/robots.txt create mode 100644 examples/react/develop/fields/src/App.js create mode 100644 examples/react/develop/fields/src/index.js diff --git a/examples/react/develop/fields/.env b/examples/react/develop/fields/.env new file mode 100644 index 00000000..6f809cc2 --- /dev/null +++ b/examples/react/develop/fields/.env @@ -0,0 +1 @@ +SKIP_PREFLIGHT_CHECK=true diff --git a/examples/react/develop/fields/.gitignore b/examples/react/develop/fields/.gitignore new file mode 100644 index 00000000..4d29575d --- /dev/null +++ b/examples/react/develop/fields/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/react/develop/fields/README.md b/examples/react/develop/fields/README.md new file mode 100644 index 00000000..02aac3f6 --- /dev/null +++ b/examples/react/develop/fields/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `yarn start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.\ +You will also see any lint errors in the console. + +### `yarn test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `yarn build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `yarn eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `yarn build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/examples/react/develop/fields/package.json b/examples/react/develop/fields/package.json new file mode 100644 index 00000000..ad32f1c1 --- /dev/null +++ b/examples/react/develop/fields/package.json @@ -0,0 +1,38 @@ +{ + "name": "fields", + "version": "0.1.0", + "private": true, + "dependencies": { + "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/react": "file:.yalc/@agile-ts/react", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-scripts": "4.0.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "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" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/examples/react/develop/fields/public/favicon.ico b/examples/react/develop/fields/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/examples/react/develop/fields/public/index.html b/examples/react/develop/fields/public/index.html new file mode 100644 index 00000000..aa069f27 --- /dev/null +++ b/examples/react/develop/fields/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/examples/react/develop/fields/public/logo192.png b/examples/react/develop/fields/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/examples/react/develop/fields/public/manifest.json b/examples/react/develop/fields/public/manifest.json new file mode 100644 index 00000000..080d6c77 --- /dev/null +++ b/examples/react/develop/fields/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/react/develop/fields/public/robots.txt b/examples/react/develop/fields/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/examples/react/develop/fields/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/react/develop/fields/src/App.js b/examples/react/develop/fields/src/App.js new file mode 100644 index 00000000..5796839d --- /dev/null +++ b/examples/react/develop/fields/src/App.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { createCollection, LogCodeManager, shared } from '@agile-ts/core'; +import reactIntegration, { useAgile, useValue } from '@agile-ts/react'; + +LogCodeManager.setAllowLogging(false); +shared.integrate(reactIntegration); + +const FIELDS = createCollection({ + initialData: Array.from(Array(5000).keys()).map((i) => ({ + id: i, + name: `Field #${i + 1}`, + })), +}); + +let renderFieldsCount = 0; + +function Field({ index }) { + const ITEM = FIELDS.getItem(index); + const item = useAgile(ITEM); + + console.log(`Rerender Fields at '${index}':`, ++renderFieldsCount); + + return ( +
+ Last {``} render at: {new Date().toISOString()} +   + { + ITEM?.patch({ name: e.target.value }); + }} + /> +
+ ); +} + +export default function App() { + const fieldKeys = useValue(FIELDS); + + return ( +
+
+ Last {``} render at: {new Date().toISOString()} +
+
+ {fieldKeys.map((key, i) => ( + + ))} + +
+ ); +} diff --git a/examples/react/develop/fields/src/index.js b/examples/react/develop/fields/src/index.js new file mode 100644 index 00000000..c1f31c5f --- /dev/null +++ b/examples/react/develop/fields/src/index.js @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); From e0d33b433b41cdcfc1834f23d44f847228990126 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Thu, 9 Sep 2021 11:37:10 +0200 Subject: [PATCH 07/15] fixed typos --- .../benchmarks/react/1000fields/index.ts | 6 +- examples/react/develop/simple-todo-list/.env | 1 + .../react/develop/simple-todo-list/.gitignore | 23 ++++++ .../react/develop/simple-todo-list/README.md | 70 ++++++++++++++++++ .../develop/simple-todo-list/package.json | 39 ++++++++++ .../simple-todo-list/public/favicon.ico | Bin 0 -> 3870 bytes .../simple-todo-list/public/index.html | 43 +++++++++++ .../simple-todo-list/public/logo192.png | Bin 0 -> 5347 bytes .../simple-todo-list/public/logo512.png | Bin 0 -> 9664 bytes .../simple-todo-list/public/manifest.json | 25 +++++++ .../simple-todo-list/public/robots.txt | 3 + .../react/develop/simple-todo-list/src/App.js | 53 +++++++++++++ .../develop/simple-todo-list/src/core.js | 9 +++ .../develop/simple-todo-list/src/index.js | 10 +++ .../src/collection/collection.persistent.ts | 19 ++--- packages/core/src/collection/collection.ts | 49 ++++-------- packages/core/src/collection/group/index.ts | 11 ++- packages/core/src/state/state.enhanced.ts | 2 +- packages/core/src/state/state.persistent.ts | 4 +- packages/core/src/storages/persistent.ts | 6 +- .../collection/collection.persistent.test.ts | 20 ++--- .../tests/unit/state/state.persistent.test.ts | 6 +- .../tests/unit/storages/persistent.test.ts | 2 +- 23 files changed, 330 insertions(+), 71 deletions(-) create mode 100644 examples/react/develop/simple-todo-list/.env create mode 100644 examples/react/develop/simple-todo-list/.gitignore create mode 100644 examples/react/develop/simple-todo-list/README.md create mode 100644 examples/react/develop/simple-todo-list/package.json create mode 100644 examples/react/develop/simple-todo-list/public/favicon.ico create mode 100644 examples/react/develop/simple-todo-list/public/index.html create mode 100644 examples/react/develop/simple-todo-list/public/logo192.png create mode 100644 examples/react/develop/simple-todo-list/public/logo512.png create mode 100644 examples/react/develop/simple-todo-list/public/manifest.json create mode 100644 examples/react/develop/simple-todo-list/public/robots.txt create mode 100644 examples/react/develop/simple-todo-list/src/App.js create mode 100644 examples/react/develop/simple-todo-list/src/core.js create mode 100644 examples/react/develop/simple-todo-list/src/index.js diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index 9b318872..1c2b070e 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -78,9 +78,9 @@ 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('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)) diff --git a/examples/react/develop/simple-todo-list/.env b/examples/react/develop/simple-todo-list/.env new file mode 100644 index 00000000..6f809cc2 --- /dev/null +++ b/examples/react/develop/simple-todo-list/.env @@ -0,0 +1 @@ +SKIP_PREFLIGHT_CHECK=true diff --git a/examples/react/develop/simple-todo-list/.gitignore b/examples/react/develop/simple-todo-list/.gitignore new file mode 100644 index 00000000..4d29575d --- /dev/null +++ b/examples/react/develop/simple-todo-list/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/examples/react/develop/simple-todo-list/README.md b/examples/react/develop/simple-todo-list/README.md new file mode 100644 index 00000000..02aac3f6 --- /dev/null +++ b/examples/react/develop/simple-todo-list/README.md @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `yarn start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.\ +You will also see any lint errors in the console. + +### `yarn test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `yarn build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `yarn eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `yarn build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) diff --git a/examples/react/develop/simple-todo-list/package.json b/examples/react/develop/simple-todo-list/package.json new file mode 100644 index 00000000..c034b229 --- /dev/null +++ b/examples/react/develop/simple-todo-list/package.json @@ -0,0 +1,39 @@ +{ + "name": "simple-todo-list", + "version": "0.1.0", + "private": true, + "dependencies": { + "@agile-ts/core": "file:.yalc/@agile-ts/core", + "@agile-ts/logger": "file:.yalc/@agile-ts/logger", + "@agile-ts/react": "file:.yalc/@agile-ts/react", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-scripts": "4.0.3" + }, + "scripts": { + "start": "react-scripts start", + "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/logger & yarn install", + "install:prod:agile": "yarn add @agile-ts/core @agile-ts/react @agile-ts/logger & yarn install" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/examples/react/develop/simple-todo-list/public/favicon.ico b/examples/react/develop/simple-todo-list/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB
3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/examples/react/develop/simple-todo-list/public/index.html b/examples/react/develop/simple-todo-list/public/index.html new file mode 100644 index 00000000..aa069f27 --- /dev/null +++ b/examples/react/develop/simple-todo-list/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/examples/react/develop/simple-todo-list/public/logo192.png b/examples/react/develop/simple-todo-list/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/examples/react/develop/simple-todo-list/public/manifest.json b/examples/react/develop/simple-todo-list/public/manifest.json new file mode 100644 index 00000000..080d6c77 --- /dev/null +++ b/examples/react/develop/simple-todo-list/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/examples/react/develop/simple-todo-list/public/robots.txt b/examples/react/develop/simple-todo-list/public/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/examples/react/develop/simple-todo-list/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/examples/react/develop/simple-todo-list/src/App.js b/examples/react/develop/simple-todo-list/src/App.js new file mode 100644 index 00000000..7844b8c2 --- /dev/null +++ b/examples/react/develop/simple-todo-list/src/App.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { generateId } from '@agile-ts/core'; +import { useAgile } from '@agile-ts/react'; +import { TODOS } from './core'; + +const App = () => { + // With the 'useAgile' Hook we bind our first Collection to the 'RandomComponent' for reactivity + const todos = useAgile(TODOS); + + // Current Input of Name Form + const [currentInput, setCurrentInput] = React.useState(''); + + return ( +
+

Simple TODOS

+ { + setCurrentInput(event.target.value); + }} + /> + + {todos.map((value) => ( +
+
{value.name}
+ +
+ ))} +
+ ); +}; + +export default App; diff --git a/examples/react/develop/simple-todo-list/src/core.js b/examples/react/develop/simple-todo-list/src/core.js new file mode 100644 index 00000000..885a77f8 --- /dev/null +++ b/examples/react/develop/simple-todo-list/src/core.js @@ -0,0 +1,9 @@ +import { createCollection } from '@agile-ts/core'; +import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger'; + +assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); + +// Create Collection +export const TODOS = createCollection({ + initialData: [{ id: 1, name: 'Clean Bathroom' }], +}).persist('todos'); // perist does store the Collection in the Local Storage diff --git a/examples/react/develop/simple-todo-list/src/index.js b/examples/react/develop/simple-todo-list/src/index.js new file mode 100644 index 00000000..c1f31c5f --- /dev/null +++ b/examples/react/develop/simple-todo-list/src/index.js @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +ReactDOM.render( + + + , + document.getElementById('root') +); diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 2b2232f1..855b8430 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -36,7 +36,7 @@ export class CollectionPersistent< config: CreatePersistentConfigInterface = {} ) { super(collection.agileInstance(), { - instantiate: false, + loadValue: false, }); config = defineConfig(config, { instantiate: true, @@ -51,7 +51,7 @@ export class CollectionPersistent< }); // Load/Store persisted value/s for the first time - if (this.ready && config.instantiate) this.initialLoading(); + if (this.ready && config.loadValue) this.initialLoading(); } /** @@ -104,6 +104,7 @@ export class CollectionPersistent< // Persist default Group and load its value manually to be 100% sure // that it was loaded completely + defaultGroup.loadedInitialValue = false; defaultGroup.persist(defaultGroupStorageKey, { loadValue: false, defaultStorageKey: this.config.defaultStorageKey || undefined, @@ -113,14 +114,6 @@ export class CollectionPersistent< if (defaultGroup.persistent?.ready) await defaultGroup.persistent.initialLoading(); - // TODO rebuild the default Group once at the end when all Items were loaded into the Collection - // because otherwise it rebuilds the Group for each loaded Item - // (-> warnings are printed for all not yet loaded Items when rebuilding the Group) - // or rethink the whole Group rebuild process by adding a 'addItem()', 'removeItem()' and 'updateItem()' function - // so that there is no need for rebuilding the whole Group when for example only Item B changed or Item C was added - // - // See Issue by starting the vue develop example app and adding some todos to the _todo_ list - // Persist Items found in the default Group's value for (const itemKey of defaultGroup._value) { const item = this.collection().getItem(itemKey); @@ -156,13 +149,14 @@ export class CollectionPersistent< followCollectionPersistKeyPattern: false, // Because of the dynamic 'storageItemKey', the key is already formatted above }); if (placeholderItem?.persistent?.ready) { - const loadedPersistedValueIntoItem = await placeholderItem.persistent.loadPersistedValue(); // TODO FIRST GROUP REBUILD (by assigning loaded value to Item) + const loadedPersistedValueIntoItem = await placeholderItem.persistent.loadPersistedValue(); // If successfully loaded Item value, assign Item to Collection if (loadedPersistedValueIntoItem) { this.collection().assignItem(placeholderItem, { overwrite: true, // Overwrite to overwrite the existing placeholder Item, if one exists - }); // TODO SECOND GROUP REBUILD (by calling rebuildGroupThatInclude() in the assignItem() method) + rebuildGroups: false, // Not necessary since the Groups that include the to assign Item were already rebuild while assigning the loaded value to the Item via 'loadPersistedValue()' + }); placeholderItem.isPersisted = true; @@ -176,6 +170,7 @@ export class CollectionPersistent< } } + defaultGroup.loadedInitialValue = true; return true; }; const success = await loadValuesIntoCollection(); diff --git a/packages/core/src/collection/collection.ts b/packages/core/src/collection/collection.ts index bf713291..f6107554 100644 --- a/packages/core/src/collection/collection.ts +++ b/packages/core/src/collection/collection.ts @@ -8,6 +8,7 @@ import { Group, GroupAddConfigInterface, GroupConfigInterface, + GroupIngestConfigInterface, GroupKey, isFunction, isValidObject, @@ -19,7 +20,6 @@ import { Selector, SelectorConfigInterface, SelectorKey, - SideEffectConfigInterface, StorageKey, TrackedChangeMethod, } from '../internal'; @@ -979,7 +979,7 @@ export class Collection< // Create Persistent (-> persist value) this.persistent = new CollectionPersistent(this, { - instantiate: _config.loadValue, + loadValue: _config.loadValue, storageKeys: _config.storageKeys, key: key, defaultStorageKey: _config.defaultStorageKey, @@ -1406,6 +1406,7 @@ export class Collection< config = defineConfig(config, { overwrite: false, background: false, + rebuildGroups: true, }); const primaryKey = this.config.primaryKey; let itemKey = item._value[primaryKey]; @@ -1443,9 +1444,10 @@ export class Collection< // Rebuild Groups that include itemKey // after adding Item with itemKey to the Collection // (because otherwise it can't find the Item as it isn't added yet) - this.rebuildGroupsThatIncludeItemKey(itemKey, { - background: config.background, - }); + if (config.rebuildGroups) + this.rebuildGroupsThatIncludeItemKey(itemKey, { + background: config.background, + }); if (increaseCollectionSize) this.size++; @@ -1461,18 +1463,10 @@ export class Collection< */ public rebuildGroupsThatIncludeItemKey( itemKey: ItemKey, - config: RebuildGroupsThatIncludeItemKeyConfigInterface = {} + config: GroupIngestConfigInterface = {} ): void { - config = defineConfig(config, { - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - }); - // Rebuild Groups that include itemKey - for (const groupKey in this.groups) { + for (const groupKey of Object.keys(this.groups)) { const group = this.getGroup(groupKey); if (group != null && group.has(itemKey)) { group.trackChange({ @@ -1481,11 +1475,7 @@ export class Collection< method: TrackedChangeMethod.UPDATE, }); - group.rebuild({ - background: config?.background, - sideEffects: config?.sideEffects, - storage: false, - }); + group.rebuild(config); } } } @@ -1604,20 +1594,6 @@ export interface UpdateItemKeyConfigInterface { background?: boolean; } -export interface RebuildGroupsThatIncludeItemKeyConfigInterface { - /** - * Whether to rebuilt the Group in background. - * So that the UI isn't notified of these changes and thus doesn't rerender. - * @default false - */ - background?: boolean; - /** - * Whether to execute the defined side effects. - * @default true - */ - sideEffects?: SideEffectConfigInterface; -} - export interface HasConfigInterface { /** * Whether Items that do not officially exist, @@ -1697,4 +1673,9 @@ export interface AssignItemConfigInterface { * @default false */ background?: boolean; + /** + * Whether to rebuild all Groups that include the itemKey of the to assign Item. + * @default true + */ + rebuildGroups?: boolean; } diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index ece977e4..b34eda3d 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -18,6 +18,7 @@ import { GroupObserver, StateObserver, defineConfig, + GroupIngestConfigInterface, } from '../../internal'; export class Group< @@ -48,6 +49,10 @@ export class Group< // (-> Simplicity and keeping the current structure to not rewrite all tests) public trackedChanges: TrackedChangeInterface[] = []; + // Whether the initial value was loaded from the corresponding Persistent + // https://github.com/agile-ts/agile/issues/155 + public loadedInitialValue = true; + /** * An extension of the State Class that categorizes and preserves the ordering of structured data. * It allows us to cluster together data from a Collection as an array of Item keys. @@ -113,7 +118,7 @@ export class Group< * @param itemKey - Key/Name identifier of the Item. */ public has(itemKey: ItemKey) { - return this.value.findIndex((key) => key === itemKey) !== -1; + return this.value.indexOf(itemKey) !== -1; } /** @@ -377,7 +382,7 @@ export class Group< * @internal * @param config - Configuration object */ - public rebuild(config: StateIngestConfigInterface = {}): this { + public rebuild(config: GroupIngestConfigInterface = {}): this { // Don't rebuild Group if Collection isn't correctly instantiated yet // (because only after a successful instantiation the Collection // contains the Items which are essential for a proper rebuild) @@ -425,7 +430,7 @@ export class Group< }); // Logging - if (notFoundItemKeys.length > 0) { + if (notFoundItemKeys.length > 0 && this.loadedInitialValue) { LogCodeManager.log( '1C:02:00', [this.collection()._key, this._key], diff --git a/packages/core/src/state/state.enhanced.ts b/packages/core/src/state/state.enhanced.ts index fe3a4c67..a922d7cc 100644 --- a/packages/core/src/state/state.enhanced.ts +++ b/packages/core/src/state/state.enhanced.ts @@ -455,7 +455,7 @@ export class EnhancedState extends State { // Create Persistent (-> persist value) this.persistent = new StatePersistent(this, { - instantiate: _config.loadValue, + loadValue: _config.loadValue, storageKeys: _config.storageKeys, key: key, defaultStorageKey: _config.defaultStorageKey, diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 5a1d7f58..759f14a2 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -25,7 +25,7 @@ export class StatePersistent extends Persistent { config: CreatePersistentConfigInterface = {} ) { super(state.agileInstance(), { - instantiate: false, + loadValue: false, }); config = defineConfig(config, { instantiate: true, @@ -40,7 +40,7 @@ export class StatePersistent extends Persistent { }); // Load/Store persisted value/s for the first time - if (this.ready && config.instantiate) this.initialLoading(); + if (this.ready && config.loadValue) this.initialLoading(); } /** diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 11deec56..4ff6f9c7 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -53,7 +53,7 @@ export class Persistent { this.config = { defaultStorageKey: config.defaultStorageKey as any }; // Instantiate Persistent - if (config.instantiate) { + if (config.loadValue) { this.instantiatePersistent({ storageKeys: config.storageKeys, key: config.key, @@ -318,11 +318,11 @@ export interface CreatePersistentConfigInterface { */ defaultStorageKey?: StorageKey; /** - * Whether the Persistent should be instantiated immediately + * Whether the Persistent should load/persist the value immediately * or whether this should be done manually. * @default true */ - instantiate?: boolean; + loadValue?: boolean; } export interface PersistentConfigInterface { diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index b4f64ef1..ca1b74bd 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -144,7 +144,7 @@ describe('CollectionPersistent Tests', () => { }); const collectionPersistent = new CollectionPersistent(dummyCollection, { - instantiate: false, + loadValue: false, }); expect(collectionPersistent).toBeInstanceOf(CollectionPersistent); @@ -428,10 +428,9 @@ describe('CollectionPersistent Tests', () => { expect( placeholderItem1?.persistent?.loadPersistedValue ).toHaveBeenCalledTimes(1); - expect(dummyCollection.assignItem).toHaveBeenCalledWith( - placeholderItem1, - { overwrite: true } - ); + expect( + dummyCollection.assignItem + ).toHaveBeenCalledWith(placeholderItem1, { overwrite: true }); expect(placeholderItem1.isPersisted).toBeTruthy(); // Placeholder Item 2 @@ -580,10 +579,9 @@ describe('CollectionPersistent Tests', () => { expect( placeholderItem1?.persistent?.loadPersistedValue ).toHaveBeenCalledTimes(1); - expect(dummyCollection.assignItem).toHaveBeenCalledWith( - placeholderItem1, - { overwrite: true } - ); + expect( + dummyCollection.assignItem + ).toHaveBeenCalledWith(placeholderItem1, { overwrite: true }); expect(placeholderItem1.isPersisted).toBeTruthy(); expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( @@ -853,7 +851,9 @@ describe('CollectionPersistent Tests', () => { it("shouldn't add rebuild Storage side effect to the default Group", () => { collectionPersistent.setupSideEffects(); - expect(dummyDefaultGroup.addSideEffect).toHaveBeenCalledWith( + expect( + dummyDefaultGroup.addSideEffect + ).toHaveBeenCalledWith( CollectionPersistent.defaultGroupSideEffectKey, expect.any(Function), { weight: 0 } diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index 1fa45381..5cc5531d 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -117,7 +117,7 @@ describe('StatePersistent Tests', () => { }); const statePersistent = new StatePersistent(dummyState, { - instantiate: false, + loadValue: false, }); expect(statePersistent.initialLoading).not.toHaveBeenCalled(); @@ -315,7 +315,9 @@ describe('StatePersistent Tests', () => { () => { statePersistent.setupSideEffects(); - expect(dummyState.addSideEffect).toHaveBeenCalledWith( + expect( + dummyState.addSideEffect + ).toHaveBeenCalledWith( StatePersistent.storeValueSideEffectKey, expect.any(Function), { weight: 0 } diff --git a/packages/core/tests/unit/storages/persistent.test.ts b/packages/core/tests/unit/storages/persistent.test.ts index 44d09106..5c404777 100644 --- a/packages/core/tests/unit/storages/persistent.test.ts +++ b/packages/core/tests/unit/storages/persistent.test.ts @@ -83,7 +83,7 @@ describe('Persistent Tests', () => { .spyOn(Persistent.prototype, 'instantiatePersistent') .mockReturnValueOnce(undefined); - const persistent = new Persistent(dummyAgile, { instantiate: false }); + const persistent = new Persistent(dummyAgile, { loadValue: false }); expect(persistent).toBeInstanceOf(Persistent); expect(persistent.instantiatePersistent).not.toHaveBeenCalled(); From 4a408502670a7f1a48a6c8d5600b8f5569bb3444 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 10 Sep 2021 08:22:28 +0200 Subject: [PATCH 08/15] fixed typos --- .../benchmarks/react/1000fields/index.ts | 22 ++++---- .../typescript/cloneDeep/bench/lodash.ts | 6 ++ .../typescript/cloneDeep/bench/looper.ts | 19 +++++++ .../typescript/cloneDeep/bench/stringify.ts | 3 + .../benchmarks/typescript/cloneDeep/index.ts | 56 +++++++++++++++++++ benchmark/package.json | 1 + benchmark/yarn.lock | 7 ++- packages/core/src/collection/group/index.ts | 1 + packages/core/src/collection/item.ts | 3 - 9 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 benchmark/benchmarks/typescript/cloneDeep/bench/lodash.ts create mode 100644 benchmark/benchmarks/typescript/cloneDeep/bench/looper.ts create mode 100644 benchmark/benchmarks/typescript/cloneDeep/bench/stringify.ts create mode 100644 benchmark/benchmarks/typescript/cloneDeep/index.ts diff --git a/benchmark/benchmarks/react/1000fields/index.ts b/benchmark/benchmarks/react/1000fields/index.ts index 1c2b070e..3bb8218a 100644 --- a/benchmark/benchmarks/react/1000fields/index.ts +++ b/benchmark/benchmarks/react/1000fields/index.ts @@ -78,18 +78,18 @@ 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('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)) - // .add('Nano Stores', configTest(nanostores)) - // .add('Recoil', configTest(recoil)) - // .add('Redux', configTest(redux)) - // .add('Valtio', configTest(valtio)) + .add('Pulse State', configTest(pulseState)) + .add('Pulse nested State', configTest(pulseNestedState)) + .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)) // Add Listener .on('start', function (this: any) { diff --git a/benchmark/benchmarks/typescript/cloneDeep/bench/lodash.ts b/benchmark/benchmarks/typescript/cloneDeep/bench/lodash.ts new file mode 100644 index 00000000..16e2fa76 --- /dev/null +++ b/benchmark/benchmarks/typescript/cloneDeep/bench/lodash.ts @@ -0,0 +1,6 @@ +// @ts-ignore +import _ from 'lodash'; + +export function cloneDeep(value: T): T { + return _.cloneDeep(value); +} diff --git a/benchmark/benchmarks/typescript/cloneDeep/bench/looper.ts b/benchmark/benchmarks/typescript/cloneDeep/bench/looper.ts new file mode 100644 index 00000000..146c5c7e --- /dev/null +++ b/benchmark/benchmarks/typescript/cloneDeep/bench/looper.ts @@ -0,0 +1,19 @@ +export function cloneDeep(value: T): T { + // Extra checking 'value == null' because 'typeof null === object' + if (value == null || typeof value !== 'object') return value; + + // Ignore everything that is no object or array but has the type of an object (e.g. classes) + const valConstructorName = Object.getPrototypeOf( + value + ).constructor.name.toLowerCase(); + if (valConstructorName !== 'object' && valConstructorName !== 'array') + return value; + + let temp; + const newObject: any = Array.isArray(value) ? [] : {}; + for (const property in value) { + temp = value[property]; + newObject[property] = cloneDeep(temp); + } + return newObject as T; +} diff --git a/benchmark/benchmarks/typescript/cloneDeep/bench/stringify.ts b/benchmark/benchmarks/typescript/cloneDeep/bench/stringify.ts new file mode 100644 index 00000000..87cee1d2 --- /dev/null +++ b/benchmark/benchmarks/typescript/cloneDeep/bench/stringify.ts @@ -0,0 +1,3 @@ +export function cloneDeep(value: T): T { + return JSON.parse(JSON.stringify(value)); +} diff --git a/benchmark/benchmarks/typescript/cloneDeep/index.ts b/benchmark/benchmarks/typescript/cloneDeep/index.ts new file mode 100644 index 00000000..1ecda695 --- /dev/null +++ b/benchmark/benchmarks/typescript/cloneDeep/index.ts @@ -0,0 +1,56 @@ +import Benchmark, { Suite } from 'benchmark'; +import { + cycleLog, + CycleResultInterface, + endBenchmarkLog, + getCycleResult, + startBenchmarkLog, +} from '../../benchmarkManager'; + +// Files to run the Benchmark on +import * as lodash from './bench/lodash'; +import * as looper from './bench/looper'; +import * as stringify from './bench/stringify'; + +const toClone = { 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('clone deep'); + +const results: CycleResultInterface[] = []; + +// Add Tests to the Benchmark Test Suite +suite + .add('Lodash', function () { + lodash.cloneDeep(toClone); + }) + .add('Looper', function () { + looper.cloneDeep(toClone); + }) + .add('Stringify', function () { + stringify.cloneDeep(toClone); + }) + + // 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 137ac46f..21852877 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -12,6 +12,7 @@ "test:1000fields": "yarn test ./benchmarks/react/1000fields", "test:computed": "yarn test ./benchmarks/react/computed", "test:defineConfig": "yarn test ./benchmarks/typescript/defineConfig", + "test:cloneDeep": "yarn test ./benchmarks/typescript/cloneDeep", "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" }, diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index 5e123e81..a7d7c012 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -3,7 +3,12 @@ "@agile-ts/core@file:.yalc/@agile-ts/core": - version "0.2.0-alpha.3" + version "0.2.0-alpha.5" + dependencies: + "@agile-ts/utils" "^0.0.7" + +"@agile-ts/logger@file:.yalc/@agile-ts/logger": + version "0.0.7" dependencies: "@agile-ts/utils" "^0.0.7" diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index b34eda3d..e2c75421 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -410,6 +410,7 @@ export class Group< this.nextGroupOutput.splice(change.index, 1); break; default: + break; } }); this.trackedChanges = []; diff --git a/packages/core/src/collection/item.ts b/packages/core/src/collection/item.ts index db98104f..44266aee 100644 --- a/packages/core/src/collection/item.ts +++ b/packages/core/src/collection/item.ts @@ -166,9 +166,6 @@ export class Item extends EnhancedState< this.addSideEffect>( Item.updateGroupSideEffectKey, (instance, config) => { - // TODO optimise this because currently the whole Group rebuilds - // although only one Item value has changed which definitely needs no complete rebuild - // https://github.com/agile-ts/agile/issues/113 instance.collection().rebuildGroupsThatIncludeItemKey(itemKey, config); }, { weight: 100 } From 61565512d94a85900821f2209f1bcbb446941d85 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 10 Sep 2021 11:57:19 +0200 Subject: [PATCH 09/15] fixed some tests --- .../src/collection/collection.persistent.ts | 2 +- packages/core/src/collection/group/index.ts | 64 +++--- packages/core/src/state/state.persistent.ts | 2 +- packages/core/src/storages/persistent.ts | 2 +- .../collection/collection.persistent.test.ts | 47 ++-- .../tests/unit/collection/collection.test.ts | 93 ++++---- .../collection/group/group.observer.test.ts | 47 ++-- .../tests/unit/collection/group/group.test.ts | 200 +++++++++++++----- .../tests/unit/state/state.enhanced.test.ts | 16 +- .../tests/unit/state/state.persistent.test.ts | 66 ++++-- 10 files changed, 361 insertions(+), 178 deletions(-) diff --git a/packages/core/src/collection/collection.persistent.ts b/packages/core/src/collection/collection.persistent.ts index 855b8430..0cbf3987 100644 --- a/packages/core/src/collection/collection.persistent.ts +++ b/packages/core/src/collection/collection.persistent.ts @@ -39,7 +39,7 @@ export class CollectionPersistent< loadValue: false, }); config = defineConfig(config, { - instantiate: true, + loadValue: true, storageKeys: [], defaultStorageKey: null as any, }); diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index e2c75421..b0a5ac99 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -42,11 +42,12 @@ export class Group< // Keeps track of all Item identifiers for Items that couldn't be found in the Collection public notFoundItemKeys: Array = []; - // Keeps track of all changes made between rebuilds (add, remove, update) - // Why not rebuilding the Group directly in the add(), remove() method? - // Because rebuilding the Group is a side effect of the Group. - // A rebuild should always happen whenever the Group mutates. + // Keeps track of all changes made between rebuilds (add, remove, update). + // Why not rebuilding the Group directly in the add(), remove() methods? + // Because rebuilding the Group is also a side effect of the Group. + // Thus a rebuild should always happen whenever the Group mutates. // (-> Simplicity and keeping the current structure to not rewrite all tests) + // Note: Changes array must be processed from front to back otherwise issues might arise!! public trackedChanges: TrackedChangeInterface[] = []; // Whether the initial value was loaded from the corresponding Persistent @@ -156,8 +157,10 @@ export class Group< // Remove itemKeys from Group _itemKeys.forEach((itemKey) => { + const exists = newGroupValue.includes(itemKey); + // Check if itemKey exists in Group - if (!newGroupValue.includes(itemKey)) { + if (!exists) { notExistingItemKeys.push(itemKey); notExistingItemKeysInCollection.push(itemKey); return; @@ -187,7 +190,7 @@ export class Group< if (notExistingItemKeysInCollection.length >= _itemKeys.length) config.background = true; - this.set(newGroupValue, config); + this.set(newGroupValue, removeProperties(config, ['softRebuild'])); return this; } @@ -208,10 +211,9 @@ export class Group< const _itemKeys = normalizeArray(itemKeys); const notExistingItemKeysInCollection: Array = []; const existingItemKeys: Array = []; - let newGroupValue = copy(this.nextStateValue); + const newGroupValue = copy(this.nextStateValue); config = defineConfig(config, { method: 'push', - overwrite: false, softRebuild: true, }); @@ -223,30 +225,21 @@ export class Group< if (!this.collection().getItem(itemKey)) notExistingItemKeysInCollection.push(itemKey); + // Handle existing Item + if (exists) { + existingItemKeys.push(itemKey); + return; + } + // Track changes to soft rebuild the Group when rebuilding the Group - if (config.softRebuild && (!exists || (exists && config.overwrite))) { + if (config.softRebuild) { this.trackChange({ - method: exists ? TrackedChangeMethod.UPDATE : TrackedChangeMethod.ADD, + method: TrackedChangeMethod.ADD, key: itemKey, - index: exists - ? newGroupValue.findIndex((ik) => ik === itemKey) - : config.method === 'push' - ? newGroupValue.length - 1 - : 0, + index: config.method === 'push' ? newGroupValue.length - 1 : 0, }); } - // Remove itemKey temporary from newGroupValue - // if it should be overwritten and already exists in the newGroupValue - if (exists) { - if (config.overwrite) { - newGroupValue = newGroupValue.filter((key) => key !== itemKey); - } else { - existingItemKeys.push(itemKey); - return; - } - } - // Add new itemKey to Group newGroupValue[config.method || 'push'](itemKey); }); @@ -262,7 +255,10 @@ export class Group< ) config.background = true; - this.set(newGroupValue, removeProperties(config, ['method', 'overwrite'])); + this.set( + newGroupValue, + removeProperties(config, ['method', 'softRebuild']) + ); return this; } @@ -481,13 +477,9 @@ export interface GroupAddConfigInterface extends StateIngestConfigInterface { */ method?: 'unshift' | 'push'; /** - * If the to add `itemKey` already exists, - * whether its position should be overwritten with the position of the new `itemKey`. - * @default false - */ - overwrite?: boolean; - /** - * TODO + * Whether to soft rebuild the Group. + * -> only rebuild the parts of the Group that have actually changed + * instead of rebuilding the whole Group. * @default true */ softRebuild?: boolean; @@ -495,7 +487,9 @@ export interface GroupAddConfigInterface extends StateIngestConfigInterface { export interface GroupRemoveConfigInterface extends StateIngestConfigInterface { /** - * TODO + * Whether to soft rebuild the Group. + * -> only rebuild the parts of the Group that have actually changed + * instead of rebuilding the whole Group. * @default true */ softRebuild?: boolean; diff --git a/packages/core/src/state/state.persistent.ts b/packages/core/src/state/state.persistent.ts index 759f14a2..8d1fa0d9 100644 --- a/packages/core/src/state/state.persistent.ts +++ b/packages/core/src/state/state.persistent.ts @@ -28,7 +28,7 @@ export class StatePersistent extends Persistent { loadValue: false, }); config = defineConfig(config, { - instantiate: true, + loadValue: true, storageKeys: [], defaultStorageKey: null as any, }); diff --git a/packages/core/src/storages/persistent.ts b/packages/core/src/storages/persistent.ts index 4ff6f9c7..f83aeae9 100644 --- a/packages/core/src/storages/persistent.ts +++ b/packages/core/src/storages/persistent.ts @@ -46,7 +46,7 @@ export class Persistent { this.agileInstance = () => agileInstance; this._key = Persistent.placeHolderKey; config = defineConfig(config, { - instantiate: true, + loadValue: true, storageKeys: [], defaultStorageKey: null as any, }); diff --git a/packages/core/tests/unit/collection/collection.persistent.test.ts b/packages/core/tests/unit/collection/collection.persistent.test.ts index ca1b74bd..4c44d3f8 100644 --- a/packages/core/tests/unit/collection/collection.persistent.test.ts +++ b/packages/core/tests/unit/collection/collection.persistent.test.ts @@ -61,6 +61,7 @@ describe('CollectionPersistent Tests', () => { }); expect(collectionPersistent.initialLoading).toHaveBeenCalled(); + // Check if Persistent was called with correct parameters expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); expect(collectionPersistent.ready).toBeTruthy(); expect(collectionPersistent.isPersisted).toBeFalsy(); @@ -94,6 +95,7 @@ describe('CollectionPersistent Tests', () => { }); expect(collectionPersistent.initialLoading).toHaveBeenCalled(); + // Check if Persistent was called with correct parameters expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); expect(collectionPersistent.ready).toBeTruthy(); expect(collectionPersistent.isPersisted).toBeFalsy(); @@ -124,6 +126,7 @@ describe('CollectionPersistent Tests', () => { }); expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); + // Check if Persistent was called with correct parameters expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); expect(collectionPersistent.ready).toBeFalsy(); expect(collectionPersistent.isPersisted).toBeFalsy(); @@ -134,7 +137,7 @@ describe('CollectionPersistent Tests', () => { }); }); - it("should create CollectionPersistent and shouldn't call initialLoading if Persistent is ready (config.instantiate = false)", () => { + it("should create CollectionPersistent and shouldn't call initialLoading if Persistent is ready (config.loadValue = false)", () => { // Overwrite instantiatePersistent once to not call it and set ready property jest .spyOn(CollectionPersistent.prototype, 'instantiatePersistent') @@ -156,6 +159,7 @@ describe('CollectionPersistent Tests', () => { }); expect(collectionPersistent.initialLoading).not.toHaveBeenCalled(); + // Check if Persistent was called with correct parameters expect(collectionPersistent._key).toBe(CollectionPersistent.placeHolderKey); expect(collectionPersistent.ready).toBeTruthy(); expect(collectionPersistent.isPersisted).toBeFalsy(); @@ -428,9 +432,13 @@ describe('CollectionPersistent Tests', () => { expect( placeholderItem1?.persistent?.loadPersistedValue ).toHaveBeenCalledTimes(1); - expect( - dummyCollection.assignItem - ).toHaveBeenCalledWith(placeholderItem1, { overwrite: true }); + expect(dummyCollection.assignItem).toHaveBeenCalledWith( + placeholderItem1, + { + overwrite: true, + rebuildGroups: false, + } + ); expect(placeholderItem1.isPersisted).toBeTruthy(); // Placeholder Item 2 @@ -453,7 +461,11 @@ describe('CollectionPersistent Tests', () => { placeholderItem2?.persistent?.loadPersistedValue ).not.toHaveBeenCalled(); expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( - placeholderItem2 + placeholderItem2, + { + overwrite: true, + rebuildGroups: false, + } ); // Because Item persistent isn't ready expect(placeholderItem2.isPersisted).toBeFalsy(); @@ -476,9 +488,12 @@ describe('CollectionPersistent Tests', () => { expect( placeholderItem3?.persistent?.loadPersistedValue ).toHaveBeenCalledTimes(1); - expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( - placeholderItem3 - ); // Because Item persistent 'leadPersistedValue()' returned false -> Item properly doesn't exist in Storage + expect( + dummyCollection.assignItem + ).not.toHaveBeenCalledWith(placeholderItem3, { + overwrite: true, + rebuildGroups: false, + }); // Because Item persistent 'leadPersistedValue()' returned false -> Item properly doesn't exist in Storage expect(placeholderItem3.isPersisted).toBeFalsy(); expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( @@ -560,7 +575,11 @@ describe('CollectionPersistent Tests', () => { '3' ); // Because Item 3 is already present in the Collection expect(dummyCollection.assignItem).not.toHaveBeenCalledWith( - placeholderItem3 + placeholderItem3, + { + overwrite: true, + rebuildGroups: false, + } ); // Because Item 3 is already present in the Collection // Placeholder Item 1 @@ -579,9 +598,13 @@ describe('CollectionPersistent Tests', () => { expect( placeholderItem1?.persistent?.loadPersistedValue ).toHaveBeenCalledTimes(1); - expect( - dummyCollection.assignItem - ).toHaveBeenCalledWith(placeholderItem1, { overwrite: true }); + expect(dummyCollection.assignItem).toHaveBeenCalledWith( + placeholderItem1, + { + overwrite: true, + rebuildGroups: false, + } + ); expect(placeholderItem1.isPersisted).toBeTruthy(); expect(collectionPersistent.setupSideEffects).toHaveBeenCalledWith( diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 86a019a1..83d9671e 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -7,6 +7,7 @@ import { CollectionPersistent, ComputedTracker, StatePersistent, + TrackedChangeMethod, } from '../../../src'; import * as Utils from '@agile-ts/utils'; import { LogMock } from '../../helper/logMock'; @@ -264,10 +265,10 @@ describe('Collection Tests', () => { key: 'group1Key', }); - expect(collection.createGroup).toHaveBeenCalledWith( - 'group1Key', - [1, 2] - ); + expect(collection.createGroup).toHaveBeenCalledWith('group1Key', [ + 1, + 2, + ]); LogMock.hasLoggedCode('1B:02:00'); expect(response).toBeInstanceOf(Group); @@ -1820,19 +1821,19 @@ describe('Collection Tests', () => { }); describe('persist function tests', () => { - it('should create persistent with CollectionKey (default config)', () => { + it('should create Persistent with CollectionKey (default config)', () => { collection.persist(); expect(collection.persistent).toBeInstanceOf(CollectionPersistent); expect(CollectionPersistent).toHaveBeenCalledWith(collection, { - instantiate: true, + loadValue: true, storageKeys: [], key: collection._key, defaultStorageKey: null, }); }); - it('should create persistent with CollectionKey (specific config)', () => { + it('should create Persistent with CollectionKey (specific config)', () => { collection.persist({ storageKeys: ['test1', 'test2'], loadValue: false, @@ -1841,26 +1842,26 @@ describe('Collection Tests', () => { expect(collection.persistent).toBeInstanceOf(CollectionPersistent); expect(CollectionPersistent).toHaveBeenCalledWith(collection, { - instantiate: false, + loadValue: false, storageKeys: ['test1', 'test2'], key: collection._key, defaultStorageKey: 'test1', }); }); - it('should create persistent with passed Key (default config)', () => { + it('should create Persistent with passed Key (default config)', () => { collection.persist('passedKey'); expect(collection.persistent).toBeInstanceOf(CollectionPersistent); expect(CollectionPersistent).toHaveBeenCalledWith(collection, { - instantiate: true, + loadValue: true, storageKeys: [], key: 'passedKey', defaultStorageKey: null, }); }); - it('should create persistent with passed Key (specific config)', () => { + it('should create Persistent with passed Key (specific config)', () => { collection.persist('passedKey', { storageKeys: ['test1', 'test2'], loadValue: false, @@ -1869,14 +1870,14 @@ describe('Collection Tests', () => { expect(collection.persistent).toBeInstanceOf(CollectionPersistent); expect(CollectionPersistent).toHaveBeenCalledWith(collection, { - instantiate: false, + loadValue: false, storageKeys: ['test1', 'test2'], key: 'passedKey', defaultStorageKey: 'test1', }); }); - it("shouldn't overwrite existing persistent", () => { + it("shouldn't overwrite existing Persistent", () => { const dummyPersistent = new CollectionPersistent(collection); collection.persistent = dummyPersistent; collection.isPersisted = true; @@ -2871,7 +2872,7 @@ describe('Collection Tests', () => { collection.assignData = jest.fn(); }); - it('should assign valid Item to Collection (default config)', () => { + it('should assign valid Item to the Collection (default config)', () => { const response = collection.assignItem(toAddDummyItem2); expect(response).toBeTruthy(); @@ -2893,21 +2894,19 @@ describe('Collection Tests', () => { LogMock.hasNotLogged('warn'); }); - it('should assign valid Item to Collection (config.background = true)', () => { + it('should assign valid Item to the Collection (specific config)', () => { const response = collection.assignItem(toAddDummyItem2, { background: true, + rebuildGroups: false, }); expect(response).toBeTruthy(); expect(collection.size).toBe(2); expect(collection.data).toHaveProperty('dummyItem2'); expect(collection.data['dummyItem2']).toBe(toAddDummyItem2); - expect(collection.rebuildGroupsThatIncludeItemKey).toHaveBeenCalledWith( - 'dummyItem2', - { - background: true, - } - ); + expect( + collection.rebuildGroupsThatIncludeItemKey + ).not.toHaveBeenCalled(); expect(collection.assignData).not.toHaveBeenCalled(); expect(toAddDummyItem2.patch).not.toHaveBeenCalled(); @@ -3034,44 +3033,56 @@ describe('Collection Tests', () => { }; dummyGroup1.rebuild = jest.fn(); + dummyGroup1.trackChange = jest.fn(); dummyGroup2.rebuild = jest.fn(); + dummyGroup2.trackChange = jest.fn(); }); - it('should call ingest on each Group that includes the passed ItemKey (default config)', () => { + it('should rebuild each Group that includes the specified itemKey (default config)', () => { collection.rebuildGroupsThatIncludeItemKey('dummyItem1'); - expect(dummyGroup1.rebuild).toHaveBeenCalledWith({ - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, - storage: false, + // Group 1 + expect(dummyGroup1.rebuild).toHaveBeenCalledWith({}); + expect(dummyGroup1.trackChange).toHaveBeenCalledWith({ + key: 'dummyItem1', + index: 0, + method: TrackedChangeMethod.UPDATE, }); + + // Group 2 expect(dummyGroup2.rebuild).not.toHaveBeenCalled(); + expect(dummyGroup2.trackChange).not.toHaveBeenCalled(); }); - it('should call ingest on each Group that includes the passed ItemKey (specific config)', () => { + it('should rebuild each Group that includes the specified itemKey (specific config)', () => { collection.rebuildGroupsThatIncludeItemKey('dummyItem2', { + key: 'frank', background: true, - sideEffects: { - enabled: false, - }, + force: true, }); + // Group 1 expect(dummyGroup1.rebuild).toHaveBeenCalledWith({ + key: 'frank', background: true, - sideEffects: { - enabled: false, - }, - storage: false, + force: true, + }); + expect(dummyGroup1.trackChange).toHaveBeenCalledWith({ + key: 'dummyItem2', + index: 1, + method: TrackedChangeMethod.UPDATE, }); + + // Group 2 expect(dummyGroup2.rebuild).toHaveBeenCalledWith({ + key: 'frank', background: true, - sideEffects: { - enabled: false, - }, - storage: false, + force: true, + }); + expect(dummyGroup2.trackChange).toHaveBeenCalledWith({ + key: 'dummyItem2', + index: 0, + method: TrackedChangeMethod.UPDATE, }); }); }); 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 d5b6a783..439cff9b 100644 --- a/packages/core/tests/unit/collection/group/group.observer.test.ts +++ b/packages/core/tests/unit/collection/group/group.observer.test.ts @@ -110,31 +110,41 @@ describe('GroupObserver Tests', () => { describe('ingest function tests', () => { beforeEach(() => { - dummyGroup.rebuild = jest.fn(); + groupObserver.ingestOutput = jest.fn(); }); - it('should rebuild the Group and ingests it into the runtime (default config)', () => { + it('should call ingestOutput with nextGroupOutput (default config)', () => { + groupObserver.group().nextGroupOutput = 'jeff' as any; + groupObserver.ingest(); - expect(dummyGroup.rebuild).toHaveBeenCalledWith({}); + expect(groupObserver.ingestOutput).toHaveBeenCalledWith( + groupObserver.group().nextGroupOutput, + {} + ); }); - it('should rebuild the Group and ingests it into the runtime (specific config)', () => { + it('should call ingestOutput with nextGroupOutput (specific config)', () => { + groupObserver.group().nextGroupOutput = 'jeff' as any; + groupObserver.ingest({ background: true, force: true, maxTriesToUpdate: 5, }); - expect(dummyGroup.rebuild).toHaveBeenCalledWith({ - background: true, - force: true, - maxTriesToUpdate: 5, - }); + expect(groupObserver.ingestOutput).toHaveBeenCalledWith( + groupObserver.group().nextGroupOutput, + { + background: true, + force: true, + maxTriesToUpdate: 5, + } + ); }); }); - describe('ingestItems function tests', () => { + describe('ingestOutput function tests', () => { beforeEach(() => { dummyAgile.runtime.ingest = jest.fn(); }); @@ -158,7 +168,7 @@ describe('GroupObserver Tests', () => { }); }); - groupObserver.ingestOutput([dummyItem1, dummyItem2]); + groupObserver.ingestOutput([dummyItem1._value, dummyItem2._value]); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, @@ -190,7 +200,7 @@ describe('GroupObserver Tests', () => { }); }); - groupObserver.ingestOutput([dummyItem1, dummyItem2], { + groupObserver.ingestOutput([dummyItem1._value, dummyItem2._value], { perform: false, force: true, sideEffects: { @@ -219,7 +229,7 @@ describe('GroupObserver Tests', () => { () => { dummyGroup._output = [dummyItem1._value, dummyItem2._value]; - groupObserver.ingestOutput([dummyItem1, dummyItem2]); + groupObserver.ingestOutput([dummyItem1._value, dummyItem2._value]); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, @@ -249,7 +259,9 @@ describe('GroupObserver Tests', () => { }); }); - groupObserver.ingestOutput([dummyItem1, dummyItem2], { force: true }); + groupObserver.ingestOutput([dummyItem1._value, dummyItem2._value], { + force: true, + }); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, @@ -281,7 +293,7 @@ describe('GroupObserver Tests', () => { }); dummyGroup.isPlaceholder = true; - groupObserver.ingestOutput([dummyItem1, dummyItem2]); + groupObserver.ingestOutput([dummyItem1._value, dummyItem2._value]); expect(groupObserver.nextGroupOutput).toStrictEqual([ dummyItem1._value, @@ -312,6 +324,7 @@ describe('GroupObserver Tests', () => { ]; dummyJob.observer.value = [dummyItem1._value]; dummyGroup._output = [dummyItem1._value]; + dummyGroup.nextGroupOutput = [dummyItem1._value]; groupObserver.perform(dummyJob); @@ -319,6 +332,10 @@ describe('GroupObserver Tests', () => { dummyItem1._value, dummyItem2._value, ]); + expect(dummyGroup.nextGroupOutput).toStrictEqual([ + dummyItem1._value, + dummyItem2._value, + ]); expect(groupObserver.value).toStrictEqual([ dummyItem1._value, diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 45ac8d30..6c8e3c1a 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -8,6 +8,7 @@ import { CollectionPersistent, GroupObserver, EnhancedState, + TrackedChangeMethod, } from '../../../../src'; import { LogMock } from '../../../helper/logMock'; @@ -47,7 +48,10 @@ describe('Group Tests', () => { expect(group.collection()).toBe(dummyCollection); expect(group._output).toStrictEqual([]); + expect(group.nextGroupOutput).toStrictEqual([]); expect(group.notFoundItemKeys).toStrictEqual([]); + expect(group.trackedChanges).toStrictEqual([]); + expect(group.loadedInitialValue).toBeTruthy(); // Check if State was called with correct parameters expect(group._key).toBeUndefined(); @@ -86,7 +90,10 @@ describe('Group Tests', () => { expect(group.collection()).toBe(dummyCollection); expect(group._output).toStrictEqual([]); + expect(group.nextGroupOutput).toStrictEqual([]); expect(group.notFoundItemKeys).toStrictEqual([]); + expect(group.trackedChanges).toStrictEqual([]); + expect(group.loadedInitialValue).toBeTruthy(); // Check if State was called with correct parameters expect(group._key).toBe('dummyKey'); @@ -122,7 +129,10 @@ describe('Group Tests', () => { expect(group.collection()).toBe(dummyCollection); expect(group._output).toStrictEqual([]); + expect(group.nextGroupOutput).toStrictEqual([]); expect(group.notFoundItemKeys).toStrictEqual([]); + expect(group.trackedChanges).toStrictEqual([]); + expect(group.loadedInitialValue).toBeTruthy(); // Check if State was called with correct parameters expect(group._key).toBeUndefined(); @@ -230,117 +240,183 @@ describe('Group Tests', () => { 'dummyItem3Key', ]; group.set = jest.fn(); + group.trackChange = jest.fn(); }); - it('should remove Item from Group not in background (default config)', () => { + it('should remove Item from Group (default config)', () => { group.remove('dummyItem1Key'); + expect(group.trackChange).toHaveBeenCalledTimes(1); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 0, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem1Key', + }); + expect(group.set).toHaveBeenCalledWith( ['dummyItem2Key', 'dummyItem3Key'], {} ); }); - it('should remove Item from Group in background (config.background = true)', () => { - group.remove('dummyItem1Key', { background: true }); + it('should remove Item from Group (specific config)', () => { + group.remove('dummyItem1Key', { + background: true, + force: true, + storage: false, + softRebuild: false, + }); + + expect(group.trackChange).not.toHaveBeenCalled(); expect(group.set).toHaveBeenCalledWith( ['dummyItem2Key', 'dummyItem3Key'], - { background: true } + { background: true, force: true, storage: false } ); }); - it("shouldn't remove not existing Item from Group (default config)", () => { + it("shouldn't remove not existing Item from Group", () => { group.remove('notExistingKey'); - expect(group.set).not.toHaveBeenCalled(); - }); - - it("should remove Item from Group that doesn't exist in Collection in background (default config)", () => { - group.remove('dummyItem3Key'); + expect(group.trackChange).not.toHaveBeenCalled(); - expect(group.set).toHaveBeenCalledWith( - ['dummyItem1Key', 'dummyItem2Key'], - { background: true } - ); + expect(group.set).not.toHaveBeenCalled(); }); - it('should remove Items from Group not in background (default config)', () => { + it('should remove Items from Group', () => { group.remove(['dummyItem1Key', 'notExistingItemKey', 'dummyItem3Key']); + expect(group.trackChange).toHaveBeenCalledTimes(2); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 0, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem1Key', + }); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 1, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem3Key', + }); + expect(group.set).toHaveBeenCalledWith(['dummyItem2Key'], {}); }); - it("should remove Items from Group in background if passing not existing Item and Item that doesn't exist in Collection (default config)", () => { - group.remove(['notExistingItemKey', 'dummyItem3Key']); + it("should remove Item/s from Group that doesn't exist in the Collection in background", () => { + group.remove('dummyItem3Key'); + + expect(group.trackChange).toHaveBeenCalledTimes(1); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 2, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem3Key', + }); expect(group.set).toHaveBeenCalledWith( ['dummyItem1Key', 'dummyItem2Key'], { background: true } ); }); + + it( + 'should remove Items from Group in background ' + + 'if passing not existing Items to remove ' + + "and Items that doesn't exist in the Collection", + () => { + group.remove(['notExistingItemKey', 'dummyItem3Key']); + + expect(group.trackChange).toHaveBeenCalledTimes(1); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 2, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem3Key', + }); + + expect(group.set).toHaveBeenCalledWith( + ['dummyItem1Key', 'dummyItem2Key'], + { background: true } + ); + } + ); }); describe('add function tests', () => { beforeEach(() => { group.nextStateValue = ['placeholder', 'dummyItem1Key', 'placeholder']; group.set = jest.fn(); + group.trackChange = jest.fn(); }); - it('should add Item to Group at the end not in background (default config)', () => { + it('should add Item at the end of the Group (default config)', () => { group.add('dummyItem2Key'); + expect(group.trackChange).toHaveBeenCalledTimes(1); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem2Key', + }); + expect(group.set).toHaveBeenCalledWith( ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem2Key'], {} ); }); - it("should add Item to Group at the beginning not in background (config.method = 'unshift')", () => { - group.add('dummyItem2Key', { method: 'unshift' }); - - expect(group.set).toHaveBeenCalledWith( - ['dummyItem2Key', 'placeholder', 'dummyItem1Key', 'placeholder'], - {} - ); - }); + it('should add Item at the end of the Group (specific config)', () => { + group.add('dummyItem2Key', { + background: true, + force: true, + storage: false, + softRebuild: false, + }); - it('should add Item to Group at the end in background (config.background = true)', () => { - group.add('dummyItem2Key', { background: true }); + expect(group.trackChange).not.toHaveBeenCalled(); expect(group.set).toHaveBeenCalledWith( ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem2Key'], - { background: true } + { background: true, force: true, storage: false } ); }); - it("should add Item to Group at the end that doesn't exist in Collection in background (default config)", () => { - group.add('dummyItem3Key'); + it("should add Item at the beginning of the Group (config.method = 'unshift')", () => { + group.add('dummyItem2Key', { method: 'unshift' }); + + expect(group.trackChange).toHaveBeenCalledTimes(1); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 0, + method: TrackedChangeMethod.ADD, + key: 'dummyItem2Key', + }); expect(group.set).toHaveBeenCalledWith( - ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem3Key'], - { background: true } + ['dummyItem2Key', 'placeholder', 'dummyItem1Key', 'placeholder'], + {} ); }); - it("shouldn't add existing Item to Group again (default config)", () => { + it("shouldn't add already existing Item to the Group (default config)", () => { group.add('dummyItem1Key'); - expect(group.set).not.toHaveBeenCalled(); - }); - - it('should remove existingItem and add it again at the end to the Group not in background (config.overwrite = true)', () => { - group.add('dummyItem1Key', { overwrite: true }); + expect(group.trackChange).not.toHaveBeenCalled(); - expect(group.set).toHaveBeenCalledWith( - ['placeholder', 'placeholder', 'dummyItem1Key'], - {} - ); + expect(group.set).not.toHaveBeenCalled(); }); - it('should add Items to Group at the end not in background (default config)', () => { + it('should add Items at the end of the Group', () => { group.add(['dummyItem1Key', 'dummyItem2Key', 'dummyItem3Key']); + expect(group.trackChange).toHaveBeenCalledTimes(2); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem2Key', + }); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 3, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }); + expect(group.set).toHaveBeenCalledWith( [ 'placeholder', @@ -353,14 +429,42 @@ describe('Group Tests', () => { ); }); - it('should add Items toGroup at the end in background if passing existing Item and in Collection not existing Item (default config)', () => { - group.add(['dummyItem1Key', 'dummyItem3Key']); + it("should add Item that doesn't exist in Collection at the end of the Group in background", () => { + group.add('dummyItem3Key'); + + expect(group.trackChange).toHaveBeenCalledTimes(1); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }); expect(group.set).toHaveBeenCalledWith( ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem3Key'], { background: true } ); }); + + it( + 'should add Items at the end of the Group in background ' + + 'if passing already added Items ' + + "and Items that doesn't exist in the Collection", + () => { + group.add(['dummyItem1Key', 'dummyItem3Key']); + + expect(group.trackChange).toHaveBeenCalledTimes(1); + expect(group.trackChange).toHaveBeenCalledWith({ + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }); + + expect(group.set).toHaveBeenCalledWith( + ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem3Key'], + { background: true } + ); + } + ); }); describe('replace function tests', () => { @@ -533,13 +637,13 @@ describe('Group Tests', () => { }); it('should ingest the built Group output and set notFoundItemKeys to the not found Item Keys (specific config)', () => { - group.rebuild({ storage: true, overwrite: true, background: false }); + group.rebuild({ background: true, force: false }); expect(group.notFoundItemKeys).toStrictEqual(['dummyItem3Key']); expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( [dummyItem1, dummyItem2], - { storage: true, overwrite: true, background: false } + { background: true, force: false } ); LogMock.hasLoggedCode( diff --git a/packages/core/tests/unit/state/state.enhanced.test.ts b/packages/core/tests/unit/state/state.enhanced.test.ts index 7c80127c..97afe465 100644 --- a/packages/core/tests/unit/state/state.enhanced.test.ts +++ b/packages/core/tests/unit/state/state.enhanced.test.ts @@ -415,19 +415,19 @@ describe('Enhanced State Tests', () => { }); describe('persist function tests', () => { - it('should create persistent with StateKey (default config)', () => { + it('should create Persistent with StateKey (default config)', () => { numberState.persist(); expect(numberState.persistent).toBeInstanceOf(StatePersistent); expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: true, + loadValue: true, storageKeys: [], key: numberState._key, defaultStorageKey: null, }); }); - it('should create persistent with StateKey (specific config)', () => { + it('should create Persistent with StateKey (specific config)', () => { numberState.persist({ storageKeys: ['test1', 'test2'], loadValue: false, @@ -436,26 +436,26 @@ describe('Enhanced State Tests', () => { expect(numberState.persistent).toBeInstanceOf(StatePersistent); expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: false, + loadValue: false, storageKeys: ['test1', 'test2'], key: numberState._key, defaultStorageKey: 'test1', }); }); - it('should create persistent with passed Key (default config)', () => { + it('should create Persistent with passed Key (default config)', () => { numberState.persist('passedKey'); expect(numberState.persistent).toBeInstanceOf(StatePersistent); expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: true, + loadValue: true, storageKeys: [], key: 'passedKey', defaultStorageKey: null, }); }); - it('should create persistent with passed Key (specific config)', () => { + it('should create Persistent with passed Key (specific config)', () => { numberState.persist('passedKey', { storageKeys: ['test1', 'test2'], loadValue: false, @@ -464,7 +464,7 @@ describe('Enhanced State Tests', () => { expect(numberState.persistent).toBeInstanceOf(StatePersistent); expect(StatePersistent).toHaveBeenCalledWith(numberState, { - instantiate: false, + loadValue: false, storageKeys: ['test1', 'test2'], key: 'passedKey', defaultStorageKey: 'test1', diff --git a/packages/core/tests/unit/state/state.persistent.test.ts b/packages/core/tests/unit/state/state.persistent.test.ts index 5cc5531d..95591c38 100644 --- a/packages/core/tests/unit/state/state.persistent.test.ts +++ b/packages/core/tests/unit/state/state.persistent.test.ts @@ -32,41 +32,44 @@ describe('StatePersistent Tests', () => { jest.clearAllMocks(); }); - it("should create StatePersistent and shouldn't call initialLoading if Persistent isn't ready (default config)", () => { - // Overwrite instantiatePersistent once to not call it and set ready property + it('should create StatePersistent and should call initialLoading if Persistent is ready (default config)', () => { + // Overwrite instantiatePersistent once to not call it jest .spyOn(StatePersistent.prototype, 'instantiatePersistent') .mockImplementationOnce(function () { // @ts-ignore - this.ready = false; + this.ready = true; }); const statePersistent = new StatePersistent(dummyState); expect(statePersistent).toBeInstanceOf(StatePersistent); - expect(statePersistent.state()).toBe(dummyState); + expect(statePersistent.instantiatePersistent).toHaveBeenCalledWith({ key: undefined, storageKeys: [], defaultStorageKey: null, }); - expect(statePersistent.initialLoading).not.toHaveBeenCalled(); + expect(statePersistent.initialLoading).toHaveBeenCalled(); + // Check if Persistent was called with correct parameters expect(statePersistent._key).toBe(StatePersistent.placeHolderKey); - expect(statePersistent.ready).toBeFalsy(); + expect(statePersistent.ready).toBeTruthy(); expect(statePersistent.isPersisted).toBeFalsy(); expect(statePersistent.onLoad).toBeUndefined(); expect(statePersistent.storageKeys).toStrictEqual([]); - expect(statePersistent.config).toStrictEqual({ defaultStorageKey: null }); + expect(statePersistent.config).toStrictEqual({ + defaultStorageKey: null, // gets set in 'instantiatePersistent' which is mocked + }); }); - it("should create StatePersistent and shouldn't call initialLoading if Persistent isn't ready (specific config)", () => { + it('should create StatePersistent and should call initialLoading if Persistent is ready (specific config)', () => { // Overwrite instantiatePersistent once to not call it and set ready property jest .spyOn(StatePersistent.prototype, 'instantiatePersistent') .mockImplementationOnce(function () { // @ts-ignore - this.ready = false; + this.ready = true; }); const statePersistent = new StatePersistent(dummyState, { @@ -81,10 +84,11 @@ describe('StatePersistent Tests', () => { storageKeys: ['test1', 'test2'], defaultStorageKey: 'test2', }); - expect(statePersistent.initialLoading).not.toHaveBeenCalled(); + expect(statePersistent.initialLoading).toHaveBeenCalled(); + // Check if Persistent was called with correct parameters expect(statePersistent._key).toBe(StatePersistent.placeHolderKey); - expect(statePersistent.ready).toBeFalsy(); + expect(statePersistent.ready).toBeTruthy(); expect(statePersistent.isPersisted).toBeFalsy(); expect(statePersistent.onLoad).toBeUndefined(); expect(statePersistent.storageKeys).toStrictEqual([]); @@ -93,21 +97,36 @@ describe('StatePersistent Tests', () => { }); }); - it('should create StatePersistent and should call initialLoading if Persistent is ready (default config)', () => { - // Overwrite instantiatePersistent once to not call it + it("should create StatePersistent and shouldn't call initialLoading if Persistent isn't ready", () => { + // Overwrite instantiatePersistent once to not call it and set ready property jest .spyOn(StatePersistent.prototype, 'instantiatePersistent') .mockImplementationOnce(function () { // @ts-ignore - this.ready = true; + this.ready = false; }); const statePersistent = new StatePersistent(dummyState); - expect(statePersistent.initialLoading).toHaveBeenCalled(); + expect(statePersistent).toBeInstanceOf(StatePersistent); + expect(statePersistent.state()).toBe(dummyState); + expect(statePersistent.instantiatePersistent).toHaveBeenCalledWith({ + key: undefined, + storageKeys: [], + defaultStorageKey: null, + }); + expect(statePersistent.initialLoading).not.toHaveBeenCalled(); + + // Check if Persistent was called with correct parameters + expect(statePersistent._key).toBe(StatePersistent.placeHolderKey); + expect(statePersistent.ready).toBeFalsy(); + expect(statePersistent.isPersisted).toBeFalsy(); + expect(statePersistent.onLoad).toBeUndefined(); + expect(statePersistent.storageKeys).toStrictEqual([]); + expect(statePersistent.config).toStrictEqual({ defaultStorageKey: null }); }); - it("should create StatePersistent and shouldn't call initialLoading if Persistent is ready (config.instantiate = false)", () => { + it("should create StatePersistent and shouldn't call initialLoading if Persistent is ready (config.loadValue = false)", () => { // Overwrite instantiatePersistent once to not call it and set ready property jest .spyOn(StatePersistent.prototype, 'instantiatePersistent') @@ -120,7 +139,22 @@ describe('StatePersistent Tests', () => { loadValue: false, }); + expect(statePersistent).toBeInstanceOf(StatePersistent); + expect(statePersistent.state()).toBe(dummyState); + expect(statePersistent.instantiatePersistent).toHaveBeenCalledWith({ + key: undefined, + storageKeys: [], + defaultStorageKey: null, + }); expect(statePersistent.initialLoading).not.toHaveBeenCalled(); + + // Check if Persistent was called with correct parameters + expect(statePersistent._key).toBe(StatePersistent.placeHolderKey); + expect(statePersistent.ready).toBeTruthy(); + expect(statePersistent.isPersisted).toBeFalsy(); + expect(statePersistent.onLoad).toBeUndefined(); + expect(statePersistent.storageKeys).toStrictEqual([]); + expect(statePersistent.config).toStrictEqual({ defaultStorageKey: null }); }); describe('StatePersistent Function Tests', () => { From f00c1c7cb62f92e4336c7c6e10983d22793e440b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Fri, 10 Sep 2021 17:15:48 +0200 Subject: [PATCH 10/15] fixed tests --- benchmark/package.json | 2 +- benchmark/yarn.lock | 8 +- .../develop/simple-todo-list/src/core.js | 2 +- packages/core/src/collection/collection.ts | 14 +- .../src/collection/group/group.observer.ts | 20 +- packages/core/src/collection/group/index.ts | 44 ++-- packages/core/src/runtime/runtime.job.ts | 6 + packages/core/src/state/state.observer.ts | 24 +- packages/core/src/state/state.runtime.job.ts | 2 + .../tests/unit/collection/collection.test.ts | 64 ++--- .../collection/group/group.observer.test.ts | 4 + .../tests/unit/collection/group/group.test.ts | 220 ++++++++++-------- .../tests/unit/runtime/runtime.job.test.ts | 5 + .../tests/unit/state/state.observer.test.ts | 4 + .../unit/state/state.runtime.job.test.ts | 5 + 15 files changed, 231 insertions(+), 193 deletions(-) diff --git a/benchmark/package.json b/benchmark/package.json index 21852877..006b4707 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -24,7 +24,7 @@ "@agile-ts/core": "file:.yalc/@agile-ts/core", "@agile-ts/logger": "file:.yalc/@agile-ts/logger", "@agile-ts/react": "file:.yalc/@agile-ts/react", - "@hookstate/core": "^3.0.8", + "@hookstate/core": "^3.0.11", "@pulsejs/core": "^4.0.0-beta.3", "@pulsejs/react": "^4.0.0-beta.3", "@reduxjs/toolkit": "^1.6.0", diff --git a/benchmark/yarn.lock b/benchmark/yarn.lock index a7d7c012..fdae0a71 100644 --- a/benchmark/yarn.lock +++ b/benchmark/yarn.lock @@ -27,10 +27,10 @@ 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== +"@hookstate/core@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@hookstate/core/-/core-3.0.11.tgz#515f2748b3741f3d7e90cbc1acff8d6ac84e5494" + integrity sha512-cbI711aFWX4d8+xkLxikmLnR+f55ePHrBMWFA4gTLEoCa1+Cg0pfNw7h7zSodYMeYt8Y5A5TVSh7a5p8lBC89A== "@pulsejs/core@^4.0.0-beta.3": version "4.0.0-beta.3" diff --git a/examples/react/develop/simple-todo-list/src/core.js b/examples/react/develop/simple-todo-list/src/core.js index 885a77f8..316d5f31 100644 --- a/examples/react/develop/simple-todo-list/src/core.js +++ b/examples/react/develop/simple-todo-list/src/core.js @@ -6,4 +6,4 @@ assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); // Create Collection export const TODOS = createCollection({ initialData: [{ id: 1, name: 'Clean Bathroom' }], -}).persist('todos'); // perist does store the Collection in the Local Storage +}).persist('todos'); // persist does store the Collection in the Local Storage diff --git a/packages/core/src/collection/collection.ts b/packages/core/src/collection/collection.ts index f6107554..ba1845f0 100644 --- a/packages/core/src/collection/collection.ts +++ b/packages/core/src/collection/collection.ts @@ -1469,13 +1469,13 @@ export class Collection< for (const groupKey of Object.keys(this.groups)) { const group = this.getGroup(groupKey); if (group != null && group.has(itemKey)) { - group.trackChange({ - key: itemKey, - index: group.nextStateValue.findIndex((ik) => itemKey === ik), - method: TrackedChangeMethod.UPDATE, - }); - - group.rebuild(config); + group.rebuild(config, [ + { + key: itemKey, + index: group.nextStateValue.findIndex((ik) => itemKey === ik), + method: TrackedChangeMethod.UPDATE, + }, + ]); } } } diff --git a/packages/core/src/collection/group/group.observer.ts b/packages/core/src/collection/group/group.observer.ts index 0029ecd1..3e5d490a 100644 --- a/packages/core/src/collection/group/group.observer.ts +++ b/packages/core/src/collection/group/group.observer.ts @@ -9,6 +9,7 @@ import { IngestConfigInterface, CreateRuntimeJobConfigInterface, defineConfig, + removeProperties, } from '../../internal'; export class GroupObserver extends Observer { @@ -72,13 +73,7 @@ export class GroupObserver extends Observer { const group = this.group(); config = defineConfig(config, { perform: true, - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, force: false, - maxTriesToUpdate: 3, }); // Force overwriting the Group value if it is a placeholder. @@ -95,13 +90,12 @@ export class GroupObserver extends Observer { // Create Runtime-Job const job = new RuntimeJob(this, { - sideEffects: config.sideEffects, - force: config.force, - background: config.background, - key: - config.key ?? - `${this._key != null ? this._key + '_' : ''}${generateId()}_output`, - maxTriesToUpdate: config.maxTriesToUpdate, + ...removeProperties(config, ['perform']), + ...{ + key: + config.key ?? + `${this._key != null ? this._key + '_' : ''}${generateId()}_output`, + }, }); // Pass created Job into the Runtime diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index b0a5ac99..63e749a9 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -42,14 +42,6 @@ export class Group< // Keeps track of all Item identifiers for Items that couldn't be found in the Collection public notFoundItemKeys: Array = []; - // Keeps track of all changes made between rebuilds (add, remove, update). - // Why not rebuilding the Group directly in the add(), remove() methods? - // Because rebuilding the Group is also a side effect of the Group. - // Thus a rebuild should always happen whenever the Group mutates. - // (-> Simplicity and keeping the current structure to not rewrite all tests) - // Note: Changes array must be processed from front to back otherwise issues might arise!! - public trackedChanges: TrackedChangeInterface[] = []; - // Whether the initial value was loaded from the corresponding Persistent // https://github.com/agile-ts/agile/issues/155 public loadedInitialValue = true; @@ -87,7 +79,9 @@ export class Group< // Add side effect to Group // that rebuilds the Group whenever the Group value changes - this.addSideEffect(Group.rebuildGroupSideEffectKey, () => this.rebuild()); + this.addSideEffect(Group.rebuildGroupSideEffectKey, (state, config) => { + this.rebuild(config, config?.any?.trackedChanges || []); + }); // Initial rebuild this.rebuild(); @@ -153,7 +147,9 @@ export class Group< let newGroupValue = copy(this.nextStateValue); config = defineConfig(config, { softRebuild: true, + any: {}, }); + config.any['trackedChanges'] = []; // TODO might be improved since the 'any' property is very vague // Remove itemKeys from Group _itemKeys.forEach((itemKey) => { @@ -166,8 +162,9 @@ export class Group< return; } + // Track changes to soft rebuild the Group when rebuilding the Group in a side effect if (config.softRebuild) { - this.trackChange({ + config.any['trackedChanges'].push({ index: newGroupValue.findIndex((ik) => ik === itemKey), method: TrackedChangeMethod.REMOVE, key: itemKey, @@ -215,7 +212,9 @@ export class Group< config = defineConfig(config, { method: 'push', softRebuild: true, + any: {}, }); + config.any['trackedChanges'] = []; // TODO might be improved since the 'any' property is very vague // Add itemKeys to Group _itemKeys.forEach((itemKey) => { @@ -231,12 +230,12 @@ export class Group< return; } - // Track changes to soft rebuild the Group when rebuilding the Group + // Track changes to soft rebuild the Group when rebuilding the Group in a side effect if (config.softRebuild) { - this.trackChange({ + config.any['trackedChanges'].push({ + index: config.method === 'push' ? newGroupValue.length - 1 : 0, method: TrackedChangeMethod.ADD, key: itemKey, - index: config.method === 'push' ? newGroupValue.length - 1 : 0, }); } @@ -377,16 +376,20 @@ export class Group< * * @internal * @param config - Configuration object + * @param trackedChanges - Changes that were made between two rebuilds. */ - public rebuild(config: GroupIngestConfigInterface = {}): this { + public rebuild( + config: GroupIngestConfigInterface = {}, + trackedChanges: TrackedChangeInterface[] = [] + ): this { // Don't rebuild Group if Collection isn't correctly instantiated yet // (because only after a successful instantiation the Collection // contains the Items which are essential for a proper rebuild) if (!this.collection().isInstantiated) return this; // Soft rebuild the Collection (-> rebuild only parts of the Collection) - if (this.trackedChanges.length > 0) { - this.trackedChanges.forEach((change) => { + if (trackedChanges.length > 0) { + trackedChanges.forEach((change) => { const item = this.collection().getItem(change.key); switch (change.method) { @@ -409,7 +412,6 @@ export class Group< break; } }); - this.trackedChanges = []; this.observers['output'].ingest(config); return this; } @@ -447,14 +449,6 @@ export class Group< return this; } - - /** - * TODO - * @param change - */ - public trackChange(change: TrackedChangeInterface) { - this.trackedChanges.push(change); - } } export type GroupKey = string | number; diff --git a/packages/core/src/runtime/runtime.job.ts b/packages/core/src/runtime/runtime.job.ts index 54748c87..73dbd4b3 100644 --- a/packages/core/src/runtime/runtime.job.ts +++ b/packages/core/src/runtime/runtime.job.ts @@ -40,6 +40,7 @@ export class RuntimeJob { }, force: false, maxTriesToUpdate: 3, + any: {}, }); this.config = { @@ -47,6 +48,7 @@ export class RuntimeJob { force: config.force, sideEffects: config.sideEffects, maxTriesToUpdate: config.maxTriesToUpdate, + any: config.any, }; this.observer = observer; this.rerender = @@ -115,6 +117,10 @@ export interface RuntimeJobConfigInterface { * @default 3 */ maxTriesToUpdate?: number | null; + /** + * Anything unrelated that might be required by a side effect. + */ + any?: any; } export interface SideEffectConfigInterface { diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index 6994237c..332a9f9e 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -14,6 +14,7 @@ import { SubscriptionContainer, ObserverKey, defineConfig, + removeProperties, } from '../internal'; export class StateObserver extends Observer { @@ -87,15 +88,7 @@ export class StateObserver extends Observer { const state = this.state(); config = defineConfig(config, { perform: true, - background: false, - sideEffects: { - enabled: true, - exclude: [], - }, force: false, - storage: true, - overwrite: false, - maxTriesToUpdate: 3, }); // Force overwriting the State value if it is a placeholder. @@ -115,15 +108,12 @@ export class StateObserver extends Observer { // Create Runtime-Job const job = new StateRuntimeJob(this, { - storage: config.storage, - sideEffects: config.sideEffects, - force: config.force, - background: config.background, - overwrite: config.overwrite, - key: - config.key ?? - `${this._key != null ? this._key + '_' : ''}${generateId()}_value`, - maxTriesToUpdate: config.maxTriesToUpdate, + ...removeProperties(config, ['perform']), + ...{ + key: + config.key ?? + `${this._key != null ? this._key + '_' : ''}${generateId()}_value`, + }, }); // Pass created Job into the Runtime diff --git a/packages/core/src/state/state.runtime.job.ts b/packages/core/src/state/state.runtime.job.ts index 16e1628a..2578c7f9 100644 --- a/packages/core/src/state/state.runtime.job.ts +++ b/packages/core/src/state/state.runtime.job.ts @@ -35,6 +35,7 @@ export class StateRuntimeJob extends RuntimeJob { storage: true, overwrite: false, maxTriesToUpdate: 3, + any: {}, }); this.config = { @@ -44,6 +45,7 @@ export class StateRuntimeJob extends RuntimeJob { storage: config.storage, overwrite: config.overwrite, maxTriesToUpdate: config.maxTriesToUpdate, + any: config.any, }; } } diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 83d9671e..463a092a 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -3033,25 +3033,23 @@ describe('Collection Tests', () => { }; dummyGroup1.rebuild = jest.fn(); - dummyGroup1.trackChange = jest.fn(); dummyGroup2.rebuild = jest.fn(); - dummyGroup2.trackChange = jest.fn(); }); it('should rebuild each Group that includes the specified itemKey (default config)', () => { collection.rebuildGroupsThatIncludeItemKey('dummyItem1'); // Group 1 - expect(dummyGroup1.rebuild).toHaveBeenCalledWith({}); - expect(dummyGroup1.trackChange).toHaveBeenCalledWith({ - key: 'dummyItem1', - index: 0, - method: TrackedChangeMethod.UPDATE, - }); + expect(dummyGroup1.rebuild).toHaveBeenCalledWith({}, [ + { + key: 'dummyItem1', + index: 0, + method: TrackedChangeMethod.UPDATE, + }, + ]); // Group 2 expect(dummyGroup2.rebuild).not.toHaveBeenCalled(); - expect(dummyGroup2.trackChange).not.toHaveBeenCalled(); }); it('should rebuild each Group that includes the specified itemKey (specific config)', () => { @@ -3062,28 +3060,36 @@ describe('Collection Tests', () => { }); // Group 1 - expect(dummyGroup1.rebuild).toHaveBeenCalledWith({ - key: 'frank', - background: true, - force: true, - }); - expect(dummyGroup1.trackChange).toHaveBeenCalledWith({ - key: 'dummyItem2', - index: 1, - method: TrackedChangeMethod.UPDATE, - }); + expect(dummyGroup1.rebuild).toHaveBeenCalledWith( + { + key: 'frank', + background: true, + force: true, + }, + [ + { + key: 'dummyItem2', + index: 1, + method: TrackedChangeMethod.UPDATE, + }, + ] + ); // Group 2 - expect(dummyGroup2.rebuild).toHaveBeenCalledWith({ - key: 'frank', - background: true, - force: true, - }); - expect(dummyGroup2.trackChange).toHaveBeenCalledWith({ - key: 'dummyItem2', - index: 0, - method: TrackedChangeMethod.UPDATE, - }); + expect(dummyGroup2.rebuild).toHaveBeenCalledWith( + { + key: 'frank', + background: true, + force: true, + }, + [ + { + key: 'dummyItem2', + index: 0, + method: TrackedChangeMethod.UPDATE, + }, + ] + ); }); }); }); 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 439cff9b..24b30205 100644 --- a/packages/core/tests/unit/collection/group/group.observer.test.ts +++ b/packages/core/tests/unit/collection/group/group.observer.test.ts @@ -165,6 +165,7 @@ describe('GroupObserver Tests', () => { }, force: false, maxTriesToUpdate: 3, + any: {}, }); }); @@ -197,6 +198,7 @@ describe('GroupObserver Tests', () => { }, force: true, maxTriesToUpdate: 5, + any: {}, }); }); @@ -256,6 +258,7 @@ describe('GroupObserver Tests', () => { }, force: true, maxTriesToUpdate: 3, + any: {}, }); }); @@ -289,6 +292,7 @@ describe('GroupObserver Tests', () => { }, force: true, maxTriesToUpdate: 3, + any: {}, }); }); dummyGroup.isPlaceholder = true; diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 6c8e3c1a..377ad586 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -50,7 +50,6 @@ describe('Group Tests', () => { expect(group._output).toStrictEqual([]); expect(group.nextGroupOutput).toStrictEqual([]); expect(group.notFoundItemKeys).toStrictEqual([]); - expect(group.trackedChanges).toStrictEqual([]); expect(group.loadedInitialValue).toBeTruthy(); // Check if State was called with correct parameters @@ -92,7 +91,6 @@ describe('Group Tests', () => { expect(group._output).toStrictEqual([]); expect(group.nextGroupOutput).toStrictEqual([]); expect(group.notFoundItemKeys).toStrictEqual([]); - expect(group.trackedChanges).toStrictEqual([]); expect(group.loadedInitialValue).toBeTruthy(); // Check if State was called with correct parameters @@ -131,7 +129,6 @@ describe('Group Tests', () => { expect(group._output).toStrictEqual([]); expect(group.nextGroupOutput).toStrictEqual([]); expect(group.notFoundItemKeys).toStrictEqual([]); - expect(group.trackedChanges).toStrictEqual([]); expect(group.loadedInitialValue).toBeTruthy(); // Check if State was called with correct parameters @@ -240,22 +237,24 @@ describe('Group Tests', () => { 'dummyItem3Key', ]; group.set = jest.fn(); - group.trackChange = jest.fn(); }); it('should remove Item from Group (default config)', () => { group.remove('dummyItem1Key'); - expect(group.trackChange).toHaveBeenCalledTimes(1); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 0, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem1Key', - }); - expect(group.set).toHaveBeenCalledWith( ['dummyItem2Key', 'dummyItem3Key'], - {} + { + any: { + trackedChanges: [ + { + index: 0, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem1Key', + }, + ], + }, + } ); }); @@ -267,53 +266,61 @@ describe('Group Tests', () => { softRebuild: false, }); - expect(group.trackChange).not.toHaveBeenCalled(); - expect(group.set).toHaveBeenCalledWith( ['dummyItem2Key', 'dummyItem3Key'], - { background: true, force: true, storage: false } + { + background: true, + force: true, + storage: false, + any: { trackedChanges: [] }, + } ); }); it("shouldn't remove not existing Item from Group", () => { group.remove('notExistingKey'); - expect(group.trackChange).not.toHaveBeenCalled(); - expect(group.set).not.toHaveBeenCalled(); }); it('should remove Items from Group', () => { group.remove(['dummyItem1Key', 'notExistingItemKey', 'dummyItem3Key']); - expect(group.trackChange).toHaveBeenCalledTimes(2); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 0, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem1Key', - }); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 1, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem3Key', + expect(group.set).toHaveBeenCalledWith(['dummyItem2Key'], { + any: { + trackedChanges: [ + { + index: 0, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem1Key', + }, + { + index: 1, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem3Key', + }, + ], + }, }); - - expect(group.set).toHaveBeenCalledWith(['dummyItem2Key'], {}); }); it("should remove Item/s from Group that doesn't exist in the Collection in background", () => { group.remove('dummyItem3Key'); - expect(group.trackChange).toHaveBeenCalledTimes(1); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 2, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem3Key', - }); - expect(group.set).toHaveBeenCalledWith( ['dummyItem1Key', 'dummyItem2Key'], - { background: true } + { + background: true, + any: { + trackedChanges: [ + { + index: 2, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem3Key', + }, + ], + }, + } ); }); @@ -324,16 +331,20 @@ describe('Group Tests', () => { () => { group.remove(['notExistingItemKey', 'dummyItem3Key']); - expect(group.trackChange).toHaveBeenCalledTimes(1); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 2, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem3Key', - }); - expect(group.set).toHaveBeenCalledWith( ['dummyItem1Key', 'dummyItem2Key'], - { background: true } + { + background: true, + any: { + trackedChanges: [ + { + index: 2, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem3Key', + }, + ], + }, + } ); } ); @@ -343,22 +354,24 @@ describe('Group Tests', () => { beforeEach(() => { group.nextStateValue = ['placeholder', 'dummyItem1Key', 'placeholder']; group.set = jest.fn(); - group.trackChange = jest.fn(); }); it('should add Item at the end of the Group (default config)', () => { group.add('dummyItem2Key'); - expect(group.trackChange).toHaveBeenCalledTimes(1); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 2, - method: TrackedChangeMethod.ADD, - key: 'dummyItem2Key', - }); - expect(group.set).toHaveBeenCalledWith( ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem2Key'], - {} + { + any: { + trackedChanges: [ + { + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem2Key', + }, + ], + }, + } ); }); @@ -370,53 +383,45 @@ describe('Group Tests', () => { softRebuild: false, }); - expect(group.trackChange).not.toHaveBeenCalled(); - expect(group.set).toHaveBeenCalledWith( ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem2Key'], - { background: true, force: true, storage: false } + { + background: true, + force: true, + storage: false, + any: { trackedChanges: [] }, + } ); }); it("should add Item at the beginning of the Group (config.method = 'unshift')", () => { group.add('dummyItem2Key', { method: 'unshift' }); - expect(group.trackChange).toHaveBeenCalledTimes(1); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 0, - method: TrackedChangeMethod.ADD, - key: 'dummyItem2Key', - }); - expect(group.set).toHaveBeenCalledWith( ['dummyItem2Key', 'placeholder', 'dummyItem1Key', 'placeholder'], - {} + { + any: { + trackedChanges: [ + { + index: 0, + method: TrackedChangeMethod.ADD, + key: 'dummyItem2Key', + }, + ], + }, + } ); }); it("shouldn't add already existing Item to the Group (default config)", () => { group.add('dummyItem1Key'); - expect(group.trackChange).not.toHaveBeenCalled(); - expect(group.set).not.toHaveBeenCalled(); }); it('should add Items at the end of the Group', () => { group.add(['dummyItem1Key', 'dummyItem2Key', 'dummyItem3Key']); - expect(group.trackChange).toHaveBeenCalledTimes(2); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 2, - method: TrackedChangeMethod.ADD, - key: 'dummyItem2Key', - }); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 3, - method: TrackedChangeMethod.ADD, - key: 'dummyItem3Key', - }); - expect(group.set).toHaveBeenCalledWith( [ 'placeholder', @@ -425,23 +430,42 @@ describe('Group Tests', () => { 'dummyItem2Key', 'dummyItem3Key', ], - {} + { + any: { + trackedChanges: [ + { + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem2Key', + }, + { + index: 3, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }, + ], + }, + } ); }); it("should add Item that doesn't exist in Collection at the end of the Group in background", () => { group.add('dummyItem3Key'); - expect(group.trackChange).toHaveBeenCalledTimes(1); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 2, - method: TrackedChangeMethod.ADD, - key: 'dummyItem3Key', - }); - expect(group.set).toHaveBeenCalledWith( ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem3Key'], - { background: true } + { + background: true, + any: { + trackedChanges: [ + { + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }, + ], + }, + } ); }); @@ -452,16 +476,20 @@ describe('Group Tests', () => { () => { group.add(['dummyItem1Key', 'dummyItem3Key']); - expect(group.trackChange).toHaveBeenCalledTimes(1); - expect(group.trackChange).toHaveBeenCalledWith({ - index: 2, - method: TrackedChangeMethod.ADD, - key: 'dummyItem3Key', - }); - expect(group.set).toHaveBeenCalledWith( ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem3Key'], - { background: true } + { + background: true, + any: { + trackedChanges: [ + { + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }, + ], + }, + } ); } ); diff --git a/packages/core/tests/unit/runtime/runtime.job.test.ts b/packages/core/tests/unit/runtime/runtime.job.test.ts index 9d0845f2..a93c132c 100644 --- a/packages/core/tests/unit/runtime/runtime.job.test.ts +++ b/packages/core/tests/unit/runtime/runtime.job.test.ts @@ -36,6 +36,7 @@ describe('RuntimeJob Tests', () => { }, force: false, maxTriesToUpdate: 3, + any: {}, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); @@ -60,6 +61,7 @@ describe('RuntimeJob Tests', () => { }, force: true, maxTriesToUpdate: 10, + any: { jeff: 'frank' }, }); expect(job._key).toBe('dummyJob'); @@ -72,6 +74,7 @@ describe('RuntimeJob Tests', () => { }, force: true, maxTriesToUpdate: 10, + any: { jeff: 'frank' }, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); @@ -97,6 +100,7 @@ describe('RuntimeJob Tests', () => { }, force: false, maxTriesToUpdate: 3, + any: {}, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); @@ -124,6 +128,7 @@ describe('RuntimeJob Tests', () => { }, force: false, maxTriesToUpdate: 3, + any: {}, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); diff --git a/packages/core/tests/unit/state/state.observer.test.ts b/packages/core/tests/unit/state/state.observer.test.ts index e6e64367..2c348943 100644 --- a/packages/core/tests/unit/state/state.observer.test.ts +++ b/packages/core/tests/unit/state/state.observer.test.ts @@ -183,6 +183,7 @@ describe('StateObserver Tests', () => { storage: true, overwrite: false, maxTriesToUpdate: 3, + any: {}, }); }); @@ -214,6 +215,7 @@ describe('StateObserver Tests', () => { storage: true, overwrite: true, maxTriesToUpdate: 5, + any: {}, }); }); @@ -270,6 +272,7 @@ describe('StateObserver Tests', () => { storage: true, overwrite: false, maxTriesToUpdate: 3, + any: {}, }); }); @@ -300,6 +303,7 @@ describe('StateObserver Tests', () => { storage: true, overwrite: true, maxTriesToUpdate: 3, + any: {}, }); }); dummyState.isPlaceholder = true; 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 fc2d2461..63bb50a8 100644 --- a/packages/core/tests/unit/state/state.runtime.job.test.ts +++ b/packages/core/tests/unit/state/state.runtime.job.test.ts @@ -48,6 +48,7 @@ describe('RuntimeJob Tests', () => { storage: true, overwrite: false, maxTriesToUpdate: 3, + any: {}, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); @@ -70,6 +71,7 @@ describe('RuntimeJob Tests', () => { }, force: true, maxTriesToUpdate: 5, + any: { jeff: 'frank' }, }); expect(job._key).toBe('dummyJob'); @@ -83,6 +85,7 @@ describe('RuntimeJob Tests', () => { storage: true, overwrite: false, maxTriesToUpdate: 5, + any: { jeff: 'frank' }, }); expect(job.rerender).toBeTruthy(); expect(job.performed).toBeFalsy(); @@ -110,6 +113,7 @@ describe('RuntimeJob Tests', () => { storage: true, overwrite: false, maxTriesToUpdate: 3, + any: {}, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); @@ -139,6 +143,7 @@ describe('RuntimeJob Tests', () => { storage: true, overwrite: false, maxTriesToUpdate: 3, + any: {}, }); expect(job.rerender).toBeFalsy(); expect(job.performed).toBeFalsy(); From 3e4fa1bf8b01d77716a39a43d6f83f4b0f308ae4 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sat, 11 Sep 2021 09:33:49 +0200 Subject: [PATCH 11/15] fixed typos --- packages/core/src/collection/collection.ts | 23 +-- packages/core/src/collection/group/index.ts | 42 +++-- .../tests/unit/collection/collection.test.ts | 49 +++--- .../tests/unit/collection/group/group.test.ts | 154 +++++++++++------- 4 files changed, 150 insertions(+), 118 deletions(-) diff --git a/packages/core/src/collection/collection.ts b/packages/core/src/collection/collection.ts index ba1845f0..ff552fbf 100644 --- a/packages/core/src/collection/collection.ts +++ b/packages/core/src/collection/collection.ts @@ -107,7 +107,7 @@ export class Collection< // Rebuild of Groups // Not necessary because if Items are added to the Collection, // (after 'isInstantiated = true') - // the Groups which contain these added Items are rebuilt. + // the Groups which contain these added Items get rebuilt. // for (const key in this.groups) this.groups[key].rebuild(); } @@ -1218,9 +1218,7 @@ export class Collection< * @public * @param itemKeys - Item/s with identifier/s to be removed. */ - public remove( - itemKeys: ItemKey | Array - ): { + public remove(itemKeys: ItemKey | Array): { fromGroups: (groups: Array | ItemKey) => Collection; everywhere: (config?: RemoveItemsConfigInterface) => Collection; } { @@ -1469,13 +1467,16 @@ export class Collection< for (const groupKey of Object.keys(this.groups)) { const group = this.getGroup(groupKey); if (group != null && group.has(itemKey)) { - group.rebuild(config, [ - { - key: itemKey, - index: group.nextStateValue.findIndex((ik) => itemKey === ik), - method: TrackedChangeMethod.UPDATE, - }, - ]); + group.rebuild( + [ + { + key: itemKey, + index: group.nextStateValue.findIndex((ik) => itemKey === ik), + method: TrackedChangeMethod.UPDATE, + }, + ], + config + ); } } } diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index 63e749a9..5cb957b7 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -80,7 +80,7 @@ export class Group< // Add side effect to Group // that rebuilds the Group whenever the Group value changes this.addSideEffect(Group.rebuildGroupSideEffectKey, (state, config) => { - this.rebuild(config, config?.any?.trackedChanges || []); + this.rebuild(config?.any?.trackedChanges || [], config); }); // Initial rebuild @@ -375,18 +375,21 @@ export class Group< * [Learn more..](https://agile-ts.org/docs/core/collection/group/methods#rebuild) * * @internal + * @param trackedChanges - Changes that were tracked between two rebuilds. * @param config - Configuration object - * @param trackedChanges - Changes that were made between two rebuilds. */ public rebuild( - config: GroupIngestConfigInterface = {}, - trackedChanges: TrackedChangeInterface[] = [] + trackedChanges: TrackedChangeInterface[] = [], + config: GroupIngestConfigInterface = {} ): this { // Don't rebuild Group if Collection isn't correctly instantiated yet // (because only after a successful instantiation the Collection // contains the Items which are essential for a proper rebuild) if (!this.collection().isInstantiated) return this; + // Item keys that couldn't be found in the Collection + const notFoundItemKeys: Array = []; + // Soft rebuild the Collection (-> rebuild only parts of the Collection) if (trackedChanges.length > 0) { trackedChanges.forEach((change) => { @@ -413,20 +416,21 @@ export class Group< } }); this.observers['output'].ingest(config); - return this; } - // Hard rebuild the whole Collection + else { + const groupItemValues: Array = []; + + // Fetch Items from Collection + this._value.forEach((itemKey) => { + const item = this.collection().getItem(itemKey); + if (item != null) groupItemValues.push(item._value); + else notFoundItemKeys.push(itemKey); + }); - const notFoundItemKeys: Array = []; // Item keys that couldn't be found in the Collection - const groupItems: Array> = []; - - // Fetch Items from Collection - this._value.forEach((itemKey) => { - const item = this.collection().getItem(itemKey); - if (item != null) groupItems.push(item); - else notFoundItemKeys.push(itemKey); - }); + // Ingest rebuilt Group output into the Runtime + this.observers['output'].ingestOutput(groupItemValues, config); + } // Logging if (notFoundItemKeys.length > 0 && this.loadedInitialValue) { @@ -439,14 +443,6 @@ export class Group< this.notFoundItemKeys = notFoundItemKeys; - // Ingest rebuilt Group output into the Runtime - this.observers['output'].ingestOutput( - groupItems.map((item) => { - return item._value; - }), - config - ); - return this; } } diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index 463a092a..b01d3085 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -265,10 +265,10 @@ describe('Collection Tests', () => { key: 'group1Key', }); - expect(collection.createGroup).toHaveBeenCalledWith('group1Key', [ - 1, - 2, - ]); + expect(collection.createGroup).toHaveBeenCalledWith( + 'group1Key', + [1, 2] + ); LogMock.hasLoggedCode('1B:02:00'); expect(response).toBeInstanceOf(Group); @@ -3040,13 +3040,16 @@ describe('Collection Tests', () => { collection.rebuildGroupsThatIncludeItemKey('dummyItem1'); // Group 1 - expect(dummyGroup1.rebuild).toHaveBeenCalledWith({}, [ - { - key: 'dummyItem1', - index: 0, - method: TrackedChangeMethod.UPDATE, - }, - ]); + expect(dummyGroup1.rebuild).toHaveBeenCalledWith( + [ + { + key: 'dummyItem1', + index: 0, + method: TrackedChangeMethod.UPDATE, + }, + ], + {} + ); // Group 2 expect(dummyGroup2.rebuild).not.toHaveBeenCalled(); @@ -3061,34 +3064,34 @@ describe('Collection Tests', () => { // Group 1 expect(dummyGroup1.rebuild).toHaveBeenCalledWith( - { - key: 'frank', - background: true, - force: true, - }, [ { key: 'dummyItem2', index: 1, method: TrackedChangeMethod.UPDATE, }, - ] - ); - - // Group 2 - expect(dummyGroup2.rebuild).toHaveBeenCalledWith( + ], { key: 'frank', background: true, force: true, - }, + } + ); + + // Group 2 + expect(dummyGroup2.rebuild).toHaveBeenCalledWith( [ { key: 'dummyItem2', index: 0, method: TrackedChangeMethod.UPDATE, }, - ] + ], + { + key: 'frank', + background: true, + force: true, + } ); }); }); diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 377ad586..0ae2be7d 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -160,14 +160,12 @@ describe('Group Tests', () => { group = new Group(dummyCollection, [], { key: 'groupKey', }); - dummyCollection.collect({ id: 'dummyItem1Key', name: 'coolName' }); - dummyCollection.collect({ id: 'dummyItem2Key', name: 'coolName' }); + dummyCollection.collect({ id: 'dummyItem1Key', name: 'jeff' }); + dummyCollection.collect({ id: 'dummyItem2Key', name: 'frank' }); + dummyCollection.collect({ id: 'dummyItem3Key', name: 'hans' }); dummyItem1 = dummyCollection.getItem('dummyItem1Key') as any; dummyItem2 = dummyCollection.getItem('dummyItem2Key') as any; - dummyItem3 = new Item(dummyCollection, { - id: 'dummyItem3Key', - name: 'coolName', - }); + dummyItem3 = dummyCollection.getItem('dummyItem3Key') as any; }); describe('output get function tests', () => { @@ -234,7 +232,7 @@ describe('Group Tests', () => { group.nextStateValue = [ 'dummyItem1Key', 'dummyItem2Key', - 'dummyItem3Key', + 'missingInCollectionItemKey', ]; group.set = jest.fn(); }); @@ -243,7 +241,7 @@ describe('Group Tests', () => { group.remove('dummyItem1Key'); expect(group.set).toHaveBeenCalledWith( - ['dummyItem2Key', 'dummyItem3Key'], + ['dummyItem2Key', 'missingInCollectionItemKey'], { any: { trackedChanges: [ @@ -267,7 +265,7 @@ describe('Group Tests', () => { }); expect(group.set).toHaveBeenCalledWith( - ['dummyItem2Key', 'dummyItem3Key'], + ['dummyItem2Key', 'missingInCollectionItemKey'], { background: true, force: true, @@ -284,7 +282,11 @@ describe('Group Tests', () => { }); it('should remove Items from Group', () => { - group.remove(['dummyItem1Key', 'notExistingItemKey', 'dummyItem3Key']); + group.remove([ + 'dummyItem1Key', + 'notExistingItemKey', + 'missingInCollectionItemKey', + ]); expect(group.set).toHaveBeenCalledWith(['dummyItem2Key'], { any: { @@ -297,7 +299,7 @@ describe('Group Tests', () => { { index: 1, method: TrackedChangeMethod.REMOVE, - key: 'dummyItem3Key', + key: 'missingInCollectionItemKey', }, ], }, @@ -305,7 +307,7 @@ describe('Group Tests', () => { }); it("should remove Item/s from Group that doesn't exist in the Collection in background", () => { - group.remove('dummyItem3Key'); + group.remove('missingInCollectionItemKey'); expect(group.set).toHaveBeenCalledWith( ['dummyItem1Key', 'dummyItem2Key'], @@ -316,7 +318,7 @@ describe('Group Tests', () => { { index: 2, method: TrackedChangeMethod.REMOVE, - key: 'dummyItem3Key', + key: 'missingInCollectionItemKey', }, ], }, @@ -329,7 +331,7 @@ describe('Group Tests', () => { 'if passing not existing Items to remove ' + "and Items that doesn't exist in the Collection", () => { - group.remove(['notExistingItemKey', 'dummyItem3Key']); + group.remove(['notExistingItemKey', 'missingInCollectionItemKey']); expect(group.set).toHaveBeenCalledWith( ['dummyItem1Key', 'dummyItem2Key'], @@ -340,7 +342,7 @@ describe('Group Tests', () => { { index: 2, method: TrackedChangeMethod.REMOVE, - key: 'dummyItem3Key', + key: 'missingInCollectionItemKey', }, ], }, @@ -420,7 +422,7 @@ describe('Group Tests', () => { }); it('should add Items at the end of the Group', () => { - group.add(['dummyItem1Key', 'dummyItem2Key', 'dummyItem3Key']); + group.add(['dummyItem1Key', 'dummyItem2Key', 'notExistingItemKey']); expect(group.set).toHaveBeenCalledWith( [ @@ -428,7 +430,7 @@ describe('Group Tests', () => { 'dummyItem1Key', 'placeholder', 'dummyItem2Key', - 'dummyItem3Key', + 'notExistingItemKey', ], { any: { @@ -441,7 +443,7 @@ describe('Group Tests', () => { { index: 3, method: TrackedChangeMethod.ADD, - key: 'dummyItem3Key', + key: 'notExistingItemKey', }, ], }, @@ -450,10 +452,10 @@ describe('Group Tests', () => { }); it("should add Item that doesn't exist in Collection at the end of the Group in background", () => { - group.add('dummyItem3Key'); + group.add('notExistingItemKey'); expect(group.set).toHaveBeenCalledWith( - ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem3Key'], + ['placeholder', 'dummyItem1Key', 'placeholder', 'notExistingItemKey'], { background: true, any: { @@ -461,7 +463,7 @@ describe('Group Tests', () => { { index: 2, method: TrackedChangeMethod.ADD, - key: 'dummyItem3Key', + key: 'notExistingItemKey', }, ], }, @@ -474,10 +476,15 @@ describe('Group Tests', () => { 'if passing already added Items ' + "and Items that doesn't exist in the Collection", () => { - group.add(['dummyItem1Key', 'dummyItem3Key']); + group.add(['dummyItem1Key', 'notExistingItemKey']); expect(group.set).toHaveBeenCalledWith( - ['placeholder', 'dummyItem1Key', 'placeholder', 'dummyItem3Key'], + [ + 'placeholder', + 'dummyItem1Key', + 'placeholder', + 'notExistingItemKey', + ], { background: true, any: { @@ -485,7 +492,7 @@ describe('Group Tests', () => { { index: 2, method: TrackedChangeMethod.ADD, - key: 'dummyItem3Key', + key: 'notExistingItemKey', }, ], }, @@ -527,7 +534,7 @@ describe('Group Tests', () => { describe('getItems function tests', () => { beforeEach(() => { - group._value = ['dummyItem1Key', 'dummyItem3Key', 'dummyItem2Key']; + group._value = ['dummyItem1Key', 'notExistingItemKey', 'dummyItem2Key']; }); it('should return all existing Items of the Group', () => { @@ -643,54 +650,79 @@ describe('Group Tests', () => { describe('rebuild function tests', () => { beforeEach(() => { - group._value = ['dummyItem1Key', 'dummyItem3Key', 'dummyItem2Key']; + group._value = [ + 'dummyItem1Key', + 'notExistingItemKey', + 'dummyItem2Key', + 'dummyItem3Key', + ]; group.observers['output'].ingestOutput = jest.fn(); }); - it('should ingest the built Group output and set notFoundItemKeys to the not found Item Keys (default config)', () => { - group.rebuild(); + it( + 'should hard rebuild the Group if no trackedChanges were specified ' + + 'and set notExistingItemKeys to the not found Item Keys (default config)', + () => { + group.rebuild(); - expect(group.notFoundItemKeys).toStrictEqual(['dummyItem3Key']); - expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' - expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( - [dummyItem1, dummyItem2], - {} - ); + expect(group.notFoundItemKeys).toStrictEqual(['notExistingItemKey']); + expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' + expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( + [dummyItem1._value, dummyItem2._value], + {} + ); - LogMock.hasLoggedCode( - '1C:02:00', - [dummyCollection._key, group._key], - ['dummyItem3Key'] - ); - }); + LogMock.hasLoggedCode( + '1C:02:00', + [dummyCollection._key, group._key], + ['notExistingItemKey'] + ); + } + ); - it('should ingest the built Group output and set notFoundItemKeys to the not found Item Keys (specific config)', () => { - group.rebuild({ background: true, force: false }); + it( + 'should hard rebuild the Group if no trackedChanges were specified ' + + 'and set notExistingItemKeys to the not found Item Keys (specific config)', + () => { + group.rebuild([], { background: true, force: false }); - expect(group.notFoundItemKeys).toStrictEqual(['dummyItem3Key']); - expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' - expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( - [dummyItem1, dummyItem2], - { background: true, force: false } - ); + expect(group.notFoundItemKeys).toStrictEqual(['notExistingItemKey']); + expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' + expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( + [dummyItem1._value, dummyItem2._value], + { background: true, force: false } + ); - LogMock.hasLoggedCode( - '1C:02:00', - [dummyCollection._key, group._key], - ['dummyItem3Key'] - ); + LogMock.hasLoggedCode( + '1C:02:00', + [dummyCollection._key, group._key], + ['notExistingItemKey'] + ); + } + ); + + it('should soft rebuild the Group if trackedChanges were specified (default config)', () => { + // TODO + }); + + it('should soft rebuild the Group if trackedChanges were specified (specific config)', () => { + // TODO }); - it("shouldn't intest the build Group output if the Collection was not properly instantiated", () => { - dummyCollection.isInstantiated = false; + it( + "shouldn't ingest the build Group output " + + 'if the Collection was not properly instantiated', + () => { + dummyCollection.isInstantiated = false; - group.rebuild(); + group.rebuild(); - expect(group.notFoundItemKeys).toStrictEqual([]); - expect(group._output).toStrictEqual([]); - expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); - LogMock.hasNotLogged('warn'); - }); + expect(group.notFoundItemKeys).toStrictEqual([]); + expect(group._output).toStrictEqual([]); + expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); + } + ); }); }); }); From 21f0df54e40518078f09160dcf3a8fc78628b2f9 Mon Sep 17 00:00:00 2001 From: BennoDev Date: Sat, 11 Sep 2021 16:52:41 +0200 Subject: [PATCH 12/15] added precise itemKeys --- packages/core/src/collection/group/index.ts | 47 ++++++-- .../tests/unit/collection/group/group.test.ts | 105 +++++++++++------- 2 files changed, 102 insertions(+), 50 deletions(-) diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index 5cb957b7..328523b9 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -34,6 +34,9 @@ export class Group< public _output: Array = []; // Next output of the Group (which can be used for dynamic Group updates) public nextGroupOutput: Array = []; + // Precise itemKeys of the Group only include itemKeys + // that actually exist in the corresponding Collection + public _preciseItemKeys: Array = []; // Manages dependencies to other States and subscriptions of UI-Components. // It also serves as an interface to the runtime. @@ -145,6 +148,10 @@ export class Group< const notExistingItemKeysInCollection: Array = []; const notExistingItemKeys: Array = []; let newGroupValue = copy(this.nextStateValue); + // Need to temporary update the preciseItemKeys + // since in the rebuild one action (trackedChanges) is performed after the other + // which requires a dynamic updated index + let updatedPreciseItemKeys = copy(this._preciseItemKeys); config = defineConfig(config, { softRebuild: true, any: {}, @@ -164,11 +171,15 @@ export class Group< // Track changes to soft rebuild the Group when rebuilding the Group in a side effect if (config.softRebuild) { - config.any['trackedChanges'].push({ - index: newGroupValue.findIndex((ik) => ik === itemKey), - method: TrackedChangeMethod.REMOVE, - key: itemKey, - }); + const index = updatedPreciseItemKeys.findIndex((ik) => ik === itemKey); + if (index !== -1) { + updatedPreciseItemKeys.splice(index, 1); + config.any['trackedChanges'].push({ + index, + method: TrackedChangeMethod.REMOVE, + key: itemKey, + }); + } } // Check if itemKey exists in Collection @@ -209,6 +220,10 @@ export class Group< const notExistingItemKeysInCollection: Array = []; const existingItemKeys: Array = []; const newGroupValue = copy(this.nextStateValue); + // Need to temporary update the preciseItemKeys + // since in the rebuild one action (trackedChanges) is performed after the other + // which requires a dynamic updated index + const updatedPreciseItemKeys = copy(this._preciseItemKeys); config = defineConfig(config, { method: 'push', softRebuild: true, @@ -232,8 +247,11 @@ export class Group< // Track changes to soft rebuild the Group when rebuilding the Group in a side effect if (config.softRebuild) { + const index = + config.method === 'push' ? updatedPreciseItemKeys.length : 0; + updatedPreciseItemKeys.push(itemKey); config.any['trackedChanges'].push({ - index: config.method === 'push' ? newGroupValue.length - 1 : 0, + index, method: TrackedChangeMethod.ADD, key: itemKey, }); @@ -397,17 +415,23 @@ export class Group< switch (change.method) { case TrackedChangeMethod.ADD: + this._preciseItemKeys.splice(change.index, 0, change.key); + // this._value.splice(change.index, 0, change.key); // Already updated in 'add' method if (item != null) { - // this._value.splice(change.index, 0, change.key); // Already updated in 'add' method this.nextGroupOutput.splice(change.index, 0, copy(item._value)); + } else { + notFoundItemKeys.push(change.key); } break; case TrackedChangeMethod.UPDATE: if (item != null) { this.nextGroupOutput[change.index] = copy(item._value); + } else { + notFoundItemKeys.push(change.key); } break; case TrackedChangeMethod.REMOVE: + this._preciseItemKeys.splice(change.index, 1); // this._value.splice(change.index, 1); // Already updated in 'remove' method this.nextGroupOutput.splice(change.index, 1); break; @@ -421,11 +445,16 @@ export class Group< else { const groupItemValues: Array = []; + // Reset precise itemKeys array to rebuild it from scratch + this._preciseItemKeys = []; + // Fetch Items from Collection this._value.forEach((itemKey) => { const item = this.collection().getItem(itemKey); - if (item != null) groupItemValues.push(item._value); - else notFoundItemKeys.push(itemKey); + if (item != null) { + groupItemValues.push(item._value); + this._preciseItemKeys.push(itemKey); + } else notFoundItemKeys.push(itemKey); }); // Ingest rebuilt Group output into the Runtime diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index 0ae2be7d..e828fdbf 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -1,13 +1,13 @@ import { - Group, Agile, Collection, - StateObserver, - ComputedTracker, - Item, CollectionPersistent, - GroupObserver, + ComputedTracker, EnhancedState, + Group, + GroupObserver, + Item, + StateObserver, TrackedChangeMethod, } from '../../../../src'; import { LogMock } from '../../../helper/logMock'; @@ -229,11 +229,14 @@ describe('Group Tests', () => { describe('remove function tests', () => { beforeEach(() => { - group.nextStateValue = [ + group._value = [ 'dummyItem1Key', 'dummyItem2Key', 'missingInCollectionItemKey', ]; + group.nextStateValue = group._value; + group._preciseItemKeys = ['dummyItem1Key', 'dummyItem2Key']; + group.set = jest.fn(); }); @@ -286,9 +289,10 @@ describe('Group Tests', () => { 'dummyItem1Key', 'notExistingItemKey', 'missingInCollectionItemKey', + 'dummyItem2Key', ]); - expect(group.set).toHaveBeenCalledWith(['dummyItem2Key'], { + expect(group.set).toHaveBeenCalledWith([], { any: { trackedChanges: [ { @@ -297,9 +301,9 @@ describe('Group Tests', () => { key: 'dummyItem1Key', }, { - index: 1, + index: 0, method: TrackedChangeMethod.REMOVE, - key: 'missingInCollectionItemKey', + key: 'dummyItem2Key', }, ], }, @@ -313,15 +317,7 @@ describe('Group Tests', () => { ['dummyItem1Key', 'dummyItem2Key'], { background: true, - any: { - trackedChanges: [ - { - index: 2, - method: TrackedChangeMethod.REMOVE, - key: 'missingInCollectionItemKey', - }, - ], - }, + any: { trackedChanges: [] }, } ); }); @@ -337,15 +333,7 @@ describe('Group Tests', () => { ['dummyItem1Key', 'dummyItem2Key'], { background: true, - any: { - trackedChanges: [ - { - index: 2, - method: TrackedChangeMethod.REMOVE, - key: 'missingInCollectionItemKey', - }, - ], - }, + any: { trackedChanges: [] }, } ); } @@ -354,7 +342,10 @@ describe('Group Tests', () => { describe('add function tests', () => { beforeEach(() => { - group.nextStateValue = ['placeholder', 'dummyItem1Key', 'placeholder']; + group._value = ['placeholder', 'dummyItem1Key', 'placeholder']; + group.nextStateValue = group._value; + group._preciseItemKeys = ['dummyItem1Key']; + group.set = jest.fn(); }); @@ -367,7 +358,7 @@ describe('Group Tests', () => { any: { trackedChanges: [ { - index: 2, + index: 1, method: TrackedChangeMethod.ADD, key: 'dummyItem2Key', }, @@ -436,12 +427,12 @@ describe('Group Tests', () => { any: { trackedChanges: [ { - index: 2, + index: 1, method: TrackedChangeMethod.ADD, key: 'dummyItem2Key', }, { - index: 3, + index: 2, method: TrackedChangeMethod.ADD, key: 'notExistingItemKey', }, @@ -461,7 +452,7 @@ describe('Group Tests', () => { any: { trackedChanges: [ { - index: 2, + index: 1, method: TrackedChangeMethod.ADD, key: 'notExistingItemKey', }, @@ -490,7 +481,7 @@ describe('Group Tests', () => { any: { trackedChanges: [ { - index: 2, + index: 1, method: TrackedChangeMethod.ADD, key: 'notExistingItemKey', }, @@ -652,7 +643,7 @@ describe('Group Tests', () => { beforeEach(() => { group._value = [ 'dummyItem1Key', - 'notExistingItemKey', + 'missingInCollectionItemKey', 'dummyItem2Key', 'dummyItem3Key', ]; @@ -668,14 +659,14 @@ describe('Group Tests', () => { expect(group.notFoundItemKeys).toStrictEqual(['notExistingItemKey']); expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( - [dummyItem1._value, dummyItem2._value], + [dummyItem1._value, dummyItem2._value, dummyItem3._value], {} ); LogMock.hasLoggedCode( '1C:02:00', [dummyCollection._key, group._key], - ['notExistingItemKey'] + ['missingInCollectionItemKey'] ); } ); @@ -684,25 +675,57 @@ describe('Group Tests', () => { 'should hard rebuild the Group if no trackedChanges were specified ' + 'and set notExistingItemKeys to the not found Item Keys (specific config)', () => { - group.rebuild([], { background: true, force: false }); + group.rebuild([], { background: true, force: false, key: 'frank' }); expect(group.notFoundItemKeys).toStrictEqual(['notExistingItemKey']); expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( - [dummyItem1._value, dummyItem2._value], - { background: true, force: false } + [dummyItem1._value, dummyItem2._value, dummyItem3._value], + { background: true, force: false, key: 'frank' } ); LogMock.hasLoggedCode( '1C:02:00', [dummyCollection._key, group._key], - ['notExistingItemKey'] + ['missingInCollectionItemKey'] ); } ); it('should soft rebuild the Group if trackedChanges were specified (default config)', () => { - // TODO + // 'dummyItem1Key', + // 'missingInCollectionItemKey', + // 'dummyItem2Key', + // 'dummyItem3Key' + // TODO the index of a TrackedChange is based on the Group value + // which also contains not existing Items + // -> the index might differ since in the Group output + // not existing Items were skipped + + group.nextGroupOutput = [ + { id: 'dummyItem1Key', name: 'jeff' }, + { id: 'dummyItem2Key', name: 'frank' }, + { id: 'dummyItem3Key', name: 'hans' }, + ]; + + group.rebuild([ + { index: 3, method: TrackedChangeMethod.ADD, key: 'dummyItem3Key' }, + { + index: 1, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem2Key', + }, + { + index: 10, + method: TrackedChangeMethod.UPDATE, + key: 'missingInCollectionItemKey', + }, + { + index: 0, + method: TrackedChangeMethod.UPDATE, + key: 'dummyItem1Key', + }, + ]); }); it('should soft rebuild the Group if trackedChanges were specified (specific config)', () => { From a6b2140819e4eb5cb9b3e513899cc8abafed4c4b Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 12 Sep 2021 15:46:21 +0200 Subject: [PATCH 13/15] fixed tests --- .../tests/unit/collection/group/group.test.ts | 160 +++++++++++++----- .../core/tests/unit/runtime/observer.test.ts | 2 + 2 files changed, 120 insertions(+), 42 deletions(-) diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index e828fdbf..f7028e7e 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -648,6 +648,7 @@ describe('Group Tests', () => { 'dummyItem3Key', ]; group.observers['output'].ingestOutput = jest.fn(); + group.observers['output'].ingest = jest.fn(); }); it( @@ -656,12 +657,14 @@ describe('Group Tests', () => { () => { group.rebuild(); - expect(group.notFoundItemKeys).toStrictEqual(['notExistingItemKey']); - expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' + expect(group.notFoundItemKeys).toStrictEqual([ + 'missingInCollectionItemKey', + ]); expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( [dummyItem1._value, dummyItem2._value, dummyItem3._value], {} ); + expect(group.observers['output'].ingest).not.toHaveBeenCalled(); LogMock.hasLoggedCode( '1C:02:00', @@ -677,8 +680,9 @@ describe('Group Tests', () => { () => { group.rebuild([], { background: true, force: false, key: 'frank' }); - expect(group.notFoundItemKeys).toStrictEqual(['notExistingItemKey']); - expect(group._output).toStrictEqual([]); // because of mocking 'ingestValue' + expect(group.notFoundItemKeys).toStrictEqual([ + 'missingInCollectionItemKey', + ]); expect(group.observers['output'].ingestOutput).toHaveBeenCalledWith( [dummyItem1._value, dummyItem2._value, dummyItem3._value], { background: true, force: false, key: 'frank' } @@ -692,45 +696,118 @@ describe('Group Tests', () => { } ); - it('should soft rebuild the Group if trackedChanges were specified (default config)', () => { - // 'dummyItem1Key', - // 'missingInCollectionItemKey', - // 'dummyItem2Key', - // 'dummyItem3Key' - // TODO the index of a TrackedChange is based on the Group value - // which also contains not existing Items - // -> the index might differ since in the Group output - // not existing Items were skipped - - group.nextGroupOutput = [ - { id: 'dummyItem1Key', name: 'jeff' }, - { id: 'dummyItem2Key', name: 'frank' }, - { id: 'dummyItem3Key', name: 'hans' }, - ]; + it( + 'should soft rebuild the Group if trackedChanges were specified ' + + 'and set notExistingItemKeys to the not found Item Keys (default config)', + () => { + group.nextGroupOutput = [ + { id: 'dummyItem1Key', name: 'jeff' }, + { id: 'dummyItem2Key', name: 'frank' }, + ]; + group._preciseItemKeys = ['dummyItem1Key', 'dummyItem2Key']; + + group.rebuild([ + { index: 2, method: TrackedChangeMethod.ADD, key: 'dummyItem3Key' }, + { + index: 1, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem2Key', + }, + { + index: 4, + method: TrackedChangeMethod.UPDATE, + key: 'missingInCollectionItemKey', + }, + { + index: 0, + method: TrackedChangeMethod.UPDATE, + key: 'dummyItem1Key', + }, + ]); - group.rebuild([ - { index: 3, method: TrackedChangeMethod.ADD, key: 'dummyItem3Key' }, - { - index: 1, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem2Key', - }, - { - index: 10, - method: TrackedChangeMethod.UPDATE, - key: 'missingInCollectionItemKey', - }, - { - index: 0, - method: TrackedChangeMethod.UPDATE, - key: 'dummyItem1Key', - }, - ]); - }); + expect(group.notFoundItemKeys).toStrictEqual([ + 'missingInCollectionItemKey', + ]); + expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); + expect(group.observers['output'].ingest).toHaveBeenCalled(); + expect(group.nextGroupOutput).toStrictEqual([ + { id: 'dummyItem1Key', name: 'jeff' }, + { id: 'dummyItem3Key', name: 'hans' }, + ]); + expect(group._preciseItemKeys).toStrictEqual([ + 'dummyItem1Key', + 'dummyItem3Key', + ]); - it('should soft rebuild the Group if trackedChanges were specified (specific config)', () => { - // TODO - }); + LogMock.hasLoggedCode( + '1C:02:00', + [dummyCollection._key, group._key], + ['missingInCollectionItemKey'] + ); + } + ); + + it( + 'should soft rebuild the Group if trackedChanges were specified ' + + 'and set notExistingItemKeys to the not found Item Keys (specific config)', + () => { + group.nextGroupOutput = [ + { id: 'dummyItem1Key', name: 'jeff' }, + { id: 'dummyItem2Key', name: 'frank' }, + ]; + group._preciseItemKeys = ['dummyItem1Key', 'dummyItem2Key']; + + group.rebuild( + [ + { + index: 2, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }, + { + index: 1, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem2Key', + }, + { + index: 4, + method: TrackedChangeMethod.UPDATE, + key: 'missingInCollectionItemKey', + }, + { + index: 0, + method: TrackedChangeMethod.UPDATE, + key: 'dummyItem1Key', + }, + ], + { key: 'frank', force: true, background: true } + ); + + expect(group.notFoundItemKeys).toStrictEqual([ + 'missingInCollectionItemKey', + ]); + expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); + expect(group.observers['output'].ingest).toHaveBeenCalledWith({ + key: 'frank', + force: true, + background: true, + }); + expect(group.nextGroupOutput).toStrictEqual([ + { id: 'dummyItem1Key', name: 'jeff' }, + { id: 'dummyItem3Key', name: 'hans' }, + ]); + expect(group._preciseItemKeys).toStrictEqual([ + 'dummyItem1Key', + 'dummyItem3Key', + ]); + + LogMock.hasLoggedCode( + '1C:02:00', + [dummyCollection._key, group._key], + ['missingInCollectionItemKey'] + ); + } + ); it( "shouldn't ingest the build Group output " + @@ -741,7 +818,6 @@ describe('Group Tests', () => { group.rebuild(); expect(group.notFoundItemKeys).toStrictEqual([]); - expect(group._output).toStrictEqual([]); expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); LogMock.hasNotLogged('warn'); } diff --git a/packages/core/tests/unit/runtime/observer.test.ts b/packages/core/tests/unit/runtime/observer.test.ts index 5810c138..5acbcf64 100644 --- a/packages/core/tests/unit/runtime/observer.test.ts +++ b/packages/core/tests/unit/runtime/observer.test.ts @@ -103,6 +103,7 @@ describe('Observer Tests', () => { }, force: false, maxTriesToUpdate: 3, + any: {}, }); }); @@ -128,6 +129,7 @@ describe('Observer Tests', () => { }, force: true, maxTriesToUpdate: 3, + any: {}, }); }); From af798b782601a45b5214555eb22136590313bc2f Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 12 Sep 2021 16:05:55 +0200 Subject: [PATCH 14/15] fixed typo --- packages/core/src/collection/group/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index 328523b9..4aac3118 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -151,7 +151,7 @@ export class Group< // Need to temporary update the preciseItemKeys // since in the rebuild one action (trackedChanges) is performed after the other // which requires a dynamic updated index - let updatedPreciseItemKeys = copy(this._preciseItemKeys); + const updatedPreciseItemKeys = copy(this._preciseItemKeys); config = defineConfig(config, { softRebuild: true, any: {}, From ad56d3975c8c00a50ffc75ac417a7654398b02e6 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 12 Sep 2021 20:02:46 +0200 Subject: [PATCH 15/15] fixed typo --- .../develop/simple-todo-list/src/core.js | 4 +- packages/core/src/collection/collection.ts | 51 +++++-- packages/core/src/collection/group/index.ts | 4 +- .../tests/unit/collection/collection.test.ts | 57 ++++++-- .../tests/unit/collection/group/group.test.ts | 129 +++++++++++------- 5 files changed, 169 insertions(+), 76 deletions(-) diff --git a/examples/react/develop/simple-todo-list/src/core.js b/examples/react/develop/simple-todo-list/src/core.js index 316d5f31..881290a6 100644 --- a/examples/react/develop/simple-todo-list/src/core.js +++ b/examples/react/develop/simple-todo-list/src/core.js @@ -1,4 +1,4 @@ -import { createCollection } from '@agile-ts/core'; +import { createCollection, globalBind } from '@agile-ts/core'; import { assignSharedAgileLoggerConfig, Logger } from '@agile-ts/logger'; assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); @@ -7,3 +7,5 @@ assignSharedAgileLoggerConfig({ level: Logger.level.DEBUG }); export const TODOS = createCollection({ initialData: [{ id: 1, name: 'Clean Bathroom' }], }).persist('todos'); // persist does store the Collection in the Local Storage + +globalBind('__core__', { TODOS }); diff --git a/packages/core/src/collection/collection.ts b/packages/core/src/collection/collection.ts index ff552fbf..60535136 100644 --- a/packages/core/src/collection/collection.ts +++ b/packages/core/src/collection/collection.ts @@ -1218,7 +1218,9 @@ export class Collection< * @public * @param itemKeys - Item/s with identifier/s to be removed. */ - public remove(itemKeys: ItemKey | Array): { + public remove( + itemKeys: ItemKey | Array + ): { fromGroups: (groups: Array | ItemKey) => Collection; everywhere: (config?: RemoveItemsConfigInterface) => Collection; } { @@ -1467,16 +1469,43 @@ export class Collection< for (const groupKey of Object.keys(this.groups)) { const group = this.getGroup(groupKey); if (group != null && group.has(itemKey)) { - group.rebuild( - [ - { - key: itemKey, - index: group.nextStateValue.findIndex((ik) => itemKey === ik), - method: TrackedChangeMethod.UPDATE, - }, - ], - config - ); + const index = group._preciseItemKeys.findIndex((ik) => itemKey === ik); + + // Update Group output at index + if (index !== -1) { + group.rebuild( + [ + { + key: itemKey, + index: index, + method: TrackedChangeMethod.UPDATE, + }, + ], + config + ); + } + // Add Item to the Group output if it isn't yet represented there to be updated + else { + const indexOfBeforeItemKey = + group.nextStateValue.findIndex((ik) => itemKey === ik) - 1; + + group.rebuild( + [ + { + key: itemKey, + index: + indexOfBeforeItemKey >= 0 + ? group._preciseItemKeys.findIndex( + (ik) => + group.nextStateValue[indexOfBeforeItemKey] === ik + ) + 1 + : 0, + method: TrackedChangeMethod.ADD, + }, + ], + config + ); + } } } } diff --git a/packages/core/src/collection/group/index.ts b/packages/core/src/collection/group/index.ts index 4aac3118..c404b7db 100644 --- a/packages/core/src/collection/group/index.ts +++ b/packages/core/src/collection/group/index.ts @@ -415,9 +415,9 @@ export class Group< switch (change.method) { case TrackedChangeMethod.ADD: - this._preciseItemKeys.splice(change.index, 0, change.key); // this._value.splice(change.index, 0, change.key); // Already updated in 'add' method if (item != null) { + this._preciseItemKeys.splice(change.index, 0, change.key); this.nextGroupOutput.splice(change.index, 0, copy(item._value)); } else { notFoundItemKeys.push(change.key); @@ -431,8 +431,8 @@ export class Group< } break; case TrackedChangeMethod.REMOVE: - this._preciseItemKeys.splice(change.index, 1); // this._value.splice(change.index, 1); // Already updated in 'remove' method + this._preciseItemKeys.splice(change.index, 1); this.nextGroupOutput.splice(change.index, 1); break; default: diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index b01d3085..9dee6d0b 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -265,10 +265,10 @@ describe('Collection Tests', () => { key: 'group1Key', }); - expect(collection.createGroup).toHaveBeenCalledWith( - 'group1Key', - [1, 2] - ); + expect(collection.createGroup).toHaveBeenCalledWith('group1Key', [ + 1, + 2, + ]); LogMock.hasLoggedCode('1B:02:00'); expect(response).toBeInstanceOf(Group); @@ -3020,10 +3020,24 @@ describe('Collection Tests', () => { let dummyGroup1: Group; let dummyGroup2: Group; + let dummyItem1: Item; + let dummyItem2: Item; + beforeEach(() => { - dummyGroup1 = new Group(collection, ['dummyItem1', 'dummyItem2'], { - key: 'dummyGroup1', - }); + dummyItem1 = new Item(collection, { id: 'dummyItem1', name: 'Jeff' }); + dummyItem2 = new Item(collection, { id: 'dummyItem2', name: 'Jeff' }); + collection.data = { + dummyItem1: dummyItem1, + dummyItem2: dummyItem2, + }; + + dummyGroup1 = new Group( + collection, + ['dummyItem1', 'missingInCollectionItemKey', 'dummyItem2'], + { + key: 'dummyGroup1', + } + ); dummyGroup2 = new Group(collection, ['dummyItem2'], { key: 'dummyGroup2', }); @@ -3036,7 +3050,7 @@ describe('Collection Tests', () => { dummyGroup2.rebuild = jest.fn(); }); - it('should rebuild each Group that includes the specified itemKey (default config)', () => { + it('should update the Item in each Group (output) that includes the specified itemKey (default config)', () => { collection.rebuildGroupsThatIncludeItemKey('dummyItem1'); // Group 1 @@ -3055,7 +3069,7 @@ describe('Collection Tests', () => { expect(dummyGroup2.rebuild).not.toHaveBeenCalled(); }); - it('should rebuild each Group that includes the specified itemKey (specific config)', () => { + it('should update the Item in each Group (output) that includes the specified itemKey (specific config)', () => { collection.rebuildGroupsThatIncludeItemKey('dummyItem2', { key: 'frank', background: true, @@ -3094,6 +3108,31 @@ describe('Collection Tests', () => { } ); }); + + it( + 'should update the Item in each Group (output) that includes the specified itemKey ' + + "although the Item doesn't exist in the Group output yet", + () => { + collection.rebuildGroupsThatIncludeItemKey( + 'missingInCollectionItemKey' + ); + + // Group 1 + expect(dummyGroup1.rebuild).toHaveBeenCalledWith( + [ + { + key: 'missingInCollectionItemKey', + index: 1, + method: TrackedChangeMethod.ADD, + }, + ], + {} + ); + + // Group 2 + expect(dummyGroup2.rebuild).not.toHaveBeenCalled(); + } + ); }); }); }); diff --git a/packages/core/tests/unit/collection/group/group.test.ts b/packages/core/tests/unit/collection/group/group.test.ts index f7028e7e..b544c54c 100644 --- a/packages/core/tests/unit/collection/group/group.test.ts +++ b/packages/core/tests/unit/collection/group/group.test.ts @@ -698,38 +698,35 @@ describe('Group Tests', () => { it( 'should soft rebuild the Group if trackedChanges were specified ' + - 'and set notExistingItemKeys to the not found Item Keys (default config)', + 'and set notExistingItemKeys to the not found itemKeys (ADD)', () => { - group.nextGroupOutput = [ - { id: 'dummyItem1Key', name: 'jeff' }, - { id: 'dummyItem2Key', name: 'frank' }, - ]; - group._preciseItemKeys = ['dummyItem1Key', 'dummyItem2Key']; + group.nextGroupOutput = [{ id: 'dummyItem1Key', name: 'jeff' }]; + group._preciseItemKeys = ['dummyItem1Key']; - group.rebuild([ - { index: 2, method: TrackedChangeMethod.ADD, key: 'dummyItem3Key' }, - { - index: 1, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem2Key', - }, - { - index: 4, - method: TrackedChangeMethod.UPDATE, - key: 'missingInCollectionItemKey', - }, - { - index: 0, - method: TrackedChangeMethod.UPDATE, - key: 'dummyItem1Key', - }, - ]); + group.rebuild( + [ + { + index: 1, + method: TrackedChangeMethod.ADD, + key: 'dummyItem3Key', + }, + { + index: 2, + method: TrackedChangeMethod.ADD, + key: 'missingInCollectionItemKey', + }, + ], + { key: 'test', background: true } + ); expect(group.notFoundItemKeys).toStrictEqual([ 'missingInCollectionItemKey', ]); expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); - expect(group.observers['output'].ingest).toHaveBeenCalled(); + expect(group.observers['output'].ingest).toHaveBeenCalledWith({ + key: 'test', + background: true, + }); expect(group.nextGroupOutput).toStrictEqual([ { id: 'dummyItem1Key', name: 'jeff' }, { id: 'dummyItem3Key', name: 'hans' }, @@ -747,40 +744,69 @@ describe('Group Tests', () => { } ); + it('should soft rebuild the Group if trackedChanges were specified (REMOVE)', () => { + group.nextGroupOutput = [ + { id: 'dummyItem1Key', name: 'jeff' }, + { id: 'dummyItem2Key', name: 'frank' }, + { id: 'dummyItem3Key', name: 'hans' }, + ]; + group._preciseItemKeys = [ + 'dummyItem1Key', + 'dummyItem2Key', + 'dummyItem3Key', + ]; + + group.rebuild( + [ + { + index: 1, + method: TrackedChangeMethod.REMOVE, + key: 'dummyItem2Key', + }, + ], + { key: 'test', background: true } + ); + + expect(group.notFoundItemKeys).toStrictEqual([]); + expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); + expect(group.observers['output'].ingest).toHaveBeenCalledWith({ + key: 'test', + background: true, + }); + expect(group.nextGroupOutput).toStrictEqual([ + { id: 'dummyItem1Key', name: 'jeff' }, + { id: 'dummyItem3Key', name: 'hans' }, + ]); + expect(group._preciseItemKeys).toStrictEqual([ + 'dummyItem1Key', + 'dummyItem3Key', + ]); + + LogMock.hasNotLogged('warn'); + }); + it( 'should soft rebuild the Group if trackedChanges were specified ' + - 'and set notExistingItemKeys to the not found Item Keys (specific config)', + 'and set notExistingItemKeys to the not found itemKeys (UPDATE)', () => { - group.nextGroupOutput = [ - { id: 'dummyItem1Key', name: 'jeff' }, - { id: 'dummyItem2Key', name: 'frank' }, - ]; - group._preciseItemKeys = ['dummyItem1Key', 'dummyItem2Key']; + dummyItem1._value = { id: 'dummyItem1Key', name: 'frank' }; + group.nextGroupOutput = [{ id: 'dummyItem1Key', name: 'jeff' }]; + group._preciseItemKeys = ['dummyItem1Key']; group.rebuild( [ { - index: 2, - method: TrackedChangeMethod.ADD, - key: 'dummyItem3Key', + index: 0, + method: TrackedChangeMethod.UPDATE, + key: 'dummyItem1Key', }, { index: 1, - method: TrackedChangeMethod.REMOVE, - key: 'dummyItem2Key', - }, - { - index: 4, method: TrackedChangeMethod.UPDATE, key: 'missingInCollectionItemKey', }, - { - index: 0, - method: TrackedChangeMethod.UPDATE, - key: 'dummyItem1Key', - }, ], - { key: 'frank', force: true, background: true } + { key: 'test', background: true } ); expect(group.notFoundItemKeys).toStrictEqual([ @@ -788,18 +814,13 @@ describe('Group Tests', () => { ]); expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); expect(group.observers['output'].ingest).toHaveBeenCalledWith({ - key: 'frank', - force: true, + key: 'test', background: true, }); expect(group.nextGroupOutput).toStrictEqual([ - { id: 'dummyItem1Key', name: 'jeff' }, - { id: 'dummyItem3Key', name: 'hans' }, - ]); - expect(group._preciseItemKeys).toStrictEqual([ - 'dummyItem1Key', - 'dummyItem3Key', + { id: 'dummyItem1Key', name: 'frank' }, ]); + expect(group._preciseItemKeys).toStrictEqual(['dummyItem1Key']); LogMock.hasLoggedCode( '1C:02:00', @@ -819,6 +840,8 @@ describe('Group Tests', () => { expect(group.notFoundItemKeys).toStrictEqual([]); expect(group.observers['output'].ingestOutput).not.toHaveBeenCalled(); + expect(group.observers['output'].ingest).not.toHaveBeenCalled(); + LogMock.hasNotLogged('warn'); } );