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 10 months 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 Component from '../../dom_components/model/Component';
import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../dom_components/model/types';
import { toLowerCase } from '../../utils/mixins';
import DataVariable, { DataVariableProps, DataVariableType } from './DataVariable';
import { DataCollectionStateMap } from './data_collection/types';
import { keyCollectionsStateMap } from './data_collection/constants';
export interface ComponentDataVariableProps extends ComponentProperties {
type: typeof DataVariableType;
dataResolver: DataVariableProps;
type?: typeof DataVariableType;
dataResolver?: DataVariableProps;
}
export default class ComponentDataVariable extends Component {
@ -16,19 +19,20 @@ export default class ComponentDataVariable extends Component {
return {
// @ts-ignore
...super.defaults,
droppable: false,
type: DataVariableType,
dataResolver: {
path: '',
defaultValue: '',
},
dataResolver: {},
droppable: false,
};
}
constructor(props: ComponentDataVariableProps, opt: ComponentOptions) {
super(props, opt);
this.dataResolver = new DataVariable(props.dataResolver, opt);
this.dataResolver = new DataVariable(props.dataResolver ?? {}, {
...opt,
collectionsStateMap: this.get(keyCollectionsStateMap),
});
this.listenToPropsChange();
}
@ -48,6 +52,10 @@ export default class ComponentDataVariable extends Component {
return this.getDataValue();
}
getCollectionsStateMap() {
return this.get(keyCollectionsStateMap) ?? {};
}
setPath(newPath: string) {
this.dataResolver.set('path', newPath);
}
@ -56,15 +64,43 @@ export default class ComponentDataVariable extends Component {
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() {
this.listenTo(
this.dataResolver,
'change',
(() => {
this.__changesUp({ m: this });
}).bind(this),
);
this.on('change:dataResolver', () => {
this.dataResolver.set(this.get('dataResolver'));
});
this.on(`change:${keyCollectionsStateMap}`, (_: Component, value: DataCollectionStateMap) => {
this.dataResolver.updateCollectionsStateMap(value);
});
}
toJSON(opts?: ObjectAny): ComponentDefinition {
const json = super.toJSON(opts);
const dataResolver = this.dataResolver.toJSON();
const dataResolver: DataVariableProps = this.dataResolver.toJSON();
delete dataResolver.type;
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) {
return toLowerCase(el.tagName) === DataVariableType;
}

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

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

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

@ -1,32 +1,141 @@
import { Model } from '../../common';
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 interface DataVariableProps {
type?: typeof DataVariableType;
path: string;
path?: string;
defaultValue?: string;
collectionId?: string;
variableType?: DataCollectionStateType;
}
export default class DataVariable extends Model<DataVariableProps> {
em?: EditorModel;
private em: EditorModel;
private collectionsStateMap: DataCollectionStateMap;
defaults() {
defaults(): DataVariableProps {
return {
type: DataVariableType,
defaultValue: '',
path: '',
collectionId: undefined,
variableType: undefined,
};
}
constructor(props: DataVariableProps, options: { em?: EditorModel }) {
constructor(props: DataVariableProps, options: { em: EditorModel; collectionsStateMap: DataCollectionStateMap }) {
super(props, options);
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() {
const { path, defaultValue } = this.attributes;
return this.em?.DataSources.getValue(path!, defaultValue);
if (this.resolvesFromCollection()) {
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 { ComponentDefinitionDefined, ToHTMLOptions } from '../../../dom_components/model/types';
import { toLowerCase } from '../../../utils/mixins';
import { isDataConditionDisplayType } from '../../utils';
import { isComponentDataOutputType } from '../../utils';
export default class ConditionalOutputBase extends Component {
get defaults(): ComponentDefinitionDefined {
@ -14,6 +14,6 @@ export default class ConditionalOutputBase extends Component {
}
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 { StringOperation } from './operators/StringOperator';
import { isUndefined } from 'underscore';
import { DataCollectionStateMap } from '../data_collection/types';
export const DataConditionType = 'data-condition' as const;
export const DataConditionEvaluationChangedEvent = 'data-condition-evaluation-changed';
@ -34,6 +35,7 @@ export interface DataConditionProps {
export class DataCondition extends Model<DataConditionProps> {
private em: EditorModel;
private collectionsStateMap: DataCollectionStateMap = {};
private resolverListeners: DataResolverListener[] = [];
private _previousEvaluationResult: boolean | null = null;
private _conditionEvaluator: DataConditionEvaluator;
@ -56,7 +58,6 @@ export class DataCondition extends Model<DataConditionProps> {
opts.em.logError('No condition was provided to a conditional component.');
}
// @ts-ignore
super(props, opts);
this.em = opts.em;
@ -108,6 +109,14 @@ export class DataCondition extends Model<DataConditionProps> {
return isConditionTrue ? resolveDynamicValue(ifTrue, this.em) : resolveDynamicValue(ifFalse, this.em);
}
resolvesFromCollection() {
return false;
}
updateCollectionsStateMap(collectionsStateMap: DataCollectionStateMap) {
this.collectionsStateMap = collectionsStateMap;
}
private listenToPropsChange() {
this.on('change:condition', this.handleConditionChange.bind(this));
this.on('change:condition change:ifTrue change:ifFalse', () => {
@ -120,9 +129,7 @@ export class DataCondition extends Model<DataConditionProps> {
}
private listenToDataVariables() {
// Clear previous listeners to avoid memory leaks
this.cleanupListeners();
this.setupConditionDataVariableListeners();
this.setupOutputDataVariableListeners();
}
@ -137,16 +144,10 @@ export class DataCondition extends Model<DataConditionProps> {
private setupOutputDataVariableListeners() {
const isConditionTrue = this.isTrue();
this.setupOutputVariableListener(this.getIfTrue(), 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) {
if (isDataVariable(outputVariable)) {
this.addListener(outputVariable, () => {
@ -160,7 +161,7 @@ export class DataCondition extends Model<DataConditionProps> {
private addListener(variable: DataVariableProps, onUpdate: () => void) {
const listener = new DataResolverListener({
em: this.em,
resolver: new DataVariable(variable, { em: this.em }),
resolver: new DataVariable(variable, { em: this.em, collectionsStateMap: this.collectionsStateMap }),
onUpdate,
});

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

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

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

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

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

@ -1,9 +1,7 @@
import EditorModel from '../editor/model/Editor';
import { DataResolver, DataResolverProps } from './types';
import { DataConditionDisplayType } from './model/conditional_variables/ComponentDataCondition';
import { DataCollectionStateMap } from './model/data_collection/types';
import DataCollectionVariable from './model/data_collection/DataCollectionVariable';
import { DataCollectionVariableType } from './model/data_collection/constants';
import { DataCollectionItemType } from './model/data_collection/constants';
import { DataConditionType, DataCondition } from './model/conditional_variables/DataCondition';
import DataVariable, { DataVariableProps, DataVariableType } from './model/DataVariable';
import Component from '../dom_components/model/Component';
@ -12,9 +10,7 @@ import { serialize } from '../utils/mixins';
import { DataConditionIfFalseType, DataConditionIfTrueType } from './model/conditional_variables/constants';
export function isDataResolverProps(value: any): value is DataResolverProps {
return (
typeof value === 'object' && [DataVariableType, DataConditionType, DataCollectionVariableType].includes(value?.type)
);
return typeof value === 'object' && [DataVariableType, DataConditionType].includes(value?.type);
}
export function isDataResolver(value: any): value is DataResolver {
@ -30,12 +26,14 @@ export function isDataCondition(variable: any) {
}
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(
resolverProps: DataResolverProps,
options: { em: EditorModel; collectionsStateMap?: DataCollectionStateMap },
options: { em: EditorModel; collectionsStateMap: DataCollectionStateMap },
) {
const { type } = resolverProps;
let resolver: DataResolver;
@ -48,10 +46,6 @@ export function getDataResolverInstance(
resolver = new DataCondition(resolverProps, options);
break;
}
case DataCollectionVariableType: {
resolver = new DataCollectionVariable(resolverProps, options);
break;
}
default:
options.em?.logError(`Unsupported dynamic type: ${type}`);
return;
@ -64,7 +58,7 @@ export function getDataResolverInstanceValue(
resolverProps: DataResolverProps,
options: {
em: EditorModel;
collectionsStateMap?: DataCollectionStateMap;
collectionsStateMap: DataCollectionStateMap;
},
) {
const resolver = getDataResolverInstance(resolverProps, options);
@ -86,6 +80,6 @@ export const ensureComponentInstance = (
return new Model(serialize(cmp ?? {}), opt);
};
export const isDataConditionDisplayType = (type: string | undefined): type is DataConditionDisplayType => {
return !!type && [DataConditionIfTrueType, DataConditionIfFalseType].includes(type);
export const isComponentDataOutputType = (type: string | undefined) => {
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 ComponentDataConditionView from '../data_sources/view/ComponentDataConditionView';
import ComponentDataCollection from '../data_sources/model/data_collection/ComponentDataCollection';
import { DataCollectionType, DataCollectionVariableType } from '../data_sources/model/data_collection/constants';
import ComponentDataCollectionVariable from '../data_sources/model/data_collection/ComponentDataCollectionVariable';
import ComponentDataCollectionVariableView from '../data_sources/view/ComponentDataCollectionVariableView';
import { DataCollectionItemType, DataCollectionType } from '../data_sources/model/data_collection/constants';
import ComponentDataCollectionView from '../data_sources/view/ComponentDataCollectionView';
import ComponentDataCondition from '../data_sources/model/conditional_variables/ComponentDataCondition';
import {
DataConditionIfFalseType,
DataConditionIfTrueType,
} 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 =
| 'component:create'
@ -204,19 +202,19 @@ export interface CanMoveResult {
export default class ComponentManager extends ItemManagerModule<DomComponentsConfig, any> {
componentTypes: ComponentStackItem[] = [
{
id: DataConditionIfTrueType,
model: ConditionalOutputBase,
id: DataCollectionItemType,
model: ComponentDataOutput,
view: ComponentView,
},
{
id: DataConditionIfFalseType,
model: ConditionalOutputBase,
id: DataConditionIfTrueType,
model: ComponentDataOutput,
view: ComponentView,
},
{
id: DataCollectionVariableType,
model: ComponentDataCollectionVariable,
view: ComponentDataCollectionVariableView,
id: DataConditionIfFalseType,
model: ComponentDataOutput,
view: ComponentView,
},
{
id: DataCollectionType,

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

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

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

@ -1,5 +1,4 @@
import { ObjectAny } from '../../common';
import { DataCollectionVariableType } from '../../data_sources/model/data_collection/constants';
import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types';
import DataResolverListener from '../../data_sources/model/DataResolverListener';
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 {
private em: EditorModel;
private collectionsStateMap?: DataCollectionStateMap;
private collectionsStateMap: DataCollectionStateMap = {};
private resolverListeners: Record<string, DataResolverListener> = {};
constructor(
@ -30,7 +29,7 @@ export class ComponentResolverWatcher {
options: ComponentResolverWatcherOptions,
) {
this.em = options.em;
this.collectionsStateMap = options.collectionsStateMap;
this.collectionsStateMap = options.collectionsStateMap ?? {};
}
bindComponent(component: Component) {
@ -40,14 +39,12 @@ export class ComponentResolverWatcher {
updateCollectionStateMap(collectionsStateMap: DataCollectionStateMap) {
this.collectionsStateMap = collectionsStateMap;
const collectionVariablesKeys = this.getDynamicValuesOfType(DataCollectionVariableType);
const collectionVariablesObject = collectionVariablesKeys.reduce(
(acc: { [key: string]: DataResolverProps | null }, key) => {
acc[key] = null;
return acc;
},
{},
);
const collectionVariablesKeys = this.getValuesResolvingFromCollections();
const collectionVariablesObject: { [key: string]: DataResolverProps | null } = {};
collectionVariablesKeys.forEach((key) => {
this.resolverListeners[key].resolver.updateCollectionsStateMap(collectionsStateMap);
collectionVariablesObject[key] = null;
});
const newVariables = this.getSerializableValues(collectionVariablesObject);
const evaluatedValues = this.addDynamicValues(newVariables);
@ -164,10 +161,9 @@ export class ComponentResolverWatcher {
return serializableValues;
}
getDynamicValuesOfType(type: DataResolverProps['type']) {
getValuesResolvingFromCollections() {
const keys = Object.keys(this.resolverListeners).filter((key: string) => {
// @ts-ignore
return this.resolverListeners[key].resolver.get('type') === type;
return this.resolverListeners[key].resolver.resolvesFromCollection();
});
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 { isEmptyObj } from '../../utils/mixins';
import Components from './Components';
import {
DataCollectionVariableType,
keyCollectionDefinition,
} from '../../data_sources/model/data_collection/constants';
import { keyCollectionDefinition } from '../../data_sources/model/data_collection/constants';
export const isSymbolMain = (cmp: Component) => isArray(cmp.get(keySymbols));
@ -178,10 +175,10 @@ const shouldPropagateProperty = (props: Record<string, any>, prop: string, compo
const isCollectionVariableDefinition = (() => {
if (prop === '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;

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

@ -17,6 +17,7 @@ import {
isDataResolverProps,
} from '../../data_sources/utils';
import { DataResolver } from '../../data_sources/types';
import { keyCollectionsStateMap } from '../../data_sources/model/data_collection/constants';
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];
if (isDataResolverProps(styleValue)) {
const dataResolver = getDataResolverInstance(styleValue, { em: this.em! });
const dataResolver = getDataResolverInstance(styleValue, {
em: this.em!,
collectionsStateMap: this.get(keyCollectionsStateMap) ?? {},
});
if (dataResolver) {
newStyle[key] = dataResolver;
@ -187,7 +191,10 @@ export default class StyleableModel<T extends ObjectHash = any> extends Model<T>
}
if (isDataResolverProps(styleValue)) {
resultStyle[key] = getDataResolverInstanceValue(styleValue, { em: this.em! });
resultStyle[key] = getDataResolverInstanceValue(styleValue, {
em: this.em!,
collectionsStateMap: this.get(keyCollectionsStateMap) ?? {},
});
}
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 {
DataCollectionItemType,
DataCollectionType,
DataCollectionVariableType,
} 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 { setupTestEditor } from '../../../../common';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
@ -14,8 +17,6 @@ describe('Collection component getters and setters', () => {
let dsm: DataSourceManager;
let dataSource: DataSource;
let wrapper: Component;
let firstRecord: DataRecord;
let secondRecord: DataRecord;
beforeEach(() => {
({ em, dsm } = setupTestEditor());
@ -28,8 +29,6 @@ describe('Collection component getters and setters', () => {
{ id: 'user3', user: 'user3', firstName: 'Name3', age: '16' },
],
});
firstRecord = dataSource.getRecord('user1')!;
secondRecord = dataSource.getRecord('user2')!;
});
afterEach(() => {
@ -40,10 +39,11 @@ describe('Collection component getters and setters', () => {
let cmp: ComponentDataCollection;
beforeEach(() => {
cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
components: [
{
@ -51,8 +51,8 @@ describe('Collection component getters and setters', () => {
tagName: 'div',
attributes: {
dataUser: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -60,17 +60,19 @@ describe('Collection component getters and setters', () => {
},
],
},
collectionConfig: {
collectionId: 'my_collection',
startIndex: 1,
endIndex: 2,
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
dataResolver: {
collectionId: 'my_collection',
startIndex: 1,
endIndex: 2,
dataSource: {
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', () => {
@ -85,14 +87,14 @@ describe('Collection component getters and setters', () => {
expect(cmp.getConfigEndIndex()).toBe(2);
});
test('getComponentDef should return the correct component definition', () => {
const componentDef = cmp.getComponentDef();
test('components should return the correct component definition', () => {
const firstChildJSON = JSON.parse(JSON.stringify(cmp.getCollectionItemComponents().at(0)));
expect(componentDef.type).toBe('default');
expect(componentDef.components).toHaveLength(1);
expect(componentDef?.components?.[0].attributes?.['dataUser']).toEqual({
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
expect(firstChildJSON['type']).toBe('default');
expect(firstChildJSON.components).toHaveLength(1);
expect(firstChildJSON.components?.[0].attributes?.['dataUser']).toEqual({
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
});
@ -138,10 +140,11 @@ describe('Collection component getters and setters', () => {
let cmp: ComponentDataCollection;
beforeEach(() => {
cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
components: [
{
@ -149,8 +152,8 @@ describe('Collection component getters and setters', () => {
tagName: 'div',
attributes: {
dataUser: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -158,30 +161,32 @@ describe('Collection component getters and setters', () => {
},
],
},
collectionConfig: {
collectionId: 'my_collection',
startIndex: 1,
endIndex: 2,
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
dataResolver: {
collectionId: 'my_collection',
startIndex: 1,
endIndex: 2,
dataSource: {
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', () => {
const newComponentDef = {
type: 'newType',
test('components should update the component definition and reflect in children', () => {
const newItemCmpDef = {
type: 'text',
components: [
{
type: 'default',
tagName: 'span',
attributes: {
'data-name': {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
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();
expect(children).toHaveLength(2);
expect(children.at(0).get('type')).toBe('newType');
expect(children.at(0).components().at(0).get('tagName')).toBe('span');
expect(children.at(0).components().at(0).getAttributes()['data-name']).toBe('Name2');
const firstItemCmp = children.at(0).components().at(0);
expect(firstItemCmp.get('type')).toBe('text');
expect(firstItemCmp.components().at(0).get('tagName')).toBe('span');
expect(firstItemCmp.components().at(0).getAttributes()['data-name']).toBe('Name2');
});
test('setStartIndex should update the start index and reflect in children', () => {
cmp.setStartIndex(0);
expect(cmp.getConfigStartIndex()).toBe(0);
expect(cmp.getItemsCount()).toBe(3);
const children = cmp.components();
expect(children).toHaveLength(3);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user1');
expect(children.at(1).components().at(0).getAttributes()['dataUser']).toBe('user2');
expect(children.at(2).components().at(0).getAttributes()['dataUser']).toBe('user3');
const firstItemCmp = children.at(0).components().at(0);
expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user1');
const secondItemCmp = children.at(1).components().at(0);
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', () => {
cmp.setEndIndex(3);
expect(cmp.getConfigEndIndex()).toBe(3);
expect(cmp.getItemsCount()).toBe(2);
const children = cmp.components();
expect(children).toHaveLength(2);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user2');
expect(children.at(1).components().at(0).getAttributes()['dataUser']).toBe('user3');
const firstItemCmp = children.at(0).components().at(0);
expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user2');
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', () => {
@ -233,24 +244,25 @@ describe('Collection component getters and setters', () => {
path: 'new_data_source_id',
});
expect(cmp.getItemsCount()).toBe(1);
const children = cmp.components();
expect(children).toHaveLength(1);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user5');
const firstItemCmp = children.at(0).components().at(0);
expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user5');
});
test('setStartIndex with zero should include the first record', () => {
cmp.setStartIndex(0);
expect(cmp.getItemsCount()).toBe(3);
const children = cmp.components();
expect(children).toHaveLength(3);
expect(children.at(0).components().at(0).getAttributes()['dataUser']).toBe('user1');
const firstItemCmp = children.at(0).components().at(0);
expect(firstItemCmp.components().at(0).getAttributes()['dataUser']).toBe('user1');
});
test('setEndIndex with zero should result in no children', () => {
cmp.setEndIndex(0);
const children = cmp.components();
expect(children).toHaveLength(0);
expect(cmp.getItemsCount()).toBe(0);
});
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',
});
const children = cmp.components();
expect(children).toHaveLength(0);
expect(cmp.getItemsCount()).toBe(0);
});
});
@ -273,10 +284,11 @@ describe('Collection component getters and setters', () => {
let cmp: ComponentDataCollection;
beforeEach(() => {
cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
components: [
{
@ -284,8 +296,8 @@ describe('Collection component getters and setters', () => {
tagName: 'div',
attributes: {
dataUser: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -293,17 +305,18 @@ describe('Collection component getters and setters', () => {
},
],
},
collectionConfig: {
collectionId: 'my_collection',
startIndex: 1,
endIndex: 2,
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
dataResolver: {
collectionId: 'my_collection',
startIndex: 1,
endIndex: 2,
dataSource: {
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', () => {
@ -319,6 +332,7 @@ describe('Collection component getters and setters', () => {
cmp.setEndIndex(3);
const html = cmp.toHTML();
expect(html).not.toContain('dataUser="user1"');
expect(html).toContain('dataUser="user2"');
expect(html).toContain('dataUser="user3"');
});
@ -337,14 +351,17 @@ describe('Collection component getters and setters', () => {
});
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"');
});
test('HTML output should be empty when endIndex is zero', () => {
cmp.setEndIndex(0);
const html = cmp.toHTML();
expect(html).not.toContain('dataUser');
const dataOutputCmp = cmp.components().at(0);
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 { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import {
DataCollectionItemType,
DataCollectionType,
DataCollectionVariableType,
} 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 { setupTestEditor } from '../../../../common';
import { getSymbolMain } from '../../../../../src/dom_components/model/SymbolUtils';
import { ProjectData } from '../../../../../src/storage_manager';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
describe('Collection component', () => {
let em: EditorModel;
@ -40,82 +43,55 @@ describe('Collection component', () => {
});
test('Collection component should be undroppable', () => {
const cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
},
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];
expect(cmp.get('droppable')).toBe(false);
});
test('Collection items should be undraggable', () => {
const cmp = wrapper.components({
test('Collection items should be undraggable and unremovable', () => {
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
},
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];
cmp.components().forEach((child) => {
expect(child.get('draggable')).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);
expect(child.get('removable')).toBe(false);
});
});
describe('Collection variables', () => {
describe('Properties', () => {
let cmp: Component;
let cmp: ComponentDataCollection;
let firstChild!: Component;
let firstGrandchild!: Component;
let secondChild!: Component;
@ -131,63 +107,67 @@ describe('Collection component', () => {
const checkRecordsWithInnerCmp = () => {
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') });
});
};
beforeEach(() => {
cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
components: [
{
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
},
{
tagName: 'span',
type: DataCollectionVariableType,
variableType: 'currentItem',
collectionId: 'my_collection',
path: 'firstName',
type: DataVariableType,
dataResolver: {
variableType: 'currentItem',
collectionId: 'my_collection',
path: 'firstName',
},
},
],
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
custom_property: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
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;
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);
secondChild = cmp.components().at(1);
secondChild = cmp.components().at(1).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', () => {
@ -222,8 +202,8 @@ describe('Collection component', () => {
expect(cmp.components().length).toBe(2);
const updatedFirstChild = cmp.components().at(0);
const updatedSecondChild = cmp.components().at(1);
const updatedFirstChild = cmp.components().at(0).components().at(0);
const updatedSecondChild = cmp.components().at(1).components().at(0);
expect(updatedFirstChild.get('name')).toBe('user2');
expect(updatedSecondChild.get('name')).toBe('user3');
@ -240,18 +220,14 @@ describe('Collection component', () => {
test('Adding a record updates the collection component correctly', () => {
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');
const newGrandchild = newChild.components().at(0);
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(secondChild.get('name')).toBe('user2');
expect(thirdChild.get('name')).toBe('user3');
@ -280,8 +256,8 @@ describe('Collection component', () => {
test('Updating the value to a different collection variable', async () => {
firstChild.set('name', {
// @ts-ignore
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'age',
});
@ -299,8 +275,8 @@ describe('Collection component', () => {
firstGrandchild.set('name', {
// @ts-ignore
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'age',
});
@ -353,18 +329,19 @@ describe('Collection component', () => {
let thirdChild!: Component;
beforeEach(() => {
cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
components: [
{
type: 'default',
attributes: {
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -373,28 +350,29 @@ describe('Collection component', () => {
],
attributes: {
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
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;
cmp = wrapper.components(cmpDef)[0];
firstChild = cmp.components().at(0);
firstChild = cmp.components().at(0).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);
thirdChild = cmp.components().at(2);
thirdChild = cmp.components().at(2).components().at(0);
});
test('Evaluating to static value', () => {
@ -446,8 +424,8 @@ describe('Collection component', () => {
firstChild.setAttributes({
name: {
// @ts-ignore
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'age',
},
@ -471,8 +449,8 @@ describe('Collection component', () => {
firstGrandchild.setAttributes({
name: {
// @ts-ignore
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'age',
},
@ -532,17 +510,18 @@ describe('Collection component', () => {
});
test('Traits', () => {
const cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
traits: [
{
name: 'attribute_trait',
value: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -551,27 +530,28 @@ describe('Collection component', () => {
name: 'property_trait',
changeProp: true,
value: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
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] as unknown as ComponentDataCollection;
expect(cmp.components()).toHaveLength(3);
const firstChild = cmp.components().at(0);
const secondChild = cmp.components().at(1);
expect(cmp.getItemsCount()).toBe(3);
const firstChild = cmp.components().at(0).components().at(0);
const secondChild = cmp.components().at(1).components().at(0);
expect(firstChild.getAttributes()['attribute_trait']).toBe('user1');
expect(firstChild.getEl()?.getAttribute('attribute_trait')).toBe('user1');
@ -593,27 +573,27 @@ describe('Collection component', () => {
});
describe('Serialization', () => {
let cmp: Component;
let cmp: ComponentDataCollection;
beforeEach(() => {
const cmpDefinition = {
const childCmpDef = {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
custom_prop: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentIndex,
type: DataVariableType,
variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection',
path: 'user',
},
attributes: {
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -622,8 +602,8 @@ describe('Collection component', () => {
{
name: 'attribute_trait',
value: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -632,8 +612,8 @@ describe('Collection component', () => {
name: 'property_trait',
changeProp: true,
value: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
@ -641,42 +621,42 @@ describe('Collection component', () => {
],
};
const collectionComponentDefinition = {
const collectionCmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
...cmpDefinition,
components: [cmpDefinition, cmpDefinition],
components: [
{
...childCmpDef,
components: [childCmpDef, childCmpDef],
},
collectionConfig: {
collectionId: 'my_collection',
startIndex: 0,
endIndex: 1,
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
],
dataResolver: {
collectionId: 'my_collection',
startIndex: 0,
endIndex: 1,
dataSource: {
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', () => {
expect(cmp.toJSON()).toMatchSnapshot(`Collection with no grandchildren`);
const firstChild = cmp.components().at(0);
const firstItemCmp = cmp.getCollectionItemComponents().at(0);
const newChildDefinition = {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentIndex,
type: DataVariableType,
variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection',
path: 'user',
},
};
firstChild.components().at(0).components(newChildDefinition);
firstItemCmp.components(newChildDefinition);
expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`);
});
@ -684,25 +664,149 @@ describe('Collection component', () => {
const projectData = editor.getProjectData();
const page = projectData.pages[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`);
const firstChild = cmp.components().at(0);
const firstItemCmp = cmp.getCollectionItemComponents().at(0);
const newChildDefinition = {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentIndex,
type: DataVariableType,
variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection',
path: 'user',
},
};
firstChild.components().at(0).components(newChildDefinition);
firstItemCmp.components(newChildDefinition);
expect(cmp.toJSON()).toMatchSnapshot(`Collection with grandchildren`);
});
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 = {
assets: [],
pages: [
@ -710,128 +814,7 @@ describe('Collection component', () => {
frames: [
{
component: {
components: [
{
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,
},
],
components: [cmpDef],
docEl: {
tagName: 'html',
},
@ -863,10 +846,10 @@ describe('Collection component', () => {
editor.loadProjectData(componentProjectData);
const components = editor.getComponents();
const component = components.models[0];
const firstChild = component.components().at(0);
const component = components.models[0] as ComponentDataCollection;
const firstChild = component.components().at(0).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);
expect(firstChild.get('name')).toBe('user1');
@ -894,33 +877,35 @@ describe('Collection component', () => {
describe('Configuration options', () => {
test('Collection with start and end indexes', () => {
const cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
name: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
type: DataVariableType,
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
},
collectionConfig: {
startIndex: 1,
endIndex: 2,
collectionId: 'my_collection',
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
},
},
dataResolver: {
startIndex: 1,
endIndex: 2,
collectionId: 'my_collection',
dataSource: {
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);
const firstChild = cmp.components().at(0);
const secondChild = cmp.components().at(1);
const firstChild = cmp.components().at(0).components().at(0);
const secondChild = cmp.components().at(1).components().at(0);
expect(firstChild.get('name')).toBe('user2');
expect(secondChild.get('name')).toBe('user3');
@ -929,32 +914,33 @@ describe('Collection component', () => {
describe('Diffirent Collection variable types', () => {
const stateVariableTests = [
{ variableType: DataCollectionStateVariableType.currentIndex, expectedValues: [0, 1, 2] },
{ variableType: DataCollectionStateVariableType.startIndex, expectedValues: [0, 0, 0] },
{ variableType: DataCollectionStateVariableType.endIndex, expectedValues: [2, 2, 2] },
{ variableType: DataCollectionStateType.currentIndex, expectedValues: [0, 1, 2] },
{ variableType: DataCollectionStateType.startIndex, expectedValues: [0, 0, 0] },
{ variableType: DataCollectionStateType.endIndex, expectedValues: [2, 2, 2] },
{
variableType: DataCollectionStateVariableType.collectionId,
variableType: DataCollectionStateType.collectionId,
expectedValues: ['my_collection', 'my_collection', 'my_collection'],
},
{ variableType: DataCollectionStateVariableType.totalItems, expectedValues: [3, 3, 3] },
{ variableType: DataCollectionStateVariableType.remainingItems, expectedValues: [2, 1, 0] },
{ variableType: DataCollectionStateType.totalItems, expectedValues: [3, 3, 3] },
{ variableType: DataCollectionStateType.remainingItems, expectedValues: [2, 1, 0] },
];
stateVariableTests.forEach(({ variableType, expectedValues }) => {
test(`Variable type: ${variableType}`, () => {
const cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
components: {
type: DataCollectionItemType,
components: {
type: 'default',
name: {
type: DataCollectionVariableType,
type: DataVariableType,
variableType: variableType,
collectionId: 'my_collection',
},
attributes: {
custom_attribute: {
type: DataCollectionVariableType,
type: DataVariableType,
variableType: variableType,
collectionId: 'my_collection',
},
@ -963,7 +949,7 @@ describe('Collection component', () => {
{
name: 'attribute_trait',
value: {
type: DataCollectionVariableType,
type: DataVariableType,
variableType: variableType,
collectionId: 'my_collection',
},
@ -972,31 +958,33 @@ describe('Collection component', () => {
name: 'property_trait',
changeProp: true,
value: {
type: DataCollectionVariableType,
type: DataVariableType,
variableType: variableType,
collectionId: 'my_collection',
},
},
],
},
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] as unknown as ComponentDataCollection;
const children = cmp.components();
expect(children).toHaveLength(3);
expect(cmp.getItemsCount()).toBe(3);
const children = cmp.components();
children.each((child, index) => {
expect(child.get('name')).toBe(expectedValues[index]);
expect(child.get('property_trait')).toBe(expectedValues[index]);
expect(child.getAttributes()['custom_attribute']).toBe(expectedValues[index]);
expect(child.getAttributes()['attribute_trait']).toBe(expectedValues[index]);
const content = child.components().at(0);
expect(content.get('name')).toBe(expectedValues[index]);
expect(content.get('property_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 { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import { DataCollectionType } from '../../../../../src/data_sources/model/data_collection/constants';
import {
DataCollectionType,
DataCollectionVariableType,
} from '../../../../../src/data_sources/model/data_collection/constants';
import { DataCollectionStateVariableType } from '../../../../../src/data_sources/model/data_collection/types';
ComponentDataCollectionProps,
DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types';
import EditorModel from '../../../../../src/editor/model/Editor';
import { ProjectData } from '../../../../../src/storage_manager';
import { setupTestEditor } from '../../../../common';
@ -39,29 +39,30 @@ describe('Collection variable components', () => {
});
test('Gets the correct static value', async () => {
const cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
type: 'default',
components: [
{
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
components: {
type: 'default',
components: [
{
type: DataVariableType,
dataResolver: {
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
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);
expect(firstGrandchild.getInnerHTML()).toContain('user1');
@ -73,27 +74,28 @@ describe('Collection variable components', () => {
});
test('Watches collection variable changes', async () => {
const cmp = wrapper.components({
const cmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
type: 'default',
components: {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
components: {
type: 'default',
components: {
type: DataVariableType,
dataResolver: {
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
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];
firstRecord.set('user', 'new_correct_value');
const firstGrandchild = cmp.components().at(0).components().at(0);
@ -110,37 +112,37 @@ describe('Collection variable components', () => {
beforeEach(() => {
const variableCmpDef = {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentItem,
collectionId: 'my_collection',
path: 'user',
type: DataVariableType,
dataResolver: {
variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection',
path: 'user',
},
};
const collectionComponentDefinition = {
const collectionCmpDef = {
type: DataCollectionType,
collectionDef: {
componentDef: {
type: 'default',
components: [
{
type: 'default',
},
variableCmpDef,
],
},
collectionConfig: {
collectionId: 'my_collection',
startIndex: 0,
endIndex: 2,
dataSource: {
type: DataVariableType,
path: 'my_data_source_id',
components: {
type: 'default',
components: [
{
type: 'default',
},
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', () => {
@ -148,10 +150,12 @@ describe('Collection variable components', () => {
const firstChild = cmp.components().at(0);
const newChildDefinition = {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentIndex,
collectionId: 'my_collection',
path: 'user',
type: DataVariableType,
dataResolver: {
variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection',
path: 'user',
},
};
firstChild.components().at(0).components(newChildDefinition);
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 newChildDefinition = {
type: DataCollectionVariableType,
variableType: DataCollectionStateVariableType.currentIndex,
collectionId: 'my_collection',
path: 'user',
type: DataVariableType,
dataResolver: {
variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection',
path: 'user',
},
};
firstChild.components().at(0).components(newChildDefinition);
@ -178,6 +184,32 @@ describe('Collection variable components', () => {
});
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 = {
assets: [],
pages: [
@ -185,33 +217,7 @@ describe('Collection variable components', () => {
frames: [
{
component: {
components: [
{
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,
},
],
components: [cmpDef],
docEl: {
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`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"attributes": {
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
@ -33,13 +24,13 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
@ -48,7 +39,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"type": "default",
@ -57,19 +48,19 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -79,32 +70,32 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -113,23 +104,32 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"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`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"attributes": {
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
@ -168,32 +159,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -203,32 +194,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -237,23 +228,32 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"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`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"attributes": {
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
@ -292,13 +283,13 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
@ -307,7 +298,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"type": "default",
@ -316,19 +307,19 @@ exports[`Collection component Serialization Serializion with Collection Variable
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -338,32 +329,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -372,23 +363,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"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`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"componentDef": {
"components": [
{
"attributes": {
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
@ -427,32 +418,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -462,32 +453,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"attribute_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
},
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
@ -496,23 +487,32 @@ exports[`Collection component Serialization Serializion with Collection Variable
"custom_prop": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentIndex",
},
"name": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"property_trait": {
"collectionId": "my_collection",
"path": "user",
"type": "data-collection-variable",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
},
],
"dataResolver": {
"collectionId": "my_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
"endIndex": 1,
"startIndex": 0,
},
"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`] = `
{
"collectionDef": {
"collectionConfig": {
"collectionId": "parent_collection",
"dataSource": {
"path": "my_data_source_id",
"type": "data-variable",
},
},
"componentDef": {
"collectionDef": {
"collectionConfig": {
"collectionId": "nested_collection",
"dataSource": {
"path": "nested_data_source_id",
"type": "data-variable",
},
},
"componentDef": {
"name": {
"path": "user",
"type": "data-collection-variable",
"variableType": "currentItem",
"components": [
{
"components": [
{
"components": [
{
"components": [
{
"name": {
"collectionId": "nested_collection",
"path": "user",
"type": "data-variable",
"variableType": "currentItem",
},
"type": "default",
},
],
"type": "data-collection-item",
},
],
"dataResolver": {
"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",

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

Loading…
Cancel
Save