Browse Source

Dynamic values improvements (#6482)

* Avoid throwing errors if no condition is passed to a DataCondition

* Refactor Condition class

* Remove expecting errors for conditions tests

* Extend and refactor BaseOperator

* Rename and refactor GenericOperator

* Rename and refactor logicalOperator to BooleanOperator

* Refactor StringOperator

* Refactor NumberOperator

* Refactor and fix DataResolverListener

* Update tests for condition Operators

* Rename Condition class to ConditionEvaluator

* Add missing types file

* Update and refactor DataCondition

* Update utils

* Refactor StyleableModel class

* Update ComponentDataCondition

* Refactor ComponentResolverWatcher

* Fix conditional styles

* Rename LogicalGroupStatement to LogicalGroupEvaluator

* Fix tests for DataCondition

* Add setter methods for component data variable

* Add setters and getter to ComponentDataCondition

* Add getters to ComponentDataVariable

* Rename test file

* Make dynamic components undroppable

* Fix collection types

* Update collections ( add setters and getter, first item editable, sync styles )

* Update data collection tests

* Format tests

* Fix some ComponentData collection bugs

* Refactor setStartIndex and setEndIndex

* Fix getComponentDef test

* Fix bug with end index = 0

* fix test for setDataSource

* fix test for HTML updates

* Format

* Add tests for the new option in getDataValue for conditions ( skipDynamicValueResolution )

* rename Operation to DataConditionOperation

* Run __onStyleChange after style changes as before

* Format

* Up BooleanOperator

* Avoid raising errors for wrong dynamic value types

* Refactor DataCondition

* Format

* Refactor how component data variable works internally

* Symbols fix

* Add docs

* Refactor componentDatacondition

* Add types for Datacondition output ( result ) types

* Refactor DataCondition

* Refactor ComponentDataCollection

* Refactor styleableModel

* Add missing files and utils

* Up tests for ComponentDataCondition

* Add new tests for component data condition

* Fix quality check

* Move ComponentDataVariable path and default value to dataResolver property

* Update component data condition structure

* Refactor data condition

* Fix ifTrue and ifFalse position in ComponentDataCondition

* Fix typescript

* Change setIfTrueContent to be setIfTrueComponents

* Fix memory leak for binding to this.postRender

* Cleanup ComponentDataConditionView

* Up tests

* Save ComponentDataVariable on dataResolverChange

* Remove DataCollection componentDef

* Add setCollectionId method

* Replace DataCollectionVariable with DataVariable

* cleanup

* Fix bug

* PR review

* Add new API endpoints

* Add getCollectionsStateMap to ComponentDataVariable

* Fix tests

---------

Co-authored-by: Artur Arseniev <artur.catch@hotmail.it>
pull/6477/head
mohamed yahia 1 year ago
committed by GitHub
parent
commit
f474fef99a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 61
      packages/core/src/data_sources/model/ComponentDataVariable.ts
  2. 28
      packages/core/src/data_sources/model/DataResolverListener.ts
  3. 121
      packages/core/src/data_sources/model/DataVariable.ts
  4. 19
      packages/core/src/data_sources/model/conditional_variables/ComponentDataOutput.ts
  5. 4
      packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts
  6. 21
      packages/core/src/data_sources/model/conditional_variables/DataCondition.ts
  7. 1
      packages/core/src/data_sources/model/conditional_variables/operators/AnyTypeOperator.ts
  8. 227
      packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts
  9. 54
      packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts
  10. 158
      packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts
  11. 4
      packages/core/src/data_sources/model/data_collection/constants.ts
  12. 44
      packages/core/src/data_sources/model/data_collection/types.ts
  13. 6
      packages/core/src/data_sources/types.ts
  14. 24
      packages/core/src/data_sources/utils.ts
  15. 21
      packages/core/src/data_sources/view/ComponentDataCollectionVariableView.ts
  16. 20
      packages/core/src/dom_components/index.ts
  17. 10
      packages/core/src/dom_components/model/ComponentDataResolverWatchers.ts
  18. 24
      packages/core/src/dom_components/model/ComponentResolverWatcher.ts
  19. 9
      packages/core/src/dom_components/model/SymbolUtils.ts
  20. 11
      packages/core/src/domain_abstract/model/StyleableModel.ts
  21. 183
      packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.getters-setters.ts
  22. 656
      packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts
  23. 192
      packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionWithDataVariable.ts
  24. 212
      packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap
  25. 141
      packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap
  26. 153
      packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionWithDataVariable.ts.snap
  27. 61
      packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap
  28. 459
      packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts

61
packages/core/src/data_sources/model/ComponentDataVariable.ts

@ -1,12 +1,15 @@
import { ModelDestroyOptions } from 'backbone';
import { ObjectAny } from '../../common'; import { ObjectAny } from '../../common';
import Component from '../../dom_components/model/Component'; import Component from '../../dom_components/model/Component';
import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../dom_components/model/types'; import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../dom_components/model/types';
import { toLowerCase } from '../../utils/mixins'; import { toLowerCase } from '../../utils/mixins';
import DataVariable, { DataVariableProps, DataVariableType } from './DataVariable'; import DataVariable, { DataVariableProps, DataVariableType } from './DataVariable';
import { DataCollectionStateMap } from './data_collection/types';
import { keyCollectionsStateMap } from './data_collection/constants';
export interface ComponentDataVariableProps extends ComponentProperties { export interface ComponentDataVariableProps extends ComponentProperties {
type: typeof DataVariableType; type?: typeof DataVariableType;
dataResolver: DataVariableProps; dataResolver?: DataVariableProps;
} }
export default class ComponentDataVariable extends Component { export default class ComponentDataVariable extends Component {
@ -16,19 +19,20 @@ export default class ComponentDataVariable extends Component {
return { return {
// @ts-ignore // @ts-ignore
...super.defaults, ...super.defaults,
droppable: false,
type: DataVariableType, type: DataVariableType,
dataResolver: { dataResolver: {},
path: '', droppable: false,
defaultValue: '',
},
}; };
} }
constructor(props: ComponentDataVariableProps, opt: ComponentOptions) { constructor(props: ComponentDataVariableProps, opt: ComponentOptions) {
super(props, opt); super(props, opt);
this.dataResolver = new DataVariable(props.dataResolver, opt); this.dataResolver = new DataVariable(props.dataResolver ?? {}, {
...opt,
collectionsStateMap: this.get(keyCollectionsStateMap),
});
this.listenToPropsChange(); this.listenToPropsChange();
} }
@ -48,6 +52,10 @@ export default class ComponentDataVariable extends Component {
return this.getDataValue(); return this.getDataValue();
} }
getCollectionsStateMap() {
return this.get(keyCollectionsStateMap) ?? {};
}
setPath(newPath: string) { setPath(newPath: string) {
this.dataResolver.set('path', newPath); this.dataResolver.set('path', newPath);
} }
@ -56,15 +64,43 @@ export default class ComponentDataVariable extends Component {
this.dataResolver.set('defaultValue', newValue); this.dataResolver.set('defaultValue', newValue);
} }
setDataResolver(props: DataVariableProps) {
this.dataResolver.set(props);
}
/**
* Sets the data source path and resets related properties.
* This will set collectionId and variableType to undefined as it's typically
* used when changing to a completely different data source.
* @param newPath The new path to set as the data source
*/
resetDataSourcePath(newPath: string) {
this.set('dataResolver', {
path: newPath,
collectionId: undefined,
variableType: undefined,
});
}
private listenToPropsChange() { private listenToPropsChange() {
this.listenTo(
this.dataResolver,
'change',
(() => {
this.__changesUp({ m: this });
}).bind(this),
);
this.on('change:dataResolver', () => { this.on('change:dataResolver', () => {
this.dataResolver.set(this.get('dataResolver')); this.dataResolver.set(this.get('dataResolver'));
}); });
this.on(`change:${keyCollectionsStateMap}`, (_: Component, value: DataCollectionStateMap) => {
this.dataResolver.updateCollectionsStateMap(value);
});
} }
toJSON(opts?: ObjectAny): ComponentDefinition { toJSON(opts?: ObjectAny): ComponentDefinition {
const json = super.toJSON(opts); const json = super.toJSON(opts);
const dataResolver = this.dataResolver.toJSON(); const dataResolver: DataVariableProps = this.dataResolver.toJSON();
delete dataResolver.type; delete dataResolver.type;
return { return {
@ -73,6 +109,13 @@ export default class ComponentDataVariable extends Component {
}; };
} }
destroy(options?: ModelDestroyOptions | undefined): false | JQueryXHR {
this.stopListening(this.dataResolver, 'change');
this.off('change:dataResolver');
this.off(`change:${keyCollectionsStateMap}`);
return super.destroy(options);
}
static isComponent(el: HTMLElement) { static isComponent(el: HTMLElement) {
return toLowerCase(el.tagName) === DataVariableType; return toLowerCase(el.tagName) === DataVariableType;
} }

28
packages/core/src/data_sources/model/DataResolverListener.ts

@ -9,8 +9,6 @@ import {
DataConditionOutputChangedEvent, DataConditionOutputChangedEvent,
DataConditionType, DataConditionType,
} from './conditional_variables/DataCondition'; } from './conditional_variables/DataCondition';
import { DataCollectionVariableType } from './data_collection/constants';
import DataCollectionVariable from './data_collection/DataCollectionVariable';
export interface DataResolverListenerProps { export interface DataResolverListenerProps {
em: EditorModel; em: EditorModel;
@ -52,9 +50,6 @@ export default class DataResolverListener {
const type = resolver.attributes.type; const type = resolver.attributes.type;
switch (type) { switch (type) {
case DataCollectionVariableType:
listeners = this.listenToDataCollectionVariable(resolver as DataCollectionVariable);
break;
case DataVariableType: case DataVariableType:
listeners = this.listenToDataVariable(resolver as DataVariable); listeners = this.listenToDataVariable(resolver as DataVariable);
break; break;
@ -79,12 +74,20 @@ export default class DataResolverListener {
private listenToDataVariable(dataVariable: DataVariable): ListenerWithCallback[] { private listenToDataVariable(dataVariable: DataVariable): ListenerWithCallback[] {
const { em } = this; const { em } = this;
const { path } = dataVariable.attributes; const dataListeners: ListenerWithCallback[] = [];
dataListeners.push(
this.createListener(dataVariable, 'change', () => {
this.listenToResolver();
this.onChange();
}),
);
const path = dataVariable.getResolverPath();
if (!path) return dataListeners;
const normPath = stringToPath(path || '').join('.'); const normPath = stringToPath(path || '').join('.');
const [ds, dr] = em.DataSources.fromPath(path!); const [ds, dr] = em.DataSources.fromPath(path!);
const dataListeners: ListenerWithCallback[] = [];
if (ds) { if (ds) {
dataListeners.push(this.createListener(ds.records, 'add remove reset')); dataListeners.push(this.createListener(ds.records, 'add remove reset'));
} }
@ -94,11 +97,6 @@ export default class DataResolverListener {
} }
dataListeners.push( dataListeners.push(
this.createListener(dataVariable, 'change:path', () => {
this.listenToResolver();
this.onChange();
}),
this.createListener(dataVariable, 'change:defaultValue'),
this.createListener(em.DataSources.all, 'add remove reset'), this.createListener(em.DataSources.all, 'add remove reset'),
this.createListener(em, `${DataSourcesEvents.path}:${normPath}`), this.createListener(em, `${DataSourcesEvents.path}:${normPath}`),
); );
@ -106,10 +104,6 @@ export default class DataResolverListener {
return dataListeners; return dataListeners;
} }
private listenToDataCollectionVariable(dataVariable: DataCollectionVariable): ListenerWithCallback[] {
return [this.createListener(dataVariable, 'change:value')];
}
private removeListeners() { private removeListeners() {
this.listeners.forEach((ls) => this.model.stopListening(ls.obj, ls.event, ls.callback)); this.listeners.forEach((ls) => this.model.stopListening(ls.obj, ls.event, ls.callback));
this.listeners = []; this.listeners = [];

121
packages/core/src/data_sources/model/DataVariable.ts

@ -1,32 +1,141 @@
import { Model } from '../../common'; import { Model } from '../../common';
import EditorModel from '../../editor/model/Editor'; import EditorModel from '../../editor/model/Editor';
import { isDataVariable } from '../utils';
import { DataCollectionStateMap, DataCollectionState, DataCollectionStateType } from './data_collection/types';
export const DataVariableType = 'data-variable' as const; export const DataVariableType = 'data-variable' as const;
export interface DataVariableProps { export interface DataVariableProps {
type?: typeof DataVariableType; type?: typeof DataVariableType;
path: string; path?: string;
defaultValue?: string; defaultValue?: string;
collectionId?: string;
variableType?: DataCollectionStateType;
} }
export default class DataVariable extends Model<DataVariableProps> { export default class DataVariable extends Model<DataVariableProps> {
em?: EditorModel; private em: EditorModel;
private collectionsStateMap: DataCollectionStateMap;
defaults() { defaults(): DataVariableProps {
return { return {
type: DataVariableType, type: DataVariableType,
defaultValue: '', defaultValue: '',
path: '', path: '',
collectionId: undefined,
variableType: undefined,
}; };
} }
constructor(props: DataVariableProps, options: { em?: EditorModel }) { constructor(props: DataVariableProps, options: { em: EditorModel; collectionsStateMap: DataCollectionStateMap }) {
super(props, options); super(props, options);
this.em = options.em; this.em = options.em;
this.collectionsStateMap = options.collectionsStateMap;
}
get path(): string {
return this.get('path') ?? '';
}
get defaultValue(): string {
return this.get('defaultValue') ?? '';
}
get collectionId(): string | undefined {
return this.get('collectionId');
}
get variableType(): DataCollectionStateType | undefined {
return this.get('variableType');
} }
getDataValue() { getDataValue() {
const { path, defaultValue } = this.attributes; if (this.resolvesFromCollection()) {
return this.em?.DataSources.getValue(path!, defaultValue); const valueOrDataVariableProps = this.resolveCollectionVariable();
if (!isDataVariable(valueOrDataVariableProps)) return valueOrDataVariableProps;
const { path = '' } = valueOrDataVariableProps;
return this.resolveDataSourcePath(path);
}
return this.resolveDataSourcePath(this.path);
}
resolvesFromCollection(): boolean {
return !!this.collectionId;
}
updateCollectionsStateMap(collectionsStateMap: DataCollectionStateMap): void {
this.collectionsStateMap = collectionsStateMap;
this.trigger('change');
}
getResolverPath(): string | false {
if (this.resolvesFromCollection()) {
const value = this.resolveCollectionVariable();
if (!isDataVariable(value)) return false;
return value.path ?? '';
}
return this.path;
}
toJSON(options?: any): DataVariableProps & { type: typeof DataVariableType } {
const defaults = this.defaults();
const json = super.toJSON(options);
const filteredJson = Object.fromEntries(
Object.entries(json).filter(([key, value]) => value !== defaults[key as keyof DataVariableProps]),
) as Partial<DataVariableProps>;
return {
...filteredJson,
type: DataVariableType,
};
}
private resolveDataSourcePath(path: string) {
return this.em.DataSources.getValue(path, this.defaultValue);
}
private resolveCollectionVariable(): unknown {
const { collectionId = '', variableType, path, defaultValue = '' } = this.attributes;
if (!this.collectionsStateMap) return defaultValue;
const collectionItem = this.collectionsStateMap[collectionId];
if (!collectionItem) return defaultValue;
if (!variableType) {
this.em.logError(`Missing collection variable type for collection: ${collectionId}`);
return defaultValue;
}
return variableType === 'currentItem'
? this.resolveCurrentItem(collectionItem, path, collectionId)
: collectionItem[variableType];
}
private resolveCurrentItem(
collectionItem: DataCollectionState,
path: string | undefined,
collectionId: string,
): unknown {
const currentItem = collectionItem.currentItem;
if (!currentItem) {
this.em.logError(`Current item is missing for collection: ${collectionId}`);
return '';
}
if (currentItem.type === DataVariableType) {
const resolvedPath = currentItem.path ? `${currentItem.path}.${path}` : path;
return { type: DataVariableType, path: resolvedPath };
}
if (path && !(currentItem as any)[path]) {
this.em.logError(`Path not found in current item: ${path} for collection: ${collectionId}`);
return '';
}
return path ? (currentItem as any)[path] : currentItem;
} }
} }

19
packages/core/src/data_sources/model/conditional_variables/ComponentDataOutput.ts

@ -0,0 +1,19 @@
import Component from '../../../dom_components/model/Component';
import { ComponentDefinitionDefined } from '../../../dom_components/model/types';
import { toLowerCase } from '../../../utils/mixins';
import { isComponentDataOutputType } from '../../utils';
export default class ComponentDataOutput extends Component {
get defaults(): ComponentDefinitionDefined {
return {
// @ts-ignore
...super.defaults,
removable: false,
draggable: false,
};
}
static isComponent(el: HTMLElement) {
return isComponentDataOutputType(toLowerCase(el.tagName));
}
}

4
packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts

@ -1,7 +1,7 @@
import Component from '../../../dom_components/model/Component'; import Component from '../../../dom_components/model/Component';
import { ComponentDefinitionDefined, ToHTMLOptions } from '../../../dom_components/model/types'; import { ComponentDefinitionDefined, ToHTMLOptions } from '../../../dom_components/model/types';
import { toLowerCase } from '../../../utils/mixins'; import { toLowerCase } from '../../../utils/mixins';
import { isDataConditionDisplayType } from '../../utils'; import { isComponentDataOutputType } from '../../utils';
export default class ConditionalOutputBase extends Component { export default class ConditionalOutputBase extends Component {
get defaults(): ComponentDefinitionDefined { get defaults(): ComponentDefinitionDefined {
@ -14,6 +14,6 @@ export default class ConditionalOutputBase extends Component {
} }
static isComponent(el: HTMLElement) { static isComponent(el: HTMLElement) {
return isDataConditionDisplayType(toLowerCase(el.tagName)); return isComponentDataOutputType(toLowerCase(el.tagName));
} }
} }

21
packages/core/src/data_sources/model/conditional_variables/DataCondition.ts

@ -9,6 +9,7 @@ import { BooleanOperation } from './operators/BooleanOperator';
import { NumberOperation } from './operators/NumberOperator'; import { NumberOperation } from './operators/NumberOperator';
import { StringOperation } from './operators/StringOperator'; import { StringOperation } from './operators/StringOperator';
import { isUndefined } from 'underscore'; import { isUndefined } from 'underscore';
import { DataCollectionStateMap } from '../data_collection/types';
export const DataConditionType = 'data-condition' as const; export const DataConditionType = 'data-condition' as const;
export const DataConditionEvaluationChangedEvent = 'data-condition-evaluation-changed'; export const DataConditionEvaluationChangedEvent = 'data-condition-evaluation-changed';
@ -34,6 +35,7 @@ export interface DataConditionProps {
export class DataCondition extends Model<DataConditionProps> { export class DataCondition extends Model<DataConditionProps> {
private em: EditorModel; private em: EditorModel;
private collectionsStateMap: DataCollectionStateMap = {};
private resolverListeners: DataResolverListener[] = []; private resolverListeners: DataResolverListener[] = [];
private _previousEvaluationResult: boolean | null = null; private _previousEvaluationResult: boolean | null = null;
private _conditionEvaluator: DataConditionEvaluator; private _conditionEvaluator: DataConditionEvaluator;
@ -56,7 +58,6 @@ export class DataCondition extends Model<DataConditionProps> {
opts.em.logError('No condition was provided to a conditional component.'); opts.em.logError('No condition was provided to a conditional component.');
} }
// @ts-ignore
super(props, opts); super(props, opts);
this.em = opts.em; this.em = opts.em;
@ -108,6 +109,14 @@ export class DataCondition extends Model<DataConditionProps> {
return isConditionTrue ? resolveDynamicValue(ifTrue, this.em) : resolveDynamicValue(ifFalse, this.em); return isConditionTrue ? resolveDynamicValue(ifTrue, this.em) : resolveDynamicValue(ifFalse, this.em);
} }
resolvesFromCollection() {
return false;
}
updateCollectionsStateMap(collectionsStateMap: DataCollectionStateMap) {
this.collectionsStateMap = collectionsStateMap;
}
private listenToPropsChange() { private listenToPropsChange() {
this.on('change:condition', this.handleConditionChange.bind(this)); this.on('change:condition', this.handleConditionChange.bind(this));
this.on('change:condition change:ifTrue change:ifFalse', () => { this.on('change:condition change:ifTrue change:ifFalse', () => {
@ -120,9 +129,7 @@ export class DataCondition extends Model<DataConditionProps> {
} }
private listenToDataVariables() { private listenToDataVariables() {
// Clear previous listeners to avoid memory leaks
this.cleanupListeners(); this.cleanupListeners();
this.setupConditionDataVariableListeners(); this.setupConditionDataVariableListeners();
this.setupOutputDataVariableListeners(); this.setupOutputDataVariableListeners();
} }
@ -137,16 +144,10 @@ export class DataCondition extends Model<DataConditionProps> {
private setupOutputDataVariableListeners() { private setupOutputDataVariableListeners() {
const isConditionTrue = this.isTrue(); const isConditionTrue = this.isTrue();
this.setupOutputVariableListener(this.getIfTrue(), isConditionTrue); this.setupOutputVariableListener(this.getIfTrue(), isConditionTrue);
this.setupOutputVariableListener(this.getIfFalse(), !isConditionTrue); this.setupOutputVariableListener(this.getIfFalse(), !isConditionTrue);
} }
/**
* Sets up a listener for an output variable (ifTrue or ifFalse).
* @param outputVariable - The output variable to listen to.
* @param isConditionTrue - Whether the condition is currently true.
*/
private setupOutputVariableListener(outputVariable: any, isConditionTrue: boolean) { private setupOutputVariableListener(outputVariable: any, isConditionTrue: boolean) {
if (isDataVariable(outputVariable)) { if (isDataVariable(outputVariable)) {
this.addListener(outputVariable, () => { this.addListener(outputVariable, () => {
@ -160,7 +161,7 @@ export class DataCondition extends Model<DataConditionProps> {
private addListener(variable: DataVariableProps, onUpdate: () => void) { private addListener(variable: DataVariableProps, onUpdate: () => void) {
const listener = new DataResolverListener({ const listener = new DataResolverListener({
em: this.em, em: this.em,
resolver: new DataVariable(variable, { em: this.em }), resolver: new DataVariable(variable, { em: this.em, collectionsStateMap: this.collectionsStateMap }),
onUpdate, onUpdate,
}); });

1
packages/core/src/data_sources/model/conditional_variables/operators/AnyTypeOperator.ts

@ -1,6 +1,5 @@
import DataVariable from '../../DataVariable'; import DataVariable from '../../DataVariable';
import { Operator } from './BaseOperator'; import { Operator } from './BaseOperator';
import EditorModel from '../../../../editor/model/Editor';
export enum AnyTypeOperation { export enum AnyTypeOperation {
equals = 'equals', equals = 'equals',

227
packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts

@ -1,17 +1,22 @@
import { bindAll, isArray } from 'underscore'; import { bindAll, isArray } from 'underscore';
import { ObjectAny } from '../../../common'; import { ObjectAny } from '../../../common';
import Component from '../../../dom_components/model/Component'; import Component, { keySymbol } from '../../../dom_components/model/Component';
import { ComponentDefinition, ComponentOptions } from '../../../dom_components/model/types'; import { ComponentAddType, ComponentDefinitionDefined, ComponentOptions } from '../../../dom_components/model/types';
import EditorModel from '../../../editor/model/Editor'; import EditorModel from '../../../editor/model/Editor';
import { isObject, serialize, toLowerCase } from '../../../utils/mixins'; import { isObject, toLowerCase } from '../../../utils/mixins';
import DataResolverListener from '../DataResolverListener'; import DataResolverListener from '../DataResolverListener';
import DataSource from '../DataSource'; import DataSource from '../DataSource';
import DataVariable, { DataVariableProps, DataVariableType } from '../DataVariable'; import DataVariable, { DataVariableProps, DataVariableType } from '../DataVariable';
import { ensureComponentInstance, isDataVariable } from '../../utils'; import { isDataVariable } from '../../utils';
import { DataCollectionType, keyCollectionDefinition, keyCollectionsStateMap, keyIsCollectionItem } from './constants'; import {
DataCollectionItemType,
DataCollectionType,
keyCollectionDefinition,
keyCollectionsStateMap,
keyIsCollectionItem,
} from './constants';
import { import {
ComponentDataCollectionProps, ComponentDataCollectionProps,
DataCollectionConfig,
DataCollectionDataSource, DataCollectionDataSource,
DataCollectionProps, DataCollectionProps,
DataCollectionState, DataCollectionState,
@ -21,28 +26,42 @@ import { getSymbolsToUpdate } from '../../../dom_components/model/SymbolUtils';
import { StyleProps, UpdateStyleOptions } from '../../../domain_abstract/model/StyleableModel'; import { StyleProps, UpdateStyleOptions } from '../../../domain_abstract/model/StyleableModel';
import { updateFromWatcher } from '../../../dom_components/model/ComponentDataResolverWatchers'; import { updateFromWatcher } from '../../../dom_components/model/ComponentDataResolverWatchers';
const AvoidStoreOptions = { avoidStore: true, partial: true };
export default class ComponentDataCollection extends Component { export default class ComponentDataCollection extends Component {
dataSourceWatcher?: DataResolverListener; dataSourceWatcher?: DataResolverListener;
get defaults(): ComponentDefinitionDefined {
return {
// @ts-ignore
...super.defaults,
droppable: false,
type: DataCollectionType,
components: [
{
type: DataCollectionItemType,
},
],
};
}
constructor(props: ComponentDataCollectionProps, opt: ComponentOptions) { constructor(props: ComponentDataCollectionProps, opt: ComponentOptions) {
const collectionDef = props[keyCollectionDefinition]; const dataResolver = props[keyCollectionDefinition];
if (opt.forCloning) { if (opt.forCloning) {
return super(props as any, opt) as unknown as ComponentDataCollection; return super(props as any, opt) as unknown as ComponentDataCollection;
} }
const em = opt.em; const em = opt.em;
const newProps = { ...props, components: undefined, droppable: false } as any; const newProps = { ...props, droppable: false } as any;
const cmp: ComponentDataCollection = super(newProps, opt) as unknown as ComponentDataCollection; const cmp: ComponentDataCollection = super(newProps, opt) as unknown as ComponentDataCollection;
if (!dataResolver) {
if (!collectionDef) {
em.logError('missing collection definition'); em.logError('missing collection definition');
return cmp; return cmp;
} }
this.rebuildChildrenFromCollection();
bindAll(this, 'rebuildChildrenFromCollection'); bindAll(this, 'rebuildChildrenFromCollection');
this.listenTo(this, `change:${keyCollectionDefinition}`, this.rebuildChildrenFromCollection); this.listenTo(this, `change:${keyCollectionDefinition}`, this.rebuildChildrenFromCollection);
this.rebuildChildrenFromCollection();
this.listenToDataSource(); this.listenToDataSource();
return cmp; return cmp;
@ -59,27 +78,27 @@ export default class ComponentDataCollection extends Component {
} }
getConfigStartIndex() { getConfigStartIndex() {
return this.collectionConfig.startIndex; return this.dataResolver.startIndex;
} }
getConfigEndIndex() { getConfigEndIndex() {
return this.collectionConfig.endIndex; return this.dataResolver.endIndex;
}
getComponentDef(): ComponentDefinition {
return this.getFirstChildJSON();
} }
getDataSource(): DataCollectionDataSource { getDataSource(): DataCollectionDataSource {
return this.collectionDef?.collectionConfig?.dataSource; return this.dataResolver?.dataSource;
} }
getCollectionId(): string { getCollectionId(): string {
return this.collectionDef?.collectionConfig?.collectionId; return this.dataResolver?.collectionId;
} }
setComponentDef(componentDef: ComponentDefinition) { getCollectionItemComponents() {
this.set(keyCollectionDefinition, { ...this.collectionDef, componentDef }); return this.firstChild.components();
}
setCollectionId(collectionId: string) {
this.updateCollectionConfig({ collectionId });
} }
setStartIndex(startIndex: number): void { setStartIndex(startIndex: number): void {
@ -95,49 +114,42 @@ export default class ComponentDataCollection extends Component {
this.updateCollectionConfig({ endIndex }); this.updateCollectionConfig({ endIndex });
} }
private updateCollectionConfig(updates: Partial<DataCollectionConfig>): void { private updateCollectionConfig(updates: Partial<DataCollectionProps>): void {
this.set(keyCollectionDefinition, { this.set(keyCollectionDefinition, {
...this.collectionDef, ...this.dataResolver,
collectionConfig: { ...updates,
...this.collectionConfig,
...updates,
},
}); });
} }
setDataSource(dataSource: DataCollectionDataSource) { setDataSource(dataSource: DataCollectionDataSource) {
this.set(keyCollectionDefinition, { this.set(keyCollectionDefinition, {
...this.collectionDef, ...this.dataResolver,
collectionConfig: { ...this.collectionConfig, dataSource }, dataSource,
}); });
} }
setCollectionItemComponents(content: ComponentAddType) {
this.firstChild.components(content);
}
private get firstChild() {
return this.components().at(0);
}
private getDataSourceItems() { private getDataSourceItems() {
return this.collectionDef?.collectionConfig ? getDataSourceItems(this.collectionConfig.dataSource, this.em) : []; return getDataSourceItems(this.dataResolver.dataSource, this.em);
} }
private getCollectionStateMap() { private getCollectionStateMap() {
return (this.get(keyCollectionsStateMap) || {}) as DataCollectionStateMap; return (this.get(keyCollectionsStateMap) || {}) as DataCollectionStateMap;
} }
private get collectionDef() { private get dataResolver() {
return (this.get(keyCollectionDefinition) || {}) as DataCollectionProps; return (this.get(keyCollectionDefinition) || {}) as DataCollectionProps;
} }
private get collectionConfig() {
return (this.collectionDef?.collectionConfig || {}) as DataCollectionConfig;
}
private get collectionDataSource() { private get collectionDataSource() {
return this.collectionConfig.dataSource; return this.dataResolver.dataSource;
}
private getFirstChildJSON() {
const firstChild = this.components().at(0);
const firstChildJSON = firstChild ? serialize(firstChild) : this.collectionDef.componentDef;
delete firstChildJSON?.draggable;
delete firstChildJSON?.removable;
return firstChildJSON;
} }
private listenToDataSource() { private listenToDataSource() {
@ -146,7 +158,10 @@ export default class ComponentDataCollection extends Component {
if (!path) return; if (!path) return;
this.dataSourceWatcher = new DataResolverListener({ this.dataSourceWatcher = new DataResolverListener({
em, em,
resolver: new DataVariable({ type: DataVariableType, path }, { em }), resolver: new DataVariable(
{ type: DataVariableType, path },
{ em, collectionsStateMap: this.get(keyCollectionsStateMap) },
),
onUpdate: this.rebuildChildrenFromCollection, onUpdate: this.rebuildChildrenFromCollection,
}); });
} }
@ -155,16 +170,18 @@ export default class ComponentDataCollection extends Component {
this.components().reset(this.getCollectionItems(), updateFromWatcher as any); this.components().reset(this.getCollectionItems(), updateFromWatcher as any);
} }
getCollectionItems() { private getCollectionItems() {
const { componentDef, collectionConfig } = this.collectionDef; const firstChild = this.ensureFirstChild();
const result = validateCollectionConfig(collectionConfig, componentDef, this.em); // TODO: Move to component view
firstChild.addStyle({ display: 'none' }, AvoidStoreOptions);
const components: Component[] = [firstChild];
const result = validateCollectionDef(this.dataResolver, this.em);
if (!result) { if (!result) {
return []; return components;
} }
const components: Component[] = []; const collectionId = this.dataResolver.collectionId;
const collectionId = collectionConfig.collectionId;
const items = this.getDataSourceItems(); const items = this.getDataSourceItems();
const startIndex = this.getConfigStartIndex() ?? 0; const startIndex = this.getConfigStartIndex() ?? 0;
@ -172,8 +189,14 @@ export default class ComponentDataCollection extends Component {
const endIndex = Math.min(items.length - 1, configEndIndex); const endIndex = Math.min(items.length - 1, configEndIndex);
const totalItems = endIndex - startIndex + 1; const totalItems = endIndex - startIndex + 1;
const parentCollectionStateMap = this.getCollectionStateMap(); const parentCollectionStateMap = this.getCollectionStateMap();
if (parentCollectionStateMap[collectionId]) {
this.em.logError(
`The collection ID "${collectionId}" already exists in the parent collection state. Overriding it is not allowed.`,
);
return components;
}
let symbolMain: Component;
for (let index = startIndex; index <= endIndex; index++) { for (let index = startIndex; index <= endIndex; index++) {
const item = items[index]; const item = items[index];
const isFirstItem = index === startIndex; const isFirstItem = index === startIndex;
@ -187,57 +210,75 @@ export default class ComponentDataCollection extends Component {
remainingItems: totalItems - (index + 1), remainingItems: totalItems - (index + 1),
}; };
if (parentCollectionStateMap[collectionId]) {
this.em.logError(
`The collection ID "${collectionId}" already exists in the parent collection state. Overriding it is not allowed.`,
);
return [];
}
const collectionsStateMap: DataCollectionStateMap = { const collectionsStateMap: DataCollectionStateMap = {
...parentCollectionStateMap, ...parentCollectionStateMap,
[collectionId]: collectionState, [collectionId]: collectionState,
}; };
if (isFirstItem) { if (isFirstItem) {
symbolMain = ensureComponentInstance( setCollectionStateMapAndPropagate(firstChild, collectionsStateMap, collectionId);
{ // TODO: Move to component view
...componentDef, firstChild.addStyle({ display: '' }, AvoidStoreOptions);
draggable: false,
removable: false, continue;
},
this.opt,
);
} }
const instance = symbolMain!.clone({ symbol: true }); const instance = firstChild!.clone({ symbol: true });
!isFirstItem && instance.set('locked', true); instance.set('locked', true, AvoidStoreOptions);
setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(instance); setCollectionStateMapAndPropagate(instance, collectionsStateMap, collectionId);
components.push(instance); components.push(instance);
} }
return components; return components;
} }
private ensureFirstChild() {
const dataConditionItemModel = this.em.Components.getType(DataCollectionItemType)!.model;
return (
this.firstChild ||
new dataConditionItemModel(
{
type: DataCollectionItemType,
},
this.opt,
)
);
}
static isComponent(el: HTMLElement) { static isComponent(el: HTMLElement) {
return toLowerCase(el.tagName) === DataCollectionType; return toLowerCase(el.tagName) === DataCollectionType;
} }
toJSON(opts?: ObjectAny) { toJSON(opts?: ObjectAny) {
const json = super.toJSON.call(this, opts) as ComponentDataCollectionProps; const json = super.toJSON.call(this, opts) as ComponentDataCollectionProps;
json[keyCollectionDefinition].componentDef = this.getFirstChildJSON();
delete json.components;
delete json.droppable; delete json.droppable;
return json; delete json[keySymbol];
delete json.attributes?.id;
const firstChild = this.firstChild as any;
return { ...json, components: [firstChild] };
} }
} }
function setCollectionStateMapAndPropagate(collectionsStateMap: DataCollectionStateMap, collectionId: string) { function applyToComponentAndChildren(operation: (cmp: Component) => void, component: Component) {
return (cmp: Component) => { operation(component);
component.components().forEach((child) => {
applyToComponentAndChildren(operation, child);
});
}
function setCollectionStateMapAndPropagate(
cmp: Component,
collectionsStateMap: DataCollectionStateMap,
collectionId: string,
) {
applyToComponentAndChildren(() => {
setCollectionStateMap(collectionsStateMap)(cmp); setCollectionStateMap(collectionsStateMap)(cmp);
const addListener = (component: Component) => { const addListener = (component: Component) => {
setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(component); setCollectionStateMapAndPropagate(component, collectionsStateMap, collectionId);
}; };
const listenerKey = `_hasAddListener${collectionId ? `_${collectionId}` : ''}`; const listenerKey = `_hasAddListener${collectionId ? `_${collectionId}` : ''}`;
@ -265,12 +306,10 @@ function setCollectionStateMapAndPropagate(collectionsStateMap: DataCollectionSt
cmp.listenTo(cmps, 'remove', removeListener); cmp.listenTo(cmps, 'remove', removeListener);
} }
cmps?.toArray().forEach((component: Component) => { cmps.forEach((cmp) => setCollectionStateMapAndPropagate(cmp, collectionsStateMap, collectionId));
setCollectionStateMapAndPropagate(collectionsStateMap, collectionId)(component);
});
cmp.on(`change:${keyCollectionsStateMap}`, handleCollectionStateMapChange); cmp.on(`change:${keyCollectionsStateMap}`, handleCollectionStateMapChange);
}; }, cmp);
} }
function handleCollectionStateMapChange(this: Component) { function handleCollectionStateMapChange(this: Component) {
@ -290,25 +329,19 @@ function logErrorIfMissing(property: any, propertyPath: string, em: EditorModel)
return true; return true;
} }
function validateCollectionConfig( function validateCollectionDef(dataResolver: DataCollectionProps, em: EditorModel) {
collectionConfig: DataCollectionConfig,
componentDef: ComponentDefinition,
em: EditorModel,
) {
const validations = [ const validations = [
{ property: collectionConfig, propertyPath: 'collectionConfig' }, { property: dataResolver?.collectionId, propertyPath: 'dataResolver.collectionId' },
{ property: componentDef, propertyPath: 'componentDef' }, { property: dataResolver?.dataSource, propertyPath: 'dataResolver.dataSource' },
{ property: collectionConfig?.collectionId, propertyPath: 'collectionConfig.collectionId' },
{ property: collectionConfig?.dataSource, propertyPath: 'collectionConfig.dataSource' },
]; ];
for (const { property, propertyPath } of validations) { for (const { propertyPath } of validations) {
if (!logErrorIfMissing(property, propertyPath, em)) { if (!logErrorIfMissing(dataResolver, propertyPath, em)) {
return []; return [];
} }
} }
const startIndex = collectionConfig?.startIndex; const startIndex = dataResolver?.startIndex;
if (startIndex !== undefined && (startIndex < 0 || !Number.isInteger(startIndex))) { if (startIndex !== undefined && (startIndex < 0 || !Number.isInteger(startIndex))) {
em.logError(`Invalid startIndex: ${startIndex}. It must be a non-negative integer.`); em.logError(`Invalid startIndex: ${startIndex}. It must be a non-negative integer.`);
@ -364,16 +397,18 @@ function getDataSourceItems(dataSource: DataCollectionDataSource, em: EditorMode
break; break;
} }
case isDataVariable(dataSource): { case isDataVariable(dataSource): {
const isDataSourceId = dataSource.path.split('.').length === 1; const path = dataSource.path;
if (!path) break;
const isDataSourceId = path.split('.').length === 1;
if (isDataSourceId) { if (isDataSourceId) {
const id = dataSource.path; items = listDataSourceVariables(path, em);
items = listDataSourceVariables(id, em);
} else { } else {
items = em.DataSources.getValue(dataSource.path, []); items = em.DataSources.getValue(path, []);
} }
break; break;
} }
default: default:
break;
} }
return items; return items;

54
packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts

@ -1,54 +0,0 @@
import Component from '../../../dom_components/model/Component';
import { ComponentOptions } from '../../../dom_components/model/types';
import { toLowerCase } from '../../../utils/mixins';
import DataCollectionVariable from './DataCollectionVariable';
import { DataCollectionVariableType, keyCollectionsStateMap } from './constants';
import { ComponentDataCollectionVariableProps, DataCollectionStateMap } from './types';
export default class ComponentDataCollectionVariable extends Component {
dataResolver: DataCollectionVariable;
get defaults() {
// @ts-expect-error
const componentDefaults = super.defaults;
return {
...componentDefaults,
type: DataCollectionVariableType,
collectionId: undefined,
variableType: undefined,
path: undefined,
droppable: false,
};
}
constructor(props: ComponentDataCollectionVariableProps, opt: ComponentOptions) {
super(props, opt);
const { type, variableType, path, collectionId } = props;
this.dataResolver = new DataCollectionVariable(
{ type, variableType, path, collectionId },
{
...opt,
collectionsStateMap: this.get(keyCollectionsStateMap),
},
);
this.listenTo(this, `change:${keyCollectionsStateMap}`, this.handleCollectionsMapStateUpdate);
}
private handleCollectionsMapStateUpdate(m: any, v: DataCollectionStateMap, opts = {}) {
this.dataResolver.updateCollectionsStateMap(v);
}
getDataValue() {
return this.dataResolver.getDataValue();
}
getInnerHTML() {
return this.getDataValue();
}
static isComponent(el: HTMLElement) {
return toLowerCase(el.tagName) === DataCollectionVariableType;
}
}

158
packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts

@ -1,158 +0,0 @@
import { DataCollectionVariableProps } from './types';
import { Model } from '../../../common';
import EditorModel from '../../../editor/model/Editor';
import DataVariable, { DataVariableType } from '../DataVariable';
import { DataCollectionVariableType } from './constants';
import { DataCollectionState, DataCollectionStateMap } from './types';
import DataResolverListener from '../DataResolverListener';
interface DataCollectionVariablePropsDefined extends DataCollectionVariableProps {
value?: any;
}
export default class DataCollectionVariable extends Model<DataCollectionVariablePropsDefined> {
em: EditorModel;
collectionsStateMap?: DataCollectionStateMap;
dataVariable?: DataVariable;
resolverListener?: DataResolverListener;
defaults(): Partial<DataCollectionVariablePropsDefined> {
return {
type: DataCollectionVariableType,
collectionId: undefined,
variableType: undefined,
path: undefined,
value: undefined,
};
}
constructor(
props: DataCollectionVariablePropsDefined,
options: {
em: EditorModel;
collectionsStateMap?: DataCollectionStateMap;
},
) {
super(props, options);
this.em = options.em;
this.collectionsStateMap = options.collectionsStateMap;
this.updateDataVariable();
}
hasDynamicValue() {
return !!this.dataVariable;
}
getDataValue() {
const { resolvedValue } = this.updateDataVariable();
if (resolvedValue?.type === DataVariableType) {
return this.dataVariable!.getDataValue();
}
return resolvedValue;
}
private updateDataVariable() {
if (!this.collectionsStateMap) return { resolvedValue: undefined };
const resolvedValue = resolveCollectionVariable(
this.attributes as DataCollectionVariableProps,
this.collectionsStateMap,
this.em,
);
let dataVariable;
if (resolvedValue?.type === DataVariableType) {
dataVariable = new DataVariable(resolvedValue, { em: this.em });
this.dataVariable = dataVariable;
this.resolverListener?.destroy();
this.resolverListener = new DataResolverListener({
em: this.em,
resolver: dataVariable,
onUpdate: () => {
this.set('value', this.dataVariable?.getDataValue());
},
});
}
this.set('value', resolvedValue);
return { resolvedValue, dataVariable };
}
updateCollectionsStateMap(collectionsStateMap: DataCollectionStateMap) {
this.collectionsStateMap = collectionsStateMap;
this.updateDataVariable();
}
destroy() {
this.resolverListener?.destroy();
this.dataVariable?.destroy();
return super.destroy();
}
toJSON(options?: any) {
const json = super.toJSON(options);
delete json.value;
!json.collectionId && delete json.collectionId;
return json;
}
}
function resolveCollectionVariable(
collectionVariableDefinition: DataCollectionVariableProps,
collectionsStateMap: DataCollectionStateMap,
em: EditorModel,
) {
const { collectionId, variableType, path } = collectionVariableDefinition;
if (!collectionsStateMap) return;
const collectionItem = collectionsStateMap[collectionId];
if (!collectionItem) {
return '';
}
if (!variableType) {
em.logError(`Missing collection variable type for collection: ${collectionId}`);
return '';
}
if (variableType === 'currentItem') {
return resolveCurrentItem(collectionItem, path, collectionId, em);
}
return collectionItem[variableType];
}
function resolveCurrentItem(
collectionItem: DataCollectionState,
path: string | undefined,
collectionId: string,
em: EditorModel,
) {
const currentItem = collectionItem.currentItem;
if (!currentItem) {
em.logError(`Current item is missing for collection: ${collectionId}`);
return '';
}
if (currentItem.type === DataVariableType) {
const resolvedPath = currentItem.path ? `${currentItem.path}.${path}` : path;
return {
...currentItem,
path: resolvedPath,
};
}
if (path && !(currentItem as any)[path]) {
em.logError(`Path not found in current item: ${path} for collection: ${collectionId}`);
return '';
}
return path ? (currentItem as any)[path] : currentItem;
}

4
packages/core/src/data_sources/model/data_collection/constants.ts

@ -1,5 +1,5 @@
export const DataCollectionType = 'data-collection'; export const DataCollectionType = 'data-collection';
export const DataCollectionVariableType = 'data-collection-variable'; export const DataCollectionItemType = 'data-collection-item';
export const keyCollectionDefinition = 'collectionDef'; export const keyCollectionDefinition = 'dataResolver';
export const keyIsCollectionItem = '__is_data_collection_item'; export const keyIsCollectionItem = '__is_data_collection_item';
export const keyCollectionsStateMap = '__collections_state_map'; export const keyCollectionsStateMap = '__collections_state_map';

44
packages/core/src/data_sources/model/data_collection/types.ts

@ -1,17 +1,10 @@
import { DataCollectionType, DataCollectionVariableType, keyCollectionDefinition } from './constants'; import { DataCollectionType, keyCollectionDefinition } from './constants';
import { ComponentDefinition, ComponentProperties } from '../../../dom_components/model/types'; import { ComponentDefinition } from '../../../dom_components/model/types';
import { DataVariableProps } from '../DataVariable'; import { DataVariableProps } from '../DataVariable';
export type DataCollectionDataSource = DataVariableProps | DataCollectionVariableProps; export type DataCollectionDataSource = DataVariableProps;
export interface DataCollectionConfig { export enum DataCollectionStateType {
collectionId: string;
startIndex?: number;
endIndex?: number;
dataSource: DataCollectionDataSource;
}
export enum DataCollectionStateVariableType {
currentIndex = 'currentIndex', currentIndex = 'currentIndex',
startIndex = 'startIndex', startIndex = 'startIndex',
currentItem = 'currentItem', currentItem = 'currentItem',
@ -22,13 +15,13 @@ export enum DataCollectionStateVariableType {
} }
export interface DataCollectionState { export interface DataCollectionState {
[DataCollectionStateVariableType.currentIndex]: number; [DataCollectionStateType.currentIndex]: number;
[DataCollectionStateVariableType.startIndex]: number; [DataCollectionStateType.startIndex]: number;
[DataCollectionStateVariableType.currentItem]: DataVariableProps; [DataCollectionStateType.currentItem]: DataVariableProps;
[DataCollectionStateVariableType.endIndex]: number; [DataCollectionStateType.endIndex]: number;
[DataCollectionStateVariableType.collectionId]: string; [DataCollectionStateType.collectionId]: string;
[DataCollectionStateVariableType.totalItems]: number; [DataCollectionStateType.totalItems]: number;
[DataCollectionStateVariableType.remainingItems]: number; [DataCollectionStateType.remainingItems]: number;
} }
export interface DataCollectionStateMap { export interface DataCollectionStateMap {
@ -40,18 +33,9 @@ export interface ComponentDataCollectionProps extends ComponentDefinition {
[keyCollectionDefinition]: DataCollectionProps; [keyCollectionDefinition]: DataCollectionProps;
} }
export interface ComponentDataCollectionVariableProps
extends DataCollectionVariableProps,
Omit<ComponentProperties, 'type'> {}
export interface DataCollectionProps { export interface DataCollectionProps {
collectionConfig: DataCollectionConfig;
componentDef: ComponentDefinition;
}
export interface DataCollectionVariableProps {
type: typeof DataCollectionVariableType;
variableType: DataCollectionStateVariableType;
collectionId: string; collectionId: string;
path?: string; startIndex?: number;
endIndex?: number;
dataSource: DataCollectionDataSource;
} }

6
packages/core/src/data_sources/types.ts

@ -1,14 +1,12 @@
import { Model, Collection, ObjectAny } from '../common'; import { Model, Collection, ObjectAny } from '../common';
import DataCollectionVariable from './model/data_collection/DataCollectionVariable';
import { DataCollectionVariableProps } from './model/data_collection/types';
import DataRecord from './model/DataRecord'; import DataRecord from './model/DataRecord';
import DataRecords from './model/DataRecords'; import DataRecords from './model/DataRecords';
import DataVariable, { DataVariableProps } from './model/DataVariable'; import DataVariable, { DataVariableProps } from './model/DataVariable';
import { DataConditionProps, DataCondition } from './model/conditional_variables/DataCondition'; import { DataConditionProps, DataCondition } from './model/conditional_variables/DataCondition';
export type DataResolver = DataVariable | DataCondition | DataCollectionVariable; export type DataResolver = DataVariable | DataCondition;
export type DataResolverProps = DataVariableProps | DataConditionProps | DataCollectionVariableProps; export type DataResolverProps = DataVariableProps | DataConditionProps;
export interface DataRecordProps extends ObjectAny { export interface DataRecordProps extends ObjectAny {
/** /**

24
packages/core/src/data_sources/utils.ts

@ -1,9 +1,7 @@
import EditorModel from '../editor/model/Editor'; import EditorModel from '../editor/model/Editor';
import { DataResolver, DataResolverProps } from './types'; import { DataResolver, DataResolverProps } from './types';
import { DataConditionDisplayType } from './model/conditional_variables/ComponentDataCondition';
import { DataCollectionStateMap } from './model/data_collection/types'; import { DataCollectionStateMap } from './model/data_collection/types';
import DataCollectionVariable from './model/data_collection/DataCollectionVariable'; import { DataCollectionItemType } from './model/data_collection/constants';
import { DataCollectionVariableType } from './model/data_collection/constants';
import { DataConditionType, DataCondition } from './model/conditional_variables/DataCondition'; import { DataConditionType, DataCondition } from './model/conditional_variables/DataCondition';
import DataVariable, { DataVariableProps, DataVariableType } from './model/DataVariable'; import DataVariable, { DataVariableProps, DataVariableType } from './model/DataVariable';
import Component from '../dom_components/model/Component'; import Component from '../dom_components/model/Component';
@ -12,9 +10,7 @@ import { serialize } from '../utils/mixins';
import { DataConditionIfFalseType, DataConditionIfTrueType } from './model/conditional_variables/constants'; import { DataConditionIfFalseType, DataConditionIfTrueType } from './model/conditional_variables/constants';
export function isDataResolverProps(value: any): value is DataResolverProps { export function isDataResolverProps(value: any): value is DataResolverProps {
return ( return typeof value === 'object' && [DataVariableType, DataConditionType].includes(value?.type);
typeof value === 'object' && [DataVariableType, DataConditionType, DataCollectionVariableType].includes(value?.type)
);
} }
export function isDataResolver(value: any): value is DataResolver { export function isDataResolver(value: any): value is DataResolver {
@ -30,12 +26,14 @@ export function isDataCondition(variable: any) {
} }
export function resolveDynamicValue(variable: any, em: EditorModel) { export function resolveDynamicValue(variable: any, em: EditorModel) {
return isDataResolverProps(variable) ? getDataResolverInstanceValue(variable, { em }) : variable; return isDataResolverProps(variable)
? getDataResolverInstanceValue(variable, { em, collectionsStateMap: {} })
: variable;
} }
export function getDataResolverInstance( export function getDataResolverInstance(
resolverProps: DataResolverProps, resolverProps: DataResolverProps,
options: { em: EditorModel; collectionsStateMap?: DataCollectionStateMap }, options: { em: EditorModel; collectionsStateMap: DataCollectionStateMap },
) { ) {
const { type } = resolverProps; const { type } = resolverProps;
let resolver: DataResolver; let resolver: DataResolver;
@ -48,10 +46,6 @@ export function getDataResolverInstance(
resolver = new DataCondition(resolverProps, options); resolver = new DataCondition(resolverProps, options);
break; break;
} }
case DataCollectionVariableType: {
resolver = new DataCollectionVariable(resolverProps, options);
break;
}
default: default:
options.em?.logError(`Unsupported dynamic type: ${type}`); options.em?.logError(`Unsupported dynamic type: ${type}`);
return; return;
@ -64,7 +58,7 @@ export function getDataResolverInstanceValue(
resolverProps: DataResolverProps, resolverProps: DataResolverProps,
options: { options: {
em: EditorModel; em: EditorModel;
collectionsStateMap?: DataCollectionStateMap; collectionsStateMap: DataCollectionStateMap;
}, },
) { ) {
const resolver = getDataResolverInstance(resolverProps, options); const resolver = getDataResolverInstance(resolverProps, options);
@ -86,6 +80,6 @@ export const ensureComponentInstance = (
return new Model(serialize(cmp ?? {}), opt); return new Model(serialize(cmp ?? {}), opt);
}; };
export const isDataConditionDisplayType = (type: string | undefined): type is DataConditionDisplayType => { export const isComponentDataOutputType = (type: string | undefined) => {
return !!type && [DataConditionIfTrueType, DataConditionIfFalseType].includes(type); return !!type && [DataCollectionItemType, DataConditionIfTrueType, DataConditionIfFalseType].includes(type);
}; };

21
packages/core/src/data_sources/view/ComponentDataCollectionVariableView.ts

@ -1,21 +0,0 @@
import ComponentView from '../../dom_components/view/ComponentView';
import DataResolverListener from '../model/DataResolverListener';
import ComponentDataCollectionVariable from '../model/data_collection/ComponentDataCollectionVariable';
export default class ComponentDataCollectionVariableView extends ComponentView<ComponentDataCollectionVariable> {
dataResolverListener?: DataResolverListener;
initialize(opt = {}) {
super.initialize(opt);
this.dataResolverListener = new DataResolverListener({
em: this.em!,
resolver: this.model.dataResolver,
onUpdate: this.postRender.bind(this),
});
}
postRender() {
this.el.innerHTML = this.model.getDataValue();
super.postRender();
}
}

20
packages/core/src/dom_components/index.ts

@ -128,16 +128,14 @@ import { DataVariableType } from '../data_sources/model/DataVariable';
import { DataConditionType } from '../data_sources/model/conditional_variables/DataCondition'; import { DataConditionType } from '../data_sources/model/conditional_variables/DataCondition';
import ComponentDataConditionView from '../data_sources/view/ComponentDataConditionView'; import ComponentDataConditionView from '../data_sources/view/ComponentDataConditionView';
import ComponentDataCollection from '../data_sources/model/data_collection/ComponentDataCollection'; import ComponentDataCollection from '../data_sources/model/data_collection/ComponentDataCollection';
import { DataCollectionType, DataCollectionVariableType } from '../data_sources/model/data_collection/constants'; import { DataCollectionItemType, DataCollectionType } from '../data_sources/model/data_collection/constants';
import ComponentDataCollectionVariable from '../data_sources/model/data_collection/ComponentDataCollectionVariable';
import ComponentDataCollectionVariableView from '../data_sources/view/ComponentDataCollectionVariableView';
import ComponentDataCollectionView from '../data_sources/view/ComponentDataCollectionView'; import ComponentDataCollectionView from '../data_sources/view/ComponentDataCollectionView';
import ComponentDataCondition from '../data_sources/model/conditional_variables/ComponentDataCondition'; import ComponentDataCondition from '../data_sources/model/conditional_variables/ComponentDataCondition';
import { import {
DataConditionIfFalseType, DataConditionIfFalseType,
DataConditionIfTrueType, DataConditionIfTrueType,
} from '../data_sources/model/conditional_variables/constants'; } from '../data_sources/model/conditional_variables/constants';
import ConditionalOutputBase from '../data_sources/model/conditional_variables/ConditionalOutputBase'; import ComponentDataOutput from '../data_sources/model/conditional_variables/ComponentDataOutput';
export type ComponentEvent = export type ComponentEvent =
| 'component:create' | 'component:create'
@ -204,19 +202,19 @@ export interface CanMoveResult {
export default class ComponentManager extends ItemManagerModule<DomComponentsConfig, any> { export default class ComponentManager extends ItemManagerModule<DomComponentsConfig, any> {
componentTypes: ComponentStackItem[] = [ componentTypes: ComponentStackItem[] = [
{ {
id: DataConditionIfTrueType, id: DataCollectionItemType,
model: ConditionalOutputBase, model: ComponentDataOutput,
view: ComponentView, view: ComponentView,
}, },
{ {
id: DataConditionIfFalseType, id: DataConditionIfTrueType,
model: ConditionalOutputBase, model: ComponentDataOutput,
view: ComponentView, view: ComponentView,
}, },
{ {
id: DataCollectionVariableType, id: DataConditionIfFalseType,
model: ComponentDataCollectionVariable, model: ComponentDataOutput,
view: ComponentDataCollectionVariableView, view: ComponentView,
}, },
{ {
id: DataCollectionType, id: DataCollectionType,

10
packages/core/src/dom_components/model/ComponentDataResolverWatchers.ts

@ -1,9 +1,5 @@
import { ObjectAny } from '../../common'; import { ObjectAny } from '../../common';
import { import { keyCollectionsStateMap, keyIsCollectionItem } from '../../data_sources/model/data_collection/constants';
DataCollectionVariableType,
keyCollectionsStateMap,
keyIsCollectionItem,
} from '../../data_sources/model/data_collection/constants';
import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types';
import Component from './Component'; import Component from './Component';
import { import {
@ -79,8 +75,8 @@ export class ComponentDataResolverWatchers {
private updateSymbolOverride() { private updateSymbolOverride() {
if (!this.component || !this.component.get(keyIsCollectionItem)) return; if (!this.component || !this.component.get(keyIsCollectionItem)) return;
const keys = this.propertyWatcher.getDynamicValuesOfType(DataCollectionVariableType); const keys = this.propertyWatcher.getValuesResolvingFromCollections();
const attributesKeys = this.attributeWatcher.getDynamicValuesOfType(DataCollectionVariableType); const attributesKeys = this.attributeWatcher.getValuesResolvingFromCollections();
const combinedKeys = [keyCollectionsStateMap, 'locked', ...keys]; const combinedKeys = [keyCollectionsStateMap, 'locked', ...keys];
const haveOverridenAttributes = Object.keys(attributesKeys).length; const haveOverridenAttributes = Object.keys(attributesKeys).length;

24
packages/core/src/dom_components/model/ComponentResolverWatcher.ts

@ -1,5 +1,4 @@
import { ObjectAny } from '../../common'; import { ObjectAny } from '../../common';
import { DataCollectionVariableType } from '../../data_sources/model/data_collection/constants';
import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types';
import DataResolverListener from '../../data_sources/model/DataResolverListener'; import DataResolverListener from '../../data_sources/model/DataResolverListener';
import { getDataResolverInstance, getDataResolverInstanceValue, isDataResolverProps } from '../../data_sources/utils'; import { getDataResolverInstance, getDataResolverInstanceValue, isDataResolverProps } from '../../data_sources/utils';
@ -21,7 +20,7 @@ type UpdateFn = (component: Component | undefined, key: string, value: any) => v
export class ComponentResolverWatcher { export class ComponentResolverWatcher {
private em: EditorModel; private em: EditorModel;
private collectionsStateMap?: DataCollectionStateMap; private collectionsStateMap: DataCollectionStateMap = {};
private resolverListeners: Record<string, DataResolverListener> = {}; private resolverListeners: Record<string, DataResolverListener> = {};
constructor( constructor(
@ -30,7 +29,7 @@ export class ComponentResolverWatcher {
options: ComponentResolverWatcherOptions, options: ComponentResolverWatcherOptions,
) { ) {
this.em = options.em; this.em = options.em;
this.collectionsStateMap = options.collectionsStateMap; this.collectionsStateMap = options.collectionsStateMap ?? {};
} }
bindComponent(component: Component) { bindComponent(component: Component) {
@ -40,14 +39,12 @@ export class ComponentResolverWatcher {
updateCollectionStateMap(collectionsStateMap: DataCollectionStateMap) { updateCollectionStateMap(collectionsStateMap: DataCollectionStateMap) {
this.collectionsStateMap = collectionsStateMap; this.collectionsStateMap = collectionsStateMap;
const collectionVariablesKeys = this.getDynamicValuesOfType(DataCollectionVariableType); const collectionVariablesKeys = this.getValuesResolvingFromCollections();
const collectionVariablesObject = collectionVariablesKeys.reduce( const collectionVariablesObject: { [key: string]: DataResolverProps | null } = {};
(acc: { [key: string]: DataResolverProps | null }, key) => { collectionVariablesKeys.forEach((key) => {
acc[key] = null; this.resolverListeners[key].resolver.updateCollectionsStateMap(collectionsStateMap);
return acc; collectionVariablesObject[key] = null;
}, });
{},
);
const newVariables = this.getSerializableValues(collectionVariablesObject); const newVariables = this.getSerializableValues(collectionVariablesObject);
const evaluatedValues = this.addDynamicValues(newVariables); const evaluatedValues = this.addDynamicValues(newVariables);
@ -164,10 +161,9 @@ export class ComponentResolverWatcher {
return serializableValues; return serializableValues;
} }
getDynamicValuesOfType(type: DataResolverProps['type']) { getValuesResolvingFromCollections() {
const keys = Object.keys(this.resolverListeners).filter((key: string) => { const keys = Object.keys(this.resolverListeners).filter((key: string) => {
// @ts-ignore return this.resolverListeners[key].resolver.resolvesFromCollection();
return this.resolverListeners[key].resolver.get('type') === type;
}); });
return keys; return keys;

9
packages/core/src/dom_components/model/SymbolUtils.ts

@ -3,10 +3,7 @@ import Component, { keySymbol, keySymbolOvrd, keySymbols } from './Component';
import { SymbolToUpOptions } from './types'; import { SymbolToUpOptions } from './types';
import { isEmptyObj } from '../../utils/mixins'; import { isEmptyObj } from '../../utils/mixins';
import Components from './Components'; import Components from './Components';
import { import { keyCollectionDefinition } from '../../data_sources/model/data_collection/constants';
DataCollectionVariableType,
keyCollectionDefinition,
} from '../../data_sources/model/data_collection/constants';
export const isSymbolMain = (cmp: Component) => isArray(cmp.get(keySymbols)); export const isSymbolMain = (cmp: Component) => isArray(cmp.get(keySymbols));
@ -178,10 +175,10 @@ const shouldPropagateProperty = (props: Record<string, any>, prop: string, compo
const isCollectionVariableDefinition = (() => { const isCollectionVariableDefinition = (() => {
if (prop === 'attributes') { if (prop === 'attributes') {
const attributes = props['attributes']; const attributes = props['attributes'];
return Object.values(attributes).some((attr: any) => attr?.type === DataCollectionVariableType); return Object.values(attributes).some((attr: any) => !!attr?.collectionId);
} }
return props[prop]?.type === DataCollectionVariableType; return !!props[prop]?.collectionId;
})(); })();
return !isSymbolOverride(component, prop) || isCollectionVariableDefinition; return !isSymbolOverride(component, prop) || isCollectionVariableDefinition;

11
packages/core/src/domain_abstract/model/StyleableModel.ts

@ -17,6 +17,7 @@ import {
isDataResolverProps, isDataResolverProps,
} from '../../data_sources/utils'; } from '../../data_sources/utils';
import { DataResolver } from '../../data_sources/types'; import { DataResolver } from '../../data_sources/types';
import { keyCollectionsStateMap } from '../../data_sources/model/data_collection/constants';
export type StyleProps = Record<string, string | string[] | DataVariableProps | DataConditionProps>; export type StyleProps = Record<string, string | string[] | DataVariableProps | DataConditionProps>;
@ -112,7 +113,10 @@ export default class StyleableModel<T extends ObjectHash = any> extends Model<T>
const styleValue = newStyle[key]; const styleValue = newStyle[key];
if (isDataResolverProps(styleValue)) { if (isDataResolverProps(styleValue)) {
const dataResolver = getDataResolverInstance(styleValue, { em: this.em! }); const dataResolver = getDataResolverInstance(styleValue, {
em: this.em!,
collectionsStateMap: this.get(keyCollectionsStateMap) ?? {},
});
if (dataResolver) { if (dataResolver) {
newStyle[key] = dataResolver; newStyle[key] = dataResolver;
@ -187,7 +191,10 @@ export default class StyleableModel<T extends ObjectHash = any> extends Model<T>
} }
if (isDataResolverProps(styleValue)) { if (isDataResolverProps(styleValue)) {
resultStyle[key] = getDataResolverInstanceValue(styleValue, { em: this.em! }); resultStyle[key] = getDataResolverInstanceValue(styleValue, {
em: this.em!,
collectionsStateMap: this.get(keyCollectionsStateMap) ?? {},
});
} }
if (isDataResolver(styleValue)) { if (isDataResolver(styleValue)) {

183
packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.getters-setters.ts

@ -1,10 +1,13 @@
import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { Component, DataSource, DataSourceManager } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import { import {
DataCollectionItemType,
DataCollectionType, DataCollectionType,
DataCollectionVariableType,
} from '../../../../../src/data_sources/model/data_collection/constants'; } from '../../../../../src/data_sources/model/data_collection/constants';
import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types'; import {
ComponentDataCollectionProps,
DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { setupTestEditor } from '../../../../common'; import { setupTestEditor } from '../../../../common';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection'; import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
@ -14,8 +17,6 @@ describe('Collection component getters and setters', () => {
let dsm: DataSourceManager; let dsm: DataSourceManager;
let dataSource: DataSource; let dataSource: DataSource;
let wrapper: Component; let wrapper: Component;
let firstRecord: DataRecord;
let secondRecord: DataRecord;
beforeEach(() => { beforeEach(() => {
({ em, dsm } = setupTestEditor()); ({ em, dsm } = setupTestEditor());
@ -28,8 +29,6 @@ describe('Collection component getters and setters', () => {
{ id: 'user3', user: 'user3', firstName: 'Name3', age: '16' }, { id: 'user3', user: 'user3', firstName: 'Name3', age: '16' },
], ],
}); });
firstRecord = dataSource.getRecord('user1')!;
secondRecord = dataSource.getRecord('user2')!;
}); });
afterEach(() => { afterEach(() => {
@ -40,10 +39,11 @@ describe('Collection component getters and setters', () => {
let cmp: ComponentDataCollection; let cmp: ComponentDataCollection;
beforeEach(() => { beforeEach(() => {
cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
components: [ components: [
{ {
@ -51,8 +51,8 @@ describe('Collection component getters and setters', () => {
tagName: 'div', tagName: 'div',
attributes: { attributes: {
dataUser: { dataUser: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -60,17 +60,19 @@ describe('Collection component getters and setters', () => {
}, },
], ],
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
startIndex: 1, collectionId: 'my_collection',
endIndex: 2, startIndex: 1,
dataSource: { endIndex: 2,
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0] as ComponentDataCollection; } as ComponentDataCollectionProps;
cmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
}); });
test('getItemsCount should return the correct number of items', () => { test('getItemsCount should return the correct number of items', () => {
@ -85,14 +87,14 @@ describe('Collection component getters and setters', () => {
expect(cmp.getConfigEndIndex()).toBe(2); expect(cmp.getConfigEndIndex()).toBe(2);
}); });
test('getComponentDef should return the correct component definition', () => { test('components should return the correct component definition', () => {
const componentDef = cmp.getComponentDef(); const firstChildJSON = JSON.parse(JSON.stringify(cmp.getCollectionItemComponents().at(0)));
expect(componentDef.type).toBe('default'); expect(firstChildJSON['type']).toBe('default');
expect(componentDef.components).toHaveLength(1); expect(firstChildJSON.components).toHaveLength(1);
expect(componentDef?.components?.[0].attributes?.['dataUser']).toEqual({ expect(firstChildJSON.components?.[0].attributes?.['dataUser']).toEqual({
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}); });
@ -138,10 +140,11 @@ describe('Collection component getters and setters', () => {
let cmp: ComponentDataCollection; let cmp: ComponentDataCollection;
beforeEach(() => { beforeEach(() => {
cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
components: [ components: [
{ {
@ -149,8 +152,8 @@ describe('Collection component getters and setters', () => {
tagName: 'div', tagName: 'div',
attributes: { attributes: {
dataUser: { dataUser: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -158,30 +161,32 @@ describe('Collection component getters and setters', () => {
}, },
], ],
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
startIndex: 1, collectionId: 'my_collection',
endIndex: 2, startIndex: 1,
dataSource: { endIndex: 2,
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0] as ComponentDataCollection; } as ComponentDataCollectionProps;
cmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
}); });
test('setComponentDef should update the component definition and reflect in children', () => { test('components should update the component definition and reflect in children', () => {
const newComponentDef = { const newItemCmpDef = {
type: 'newType', type: 'text',
components: [ components: [
{ {
type: 'default', type: 'default',
tagName: 'span', tagName: 'span',
attributes: { attributes: {
'data-name': { 'data-name': {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'firstName', path: 'firstName',
}, },
@ -189,34 +194,40 @@ describe('Collection component getters and setters', () => {
}, },
], ],
}; };
cmp.setComponentDef(newComponentDef); cmp.setCollectionItemComponents(newItemCmpDef);
expect(cmp.getItemsCount()).toBe(2);
const children = cmp.components(); const children = cmp.components();
expect(children).toHaveLength(2); const firstItemCmp = children.at(0).components().at(0);
expect(children.at(0).get('type')).toBe('newType'); expect(firstItemCmp.get('type')).toBe('text');
expect(children.at(0).components().at(0).get('tagName')).toBe('span'); expect(firstItemCmp.components().at(0).get('tagName')).toBe('span');
expect(children.at(0).components().at(0).getAttributes()['data-name']).toBe('Name2'); expect(firstItemCmp.components().at(0).getAttributes()['data-name']).toBe('Name2');
}); });
test('setStartIndex should update the start index and reflect in children', () => { test('setStartIndex should update the start index and reflect in children', () => {
cmp.setStartIndex(0); cmp.setStartIndex(0);
expect(cmp.getConfigStartIndex()).toBe(0); expect(cmp.getConfigStartIndex()).toBe(0);
expect(cmp.getItemsCount()).toBe(3);
const children = cmp.components(); const children = cmp.components();
expect(children).toHaveLength(3); const firstItemCmp = children.at(0).components().at(0);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user1'); expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user1');
expect(children.at(1).components().at(0).getAttributes()['dataUser']).toBe('user2'); const secondItemCmp = children.at(1).components().at(0);
expect(children.at(2).components().at(0).getAttributes()['dataUser']).toBe('user3'); expect(secondItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user2');
const thirdItemCmp = children.at(2).components().at(0);
expect(thirdItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user3');
}); });
test('setEndIndex should update the end index and reflect in children', () => { test('setEndIndex should update the end index and reflect in children', () => {
cmp.setEndIndex(3); cmp.setEndIndex(3);
expect(cmp.getConfigEndIndex()).toBe(3); expect(cmp.getConfigEndIndex()).toBe(3);
expect(cmp.getItemsCount()).toBe(2);
const children = cmp.components(); const children = cmp.components();
expect(children).toHaveLength(2); const firstItemCmp = children.at(0).components().at(0);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user2'); expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user2');
expect(children.at(1).components().at(0).getAttributes()['dataUser']).toBe('user3'); const secondItemCmp = children.at(1).components().at(0);
expect(secondItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user3');
}); });
test('setDataSource should update the data source and reflect in children', () => { test('setDataSource should update the data source and reflect in children', () => {
@ -233,24 +244,25 @@ describe('Collection component getters and setters', () => {
path: 'new_data_source_id', path: 'new_data_source_id',
}); });
expect(cmp.getItemsCount()).toBe(1);
const children = cmp.components(); const children = cmp.components();
expect(children).toHaveLength(1); const firstItemCmp = children.at(0).components().at(0);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user5'); expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user5');
}); });
test('setStartIndex with zero should include the first record', () => { test('setStartIndex with zero should include the first record', () => {
cmp.setStartIndex(0); cmp.setStartIndex(0);
expect(cmp.getItemsCount()).toBe(3);
const children = cmp.components(); const children = cmp.components();
expect(children).toHaveLength(3); const firstItemCmp = children.at(0).components().at(0);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user1'); expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user1');
}); });
test('setEndIndex with zero should result in no children', () => { test('setEndIndex with zero should result in no children', () => {
cmp.setEndIndex(0); cmp.setEndIndex(0);
const children = cmp.components(); expect(cmp.getItemsCount()).toBe(0);
expect(children).toHaveLength(0);
}); });
test('setDataSource with an empty data source should result in no children', () => { test('setDataSource with an empty data source should result in no children', () => {
@ -264,8 +276,7 @@ describe('Collection component getters and setters', () => {
path: 'empty_data_source_id', path: 'empty_data_source_id',
}); });
const children = cmp.components(); expect(cmp.getItemsCount()).toBe(0);
expect(children).toHaveLength(0);
}); });
}); });
@ -273,10 +284,11 @@ describe('Collection component getters and setters', () => {
let cmp: ComponentDataCollection; let cmp: ComponentDataCollection;
beforeEach(() => { beforeEach(() => {
cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
components: [ components: [
{ {
@ -284,8 +296,8 @@ describe('Collection component getters and setters', () => {
tagName: 'div', tagName: 'div',
attributes: { attributes: {
dataUser: { dataUser: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -293,17 +305,18 @@ describe('Collection component getters and setters', () => {
}, },
], ],
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
startIndex: 1, collectionId: 'my_collection',
endIndex: 2, startIndex: 1,
dataSource: { endIndex: 2,
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0] as ComponentDataCollection; } as ComponentDataCollectionProps;
cmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
}); });
test('HTML output should reflect changes in startIndex', () => { test('HTML output should reflect changes in startIndex', () => {
@ -319,6 +332,7 @@ describe('Collection component getters and setters', () => {
cmp.setEndIndex(3); cmp.setEndIndex(3);
const html = cmp.toHTML(); const html = cmp.toHTML();
expect(html).not.toContain('dataUser="user1"');
expect(html).toContain('dataUser="user2"'); expect(html).toContain('dataUser="user2"');
expect(html).toContain('dataUser="user3"'); expect(html).toContain('dataUser="user3"');
}); });
@ -337,14 +351,17 @@ describe('Collection component getters and setters', () => {
}); });
const html = cmp.toHTML(); const html = cmp.toHTML();
expect(html).not.toContain('dataUser="user1"');
expect(html).not.toContain('dataUser="user2"');
expect(html).not.toContain('dataUser="user3"');
expect(html).toContain('dataUser="user5"'); expect(html).toContain('dataUser="user5"');
}); });
test('HTML output should be empty when endIndex is zero', () => { test('HTML output should be empty when endIndex is zero', () => {
cmp.setEndIndex(0); cmp.setEndIndex(0);
const html = cmp.toHTML(); const dataOutputCmp = cmp.components().at(0);
expect(html).not.toContain('dataUser'); expect(dataOutputCmp.getStyle().display).toBe('none');
}); });
}); });
}); });

656
packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts

@ -1,14 +1,17 @@
import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import { import {
DataCollectionItemType,
DataCollectionType, DataCollectionType,
DataCollectionVariableType,
} from '../../../../../src/data_sources/model/data_collection/constants'; } from '../../../../../src/data_sources/model/data_collection/constants';
import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types'; import {
ComponentDataCollectionProps,
DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { setupTestEditor } from '../../../../common'; import { setupTestEditor } from '../../../../common';
import { getSymbolMain } from '../../../../../src/dom_components/model/SymbolUtils';
import { ProjectData } from '../../../../../src/storage_manager'; import { ProjectData } from '../../../../../src/storage_manager';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
describe('Collection component', () => { describe('Collection component', () => {
let em: EditorModel; let em: EditorModel;
@ -40,82 +43,55 @@ describe('Collection component', () => {
}); });
test('Collection component should be undroppable', () => { test('Collection component should be undroppable', () => {
const cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
const cmp = wrapper.components(cmpDef)[0];
expect(cmp.get('droppable')).toBe(false); expect(cmp.get('droppable')).toBe(false);
}); });
test('Collection items should be undraggable', () => { test('Collection items should be undraggable and unremovable', () => {
const cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
const cmp = wrapper.components(cmpDef)[0];
cmp.components().forEach((child) => { cmp.components().forEach((child) => {
expect(child.get('draggable')).toBe(false); expect(child.get('draggable')).toBe(false);
}); expect(child.get('removable')).toBe(false);
});
test('Collection items should be symbols', () => {
const cmp = wrapper.components({
type: DataCollectionType,
collectionDef: {
componentDef: {
type: 'default',
components: [
{
type: 'default',
},
],
},
collectionConfig: {
collectionId: 'my_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
},
})[0];
expect(cmp.components()).toHaveLength(3);
cmp.components().forEach((child) => expect(child.get('type')).toBe('default'));
const children = cmp.components();
const firstChild = children.at(0);
children.slice(1).forEach((component) => {
expect(getSymbolMain(component)).toBe(firstChild);
}); });
}); });
describe('Collection variables', () => { describe('Collection variables', () => {
describe('Properties', () => { describe('Properties', () => {
let cmp: Component; let cmp: ComponentDataCollection;
let firstChild!: Component; let firstChild!: Component;
let firstGrandchild!: Component; let firstGrandchild!: Component;
let secondChild!: Component; let secondChild!: Component;
@ -131,63 +107,67 @@ describe('Collection component', () => {
const checkRecordsWithInnerCmp = () => { const checkRecordsWithInnerCmp = () => {
dataSource.getRecords().forEach((record, i) => { dataSource.getRecords().forEach((record, i) => {
const innerCmp = cmp.components().at(i).components().at(1); const innerCmp = cmp.components().at(i).components().at(0).components().at(1);
checkHtmlModelAndView({ cmp: innerCmp, innerHTML: record.get('firstName') }); checkHtmlModelAndView({ cmp: innerCmp, innerHTML: record.get('firstName') });
}); });
}; };
beforeEach(() => { beforeEach(() => {
cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
components: [ components: [
{ {
type: 'default', type: 'default',
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}, },
{ {
tagName: 'span', tagName: 'span',
type: DataCollectionVariableType, type: DataVariableType,
variableType: 'currentItem', dataResolver: {
collectionId: 'my_collection', variableType: 'currentItem',
path: 'firstName', collectionId: 'my_collection',
path: 'firstName',
},
}, },
], ],
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
custom_property: { custom_property: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
cmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
firstChild = cmp.components().at(0); firstChild = cmp.components().at(0).components().at(0);
firstGrandchild = firstChild.components().at(0); firstGrandchild = firstChild.components().at(0);
secondChild = cmp.components().at(1); secondChild = cmp.components().at(1).components().at(0);
secondGrandchild = secondChild.components().at(0); secondGrandchild = secondChild.components().at(0);
thirdChild = cmp.components().at(2); thirdChild = cmp.components().at(2).components().at(0);
}); });
test('Evaluating to static value', () => { test('Evaluating to static value', () => {
@ -222,8 +202,8 @@ describe('Collection component', () => {
expect(cmp.components().length).toBe(2); expect(cmp.components().length).toBe(2);
const updatedFirstChild = cmp.components().at(0); const updatedFirstChild = cmp.components().at(0).components().at(0);
const updatedSecondChild = cmp.components().at(1); const updatedSecondChild = cmp.components().at(1).components().at(0);
expect(updatedFirstChild.get('name')).toBe('user2'); expect(updatedFirstChild.get('name')).toBe('user2');
expect(updatedSecondChild.get('name')).toBe('user3'); expect(updatedSecondChild.get('name')).toBe('user3');
@ -240,18 +220,14 @@ describe('Collection component', () => {
test('Adding a record updates the collection component correctly', () => { test('Adding a record updates the collection component correctly', () => {
dataSource.addRecord({ id: 'user4', user: 'user4', firstName: 'Name4', age: '20' }); dataSource.addRecord({ id: 'user4', user: 'user4', firstName: 'Name4', age: '20' });
expect(cmp.components().length).toBe(4); expect(cmp.getItemsCount()).toBe(4);
const newChild = cmp.components().at(3); const newChild = cmp.components().at(3).components().at(0);
expect(newChild.get('name')).toBe('user4'); expect(newChild.get('name')).toBe('user4');
const newGrandchild = newChild.components().at(0); const newGrandchild = newChild.components().at(0);
expect(newGrandchild.get('name')).toBe('user4'); expect(newGrandchild.get('name')).toBe('user4');
const firstChild = cmp.components().at(0);
const secondChild = cmp.components().at(1);
const thirdChild = cmp.components().at(2);
expect(firstChild.get('name')).toBe('user1'); expect(firstChild.get('name')).toBe('user1');
expect(secondChild.get('name')).toBe('user2'); expect(secondChild.get('name')).toBe('user2');
expect(thirdChild.get('name')).toBe('user3'); expect(thirdChild.get('name')).toBe('user3');
@ -280,8 +256,8 @@ describe('Collection component', () => {
test('Updating the value to a different collection variable', async () => { test('Updating the value to a different collection variable', async () => {
firstChild.set('name', { firstChild.set('name', {
// @ts-ignore // @ts-ignore
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'age', path: 'age',
}); });
@ -299,8 +275,8 @@ describe('Collection component', () => {
firstGrandchild.set('name', { firstGrandchild.set('name', {
// @ts-ignore // @ts-ignore
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'age', path: 'age',
}); });
@ -353,18 +329,19 @@ describe('Collection component', () => {
let thirdChild!: Component; let thirdChild!: Component;
beforeEach(() => { beforeEach(() => {
cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
components: [ components: [
{ {
type: 'default', type: 'default',
attributes: { attributes: {
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -373,28 +350,29 @@ describe('Collection component', () => {
], ],
attributes: { attributes: {
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}, },
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
cmp = wrapper.components(cmpDef)[0];
firstChild = cmp.components().at(0); firstChild = cmp.components().at(0).components().at(0);
firstGrandchild = firstChild.components().at(0); firstGrandchild = firstChild.components().at(0);
secondChild = cmp.components().at(1); secondChild = cmp.components().at(1).components().at(0);
secondGrandchild = secondChild.components().at(0); secondGrandchild = secondChild.components().at(0);
thirdChild = cmp.components().at(2); thirdChild = cmp.components().at(2).components().at(0);
}); });
test('Evaluating to static value', () => { test('Evaluating to static value', () => {
@ -446,8 +424,8 @@ describe('Collection component', () => {
firstChild.setAttributes({ firstChild.setAttributes({
name: { name: {
// @ts-ignore // @ts-ignore
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'age', path: 'age',
}, },
@ -471,8 +449,8 @@ describe('Collection component', () => {
firstGrandchild.setAttributes({ firstGrandchild.setAttributes({
name: { name: {
// @ts-ignore // @ts-ignore
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'age', path: 'age',
}, },
@ -532,17 +510,18 @@ describe('Collection component', () => {
}); });
test('Traits', () => { test('Traits', () => {
const cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
traits: [ traits: [
{ {
name: 'attribute_trait', name: 'attribute_trait',
value: { value: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -551,27 +530,28 @@ describe('Collection component', () => {
name: 'property_trait', name: 'property_trait',
changeProp: true, changeProp: true,
value: { value: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}, },
], ],
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
const cmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
expect(cmp.components()).toHaveLength(3); expect(cmp.getItemsCount()).toBe(3);
const firstChild = cmp.components().at(0); const firstChild = cmp.components().at(0).components().at(0);
const secondChild = cmp.components().at(1); const secondChild = cmp.components().at(1).components().at(0);
expect(firstChild.getAttributes()['attribute_trait']).toBe('user1'); expect(firstChild.getAttributes()['attribute_trait']).toBe('user1');
expect(firstChild.getEl()?.getAttribute('attribute_trait')).toBe('user1'); expect(firstChild.getEl()?.getAttribute('attribute_trait')).toBe('user1');
@ -593,27 +573,27 @@ describe('Collection component', () => {
}); });
describe('Serialization', () => { describe('Serialization', () => {
let cmp: Component; let cmp: ComponentDataCollection;
beforeEach(() => { beforeEach(() => {
const cmpDefinition = { const childCmpDef = {
type: 'default', type: 'default',
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
custom_prop: { custom_prop: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentIndex, variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
attributes: { attributes: {
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -622,8 +602,8 @@ describe('Collection component', () => {
{ {
name: 'attribute_trait', name: 'attribute_trait',
value: { value: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -632,8 +612,8 @@ describe('Collection component', () => {
name: 'property_trait', name: 'property_trait',
changeProp: true, changeProp: true,
value: { value: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
@ -641,42 +621,42 @@ describe('Collection component', () => {
], ],
}; };
const collectionComponentDefinition = { const collectionCmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: [
componentDef: { {
...cmpDefinition, ...childCmpDef,
components: [cmpDefinition, cmpDefinition], components: [childCmpDef, childCmpDef],
}, },
collectionConfig: { ],
collectionId: 'my_collection', dataResolver: {
startIndex: 0, collectionId: 'my_collection',
endIndex: 1, startIndex: 0,
dataSource: { endIndex: 1,
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
}; } as ComponentDataCollectionProps;
cmp = wrapper.components(collectionComponentDefinition)[0]; cmp = wrapper.components(collectionCmpDef)[0] as unknown as ComponentDataCollection;
}); });
test('Serializion with Collection Variables to JSON', () => { test('Serializion with Collection Variables to JSON', () => {
expect(cmp.toJSON()).toMatchSnapshot(`Collection with no grandchildren`); expect(cmp.toJSON()).toMatchSnapshot(`Collection with no grandchildren`);
const firstChild = cmp.components().at(0); const firstItemCmp = cmp.getCollectionItemComponents().at(0);
const newChildDefinition = { const newChildDefinition = {
type: 'default', type: 'default',
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentIndex, variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}; };
firstChild.components().at(0).components(newChildDefinition); firstItemCmp.components(newChildDefinition);
expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`); expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`);
}); });
@ -684,25 +664,149 @@ describe('Collection component', () => {
const projectData = editor.getProjectData(); const projectData = editor.getProjectData();
const page = projectData.pages[0]; const page = projectData.pages[0];
const frame = page.frames[0]; const frame = page.frames[0];
const component = frame.component.components[0]; const component = frame.component.components[0] as ComponentDataCollection;
expect(component).toMatchSnapshot(`Collection with no grandchildren`); expect(component).toMatchSnapshot(`Collection with no grandchildren`);
const firstChild = cmp.components().at(0); const firstItemCmp = cmp.getCollectionItemComponents().at(0);
const newChildDefinition = { const newChildDefinition = {
type: 'default', type: 'default',
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentIndex, variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}; };
firstChild.components().at(0).components(newChildDefinition); firstItemCmp.components(newChildDefinition);
expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`); expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`);
}); });
test('Loading', () => { test('Loading', () => {
const cmpDef = {
type: DataCollectionType,
components: [
{
type: DataCollectionItemType,
components: {
attributes: {
attribute_trait: {
path: 'user',
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
},
name: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
},
components: [
{
attributes: {
attribute_trait: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
name: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
},
name: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
custom_prop: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: 'currentIndex',
},
property_trait: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
type: 'default',
},
{
attributes: {
attribute_trait: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
name: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
},
name: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
custom_prop: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: 'currentIndex',
},
property_trait: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
type: 'default',
},
],
name: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
custom_prop: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: 'currentIndex',
},
property_trait: {
path: 'user',
type: DataVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateType.currentItem,
},
type: 'default',
},
},
],
dataResolver: {
collectionId: 'my_collection',
dataSource: {
path: 'my_data_source_id',
type: DataVariableType,
},
endIndex: 1,
startIndex: 0,
},
} as ComponentDataCollectionProps;
const componentProjectData: ProjectData = { const componentProjectData: ProjectData = {
assets: [], assets: [],
pages: [ pages: [
@ -710,128 +814,7 @@ describe('Collection component', () => {
frames: [ frames: [
{ {
component: { component: {
components: [ components: [cmpDef],
{
collectionDef: {
componentDef: {
attributes: {
attribute_trait: {
path: 'user',
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
},
name: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
},
components: [
{
attributes: {
attribute_trait: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
name: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
},
name: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
custom_prop: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: 'currentIndex',
},
property_trait: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
type: 'default',
},
{
attributes: {
attribute_trait: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
name: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
},
name: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
custom_prop: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: 'currentIndex',
},
property_trait: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
type: 'default',
},
],
name: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
custom_prop: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: 'currentIndex',
},
property_trait: {
path: 'user',
type: DataCollectionVariableType,
collectionId: 'my_collection',
variableType: DataCollectionStateVariableType.currentItem,
},
type: 'default',
},
collectionConfig: {
collectionId: 'my_collection',
dataSource: {
path: 'my_data_source_id',
type: DataVariableType,
},
endIndex: 1,
startIndex: 0,
},
},
type: DataCollectionType,
},
],
docEl: { docEl: {
tagName: 'html', tagName: 'html',
}, },
@ -863,10 +846,10 @@ describe('Collection component', () => {
editor.loadProjectData(componentProjectData); editor.loadProjectData(componentProjectData);
const components = editor.getComponents(); const components = editor.getComponents();
const component = components.models[0]; const component = components.models[0] as ComponentDataCollection;
const firstChild = component.components().at(0); const firstChild = component.components().at(0).components().at(0);
const firstGrandchild = firstChild.components().at(0); const firstGrandchild = firstChild.components().at(0);
const secondChild = component.components().at(1); const secondChild = component.components().at(1).components().at(0);
const secondGrandchild = secondChild.components().at(0); const secondGrandchild = secondChild.components().at(0);
expect(firstChild.get('name')).toBe('user1'); expect(firstChild.get('name')).toBe('user1');
@ -894,33 +877,35 @@ describe('Collection component', () => {
describe('Configuration options', () => { describe('Configuration options', () => {
test('Collection with start and end indexes', () => { test('Collection with start and end indexes', () => {
const cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}, },
collectionConfig: { },
startIndex: 1, dataResolver: {
endIndex: 2, startIndex: 1,
collectionId: 'my_collection', endIndex: 2,
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
const cmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
expect(cmp.components()).toHaveLength(2); expect(cmp.components()).toHaveLength(2);
const firstChild = cmp.components().at(0); const firstChild = cmp.components().at(0).components().at(0);
const secondChild = cmp.components().at(1); const secondChild = cmp.components().at(1).components().at(0);
expect(firstChild.get('name')).toBe('user2'); expect(firstChild.get('name')).toBe('user2');
expect(secondChild.get('name')).toBe('user3'); expect(secondChild.get('name')).toBe('user3');
@ -929,32 +914,33 @@ describe('Collection component', () => {
describe('Diffirent Collection variable types', () => { describe('Diffirent Collection variable types', () => {
const stateVariableTests = [ const stateVariableTests = [
{ variableType: DataCollectionStateVariableType.currentIndex, expectedValues: [0, 1, 2] }, { variableType: DataCollectionStateType.currentIndex, expectedValues: [0, 1, 2] },
{ variableType: DataCollectionStateVariableType.startIndex, expectedValues: [0, 0, 0] }, { variableType: DataCollectionStateType.startIndex, expectedValues: [0, 0, 0] },
{ variableType: DataCollectionStateVariableType.endIndex, expectedValues: [2, 2, 2] }, { variableType: DataCollectionStateType.endIndex, expectedValues: [2, 2, 2] },
{ {
variableType: DataCollectionStateVariableType.collectionId, variableType: DataCollectionStateType.collectionId,
expectedValues: ['my_collection', 'my_collection', 'my_collection'], expectedValues: ['my_collection', 'my_collection', 'my_collection'],
}, },
{ variableType: DataCollectionStateVariableType.totalItems, expectedValues: [3, 3, 3] }, { variableType: DataCollectionStateType.totalItems, expectedValues: [3, 3, 3] },
{ variableType: DataCollectionStateVariableType.remainingItems, expectedValues: [2, 1, 0] }, { variableType: DataCollectionStateType.remainingItems, expectedValues: [2, 1, 0] },
]; ];
stateVariableTests.forEach(({ variableType, expectedValues }) => { stateVariableTests.forEach(({ variableType, expectedValues }) => {
test(`Variable type: ${variableType}`, () => { test(`Variable type: ${variableType}`, () => {
const cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
components: {
type: 'default', type: 'default',
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: variableType, variableType: variableType,
collectionId: 'my_collection', collectionId: 'my_collection',
}, },
attributes: { attributes: {
custom_attribute: { custom_attribute: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: variableType, variableType: variableType,
collectionId: 'my_collection', collectionId: 'my_collection',
}, },
@ -963,7 +949,7 @@ describe('Collection component', () => {
{ {
name: 'attribute_trait', name: 'attribute_trait',
value: { value: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: variableType, variableType: variableType,
collectionId: 'my_collection', collectionId: 'my_collection',
}, },
@ -972,31 +958,33 @@ describe('Collection component', () => {
name: 'property_trait', name: 'property_trait',
changeProp: true, changeProp: true,
value: { value: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: variableType, variableType: variableType,
collectionId: 'my_collection', collectionId: 'my_collection',
}, },
}, },
], ],
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
const cmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
const children = cmp.components(); expect(cmp.getItemsCount()).toBe(3);
expect(children).toHaveLength(3);
const children = cmp.components();
children.each((child, index) => { children.each((child, index) => {
expect(child.get('name')).toBe(expectedValues[index]); const content = child.components().at(0);
expect(child.get('property_trait')).toBe(expectedValues[index]); expect(content.get('name')).toBe(expectedValues[index]);
expect(child.getAttributes()['custom_attribute']).toBe(expectedValues[index]); expect(content.get('property_trait')).toBe(expectedValues[index]);
expect(child.getAttributes()['attribute_trait']).toBe(expectedValues[index]); expect(content.getAttributes()['custom_attribute']).toBe(expectedValues[index]);
expect(content.getAttributes()['attribute_trait']).toBe(expectedValues[index]);
}); });
}); });
}); });

192
packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts → packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionWithDataVariable.ts

@ -1,10 +1,10 @@
import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import { DataCollectionType } from '../../../../../src/data_sources/model/data_collection/constants';
import { import {
DataCollectionType, ComponentDataCollectionProps,
DataCollectionVariableType, DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/constants'; } from '../../../../../src/data_sources/model/data_collection/types';
import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { ProjectData } from '../../../../../src/storage_manager'; import { ProjectData } from '../../../../../src/storage_manager';
import { setupTestEditor } from '../../../../common'; import { setupTestEditor } from '../../../../common';
@ -39,29 +39,30 @@ describe('Collection variable components', () => {
}); });
test('Gets the correct static value', async () => { test('Gets the correct static value', async () => {
const cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: 'default',
type: 'default', components: [
components: [ {
{ type: DataVariableType,
type: DataCollectionVariableType, dataResolver: {
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
],
},
collectionConfig: {
collectionId: 'my_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
}, },
],
},
dataResolver: {
collectionId: 'my_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
const cmp = wrapper.components(cmpDef)[0];
const firstGrandchild = cmp.components().at(0).components().at(0); const firstGrandchild = cmp.components().at(0).components().at(0);
expect(firstGrandchild.getInnerHTML()).toContain('user1'); expect(firstGrandchild.getInnerHTML()).toContain('user1');
@ -73,27 +74,28 @@ describe('Collection variable components', () => {
}); });
test('Watches collection variable changes', async () => { test('Watches collection variable changes', async () => {
const cmp = wrapper.components({ const cmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: 'default',
type: 'default', components: {
components: { type: DataVariableType,
type: DataCollectionVariableType, dataResolver: {
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
path: 'user', path: 'user',
}, },
}, },
collectionConfig: { },
collectionId: 'my_collection', dataResolver: {
dataSource: { collectionId: 'my_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'my_data_source_id',
}, },
}, },
})[0]; } as ComponentDataCollectionProps;
const cmp = wrapper.components(cmpDef)[0];
firstRecord.set('user', 'new_correct_value'); firstRecord.set('user', 'new_correct_value');
const firstGrandchild = cmp.components().at(0).components().at(0); const firstGrandchild = cmp.components().at(0).components().at(0);
@ -110,37 +112,37 @@ describe('Collection variable components', () => {
beforeEach(() => { beforeEach(() => {
const variableCmpDef = { const variableCmpDef = {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, dataResolver: {
collectionId: 'my_collection', variableType: DataCollectionStateType.currentItem,
path: 'user', collectionId: 'my_collection',
path: 'user',
},
}; };
const collectionComponentDefinition = { const collectionCmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: 'default',
type: 'default', components: [
components: [ {
{ type: 'default',
type: 'default',
},
variableCmpDef,
],
},
collectionConfig: {
collectionId: 'my_collection',
startIndex: 0,
endIndex: 2,
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
}, },
variableCmpDef,
],
},
dataResolver: {
collectionId: 'my_collection',
startIndex: 0,
endIndex: 2,
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
}, },
}, },
}; } as ComponentDataCollectionProps;
cmp = wrapper.components(collectionComponentDefinition)[0]; cmp = wrapper.components(collectionCmpDef)[0];
}); });
test('Serializion to JSON', () => { test('Serializion to JSON', () => {
@ -148,10 +150,12 @@ describe('Collection variable components', () => {
const firstChild = cmp.components().at(0); const firstChild = cmp.components().at(0);
const newChildDefinition = { const newChildDefinition = {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentIndex, dataResolver: {
collectionId: 'my_collection', variableType: DataCollectionStateType.currentIndex,
path: 'user', collectionId: 'my_collection',
path: 'user',
},
}; };
firstChild.components().at(0).components(newChildDefinition); firstChild.components().at(0).components(newChildDefinition);
expect(cmp.toJSON()).toMatchSnapshot(`Collection with collection variable component ( with grandchildren )`); expect(cmp.toJSON()).toMatchSnapshot(`Collection with collection variable component ( with grandchildren )`);
@ -167,10 +171,12 @@ describe('Collection variable components', () => {
const firstChild = cmp.components().at(0); const firstChild = cmp.components().at(0);
const newChildDefinition = { const newChildDefinition = {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentIndex, dataResolver: {
collectionId: 'my_collection', variableType: DataCollectionStateType.currentIndex,
path: 'user', collectionId: 'my_collection',
path: 'user',
},
}; };
firstChild.components().at(0).components(newChildDefinition); firstChild.components().at(0).components(newChildDefinition);
@ -178,6 +184,32 @@ describe('Collection variable components', () => {
}); });
test('Loading', () => { test('Loading', () => {
const cmpDef = {
components: {
components: [
{
type: DataVariableType,
dataResolver: {
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
},
],
type: 'default',
},
dataResolver: {
collectionId: 'my_collection',
dataSource: {
path: 'my_data_source_id',
type: DataVariableType,
},
endIndex: 1,
startIndex: 0,
},
type: DataCollectionType,
} as ComponentDataCollectionProps;
const componentProjectData: ProjectData = { const componentProjectData: ProjectData = {
assets: [], assets: [],
pages: [ pages: [
@ -185,33 +217,7 @@ describe('Collection variable components', () => {
frames: [ frames: [
{ {
component: { component: {
components: [ components: [cmpDef],
{
collectionDef: {
componentDef: {
type: 'default',
components: [
{
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
],
},
collectionConfig: {
collectionId: 'my_collection',
dataSource: {
path: 'my_data_source_id',
type: DataVariableType,
},
endIndex: 1,
startIndex: 0,
},
},
type: DataCollectionType,
},
],
docEl: { docEl: {
tagName: 'html', tagName: 'html',
}, },

212
packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap

@ -2,28 +2,19 @@
exports[`Collection component Serialization Saving: Collection with grandchildren 1`] = ` exports[`Collection component Serialization Saving: Collection with grandchildren 1`] = `
{ {
"collectionDef": { "components": [
"collectionConfig": { {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"attributes": { "attributes": {
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
@ -33,13 +24,13 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
@ -48,7 +39,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"type": "default", "type": "default",
@ -57,19 +48,19 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -79,32 +70,32 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -113,23 +104,32 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
}, },
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
}, },
"type": "data-collection", "type": "data-collection",
} }
@ -137,28 +137,19 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
exports[`Collection component Serialization Saving: Collection with no grandchildren 1`] = ` exports[`Collection component Serialization Saving: Collection with no grandchildren 1`] = `
{ {
"collectionDef": { "components": [
"collectionConfig": { {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"attributes": { "attributes": {
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
@ -168,32 +159,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -203,32 +194,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -237,23 +228,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
}, },
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
}, },
"type": "data-collection", "type": "data-collection",
} }
@ -261,28 +261,19 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with grandchildren 1`] = ` exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with grandchildren 1`] = `
{ {
"collectionDef": { "components": [
"collectionConfig": { {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"attributes": { "attributes": {
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
@ -292,13 +283,13 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
@ -307,7 +298,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"type": "default", "type": "default",
@ -316,19 +307,19 @@ exports[`Collection component Serialization Serializion with Collection Variable
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -338,32 +329,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -372,23 +363,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
}, },
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
}, },
"type": "data-collection", "type": "data-collection",
} }
@ -396,28 +396,19 @@ exports[`Collection component Serialization Serializion with Collection Variable
exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with no grandchildren 1`] = ` exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with no grandchildren 1`] = `
{ {
"collectionDef": { "components": [
"collectionConfig": { {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"attributes": { "attributes": {
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
@ -427,32 +418,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -462,32 +453,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": { "attribute_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
}, },
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
@ -496,23 +487,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"custom_prop": { "custom_prop": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentIndex", "variableType": "currentIndex",
}, },
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"property_trait": { "property_trait": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
"type": "data-collection-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"type": "default", "type": "default",
}, },
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
}, },
"type": "data-collection", "type": "data-collection",
} }

141
packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap

@ -1,141 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Collection variable components Serialization Saving: Collection with collection variable component ( no grandchildren ) 1`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"type": "default",
},
{
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"variableType": "currentItem",
},
],
"type": "default",
},
},
"type": "data-collection",
}
`;
exports[`Collection variable components Serialization Saving: Collection with collection variable component ( with grandchildren ) 1`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"components": [
{
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"variableType": "currentIndex",
},
],
"type": "default",
},
{
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"variableType": "currentItem",
},
],
"type": "default",
},
},
"type": "data-collection",
}
`;
exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( no grandchildren ) 1`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"type": "default",
},
{
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"variableType": "currentItem",
},
],
"type": "default",
},
},
"type": "data-collection",
}
`;
exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( with grandchildren ) 1`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"components": [
{
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"variableType": "currentIndex",
},
],
"type": "default",
},
{
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"variableType": "currentItem",
},
],
"type": "default",
},
},
"type": "data-collection",
}
`;

153
packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionWithDataVariable.ts.snap

@ -0,0 +1,153 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Collection variable components Serialization Saving: Collection with collection variable component ( no grandchildren ) 1`] = `
{
"components": [
{
"components": [
{
"type": "default",
},
{
"dataResolver": {
"collectionId": "my_collection",
"path": "user",
"variableType": "currentItem",
},
"type": "data-variable",
},
],
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"type": "data-collection",
}
`;
exports[`Collection variable components Serialization Saving: Collection with collection variable component ( with grandchildren ) 1`] = `
{
"components": [
{
"components": [
{
"components": [
{
"dataResolver": {
"collectionId": "my_collection",
"path": "user",
"variableType": "currentIndex",
},
"type": "data-variable",
},
],
"type": "default",
},
{
"dataResolver": {
"collectionId": "my_collection",
"path": "user",
"variableType": "currentItem",
},
"type": "data-variable",
},
],
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"type": "data-collection",
}
`;
exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( no grandchildren ) 1`] = `
{
"components": [
{
"components": [
{
"type": "default",
},
{
"dataResolver": {
"collectionId": "my_collection",
"path": "user",
"variableType": "currentItem",
},
"type": "data-variable",
},
],
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"type": "data-collection",
}
`;
exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( with grandchildren ) 1`] = `
{
"components": [
{
"components": [
{
"components": [
{
"dataResolver": {
"collectionId": "my_collection",
"path": "user",
"variableType": "currentIndex",
},
"type": "data-variable",
},
],
"type": "default",
},
{
"dataResolver": {
"collectionId": "my_collection",
"path": "user",
"variableType": "currentItem",
},
"type": "data-variable",
},
],
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 2,
"startIndex": 0,
},
"type": "data-collection",
}
`;

61
packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap

@ -2,33 +2,44 @@
exports[`Collection component Nested collections are correctly serialized 1`] = ` exports[`Collection component Nested collections are correctly serialized 1`] = `
{ {
"collectionDef": { "components": [
"collectionConfig": { {
"collectionId": "parent_collection", "components": [
"dataSource": { {
"path": "my_data_source_id", "components": [
"type": "data-variable", {
}, "components": [
}, {
"componentDef": { "name": {
"collectionDef": { "collectionId": "nested_collection",
"collectionConfig": { "path": "user",
"collectionId": "nested_collection", "type": "data-variable",
"dataSource": { "variableType": "currentItem",
"path": "nested_data_source_id", },
"type": "data-variable", "type": "default",
}, },
}, ],
"componentDef": { "type": "data-collection-item",
"name": { },
"path": "user", ],
"type": "data-collection-variable", "dataResolver": {
"variableType": "currentItem", "collectionId": "nested_collection",
"dataSource": {
"path": "nested_data_source_id",
"type": "data-variable",
},
}, },
"type": "default", "type": "data-collection",
}, },
}, ],
"type": "data-collection", "type": "data-collection-item",
},
],
"dataResolver": {
"collectionId": "parent_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
}, },
}, },
"type": "data-collection", "type": "data-collection",

459
packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts

@ -1,27 +1,46 @@
import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
import { import {
DataCollectionItemType,
DataCollectionType, DataCollectionType,
DataCollectionVariableType,
} from '../../../../../src/data_sources/model/data_collection/constants'; } from '../../../../../src/data_sources/model/data_collection/constants';
import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types'; import {
ComponentDataCollectionProps,
DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { setupTestEditor } from '../../../../common'; import { setupTestEditor } from '../../../../common';
describe('Collection component', () => { describe('Collection component', () => {
let em: EditorModel; let em: EditorModel;
let editor: Editor;
let dsm: DataSourceManager; let dsm: DataSourceManager;
let dataSource: DataSource; let dataSource: DataSource;
let nestedDataSource: DataSource; let nestedDataSource: DataSource;
let wrapper: Component; let wrapper: Component;
let firstRecord: DataRecord; let firstRecord: DataRecord;
let secondRecord: DataRecord;
let firstNestedRecord: DataRecord; let firstNestedRecord: DataRecord;
let secondNestedRecord: DataRecord; let cmpDef: ComponentDataCollectionProps | undefined;
let nestedCmpDef: ComponentDataCollectionProps | undefined;
let parentCmp: ComponentDataCollection;
let nestedCmp: ComponentDataCollection;
function getCmpDef(nestedCmpDef: ComponentDataCollectionProps): ComponentDataCollectionProps {
return {
type: DataCollectionType,
components: { type: DataCollectionItemType, components: nestedCmpDef },
dataResolver: {
collectionId: 'parent_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
};
}
beforeEach(() => { beforeEach(() => {
({ em, editor, dsm } = setupTestEditor()); ({ em, dsm } = setupTestEditor());
wrapper = em.getWrapper()!; wrapper = em.getWrapper()!;
dataSource = dsm.add({ dataSource = dsm.add({
id: 'my_data_source_id', id: 'my_data_source_id',
@ -41,96 +60,54 @@ describe('Collection component', () => {
}); });
firstRecord = dataSource.getRecord('user1')!; firstRecord = dataSource.getRecord('user1')!;
secondRecord = dataSource.getRecord('user2')!;
firstNestedRecord = nestedDataSource.getRecord('nested_user1')!; firstNestedRecord = nestedDataSource.getRecord('nested_user1')!;
secondNestedRecord = nestedDataSource.getRecord('nested_user2')!;
});
afterEach(() => {
em.destroy();
});
test('Nested collections bind to correct data sources', () => { nestedCmpDef = {
const parentCollection = wrapper.components({
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
type: DataCollectionType, components: {
collectionDef: { type: 'default',
componentDef: { name: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
collectionId: 'nested_collection',
path: 'user',
},
},
collectionConfig: {
collectionId: 'nested_collection',
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
},
},
collectionConfig: {
collectionId: 'parent_collection',
dataSource: {
type: DataVariableType, type: DataVariableType,
path: 'my_data_source_id', variableType: DataCollectionStateType.currentItem,
collectionId: 'nested_collection',
path: 'user',
}, },
}, },
}, },
})[0]; dataResolver: {
collectionId: 'nested_collection',
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
};
cmpDef = getCmpDef(nestedCmpDef);
const nestedCollection = parentCollection.components().at(0); parentCmp = wrapper.components(cmpDef)[0] as unknown as ComponentDataCollection;
const nestedFirstChild = nestedCollection.components().at(0); nestedCmp = parentCmp.getCollectionItemComponents().at(0) as ComponentDataCollection;
const nestedSecondChild = nestedCollection.components().at(1); });
afterEach(() => {
em.destroy();
nestedCmpDef = undefined;
cmpDef = undefined;
});
test('Nested collections bind to correct data sources', () => {
const nestedFirstChild = nestedCmp.components().at(0).components().at(0);
const nestedSecondChild = nestedCmp.components().at(1).components().at(0);
expect(nestedFirstChild.get('name')).toBe('nested_user1'); expect(nestedFirstChild.get('name')).toBe('nested_user1');
expect(nestedSecondChild.get('name')).toBe('nested_user2'); expect(nestedSecondChild.get('name')).toBe('nested_user2');
}); });
test('Updates in parent collection propagate to nested collections', () => { test('Updates in parent collection propagate to nested collections', () => {
const parentCollection = wrapper.components({ const nestedFirstChild = nestedCmp.components().at(0).components().at(0);
type: DataCollectionType, const nestedSecondChild = nestedCmp.components().at(1).components().at(0);
collectionDef: {
componentDef: {
type: DataCollectionType,
collectionDef: {
componentDef: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
collectionId: 'nested_collection',
path: 'user',
},
},
collectionConfig: {
collectionId: 'nested_collection',
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
},
},
collectionConfig: {
collectionId: 'parent_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
},
})[0];
const nestedCollection = parentCollection.components().at(0);
const nestedFirstChild = nestedCollection.components().at(0);
const nestedSecondChild = nestedCollection.components().at(1);
firstNestedRecord.set('user', 'updated_user1'); firstNestedRecord.set('user', 'updated_user1');
expect(nestedFirstChild.get('name')).toBe('updated_user1'); expect(nestedFirstChild.get('name')).toBe('updated_user1');
@ -138,173 +115,83 @@ describe('Collection component', () => {
}); });
test('Nested collections are correctly serialized', () => { test('Nested collections are correctly serialized', () => {
const parentCollection = wrapper.components({ const serialized = parentCmp.toJSON();
type: DataCollectionType,
collectionDef: {
componentDef: {
type: DataCollectionType,
collectionDef: {
componentDef: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
path: 'user',
},
},
collectionConfig: {
collectionId: 'nested_collection',
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
},
},
collectionConfig: {
collectionId: 'parent_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
},
})[0];
const serialized = parentCollection.toJSON();
expect(serialized).toMatchSnapshot(); expect(serialized).toMatchSnapshot();
}); });
test('Nested collections respect startIndex and endIndex', () => { test('Nested collections respect startIndex and endIndex', () => {
const parentCollection = wrapper.components({ nestedCmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
type: DataCollectionType, components: {
collectionDef: { type: 'default',
componentDef: { name: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
collectionId: 'nested_collection',
path: 'user',
},
},
collectionConfig: {
collectionId: 'nested_collection',
startIndex: 0,
endIndex: 1,
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
},
},
collectionConfig: {
collectionId: 'parent_collection',
dataSource: {
type: DataVariableType, type: DataVariableType,
path: 'my_data_source_id', variableType: DataCollectionStateType.currentItem,
collectionId: 'nested_collection',
path: 'user',
}, },
}, },
}, },
})[0]; dataResolver: {
collectionId: 'nested_collection',
startIndex: 0,
endIndex: 1,
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
};
const nestedCollection = parentCollection.components().at(0); const updatedParentCmp = wrapper.components(getCmpDef(nestedCmpDef))[0] as unknown as ComponentDataCollection;
expect(nestedCollection.components().length).toBe(2); const updatedNestedCmp = updatedParentCmp.getCollectionItemComponents().at(0) as ComponentDataCollection;
expect(updatedNestedCmp.getItemsCount()).toBe(2);
}); });
test('Nested collection gets and watches value from the parent collection', () => { test('Nested collection gets and watches value from the parent collection', () => {
const parentCollection = wrapper.components({ nestedCmpDef = {
type: DataCollectionType, type: DataCollectionType,
collectionDef: { components: {
componentDef: { type: DataCollectionItemType,
type: DataCollectionType, components: {
collectionDef: { type: 'default',
componentDef: { name: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
collectionId: 'parent_collection',
path: 'user',
},
},
collectionConfig: {
collectionId: 'nested_collection',
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
},
},
collectionConfig: {
collectionId: 'parent_collection',
dataSource: {
type: DataVariableType, type: DataVariableType,
path: 'my_data_source_id', variableType: DataCollectionStateType.currentItem,
collectionId: 'parent_collection',
path: 'user',
}, },
}, },
}, },
})[0]; dataResolver: {
collectionId: 'nested_collection',
startIndex: 0,
endIndex: 1,
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
};
const nestedCollection = parentCollection.components().at(0); const updatedParentCmp = wrapper.components(getCmpDef(nestedCmpDef))[0] as unknown as ComponentDataCollection;
const firstNestedChild = nestedCollection.components().at(0); const updatedNestedCmp = updatedParentCmp.getCollectionItemComponents().at(0) as ComponentDataCollection;
const firstNestedChild = updatedNestedCmp.getCollectionItemComponents().at(0);
// Verify initial value
expect(firstNestedChild.get('name')).toBe('user1'); expect(firstNestedChild.get('name')).toBe('user1');
// Update value in parent collection and verify nested collection updates
firstRecord.set('user', 'updated_user1'); firstRecord.set('user', 'updated_user1');
expect(firstNestedChild.get('name')).toBe('updated_user1'); expect(firstNestedChild.get('name')).toBe('updated_user1');
}); });
test('Nested collection switches to using its own collection variable', () => { test('Nested collection switches to using its own collection variable', () => {
const parentCollection = wrapper.components({ const firstChild = nestedCmp.components().at(0).components().at(0);
type: DataCollectionType,
collectionDef: {
componentDef: {
type: DataCollectionType,
collectionDef: {
componentDef: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
path: 'user',
collectionId: 'parent_collection',
},
},
collectionConfig: {
collectionId: 'nested_collection',
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
},
},
collectionConfig: {
collectionId: 'parent_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
},
})[0];
const nestedCollection = parentCollection.components().at(0);
const firstChild = nestedCollection.components().at(0);
// Replace the collection variable with one from the inner collection
firstChild.set('name', { firstChild.set('name', {
// @ts-ignore // @ts-ignore
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
path: 'user', path: 'user',
collectionId: 'nested_collection', collectionId: 'nested_collection',
}); });
@ -313,118 +200,78 @@ describe('Collection component', () => {
}); });
describe('Nested Collection Component with Parent and Nested Data Sources', () => { describe('Nested Collection Component with Parent and Nested Data Sources', () => {
let parentCollection: Component;
let nestedCollection: Component;
beforeEach(() => { beforeEach(() => {
// Initialize the parent and nested collections nestedCmpDef = {
parentCollection = wrapper.components({
type: DataCollectionType, type: DataCollectionType,
collectionDef: { name: {
componentDef: { type: DataVariableType,
type: DataCollectionType, variableType: DataCollectionStateType.currentItem,
collectionId: 'parent_collection',
path: 'user',
},
components: {
type: DataCollectionItemType,
components: {
type: 'default',
name: { name: {
type: DataCollectionVariableType, type: DataVariableType,
variableType: DataCollectionStateVariableType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'parent_collection', collectionId: 'nested_collection',
path: 'user', path: 'user',
}, },
collectionDef: {
componentDef: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
collectionId: 'nested_collection',
path: 'user',
},
},
collectionConfig: {
collectionId: 'nested_collection',
dataSource: {
type: DataVariableType,
path: 'nested_data_source_id',
},
},
},
}, },
collectionConfig: { },
collectionId: 'parent_collection', dataResolver: {
dataSource: { collectionId: 'nested_collection',
type: DataVariableType, dataSource: {
path: 'my_data_source_id', type: DataVariableType,
}, path: 'nested_data_source_id',
}, },
}, },
})[0]; };
nestedCollection = parentCollection.components().at(0); parentCmp = wrapper.components(getCmpDef(nestedCmpDef))[0] as unknown as ComponentDataCollection;
nestedCmp = parentCmp.getCollectionItemComponents().at(0) as ComponentDataCollection;
}); });
test('Removing a record from the parent data source updates the parent collection correctly', () => { test('Removing a record from the parent data source updates the parent collection correctly', () => {
// Verify initial state expect(parentCmp.getItemsCount()).toBe(2);
expect(parentCollection.components().length).toBe(2); // 2 parent records initially
// Remove a record from the parent data source
dataSource.removeRecord('user1'); dataSource.removeRecord('user1');
expect(parentCmp.getItemsCount()).toBe(1);
// Verify that the parent collection updates correctly expect(parentCmp.components().at(0).components().at(0).get('name')).toBe('user2');
expect(parentCollection.components().length).toBe(1); // Only 1 parent record remains expect(nestedCmp.getItemsCount()).toBe(3);
expect(parentCollection.components().at(0).get('name')).toBe('user2'); // Verify updated name expect(nestedCmp.components().at(0).components().at(0).get('name')).toBe('nested_user1');
// Verify that the nested collection is unaffected
expect(nestedCollection.components().length).toBe(3); // Nested records remain the same
expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify nested name
}); });
test('Adding a record to the parent data source updates the parent collection correctly', () => { test('Adding a record to the parent data source updates the parent collection correctly', () => {
// Verify initial state expect(parentCmp.getItemsCount()).toBe(2);
expect(parentCollection.components().length).toBe(2); // 2 parent records initially
// Add a new record to the parent data source
dataSource.addRecord({ id: 'user3', user: 'user3', age: '16' }); dataSource.addRecord({ id: 'user3', user: 'user3', age: '16' });
expect(parentCmp.getItemsCount()).toBe(3);
// Verify that the parent collection updates correctly expect(parentCmp.components().at(2).components().at(0).get('name')).toBe('user3');
expect(parentCollection.components().length).toBe(3); // 3 parent records now expect(nestedCmp.getItemsCount()).toBe(3);
expect(parentCollection.components().at(2).get('name')).toBe('user3'); // Verify new name expect(nestedCmp.components().at(0).components().at(0).get('name')).toBe('nested_user1');
// Verify that the nested collection is unaffected
expect(nestedCollection.components().length).toBe(3); // Nested records remain the same
expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify nested name
expect(parentCollection.components().at(2).components().at(0).get('name')).toBe('nested_user1'); // Verify nested name
}); });
test('Removing a record from the nested data source updates the nested collection correctly', () => { test('Removing a record from the nested data source updates the nested collection correctly', () => {
// Verify initial state expect(nestedCmp.getItemsCount()).toBe(3);
expect(nestedCollection.components().length).toBe(3); // 3 nested records initially
// Remove a record from the nested data source
nestedDataSource.removeRecord('nested_user1'); nestedDataSource.removeRecord('nested_user1');
expect(nestedCmp.getItemsCount()).toBe(2);
// Verify that the nested collection updates correctly expect(nestedCmp.components().at(0).components().at(0).get('name')).toBe('nested_user2');
expect(nestedCollection.components().length).toBe(2); // Only 2 nested records remain expect(nestedCmp.components().at(1).components().at(0).get('name')).toBe('nested_user3');
expect(nestedCollection.components().at(0).get('name')).toBe('nested_user2'); // Verify updated name
expect(nestedCollection.components().at(1).get('name')).toBe('nested_user3'); // Verify updated name
}); });
test('Adding a record to the nested data source updates the nested collection correctly', () => { test('Adding a record to the nested data source updates the nested collection correctly', () => {
// Verify initial state expect(nestedCmp.getItemsCount()).toBe(3);
expect(nestedCollection.components().length).toBe(3); // 3 nested records initially expect(nestedCmp.components().at(0).components().at(0).get('name')).toBe('nested_user1');
expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify initial name expect(nestedCmp.components().at(1).components().at(0).get('name')).toBe('nested_user2');
expect(nestedCollection.components().at(1).get('name')).toBe('nested_user2'); // Verify initial name expect(nestedCmp.components().at(2).components().at(0).get('name')).toBe('nested_user3');
expect(nestedCollection.components().at(2).get('name')).toBe('nested_user3'); // Verify initial name
// Add a new record to the nested data source
nestedDataSource.addRecord({ id: 'user4', user: 'nested_user4', age: '18' }); nestedDataSource.addRecord({ id: 'user4', user: 'nested_user4', age: '18' });
expect(nestedCmp.getItemsCount()).toBe(4);
// Verify that the nested collection updates correctly expect(nestedCmp.components().at(3).components().at(0).get('name')).toBe('nested_user4');
expect(nestedCollection.components().length).toBe(4); // 4 nested records now expect(nestedCmp.components().at(0).components().at(0).get('name')).toBe('nested_user1');
expect(nestedCollection.components().at(3).get('name')).toBe('nested_user4'); // Verify new name expect(nestedCmp.components().at(1).components().at(0).get('name')).toBe('nested_user2');
expect(nestedCmp.components().at(2).components().at(0).get('name')).toBe('nested_user3');
// Verify existing records are unaffected
expect(nestedCollection.components().at(0).get('name')).toBe('nested_user1'); // Verify existing name
expect(nestedCollection.components().at(1).get('name')).toBe('nested_user2'); // Verify existing name
expect(nestedCollection.components().at(2).get('name')).toBe('nested_user3'); // Verify existing name
}); });
}); });
}); });

Loading…
Cancel
Save