diff --git a/packages/core/src/data_sources/model/ComponentDataVariable.ts b/packages/core/src/data_sources/model/ComponentDataVariable.ts index 6f8aa0d97..31805f0f2 100644 --- a/packages/core/src/data_sources/model/ComponentDataVariable.ts +++ b/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; } diff --git a/packages/core/src/data_sources/model/DataResolverListener.ts b/packages/core/src/data_sources/model/DataResolverListener.ts index b10f7e262..e2ad1ec12 100644 --- a/packages/core/src/data_sources/model/DataResolverListener.ts +++ b/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 = []; diff --git a/packages/core/src/data_sources/model/DataVariable.ts b/packages/core/src/data_sources/model/DataVariable.ts index f64a86d87..b866270d4 100644 --- a/packages/core/src/data_sources/model/DataVariable.ts +++ b/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 { - 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; + + 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; } } diff --git a/packages/core/src/data_sources/model/conditional_variables/ComponentDataOutput.ts b/packages/core/src/data_sources/model/conditional_variables/ComponentDataOutput.ts new file mode 100644 index 000000000..58f4bb811 --- /dev/null +++ b/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)); + } +} diff --git a/packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts b/packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts index 65bc1f92b..9125e7d10 100644 --- a/packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts +++ b/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)); } } diff --git a/packages/core/src/data_sources/model/conditional_variables/DataCondition.ts b/packages/core/src/data_sources/model/conditional_variables/DataCondition.ts index 1f3c03cc1..765b27d9d 100644 --- a/packages/core/src/data_sources/model/conditional_variables/DataCondition.ts +++ b/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 { 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 { 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 { 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 { } private listenToDataVariables() { - // Clear previous listeners to avoid memory leaks this.cleanupListeners(); - this.setupConditionDataVariableListeners(); this.setupOutputDataVariableListeners(); } @@ -137,16 +144,10 @@ export class DataCondition extends Model { 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 { 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, }); diff --git a/packages/core/src/data_sources/model/conditional_variables/operators/AnyTypeOperator.ts b/packages/core/src/data_sources/model/conditional_variables/operators/AnyTypeOperator.ts index 128f44d8f..58eacaea3 100644 --- a/packages/core/src/data_sources/model/conditional_variables/operators/AnyTypeOperator.ts +++ b/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', diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts index bfdc8ff6d..6a893768d 100644 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/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): void { + private updateCollectionConfig(updates: Partial): 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; diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts deleted file mode 100644 index c268ae3b1..000000000 --- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts b/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts deleted file mode 100644 index 914531b0d..000000000 --- a/packages/core/src/data_sources/model/data_collection/DataCollectionVariable.ts +++ /dev/null @@ -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 { - em: EditorModel; - collectionsStateMap?: DataCollectionStateMap; - dataVariable?: DataVariable; - resolverListener?: DataResolverListener; - - defaults(): Partial { - 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; -} diff --git a/packages/core/src/data_sources/model/data_collection/constants.ts b/packages/core/src/data_sources/model/data_collection/constants.ts index 64084cea2..5ef251ea8 100644 --- a/packages/core/src/data_sources/model/data_collection/constants.ts +++ b/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'; diff --git a/packages/core/src/data_sources/model/data_collection/types.ts b/packages/core/src/data_sources/model/data_collection/types.ts index 1431b53d6..7bbd91f9f 100644 --- a/packages/core/src/data_sources/model/data_collection/types.ts +++ b/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 {} - 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; } diff --git a/packages/core/src/data_sources/types.ts b/packages/core/src/data_sources/types.ts index 7b7931331..7dad137a8 100644 --- a/packages/core/src/data_sources/types.ts +++ b/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 { /** diff --git a/packages/core/src/data_sources/utils.ts b/packages/core/src/data_sources/utils.ts index 72a3bca60..101a041da 100644 --- a/packages/core/src/data_sources/utils.ts +++ b/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); }; diff --git a/packages/core/src/data_sources/view/ComponentDataCollectionVariableView.ts b/packages/core/src/data_sources/view/ComponentDataCollectionVariableView.ts deleted file mode 100644 index 8fffd260c..000000000 --- a/packages/core/src/data_sources/view/ComponentDataCollectionVariableView.ts +++ /dev/null @@ -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 { - 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(); - } -} diff --git a/packages/core/src/dom_components/index.ts b/packages/core/src/dom_components/index.ts index 6de1385f8..46a7b5f46 100644 --- a/packages/core/src/dom_components/index.ts +++ b/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 { 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, diff --git a/packages/core/src/dom_components/model/ComponentDataResolverWatchers.ts b/packages/core/src/dom_components/model/ComponentDataResolverWatchers.ts index 3b774cae4..721453bc9 100644 --- a/packages/core/src/dom_components/model/ComponentDataResolverWatchers.ts +++ b/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; diff --git a/packages/core/src/dom_components/model/ComponentResolverWatcher.ts b/packages/core/src/dom_components/model/ComponentResolverWatcher.ts index ed6d4a851..05f67ac66 100644 --- a/packages/core/src/dom_components/model/ComponentResolverWatcher.ts +++ b/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 = {}; 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; diff --git a/packages/core/src/dom_components/model/SymbolUtils.ts b/packages/core/src/dom_components/model/SymbolUtils.ts index d8c2082f1..79f98d3b1 100644 --- a/packages/core/src/dom_components/model/SymbolUtils.ts +++ b/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, 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; diff --git a/packages/core/src/domain_abstract/model/StyleableModel.ts b/packages/core/src/domain_abstract/model/StyleableModel.ts index e70550a12..03e148d6c 100644 --- a/packages/core/src/domain_abstract/model/StyleableModel.ts +++ b/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; @@ -112,7 +113,10 @@ export default class StyleableModel extends Model 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 extends Model } if (isDataResolverProps(styleValue)) { - resultStyle[key] = getDataResolverInstanceValue(styleValue, { em: this.em! }); + resultStyle[key] = getDataResolverInstanceValue(styleValue, { + em: this.em!, + collectionsStateMap: this.get(keyCollectionsStateMap) ?? {}, + }); } if (isDataResolver(styleValue)) { diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.getters-setters.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.getters-setters.ts index 6f889555c..a83193790 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.getters-setters.ts +++ b/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'); }); }); }); diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts index 55c3dc296..9be911ba3 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts +++ b/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]); }); }); }); diff --git a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts b/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionWithDataVariable.ts similarity index 68% rename from packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts rename to packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionWithDataVariable.ts index a66c0136c..774027f5f 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionVariable.ts +++ b/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', }, diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap index 79be166cd..4ff379359 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap +++ b/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", } diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap deleted file mode 100644 index 8edb6a4d2..000000000 --- a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionVariable.ts.snap +++ /dev/null @@ -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", -} -`; diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionWithDataVariable.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionWithDataVariable.ts.snap new file mode 100644 index 000000000..a399b59df --- /dev/null +++ b/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", +} +`; diff --git a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap b/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap index 92ce3f6c8..b88dfe422 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap +++ b/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", diff --git a/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts b/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts index e7b3a48f2..8bc39e5f4 100644 --- a/packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts +++ b/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'); }); }); });