Browse Source

Fixes and improvements for datasources (#6649)

* Improve data source TS

* Skip running updateSymbolOverride on non-symbol components

* Refactor data types

* Update default root resolver item

* Resolve direct data sources in ComponentWrapper

* Add updateDebug event

* Fix data resolver update logic

* Fix id removal in ComponentDataCollection and tests

* Clone CSS rules pasted cross components

* Fix attributes/styles collide in data sources
release-v0.22.14-rc.3
Artur Arseniev 3 months ago
committed by GitHub
parent
commit
754e2f4f56
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      packages/core/src/css_composer/model/CssRule.ts
  2. 32
      packages/core/src/data_sources/index.ts
  3. 3
      packages/core/src/data_sources/model/ComponentWithCollectionsState.ts
  4. 8
      packages/core/src/data_sources/model/ComponentWithDataResolver.ts
  5. 5
      packages/core/src/data_sources/model/DataSource.ts
  6. 3
      packages/core/src/data_sources/model/DataVariable.ts
  7. 19
      packages/core/src/data_sources/model/conditional_variables/ComponentDataCondition.ts
  8. 2
      packages/core/src/data_sources/model/conditional_variables/constants.ts
  9. 31
      packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts
  10. 2
      packages/core/src/data_sources/model/data_collection/constants.ts
  11. 8
      packages/core/src/data_sources/model/data_collection/types.ts
  12. 9
      packages/core/src/data_sources/types.ts
  13. 11
      packages/core/src/data_sources/utils.ts
  14. 64
      packages/core/src/dom_components/index.ts
  15. 23
      packages/core/src/dom_components/model/Component.ts
  16. 43
      packages/core/src/dom_components/model/ComponentWrapper.ts
  17. 52
      packages/core/src/dom_components/model/Components.ts
  18. 18
      packages/core/src/dom_components/model/ModelDataResolverWatchers.ts
  19. 1
      packages/core/src/dom_components/types.ts
  20. 6
      packages/core/src/domain_abstract/model/StyleableModel.ts
  21. 7
      packages/core/src/editor/model/Editor.ts
  22. 1
      packages/core/src/editor/types.ts
  23. 2
      packages/core/src/index.ts
  24. 5
      packages/core/src/pages/index.ts
  25. 12
      packages/core/test/common.ts
  26. 57
      packages/core/test/specs/data_sources/dynamic_values/attributes.ts
  27. 8
      packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.ts
  28. 10
      packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.getters-setters.ts
  29. 47
      packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollection.ts
  30. 14
      packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionWithDataVariable.ts
  31. 40
      packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollection.ts.snap
  32. 54
      packages/core/test/specs/data_sources/model/data_collection/__snapshots__/ComponentDataCollectionWithDataVariable.ts.snap
  33. 15
      packages/core/test/specs/data_sources/model/data_collection/__snapshots__/nestedComponentDataCollections.ts.snap
  34. 20
      packages/core/test/specs/data_sources/model/data_collection/nestedComponentDataCollections.ts
  35. 24
      packages/core/test/specs/dom_components/model/Component.ts
  36. 56
      packages/core/test/specs/dom_components/model/ComponentWrapper.ts

4
packages/core/src/css_composer/model/CssRule.ts

@ -129,10 +129,10 @@ export default class CssRule extends StyleableModel<CssRuleProperties> {
this.on('change', this.__onChange); this.on('change', this.__onChange);
} }
__onChange(m: CssRule, opts: any) { __onChange(rule: CssRule, options: any) {
const { em } = this; const { em } = this;
const changed = this.changedAttributes(); const changed = this.changedAttributes();
changed && !isEmptyObj(changed) && em?.changesUp(opts); changed && !isEmptyObj(changed) && em?.changesUp(options, { rule, changed, options });
} }
clone(): typeof this { clone(): typeof this {

32
packages/core/src/data_sources/index.ts

@ -21,21 +21,34 @@
* @module DataSources * @module DataSources
*/ */
import { Events } from 'backbone';
import { isEmpty } from 'underscore'; import { isEmpty } from 'underscore';
import { ItemManagerModule, ModuleConfig } from '../abstract/Module'; import { ItemManagerModule, ModuleConfig } from '../abstract/Module';
import { AddOptions, collectionEvents, ObjectAny, RemoveOptions } from '../common'; import { AddOptions, collectionEvents, ObjectAny, RemoveOptions } from '../common';
import EditorModel from '../editor/model/Editor'; import EditorModel from '../editor/model/Editor';
import { get, set, stringToPath } from '../utils/mixins'; import { get, set, stringToPath } from '../utils/mixins';
import defConfig, { DataSourcesConfig } from './config/config'; import defConfig, { DataSourcesConfig } from './config/config';
import { AnyTypeOperation } from './model/conditional_variables/operators/AnyTypeOperator';
import { BooleanOperation } from './model/conditional_variables/operators/BooleanOperator';
import { NumberOperation } from './model/conditional_variables/operators/NumberOperator';
import { StringOperation } from './model/conditional_variables/operators/StringOperator';
import { DataCollectionStateType } from './model/data_collection/types';
import DataRecord from './model/DataRecord'; import DataRecord from './model/DataRecord';
import DataSource from './model/DataSource'; import DataSource from './model/DataSource';
import DataSources from './model/DataSources'; import DataSources from './model/DataSources';
import { DataSourcesEvents, DataSourceProps, DataRecordProps } from './types'; import { DataComponentTypes, DataRecordProps, DataSourceProps, DataSourcesEvents } from './types';
import { Events } from 'backbone';
export default class DataSourceManager extends ItemManagerModule<DataSourcesConfig & ModuleConfig, DataSources> { export default class DataSourceManager extends ItemManagerModule<DataSourcesConfig & ModuleConfig, DataSources> {
storageKey = 'dataSources'; storageKey = 'dataSources';
events = DataSourcesEvents; events = DataSourcesEvents;
dataComponentTypes = DataComponentTypes;
dataCollectionStateTypes = DataCollectionStateType;
dataOperationTypes = {
any: AnyTypeOperation,
boolean: BooleanOperation,
number: NumberOperation,
string: StringOperation,
};
destroy(): void {} destroy(): void {}
constructor(em: EditorModel) { constructor(em: EditorModel) {
@ -74,6 +87,16 @@ export default class DataSourceManager extends ItemManagerModule<DataSourcesConf
return this.all.get(id); return this.all.get(id);
} }
/**
* Return all data sources.
* @returns {Array<[DataSource]>}
* @example
* const ds = dsm.getAll();
*/
getAll() {
return [...this.all.models];
}
/** /**
* Get value from data sources by path. * Get value from data sources by path.
* @param {String} path Path to value. * @param {String} path Path to value.
@ -211,7 +234,10 @@ export default class DataSourceManager extends ItemManagerModule<DataSourcesConf
postLoad() { postLoad() {
const { em, all } = this; const { em, all } = this;
em.listenTo(all, collectionEvents, (m, c, o) => em.changesUp(o || c)); em.listenTo(all, collectionEvents, (dataSource, c, o) => {
const options = o || c;
em.changesUp(options, { dataSource, options });
});
this.em.UndoManager.add(all); this.em.UndoManager.add(all);
} }
} }

3
packages/core/src/data_sources/model/ComponentWithCollectionsState.ts

@ -83,8 +83,9 @@ export default class ComponentWithCollectionsState<DataResolverType> extends Com
} }
protected getDataSourceItems(): DataSourceRecords { protected getDataSourceItems(): DataSourceRecords {
const dataSourceProps = this.dataSourceProps; const { dataSourceProps } = this;
if (!dataSourceProps) return []; if (!dataSourceProps) return [];
const items = this.listDataSourceItems(dataSourceProps); const items = this.listDataSourceItems(dataSourceProps);
if (items && isArray(items)) { if (items && isArray(items)) {
return items; return items;

8
packages/core/src/data_sources/model/ComponentWithDataResolver.ts

@ -55,14 +55,6 @@ export abstract class ComponentWithDataResolver<T extends DataResolverProps> ext
} }
protected listenToPropsChange() { protected listenToPropsChange() {
this.listenTo(
this.dataResolver,
'change',
(() => {
this.__changesUp({ m: this });
}).bind(this),
);
this.on('change:dataResolver', () => { this.on('change:dataResolver', () => {
// @ts-ignore // @ts-ignore
this.dataResolver.set(this.get('dataResolver')); this.dataResolver.set(this.get('dataResolver'));

5
packages/core/src/data_sources/model/DataSource.ts

@ -316,7 +316,8 @@ export default class DataSource<DRProps extends DataRecordProps = DataRecordProp
return this.schema[fieldKey]; return this.schema[fieldKey];
} }
private handleChanges(m: any, c: any, o: any) { private handleChanges(dataRecord: any, c: any, o: any) {
this.em.changesUp(o || c); const options = o || c;
this.em.changesUp(options, { dataRecord, options });
} }
} }

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

@ -1,6 +1,7 @@
import { Model } from '../../common'; import { Model } from '../../common';
import { keyRootData } from '../../dom_components/constants'; import { keyRootData } from '../../dom_components/constants';
import EditorModel from '../../editor/model/Editor'; import EditorModel from '../../editor/model/Editor';
import { DataComponentTypes } from '../types';
import { isDataVariable } from '../utils'; import { isDataVariable } from '../utils';
import { import {
DataCollectionStateMap, DataCollectionStateMap,
@ -9,7 +10,7 @@ import {
RootDataType, RootDataType,
} from './data_collection/types'; } from './data_collection/types';
export const DataVariableType = 'data-variable' as const; export const DataVariableType = DataComponentTypes.variable as const;
export interface DataVariableProps { export interface DataVariableProps {
type?: typeof DataVariableType; type?: typeof DataVariableType;

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

@ -1,3 +1,4 @@
import Component from '../../../dom_components/model/Component';
import { import {
ComponentAddType, ComponentAddType,
ComponentDefinitionDefined, ComponentDefinitionDefined,
@ -6,19 +7,15 @@ import {
ToHTMLOptions, ToHTMLOptions,
} from '../../../dom_components/model/types'; } from '../../../dom_components/model/types';
import { toLowerCase } from '../../../utils/mixins'; import { toLowerCase } from '../../../utils/mixins';
import { DataComponentTypes, DataResolver } from '../../types';
import { ComponentWithDataResolver } from '../ComponentWithDataResolver';
import { DataCollectionStateMap } from '../data_collection/types';
import { DataCondition, DataConditionProps, DataConditionType } from './DataCondition'; import { DataCondition, DataConditionProps, DataConditionType } from './DataCondition';
import { ConditionProps } from './DataConditionEvaluator'; import { ConditionProps } from './DataConditionEvaluator';
import { StringOperation } from './operators/StringOperator'; import { StringOperation } from './operators/StringOperator';
import { DataConditionIfTrueType, DataConditionIfFalseType } from './constants';
import { ComponentWithDataResolver } from '../ComponentWithDataResolver';
import Component from '../../../dom_components/model/Component';
import { DataResolver } from '../../types';
import { DataCollectionStateMap } from '../data_collection/types';
export type DataConditionDisplayType = typeof DataConditionIfTrueType | typeof DataConditionIfFalseType;
export interface ComponentDataConditionProps extends ComponentProperties { export interface ComponentDataConditionProps extends ComponentProperties {
type: typeof DataConditionType; type: DataComponentTypes.condition;
dataResolver: DataConditionProps; dataResolver: DataConditionProps;
} }
@ -28,7 +25,7 @@ export default class ComponentDataCondition extends ComponentWithDataResolver<Da
// @ts-ignore // @ts-ignore
...super.defaults, ...super.defaults,
droppable: false, droppable: false,
type: DataConditionType, type: DataComponentTypes.condition,
dataResolver: { dataResolver: {
condition: { condition: {
left: '', left: '',
@ -38,10 +35,10 @@ export default class ComponentDataCondition extends ComponentWithDataResolver<Da
}, },
components: [ components: [
{ {
type: DataConditionIfTrueType, type: DataComponentTypes.conditionTrue,
}, },
{ {
type: DataConditionIfFalseType, type: DataComponentTypes.conditionFalse,
}, },
], ],
}; };

2
packages/core/src/data_sources/model/conditional_variables/constants.ts

@ -1,2 +0,0 @@
export const DataConditionIfTrueType = 'data-condition-true-content';
export const DataConditionIfFalseType = 'data-condition-false-content';

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

@ -1,21 +1,22 @@
import { isArray, size } from 'underscore'; import { isArray } from 'underscore';
import { ObjectAny } from '../../../common'; import { ObjectAny } from '../../../common';
import Component, { keySymbol } from '../../../dom_components/model/Component'; import Component, { keySymbol } from '../../../dom_components/model/Component';
import { keyDataValues, updateFromWatcher } from '../../../dom_components/model/ModelDataResolverWatchers';
import { detachSymbolInstance, getSymbolInstances } from '../../../dom_components/model/SymbolUtils';
import { ComponentAddType, ComponentDefinitionDefined, ComponentOptions } from '../../../dom_components/model/types'; import { ComponentAddType, ComponentDefinitionDefined, ComponentOptions } from '../../../dom_components/model/types';
import EditorModel from '../../../editor/model/Editor'; import EditorModel from '../../../editor/model/Editor';
import { toLowerCase } from '../../../utils/mixins'; import { toLowerCase } from '../../../utils/mixins';
import { DataComponentTypes } from '../../types';
import ComponentWithCollectionsState, { DataVariableMap } from '../ComponentWithCollectionsState';
import DataResolverListener from '../DataResolverListener'; import DataResolverListener from '../DataResolverListener';
import { DataVariableProps } from '../DataVariable'; import { DataVariableProps } from '../DataVariable';
import { DataCollectionItemType, DataCollectionType, keyCollectionDefinition } from './constants'; import { keyCollectionDefinition } from './constants';
import { import {
ComponentDataCollectionProps, ComponentDataCollectionProps,
DataCollectionDataSource, DataCollectionDataSource,
DataCollectionProps, DataCollectionProps,
DataCollectionStateMap, DataCollectionStateMap,
} from './types'; } from './types';
import { detachSymbolInstance, getSymbolInstances } from '../../../dom_components/model/SymbolUtils';
import { keyDataValues, updateFromWatcher } from '../../../dom_components/model/ModelDataResolverWatchers';
import ComponentWithCollectionsState, { DataVariableMap } from '../ComponentWithCollectionsState';
const AvoidStoreOptions = { avoidStore: true, partial: true }; const AvoidStoreOptions = { avoidStore: true, partial: true };
@ -28,10 +29,10 @@ export default class ComponentDataCollection extends ComponentWithCollectionsSta
...super.defaults, ...super.defaults,
droppable: false, droppable: false,
dataResolver: {}, dataResolver: {},
type: DataCollectionType, type: DataComponentTypes.collection,
components: [ components: [
{ {
type: DataCollectionItemType, type: DataComponentTypes.collectionItem,
}, },
], ],
}; };
@ -281,17 +282,8 @@ export default class ComponentDataCollection extends ComponentWithCollectionsSta
} }
private ensureFirstChild() { private ensureFirstChild() {
const dataConditionItemModel = this.em.Components.getType(DataCollectionItemType)!.model; const dataConditionItemModel = this.em.Components.getType(DataComponentTypes.collectionItem)!.model;
return this.firstChild || new dataConditionItemModel({ type: DataComponentTypes.collectionItem }, this.opt);
return (
this.firstChild ||
new dataConditionItemModel(
{
type: DataCollectionItemType,
},
this.opt,
)
);
} }
private get collectionId() { private get collectionId() {
@ -299,14 +291,13 @@ export default class ComponentDataCollection extends ComponentWithCollectionsSta
} }
static isComponent(el: HTMLElement) { static isComponent(el: HTMLElement) {
return toLowerCase(el.tagName) === DataCollectionType; return toLowerCase(el.tagName) === DataComponentTypes.collection;
} }
toJSON(opts?: ObjectAny) { toJSON(opts?: ObjectAny) {
const json = super.toJSON.call(this, opts) as ComponentDataCollectionProps; const json = super.toJSON.call(this, opts) as ComponentDataCollectionProps;
delete json.droppable; delete json.droppable;
delete json[keySymbol]; delete json[keySymbol];
delete json.attributes?.id;
const firstChild = this.firstChild as any; const firstChild = this.firstChild as any;
return { ...json, components: [firstChild] }; return { ...json, components: [firstChild] };

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

@ -1,3 +1 @@
export const DataCollectionType = 'data-collection';
export const DataCollectionItemType = 'data-collection-item';
export const keyCollectionDefinition = 'dataResolver'; export const keyCollectionDefinition = 'dataResolver';

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

@ -1,8 +1,8 @@
import { DataCollectionType, keyCollectionDefinition } from './constants'; import { ObjectAny } from '../../../common';
import { ComponentDefinition } from '../../../dom_components/model/types'; import { ComponentDefinition } from '../../../dom_components/model/types';
import { DataComponentTypes } from '../../types';
import { DataVariableProps } from '../DataVariable'; import { DataVariableProps } from '../DataVariable';
import { keyRootData } from '../../../dom_components/constants'; import { keyCollectionDefinition } from './constants';
import { ObjectAny } from '../../../common';
export type DataCollectionDataSource = DataVariableProps; export type DataCollectionDataSource = DataVariableProps;
@ -36,7 +36,7 @@ export interface DataCollectionStateMap {
} }
export interface ComponentDataCollectionProps extends ComponentDefinition { export interface ComponentDataCollectionProps extends ComponentDefinition {
type: typeof DataCollectionType; type: `${DataComponentTypes.collection}`;
[keyCollectionDefinition]: DataCollectionProps; [keyCollectionDefinition]: DataCollectionProps;
} }

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

@ -13,6 +13,15 @@ export type ResolverFromProps<T extends DataResolverProps> = T extends DataVaria
? DataCondition ? DataCondition
: never; : never;
export enum DataComponentTypes {
variable = 'data-variable',
condition = 'data-condition',
conditionTrue = 'data-condition-true-content',
conditionFalse = 'data-condition-false-content',
collection = 'data-collection',
collectionItem = 'data-collection-item',
}
export interface DataRecordProps extends ObjectAny { export interface DataRecordProps extends ObjectAny {
/** /**
* Record id. * Record id.

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

@ -1,12 +1,10 @@
import EditorModel from '../editor/model/Editor'; import EditorModel from '../editor/model/Editor';
import { DataResolver, DataResolverProps, ResolverFromProps } from './types'; import { DataComponentTypes, DataResolver, DataResolverProps, ResolverFromProps } from './types';
import { DataCollectionStateMap } from './model/data_collection/types'; import { DataCollectionStateMap } from './model/data_collection/types';
import { DataCollectionItemType } from './model/data_collection/constants';
import { DataConditionType, DataCondition } from './model/conditional_variables/DataCondition'; import { DataConditionType, DataCondition } from './model/conditional_variables/DataCondition';
import DataVariable, { DataVariableProps, DataVariableType } from './model/DataVariable'; import DataVariable, { DataVariableProps, DataVariableType } from './model/DataVariable';
import { ComponentDefinition, ComponentOptions } from '../dom_components/model/types'; import { ComponentDefinition, ComponentOptions } from '../dom_components/model/types';
import { serialize } from '../utils/mixins'; import { serialize } from '../utils/mixins';
import { DataConditionIfFalseType, DataConditionIfTrueType } from './model/conditional_variables/constants';
import { getSymbolMain } from '../dom_components/model/SymbolUtils'; import { getSymbolMain } from '../dom_components/model/SymbolUtils';
import Component from '../dom_components/model/Component'; import Component from '../dom_components/model/Component';
@ -85,7 +83,12 @@ export const ensureComponentInstance = (
}; };
export const isComponentDataOutputType = (type: string | undefined) => { export const isComponentDataOutputType = (type: string | undefined) => {
return !!type && [DataCollectionItemType, DataConditionIfTrueType, DataConditionIfFalseType].includes(type); return (
!!type &&
[DataComponentTypes.collectionItem, DataComponentTypes.conditionTrue, DataComponentTypes.conditionFalse].includes(
type as DataComponentTypes,
)
);
}; };
export function enumToArray(enumObj: any) { export function enumToArray(enumObj: any) {

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

@ -55,13 +55,23 @@
*/ */
import { debounce, isArray, isEmpty, isFunction, isString, isSymbol, result } from 'underscore'; import { debounce, isArray, isEmpty, isFunction, isString, isSymbol, result } from 'underscore';
import { ItemManagerModule } from '../abstract/Module'; import { ItemManagerModule } from '../abstract/Module';
import { BlockProperties } from '../block_manager/model/Block';
import { ObjectAny } from '../common'; import { ObjectAny } from '../common';
import ComponentDataVariable from '../data_sources/model/ComponentDataVariable';
import ComponentDataCondition from '../data_sources/model/conditional_variables/ComponentDataCondition';
import ComponentDataOutput from '../data_sources/model/conditional_variables/ComponentDataOutput';
import ComponentDataCollection from '../data_sources/model/data_collection/ComponentDataCollection';
import { DataComponentTypes } from '../data_sources/types';
import ComponentDataCollectionView from '../data_sources/view/ComponentDataCollectionView';
import ComponentDataConditionView from '../data_sources/view/ComponentDataConditionView';
import ComponentDataVariableView from '../data_sources/view/ComponentDataVariableView';
import EditorModel from '../editor/model/Editor'; import EditorModel from '../editor/model/Editor';
import { isComponent } from '../utils/mixins'; import { isComponent } from '../utils/mixins';
import defConfig, { DomComponentsConfig } from './config/config'; import defConfig, { DomComponentsConfig } from './config/config';
import Component, { IComponent, keyUpdate, keyUpdateInside } from './model/Component'; import Component, { IComponent, keyUpdate, keyUpdateInside } from './model/Component';
import ComponentComment from './model/ComponentComment'; import ComponentComment from './model/ComponentComment';
import ComponentFrame from './model/ComponentFrame'; import ComponentFrame from './model/ComponentFrame';
import ComponentHead, { type as typeHead } from './model/ComponentHead';
import ComponentImage from './model/ComponentImage'; import ComponentImage from './model/ComponentImage';
import ComponentLabel from './model/ComponentLabel'; import ComponentLabel from './model/ComponentLabel';
import ComponentLink from './model/ComponentLink'; import ComponentLink from './model/ComponentLink';
@ -80,6 +90,18 @@ import ComponentTextNode from './model/ComponentTextNode';
import ComponentVideo from './model/ComponentVideo'; import ComponentVideo from './model/ComponentVideo';
import ComponentWrapper from './model/ComponentWrapper'; import ComponentWrapper from './model/ComponentWrapper';
import Components from './model/Components'; import Components from './model/Components';
import {
detachSymbolInstance,
getSymbolInstances,
getSymbolMain,
getSymbolsToUpdate,
getSymbolTop,
isSymbol as isSymbolComponent,
isSymbolInstance,
isSymbolMain,
isSymbolRoot,
} from './model/SymbolUtils';
import Symbols from './model/Symbols';
import { import {
AddComponentsOption, AddComponentsOption,
ComponentAdd, ComponentAdd,
@ -87,6 +109,7 @@ import {
ComponentDefinitionDefined, ComponentDefinitionDefined,
ComponentStackItem, ComponentStackItem,
} from './model/types'; } from './model/types';
import { ComponentsEvents, SymbolInfo } from './types';
import ComponentCommentView from './view/ComponentCommentView'; import ComponentCommentView from './view/ComponentCommentView';
import ComponentFrameView from './view/ComponentFrameView'; import ComponentFrameView from './view/ComponentFrameView';
import ComponentImageView from './view/ComponentImageView'; import ComponentImageView from './view/ComponentImageView';
@ -107,35 +130,6 @@ import ComponentVideoView from './view/ComponentVideoView';
import ComponentView, { IComponentView } from './view/ComponentView'; import ComponentView, { IComponentView } from './view/ComponentView';
import ComponentWrapperView from './view/ComponentWrapperView'; import ComponentWrapperView from './view/ComponentWrapperView';
import ComponentsView from './view/ComponentsView'; import ComponentsView from './view/ComponentsView';
import ComponentHead, { type as typeHead } from './model/ComponentHead';
import {
getSymbolMain,
getSymbolInstances,
getSymbolsToUpdate,
isSymbolMain,
isSymbolInstance,
detachSymbolInstance,
isSymbolRoot,
isSymbol as isSymbolComponent,
getSymbolTop,
} from './model/SymbolUtils';
import { ComponentsEvents, SymbolInfo } from './types';
import Symbols from './model/Symbols';
import { BlockProperties } from '../block_manager/model/Block';
import ComponentDataVariable from '../data_sources/model/ComponentDataVariable';
import ComponentDataVariableView from '../data_sources/view/ComponentDataVariableView';
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 { 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 ComponentDataOutput from '../data_sources/model/conditional_variables/ComponentDataOutput';
export type ComponentEvent = export type ComponentEvent =
| 'component:create' | 'component:create'
@ -202,32 +196,32 @@ export interface CanMoveResult {
export default class ComponentManager extends ItemManagerModule<DomComponentsConfig, any> { export default class ComponentManager extends ItemManagerModule<DomComponentsConfig, any> {
componentTypes: ComponentStackItem[] = [ componentTypes: ComponentStackItem[] = [
{ {
id: DataCollectionItemType, id: DataComponentTypes.collectionItem,
model: ComponentDataOutput, model: ComponentDataOutput,
view: ComponentView, view: ComponentView,
}, },
{ {
id: DataConditionIfTrueType, id: DataComponentTypes.conditionTrue,
model: ComponentDataOutput, model: ComponentDataOutput,
view: ComponentView, view: ComponentView,
}, },
{ {
id: DataConditionIfFalseType, id: DataComponentTypes.conditionFalse,
model: ComponentDataOutput, model: ComponentDataOutput,
view: ComponentView, view: ComponentView,
}, },
{ {
id: DataCollectionType, id: DataComponentTypes.collection,
model: ComponentDataCollection, model: ComponentDataCollection,
view: ComponentDataCollectionView, view: ComponentDataCollectionView,
}, },
{ {
id: DataConditionType, id: DataComponentTypes.condition,
model: ComponentDataCondition, model: ComponentDataCondition,
view: ComponentDataConditionView, view: ComponentDataConditionView,
}, },
{ {
id: DataVariableType, id: DataComponentTypes.variable,
model: ComponentDataVariable, model: ComponentDataVariable,
view: ComponentDataVariableView, view: ComponentDataVariableView,
}, },

23
packages/core/src/dom_components/model/Component.ts

@ -431,7 +431,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
['status', 'open', 'toolbar', 'traits'].forEach((name) => delete changed[name]); ['status', 'open', 'toolbar', 'traits'].forEach((name) => delete changed[name]);
// Propagate component prop changes // Propagate component prop changes
if (!isEmptyObj(changed)) { if (!isEmptyObj(changed)) {
this.__changesUp(opts); this.__changesUp(opts, { changed });
this.__propSelfToParent({ component: this, changed, options: opts }); this.__propSelfToParent({ component: this, changed, options: opts });
} }
} }
@ -469,9 +469,11 @@ export default class Component extends StyleableModel<ComponentProperties> {
}); });
} }
__changesUp(opts: any) { __changesUp(options: any, data: Record<string, any> = {}) {
const { em, frame } = this; const { em, frame } = this;
[frame, em].forEach((md) => md && md.changesUp(opts)); [frame, em].forEach((md) => {
md?.changesUp(options, { component: this, options, ...data });
});
} }
__propSelfToParent(props: any) { __propSelfToParent(props: any) {
@ -1654,7 +1656,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
delete obj[keySymbol]; delete obj[keySymbol];
delete obj[keySymbolOvrd]; delete obj[keySymbolOvrd];
delete obj[keySymbols]; delete obj[keySymbols];
delete obj.attributes.id;
} }
if (!opts.fromUndo) { if (!opts.fromUndo) {
@ -2134,10 +2135,15 @@ export default class Component extends StyleableModel<ComponentProperties> {
components: ComponentDefinitionDefined | ComponentDefinitionDefined[], components: ComponentDefinitionDefined | ComponentDefinitionDefined[],
styles: CssRuleJSON[] = [], styles: CssRuleJSON[] = [],
list: ObjectAny = {}, list: ObjectAny = {},
opts: { keepIds?: string[]; idMap?: PrevToNewIdMap } = {}, opts: {
keepIds?: string[];
idMap?: PrevToNewIdMap;
updatedIds?: Record<string, ComponentDefinitionDefined[]>;
} = {},
) { ) {
opts.updatedIds = opts.updatedIds || {};
const comps = isArray(components) ? components : [components]; const comps = isArray(components) ? components : [components];
const { keepIds = [], idMap = {} } = opts; const { keepIds = [], idMap = {}, updatedIds } = opts;
comps.forEach((comp) => { comps.forEach((comp) => {
comp.attributes; comp.attributes;
const { attributes = {}, components } = comp; const { attributes = {}, components } = comp;
@ -2146,6 +2152,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
// Check if we have collisions with current components // Check if we have collisions with current components
if (id && list[id] && keepIds.indexOf(id) < 0) { if (id && list[id] && keepIds.indexOf(id) < 0) {
const newId = Component.getIncrementId(id, list); const newId = Component.getIncrementId(id, list);
updatedIds[id] = updatedIds[id] ? [...updatedIds[id], comp] : [comp];
idMap[id] = newId; idMap[id] = newId;
attributes.id = newId; attributes.id = newId;
// Update passed styles // Update passed styles
@ -2160,5 +2167,9 @@ export default class Component extends StyleableModel<ComponentProperties> {
components && Component.checkId(components, styles, list, opts); components && Component.checkId(components, styles, list, opts);
}); });
return {
updatedIds: opts.updatedIds,
};
} }
} }

43
packages/core/src/dom_components/model/ComponentWrapper.ts

@ -1,28 +1,27 @@
import { isUndefined } from 'underscore'; import { isNumber, isString, isUndefined } from 'underscore';
import { attrToString } from '../../utils/dom'; import ComponentWithCollectionsState from '../../data_sources/model/ComponentWithCollectionsState';
import Component from './Component';
import ComponentHead, { type as typeHead } from './ComponentHead';
import { ComponentOptions, ComponentProperties, ToHTMLOptions } from './types';
import Components from './Components';
import DataResolverListener from '../../data_sources/model/DataResolverListener'; import DataResolverListener from '../../data_sources/model/DataResolverListener';
import { DataVariableProps } from '../../data_sources/model/DataVariable'; import { DataVariableProps } from '../../data_sources/model/DataVariable';
import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types';
import ComponentWithCollectionsState, { import { attrToString } from '../../utils/dom';
DataSourceRecords,
} from '../../data_sources/model/ComponentWithCollectionsState';
import { keyRootData } from '../constants'; import { keyRootData } from '../constants';
import Component from './Component';
import ComponentHead, { type as typeHead } from './ComponentHead';
import Components from './Components';
import { ComponentOptions, ComponentProperties, ToHTMLOptions } from './types';
type ResolverCurrentItemType = string | number; type ResolverCurrentItemType = string | number;
export default class ComponentWrapper extends ComponentWithCollectionsState<DataVariableProps> { export default class ComponentWrapper extends ComponentWithCollectionsState<DataVariableProps> {
dataSourceWatcher?: DataResolverListener; dataSourceWatcher?: DataResolverListener;
private _resolverCurrentItem?: ResolverCurrentItemType; private _resolverCurrentItem?: ResolverCurrentItemType = 0;
private _isWatchingCollectionStateMap = false; private _isWatchingCollectionStateMap = false;
get defaults() { get defaults() {
return { return {
// @ts-ignore // @ts-ignore
...super.defaults, ...super.defaults,
dataResolver: null,
tagName: 'body', tagName: 'body',
removable: false, removable: false,
copyable: false, copyable: false,
@ -130,6 +129,10 @@ export default class ComponentWrapper extends ComponentWithCollectionsState<Data
this.onCollectionsStateMapUpdate(this.getCollectionsStateMap()); this.onCollectionsStateMapUpdate(this.getCollectionsStateMap());
} }
setResolverCurrentItem(value: ResolverCurrentItemType) {
this.resolverCurrentItem = value;
}
protected onDataSourceChange() { protected onDataSourceChange() {
this.onCollectionsStateMapUpdate(this.getCollectionsStateMap()); this.onCollectionsStateMapUpdate(this.getCollectionsStateMap());
} }
@ -153,16 +156,16 @@ export default class ComponentWrapper extends ComponentWithCollectionsState<Data
} }
private getCollectionsStateMap(): DataCollectionStateMap { private getCollectionsStateMap(): DataCollectionStateMap {
const { dataResolverPath: dataSourcePath, resolverCurrentItem } = this; const { dataResolverPath, resolverCurrentItem, em } = this;
const dsm = em.DataSources;
if (!dataSourcePath) { if (!dataResolverPath) return {};
return {};
} const allItems = this.getDataSourceItems() as any;
const selectedItems = isNumber(resolverCurrentItem)
const allItems = this.getDataSourceItems(); ? allItems[resolverCurrentItem]
const selectedItems = !isUndefined(resolverCurrentItem) : isString(resolverCurrentItem)
? allItems[resolverCurrentItem as keyof DataSourceRecords] ? dsm.getValue(`${dataResolverPath}.${resolverCurrentItem}`)
: allItems; : allItems;
return { return {
[keyRootData]: selectedItems, [keyRootData]: selectedItems,

52
packages/core/src/dom_components/model/Components.ts

@ -25,7 +25,7 @@ export interface ResetCommonUpdateProps {
} }
export interface ResetFromStringOptions { export interface ResetFromStringOptions {
visitedCmps?: Record<string, Component[]>; visitedCmps?: Record<string, ComponentDefinitionDefined[]>;
keepIds?: string[]; keepIds?: string[];
updateOptions?: { updateOptions?: {
onAttributes?: (props: ResetCommonUpdateProps & { attributes: Record<string, any> }) => void; onAttributes?: (props: ResetCommonUpdateProps & { attributes: Record<string, any> }) => void;
@ -161,32 +161,11 @@ Component> {
resetFromString(input = '', opts: ResetFromStringOptions = {}) { resetFromString(input = '', opts: ResetFromStringOptions = {}) {
opts.keepIds = getComponentIds(this); opts.keepIds = getComponentIds(this);
const { domc, em, parent } = this; const { domc, em, parent } = this;
const cssc = em?.Css;
const allByID = domc?.allById() || {}; const allByID = domc?.allById() || {};
const parsed = this.parseString(input, opts); const parsed = this.parseString(input, { ...opts, cloneRules: true });
const fromDefOpts = { skipViewUpdate: true, ...opts }; const fromDefOpts = { skipViewUpdate: true, ...opts };
const newCmps = getComponentsFromDefs(parsed, allByID, fromDefOpts); const newCmps = getComponentsFromDefs(parsed, allByID, fromDefOpts);
const { visitedCmps = {} } = fromDefOpts; Components.cloneCssRules(em, fromDefOpts.visitedCmps);
// Clone styles for duplicated components
Object.keys(visitedCmps).forEach((id) => {
const cmps = visitedCmps[id];
if (cmps.length) {
// Get all available rules of the component
const rulesToClone = cssc?.getRules(`#${id}`) || [];
if (rulesToClone.length) {
cmps.forEach((cmp) => {
rulesToClone.forEach((rule) => {
const newRule = rule.clone();
// @ts-ignore
newRule.set('selectors', [`#${cmp.attributes.id}`]);
cssc!.getAll().add(newRule);
});
});
}
}
});
this.reset(newCmps, opts as any); this.reset(newCmps, opts as any);
em?.trigger('component:content', parent, opts, input); em?.trigger('component:content', parent, opts, input);
@ -318,7 +297,8 @@ Component> {
} }
// We need this to avoid duplicate IDs // We need this to avoid duplicate IDs
Component.checkId(components, parsed.css, domc!.componentsById, opt); const result = Component.checkId(components, parsed.css, domc!.componentsById, opt);
opt.cloneRules && Components.cloneCssRules(em, result.updatedIds);
if (parsed.css && cssc && !opt.temporary) { if (parsed.css && cssc && !opt.temporary) {
const { at, ...optsToPass } = opt; const { at, ...optsToPass } = opt;
@ -447,4 +427,26 @@ Component> {
} }
} }
} }
static cloneCssRules(em: EditorModel, cmpsMap: Record<string, ComponentDefinitionDefined[]> = {}) {
const { Css } = em;
Object.keys(cmpsMap).forEach((id) => {
const cmps = cmpsMap[id];
if (cmps.length) {
// Get all available rules of the component
const rulesToClone = (Css.getRules(`#${id}`) || []).filter((rule) => !isEmpty(rule.attributes.style));
if (rulesToClone.length) {
const rules = Css.getAll();
cmps.forEach((cmp) => {
rulesToClone.forEach((rule) => {
const newRule = rule.clone();
newRule.set('selectors', [`#${cmp.attributes.id}`] as any);
rules.add(newRule);
});
});
}
}
});
}
} }

18
packages/core/src/dom_components/model/ModelDataResolverWatchers.ts

@ -5,8 +5,8 @@ import {
DataWatchersOptions, DataWatchersOptions,
WatchableModel, WatchableModel,
} from './ModelResolverWatcher'; } from './ModelResolverWatcher';
import { getSymbolsToUpdate } from './SymbolUtils'; import { getSymbolsToUpdate, isSymbol } from './SymbolUtils';
import Component from './Component'; import Component, { keySymbolOvrd } from './Component';
import { StyleableModelProperties } from '../../domain_abstract/model/StyleableModel'; import { StyleableModelProperties } from '../../domain_abstract/model/StyleableModel';
import { isEmpty, isObject } from 'underscore'; import { isEmpty, isObject } from 'underscore';
@ -166,8 +166,8 @@ export class ModelDataResolverWatchers<T extends StyleableModelProperties> {
} }
private updateSymbolOverride() { private updateSymbolOverride() {
const model = this.model; const { model } = this;
if (!this.isComponent(model)) return; if (!this.isComponent(model) || !isSymbol(model)) return;
const isCollectionItem = !!Object.keys(model?.collectionsStateMap ?? {}).length; const isCollectionItem = !!Object.keys(model?.collectionsStateMap ?? {}).length;
if (!isCollectionItem) return; if (!isCollectionItem) return;
@ -187,7 +187,15 @@ export class ModelDataResolverWatchers<T extends StyleableModelProperties> {
} }
private filterProps(props: ObjectAny) { private filterProps(props: ObjectAny) {
const excludedFromEvaluation = ['components', 'dataResolver', keyDataValues]; const excludedFromEvaluation = [
'components',
'dataResolver',
'status',
'state',
'open',
keySymbolOvrd,
keyDataValues,
];
const filteredProps = Object.fromEntries( const filteredProps = Object.fromEntries(
Object.entries(props).filter(([key]) => !excludedFromEvaluation.includes(key)), Object.entries(props).filter(([key]) => !excludedFromEvaluation.includes(key)),
); );

1
packages/core/src/dom_components/types.ts

@ -19,6 +19,7 @@ export interface SymbolInfo {
export interface ParseStringOptions extends AddOptions, OptionAsDocument, WithHTMLParserOptions { export interface ParseStringOptions extends AddOptions, OptionAsDocument, WithHTMLParserOptions {
keepIds?: string[]; keepIds?: string[];
cloneRules?: boolean;
} }
export enum ComponentsEvents { export enum ComponentsEvents {

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

@ -182,9 +182,9 @@ export default class StyleableModel<T extends StyleableModelProperties = any> ex
return; return;
} }
}); });
const resolvedProps = this.dataResolverWatchers.addProps({ style: newStyle }, opts) as Partial<T>;
this.set(resolvedProps, opts as any); this.set({ style: newStyle }, opts);
newStyle = resolvedProps['style']! as StyleProps; newStyle = this.attributes['style'] as StyleProps;
const changedKeys = Object.keys(shallowDiff(propOrig, propNew)); const changedKeys = Object.keys(shallowDiff(propOrig, propNew));
const diff: ObjectAny = changedKeys.reduce((acc, key) => { const diff: ObjectAny = changedKeys.reduce((acc, key) => {

7
packages/core/src/editor/model/Editor.ts

@ -464,12 +464,13 @@ export default class EditorModel extends Model {
* @param {Object} opt Options * @param {Object} opt Options
* @private * @private
* */ * */
handleUpdates(model: any, val: any, opt: any = {}) { handleUpdates(opt: any = {}, data: Record<string, any>) {
// Component has been added temporarily - do not update storage or record changes // Component has been added temporarily - do not update storage or record changes
if (this.__skip || !this.loadTriggered || opt.temporary || opt.noCount || opt.avoidStore || opt.partial) { if (this.__skip || !this.loadTriggered || opt.temporary || opt.noCount || opt.avoidStore || opt.partial) {
return; return;
} }
this.trigger(this.events.updateBefore, data);
this.timedInterval && clearTimeout(this.timedInterval); this.timedInterval && clearTimeout(this.timedInterval);
this.timedInterval = setTimeout(() => { this.timedInterval = setTimeout(() => {
const curr = this.getDirtyCount() || 0; const curr = this.getDirtyCount() || 0;
@ -478,8 +479,8 @@ export default class EditorModel extends Model {
}, 0); }, 0);
} }
changesUp(opts: any) { changesUp(opts: any, data: Record<string, any>) {
this.handleUpdates(0, 0, opts); this.handleUpdates(opts, data);
} }
/** /**

1
packages/core/src/editor/types.ts

@ -53,6 +53,7 @@ export enum EditorEvents {
* editor.on('update', () => { ... }); * editor.on('update', () => { ... });
*/ */
update = 'update', update = 'update',
updateBefore = 'updateBefore',
/** /**
* @event `undo` Undo executed. * @event `undo` Undo executed.

2
packages/core/src/index.ts

@ -149,6 +149,8 @@ export type { default as DataRecord } from './data_sources/model/DataRecord';
export type { default as DataRecords } from './data_sources/model/DataRecords'; export type { default as DataRecords } from './data_sources/model/DataRecords';
export type { default as DataVariable } from './data_sources/model/DataVariable'; export type { default as DataVariable } from './data_sources/model/DataVariable';
export type { default as ComponentDataVariable } from './data_sources/model/ComponentDataVariable'; export type { default as ComponentDataVariable } from './data_sources/model/ComponentDataVariable';
export type { default as ComponentWithCollectionsState } from './data_sources/model/ComponentWithCollectionsState';
export type { ComponentWithDataResolver } from './data_sources/model/ComponentWithDataResolver';
export type { default as ComponentDataCollection } from './data_sources/model/data_collection/ComponentDataCollection'; export type { default as ComponentDataCollection } from './data_sources/model/data_collection/ComponentDataCollection';
export type { default as ComponentDataCondition } from './data_sources/model/conditional_variables/ComponentDataCondition'; export type { default as ComponentDataCondition } from './data_sources/model/conditional_variables/ComponentDataCondition';
export type { export type {

5
packages/core/src/pages/index.ts

@ -116,7 +116,10 @@ export default class PageManager extends ItemManagerModule<PageManagerConfig, Pa
const um = em.UndoManager; const um = em.UndoManager;
um.add(model); um.add(model);
um.add(pages); um.add(pages);
pages.on('add remove reset change', (m, c, o) => em.changesUp(o || c)); pages.on('add remove reset change', (page, c, o) => {
const options = o || c;
em.changesUp(o || c, { page, options });
});
} }
/** /**

12
packages/core/test/common.ts

@ -1,11 +1,7 @@
import { DataSource } from '../src';
import CanvasEvents from '../src/canvas/types'; import CanvasEvents from '../src/canvas/types';
import { ObjectAny } from '../src/common'; import { ObjectAny } from '../src/common';
import {
DataConditionIfFalseType,
DataConditionIfTrueType,
} from '../src/data_sources/model/conditional_variables/constants';
import { NumberOperation } from '../src/data_sources/model/conditional_variables/operators/NumberOperator'; import { NumberOperation } from '../src/data_sources/model/conditional_variables/operators/NumberOperator';
import { DataComponentTypes } from '../src/data_sources/types';
import Editor from '../src/editor'; import Editor from '../src/editor';
import { EditorConfig } from '../src/editor/config/config'; import { EditorConfig } from '../src/editor/config/config';
import EditorModel from '../src/editor/model/Editor'; import EditorModel from '../src/editor/model/Editor';
@ -61,6 +57,9 @@ export function setupTestEditor(opts?: { withCanvas?: boolean; config?: Partial<
editor.DataSources.postLoad(); editor.DataSources.postLoad();
editor.Components.postLoad(); editor.Components.postLoad();
editor.Pages.postLoad(); editor.Pages.postLoad();
em.set({ readyLoad: true, readyCanvas: true, ready: true });
em.loadTriggered = true;
} }
return { editor, em, dsm, um, cmpRoot, fixtures }; return { editor, em, dsm, um, cmpRoot, fixtures };
@ -138,11 +137,12 @@ const createConditionalComponentDef = (type: string, content: string) => ({
components: [createContent(content)], components: [createContent(content)],
}); });
const DataConditionIfTrueType = DataComponentTypes.conditionTrue;
const DataConditionIfFalseType = DataComponentTypes.conditionFalse;
export const ifTrueText = 'true text'; export const ifTrueText = 'true text';
export const newIfTrueText = 'new true text'; export const newIfTrueText = 'new true text';
export const ifFalseText = 'false text'; export const ifFalseText = 'false text';
export const newIfFalseText = 'new false text'; export const newIfFalseText = 'new false text';
export const ifTrueComponentDef = createConditionalComponentDef(DataConditionIfTrueType, ifTrueText); export const ifTrueComponentDef = createConditionalComponentDef(DataConditionIfTrueType, ifTrueText);
export const newIfTrueComponentDef = createConditionalComponentDef(DataConditionIfTrueType, newIfTrueText); export const newIfTrueComponentDef = createConditionalComponentDef(DataConditionIfTrueType, newIfTrueText);
export const ifFalseComponentDef = createConditionalComponentDef(DataConditionIfFalseType, ifFalseText); export const ifFalseComponentDef = createConditionalComponentDef(DataConditionIfFalseType, ifFalseText);

57
packages/core/test/specs/data_sources/dynamic_values/attributes.ts

@ -240,6 +240,63 @@ describe('Dynamic Attributes', () => {
const input = cmp.getEl(); const input = cmp.getEl();
expect(input?.getAttribute('dynamicAttribute')).toBe(null); expect(input?.getAttribute('dynamicAttribute')).toBe(null);
}); });
test('attributes should not collide with styles', () => {
({ em, dsm, cmpRoot } = setupTestEditor({ config: { avoidInlineStyle: false } }));
dsm.add({
id: 'ds_id',
records: [
{ id: 'id1', value: 'test-value' },
{ id: 'id2', value: 'second-test-value' },
],
});
const cmp = cmpRoot.append({
tagName: 'input',
})[0];
cmp.addAttributes({
static: 'static-value-attr',
dynamic: {
type: DataVariableType,
defaultValue: 'default',
path: 'ds_id.id1.value',
},
});
cmp.addStyle({
static: 'static-value-style',
dynamic: {
type: DataVariableType,
defaultValue: 'default',
path: 'ds_id.id2.value',
},
});
testAttribute(cmp, 'style', 'static:static-value-style;dynamic:second-test-value;');
testAttribute(cmp, 'dynamic', 'test-value');
testAttribute(cmp, 'static', 'static-value-attr');
cmp.addAttributes({
static: 'static-value-attr-2',
dynamic: {
type: DataVariableType,
defaultValue: 'default',
path: 'ds_id.id2.value',
},
});
cmp.addStyle({
static: 'static-value-style-2',
dynamic: {
type: DataVariableType,
defaultValue: 'default',
path: 'ds_id.id1.value',
},
});
testAttribute(cmp, 'style', 'static:static-value-style-2;dynamic:test-value;');
testAttribute(cmp, 'dynamic', 'second-test-value');
testAttribute(cmp, 'static', 'static-value-attr-2');
});
}); });
function changeDataSourceValue(dsm: DataSourceManager, id: string) { function changeDataSourceValue(dsm: DataSourceManager, id: string) {

8
packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.ts

@ -1,9 +1,10 @@
import { Component, Components, ComponentView, DataSourceManager, Editor } from '../../../../../src'; import { DataSourceManager, Editor } from '../../../../../src';
import { DataConditionIfTrueType } from '../../../../../src/data_sources/model/conditional_variables/constants';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import ComponentDataCondition from '../../../../../src/data_sources/model/conditional_variables/ComponentDataCondition';
import { DataConditionType } from '../../../../../src/data_sources/model/conditional_variables/DataCondition'; import { DataConditionType } from '../../../../../src/data_sources/model/conditional_variables/DataCondition';
import { AnyTypeOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/AnyTypeOperator'; import { AnyTypeOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/AnyTypeOperator';
import { NumberOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/NumberOperator'; import { NumberOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/NumberOperator';
import { DataComponentTypes } from '../../../../../src/data_sources/types';
import ComponentDataConditionView from '../../../../../src/data_sources/view/ComponentDataConditionView'; import ComponentDataConditionView from '../../../../../src/data_sources/view/ComponentDataConditionView';
import ComponentWrapper from '../../../../../src/dom_components/model/ComponentWrapper'; import ComponentWrapper from '../../../../../src/dom_components/model/ComponentWrapper';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
@ -17,7 +18,6 @@ import {
setupTestEditor, setupTestEditor,
TRUE_CONDITION, TRUE_CONDITION,
} from '../../../../common'; } from '../../../../common';
import ComponentDataCondition from '../../../../../src/data_sources/model/conditional_variables/ComponentDataCondition';
describe('ComponentDataCondition', () => { describe('ComponentDataCondition', () => {
let editor: Editor; let editor: Editor;
@ -147,7 +147,7 @@ describe('ComponentDataCondition', () => {
}, },
components: [ components: [
{ {
type: DataConditionIfTrueType, type: DataComponentTypes.conditionTrue,
components: { components: {
type: DataConditionType, type: DataConditionType,
dataResolver: { dataResolver: {

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

@ -1,16 +1,16 @@
import { Component, DataSource, DataSourceManager } from '../../../../../src'; import { Component, DataSource, DataSourceManager } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import { import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
DataCollectionItemType,
DataCollectionType,
} from '../../../../../src/data_sources/model/data_collection/constants';
import { import {
ComponentDataCollectionProps, ComponentDataCollectionProps,
DataCollectionStateType, DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types'; } from '../../../../../src/data_sources/model/data_collection/types';
import { DataComponentTypes } from '../../../../../src/data_sources/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { setupTestEditor } from '../../../../common'; import { setupTestEditor } from '../../../../common';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
const DataCollectionItemType = DataComponentTypes.collectionItem;
const DataCollectionType = DataComponentTypes.collection;
describe('Collection component getters and setters', () => { describe('Collection component getters and setters', () => {
let em: EditorModel; let em: EditorModel;

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

@ -1,17 +1,17 @@
import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import { import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
DataCollectionItemType,
DataCollectionType,
} from '../../../../../src/data_sources/model/data_collection/constants';
import { import {
ComponentDataCollectionProps, ComponentDataCollectionProps,
DataCollectionStateType, DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types'; } from '../../../../../src/data_sources/model/data_collection/types';
import { DataComponentTypes } from '../../../../../src/data_sources/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { setupTestEditor } from '../../../../common';
import { ProjectData } from '../../../../../src/storage_manager'; import { ProjectData } from '../../../../../src/storage_manager';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection'; import { setupTestEditor } from '../../../../common';
const DataCollectionItemType = DataComponentTypes.collectionItem;
const DataCollectionType = DataComponentTypes.collection;
describe('Collection component', () => { describe('Collection component', () => {
let em: EditorModel; let em: EditorModel;
@ -104,6 +104,12 @@ describe('Collection component', () => {
expect(cmp.getInnerHTML()).toBe(innerHTML); expect(cmp.getInnerHTML()).toBe(innerHTML);
expect(cmp.toHTML()).toBe(`<${tagName} id="${cmp.getId()}">${innerHTML}</${tagName}>`); expect(cmp.toHTML()).toBe(`<${tagName} id="${cmp.getId()}">${innerHTML}</${tagName}>`);
expect(cmp.getEl()?.innerHTML).toBe(innerHTML); expect(cmp.getEl()?.innerHTML).toBe(innerHTML);
expect(JSON.parse(JSON.stringify(cmp.toJSON()))).toEqual({
tagName: cmp.tagName,
dataResolver: cmp.get('dataResolver'),
type: cmp.getType(),
attributes: cmp.getAttributes(),
});
}; };
const checkRecordsWithInnerCmp = () => { const checkRecordsWithInnerCmp = () => {
@ -621,13 +627,34 @@ describe('Collection component', () => {
const collectionCmpDef = { const collectionCmpDef = {
type: DataCollectionType, type: DataCollectionType,
attributes: { id: 'cmp-coll' },
components: [ components: [
{ {
type: DataCollectionItemType, type: DataCollectionItemType,
attributes: { id: 'cmp-coll-item' },
components: [ components: [
{ {
...childCmpDef, ...childCmpDef,
components: [childCmpDef, childCmpDef], attributes: {
...childCmpDef.attributes,
id: 'cmp-coll-item-child-1',
},
components: [
{
...childCmpDef,
attributes: {
...childCmpDef.attributes,
id: 'cmp-coll-item-child-1-1',
},
},
{
...childCmpDef,
attributes: {
...childCmpDef.attributes,
id: 'cmp-coll-item-child-1-2',
},
},
],
}, },
], ],
}, },
@ -652,6 +679,9 @@ describe('Collection component', () => {
const firstItemCmp = cmp.getCollectionItemComponents().at(0); const firstItemCmp = cmp.getCollectionItemComponents().at(0);
const newChildDefinition = { const newChildDefinition = {
type: 'default', type: 'default',
attributes: {
id: 'cmp-coll-item-child-UP',
},
name: { name: {
type: DataVariableType, type: DataVariableType,
variableType: DataCollectionStateType.currentIndex, variableType: DataCollectionStateType.currentIndex,
@ -674,6 +704,9 @@ describe('Collection component', () => {
const firstItemCmp = cmp.getCollectionItemComponents().at(0); const firstItemCmp = cmp.getCollectionItemComponents().at(0);
const newChildDefinition = { const newChildDefinition = {
type: 'default', type: 'default',
attributes: {
id: 'cmp-coll-item-child-UP',
},
name: { name: {
type: DataVariableType, type: DataVariableType,
variableType: DataCollectionStateType.currentIndex, variableType: DataCollectionStateType.currentIndex,

14
packages/core/test/specs/data_sources/model/data_collection/ComponentDataCollectionWithDataVariable.ts

@ -1,17 +1,17 @@
import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import {
DataCollectionItemType,
DataCollectionType,
} from '../../../../../src/data_sources/model/data_collection/constants';
import { import {
ComponentDataCollectionProps, ComponentDataCollectionProps,
DataCollectionStateType, DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types'; } from '../../../../../src/data_sources/model/data_collection/types';
import { DataComponentTypes } from '../../../../../src/data_sources/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { ProjectData } from '../../../../../src/storage_manager'; import { ProjectData } from '../../../../../src/storage_manager';
import { setupTestEditor } from '../../../../common'; import { setupTestEditor } from '../../../../common';
const DataCollectionItemType = DataComponentTypes.collectionItem;
const DataCollectionType = DataComponentTypes.collection;
describe('Collection variable components', () => { describe('Collection variable components', () => {
let em: EditorModel; let em: EditorModel;
let editor: Editor; let editor: Editor;
@ -116,6 +116,7 @@ describe('Collection variable components', () => {
beforeEach(() => { beforeEach(() => {
const variableCmpDef = { const variableCmpDef = {
type: DataVariableType, type: DataVariableType,
attributes: { id: 'cmp-coll-item-child' },
dataResolver: { dataResolver: {
variableType: DataCollectionStateType.currentItem, variableType: DataCollectionStateType.currentItem,
collectionId: 'my_collection', collectionId: 'my_collection',
@ -125,11 +126,14 @@ describe('Collection variable components', () => {
const collectionCmpDef = { const collectionCmpDef = {
type: DataCollectionType, type: DataCollectionType,
attributes: { id: 'cmp-coll' },
components: { components: {
type: DataCollectionItemType, type: DataCollectionItemType,
attributes: { id: 'cmp-coll-item' },
components: [ components: [
{ {
type: 'default', type: 'default',
attributes: { id: 'cmp-coll-item-child-1' },
}, },
variableCmpDef, variableCmpDef,
], ],
@ -154,6 +158,7 @@ describe('Collection variable components', () => {
const firstChild = cmp.components().at(0); const firstChild = cmp.components().at(0);
const newChildDefinition = { const newChildDefinition = {
type: DataVariableType, type: DataVariableType,
attributes: { id: 'cmp-var' },
dataResolver: { dataResolver: {
variableType: DataCollectionStateType.currentIndex, variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection', collectionId: 'my_collection',
@ -175,6 +180,7 @@ describe('Collection variable components', () => {
const firstChild = cmp.components().at(0); const firstChild = cmp.components().at(0);
const newChildDefinition = { const newChildDefinition = {
type: DataVariableType, type: DataVariableType,
attributes: { id: 'cmp-var' },
dataResolver: { dataResolver: {
variableType: DataCollectionStateType.currentIndex, variableType: DataCollectionStateType.currentIndex,
collectionId: 'my_collection', collectionId: 'my_collection',

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

@ -2,8 +2,14 @@
exports[`Collection component Serialization Saving: Collection with grandchildren 1`] = ` exports[`Collection component Serialization Saving: Collection with grandchildren 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": { "attributes": {
@ -13,6 +19,7 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -22,6 +29,9 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
}, },
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item-child-UP",
},
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -70,8 +80,14 @@ exports[`Collection component Serialization Saving: Collection with grandchildre
exports[`Collection component Serialization Saving: Collection with no grandchildren 1`] = ` exports[`Collection component Serialization Saving: Collection with no grandchildren 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": { "attributes": {
@ -81,6 +97,7 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -97,6 +114,7 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1-1",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -132,6 +150,7 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1-2",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -199,8 +218,14 @@ exports[`Collection component Serialization Saving: Collection with no grandchil
exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with grandchildren 1`] = ` exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with grandchildren 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": { "attributes": {
@ -210,6 +235,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -226,6 +252,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1-1",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -235,6 +262,9 @@ exports[`Collection component Serialization Serializion with Collection Variable
}, },
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item-child-UP",
},
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -272,6 +302,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1-2",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -339,8 +370,14 @@ exports[`Collection component Serialization Serializion with Collection Variable
exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with no grandchildren 1`] = ` exports[`Collection component Serialization Serializion with Collection Variables to JSON: Collection with no grandchildren 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": { "attributes": {
@ -350,6 +387,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -366,6 +404,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1-1",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -401,6 +440,7 @@ exports[`Collection component Serialization Serializion with Collection Variable
"type": "data-variable", "type": "data-variable",
"variableType": "currentItem", "variableType": "currentItem",
}, },
"id": "cmp-coll-item-child-1-2",
"name": { "name": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",

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

@ -2,13 +2,25 @@
exports[`Collection variable components Serialization Saving: Collection with collection variable component ( no grandchildren ) 1`] = ` exports[`Collection variable components Serialization Saving: Collection with collection variable component ( no grandchildren ) 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item-child-1",
},
"type": "default", "type": "default",
}, },
{ {
"attributes": {
"id": "cmp-coll-item-child",
},
"dataResolver": { "dataResolver": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -35,12 +47,24 @@ exports[`Collection variable components Serialization Saving: Collection with co
exports[`Collection variable components Serialization Saving: Collection with collection variable component ( with grandchildren ) 1`] = ` exports[`Collection variable components Serialization Saving: Collection with collection variable component ( with grandchildren ) 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item-child-1",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-var",
},
"dataResolver": { "dataResolver": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -52,6 +76,9 @@ exports[`Collection variable components Serialization Saving: Collection with co
"type": "default", "type": "default",
}, },
{ {
"attributes": {
"id": "cmp-coll-item-child",
},
"dataResolver": { "dataResolver": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -78,13 +105,25 @@ exports[`Collection variable components Serialization Saving: Collection with co
exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( no grandchildren ) 1`] = ` exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( no grandchildren ) 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item-child-1",
},
"type": "default", "type": "default",
}, },
{ {
"attributes": {
"id": "cmp-coll-item-child",
},
"dataResolver": { "dataResolver": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -111,12 +150,24 @@ exports[`Collection variable components Serialization Serializion to JSON: Colle
exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( with grandchildren ) 1`] = ` exports[`Collection variable components Serialization Serializion to JSON: Collection with collection variable component ( with grandchildren ) 1`] = `
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item-child-1",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-var",
},
"dataResolver": { "dataResolver": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",
@ -128,6 +179,9 @@ exports[`Collection variable components Serialization Serializion to JSON: Colle
"type": "default", "type": "default",
}, },
{ {
"attributes": {
"id": "cmp-coll-item-child",
},
"dataResolver": { "dataResolver": {
"collectionId": "my_collection", "collectionId": "my_collection",
"path": "user", "path": "user",

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

@ -2,14 +2,29 @@
exports[`Collection component Nested collections are correctly serialized 1`] = ` exports[`Collection component Nested collections are correctly serialized 1`] = `
{ {
"attributes": {
"id": "cmp-coll-parent",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-parent-item",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item",
},
"components": [ "components": [
{ {
"attributes": {
"id": "cmp-coll-item-child-1",
},
"name": { "name": {
"collectionId": "nested_collection", "collectionId": "nested_collection",
"path": "user", "path": "user",

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

@ -1,17 +1,17 @@
import { Component, DataRecord, DataSource, DataSourceManager, Editor } from '../../../../../src'; import { Component, DataRecord, DataSource, DataSourceManager } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable'; import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection'; import ComponentDataCollection from '../../../../../src/data_sources/model/data_collection/ComponentDataCollection';
import {
DataCollectionItemType,
DataCollectionType,
} from '../../../../../src/data_sources/model/data_collection/constants';
import { import {
ComponentDataCollectionProps, ComponentDataCollectionProps,
DataCollectionStateType, DataCollectionStateType,
} from '../../../../../src/data_sources/model/data_collection/types'; } from '../../../../../src/data_sources/model/data_collection/types';
import { DataComponentTypes } from '../../../../../src/data_sources/types';
import EditorModel from '../../../../../src/editor/model/Editor'; import EditorModel from '../../../../../src/editor/model/Editor';
import { setupTestEditor } from '../../../../common'; import { setupTestEditor } from '../../../../common';
const DataCollectionItemType = DataComponentTypes.collectionItem;
const DataCollectionType = DataComponentTypes.collection;
describe('Collection component', () => { describe('Collection component', () => {
let em: EditorModel; let em: EditorModel;
let dsm: DataSourceManager; let dsm: DataSourceManager;
@ -28,7 +28,12 @@ describe('Collection component', () => {
function getCmpDef(nestedCmpDef: ComponentDataCollectionProps): ComponentDataCollectionProps { function getCmpDef(nestedCmpDef: ComponentDataCollectionProps): ComponentDataCollectionProps {
return { return {
type: DataCollectionType, type: DataCollectionType,
components: { type: DataCollectionItemType, components: nestedCmpDef }, attributes: { id: 'cmp-coll-parent' },
components: {
type: DataCollectionItemType,
attributes: { id: 'cmp-coll-parent-item' },
components: nestedCmpDef,
},
dataResolver: { dataResolver: {
collectionId: 'parent_collection', collectionId: 'parent_collection',
dataSource: { dataSource: {
@ -64,10 +69,13 @@ describe('Collection component', () => {
nestedCmpDef = { nestedCmpDef = {
type: DataCollectionType, type: DataCollectionType,
attributes: { id: 'cmp-coll' },
components: { components: {
type: DataCollectionItemType, type: DataCollectionItemType,
attributes: { id: 'cmp-coll-item' },
components: { components: {
type: 'default', type: 'default',
attributes: { id: 'cmp-coll-item-child-1' },
name: { name: {
type: DataVariableType, type: DataVariableType,
variableType: DataCollectionStateType.currentItem, variableType: DataCollectionStateType.currentItem,

24
packages/core/test/specs/dom_components/model/Component.ts

@ -460,6 +460,30 @@ describe('Component', () => {
); );
}); });
test('Ensure duplicated component clones the rules also cross components', () => {
const idName = 'test';
const cmp = dcomp.addComponent(`
<div>
<div id="${idName}">Comp 1</div>
</div>
<style>
#test { color: red; }
@media (max-width: 992px) {
#test { color: blue; }
}
</style>
`) as Component;
const cmp2 = dcomp.addComponent(`<div>Text</div>`) as Component;
cmp2.components().resetFromString(`<div id="${idName}">Comp 2</div>`);
const newId = cmp2.components().at(0).getId();
expect(em.getHtml()).toBe(
`<body><div><div id="test">Comp 1</div></div><div><div id="test-2">Comp 2</div></div></body>`,
);
expect(em.getCss()).toBe(
`#test{color:red;}#${newId}{color:red;}@media (max-width: 992px){#test{color:blue;}#${newId}{color:blue;}}`,
);
});
test('Ability to stop/change propagation chain', () => { test('Ability to stop/change propagation chain', () => {
obj.append({ obj.append({
removable: false, removable: false,

56
packages/core/test/specs/dom_components/model/ComponentWrapper.ts

@ -1,9 +1,10 @@
import { DataSourceManager, DataSource, DataRecord } from '../../../../src'; import { DataRecord, DataSourceManager } from '../../../../src';
import { DataVariableProps, DataVariableType } from '../../../../src/data_sources/model/DataVariable'; import { DataVariableProps, DataVariableType } from '../../../../src/data_sources/model/DataVariable';
import { DataComponentTypes } from '../../../../src/data_sources/types';
import { keyRootData } from '../../../../src/dom_components/constants';
import Component from '../../../../src/dom_components/model/Component'; import Component from '../../../../src/dom_components/model/Component';
import ComponentHead from '../../../../src/dom_components/model/ComponentHead'; import ComponentHead from '../../../../src/dom_components/model/ComponentHead';
import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper'; import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper';
import { keyRootData } from '../../../../src/dom_components/constants';
import Editor from '../../../../src/editor'; import Editor from '../../../../src/editor';
import EditorModel from '../../../../src/editor/model/Editor'; import EditorModel from '../../../../src/editor/model/Editor';
import { setupTestEditor } from '../../../common'; import { setupTestEditor } from '../../../common';
@ -43,10 +44,11 @@ describe('ComponentWrapper', () => {
describe('ComponentWrapper with DataResolver', () => { describe('ComponentWrapper with DataResolver', () => {
let em: EditorModel; let em: EditorModel;
let dsm: DataSourceManager; let dsm: DataSourceManager;
let blogDataSource: DataSource;
let wrapper: ComponentWrapper; let wrapper: ComponentWrapper;
let firstRecord: DataRecord; let firstRecord: DataRecord;
const contentDataSourceId = 'contentDataSource';
const blogDataSourceId = 'blogs';
const firstBlog = { id: 'blog1', title: 'How to Test Components' }; const firstBlog = { id: 'blog1', title: 'How to Test Components' };
const blogsData = [ const blogsData = [
firstBlog, firstBlog,
@ -60,11 +62,11 @@ describe('ComponentWrapper', () => {
}; };
beforeEach(() => { beforeEach(() => {
({ em, dsm } = setupTestEditor()); ({ em, dsm } = setupTestEditor({ withCanvas: true }));
wrapper = em.getWrapper() as ComponentWrapper; wrapper = em.getWrapper() as ComponentWrapper;
blogDataSource = dsm.add({ dsm.add({
id: 'contentDataSource', id: contentDataSourceId,
records: [ records: [
{ {
id: 'blogs', id: 'blogs',
@ -77,7 +79,12 @@ describe('ComponentWrapper', () => {
], ],
}); });
firstRecord = em.DataSources.get('contentDataSource').getRecord('blogs')!; dsm.add({
id: blogDataSourceId,
records: blogsData,
});
firstRecord = em.DataSources.get(contentDataSourceId).getRecord('blogs')!;
}); });
afterEach(() => { afterEach(() => {
@ -93,15 +100,20 @@ describe('ComponentWrapper', () => {
wrapper.append({ wrapper.append({
type: 'default', type: 'default',
title: { title: {
type: 'data-variable', type: DataComponentTypes.variable,
collectionId: keyRootData, collectionId: keyRootData,
path, path,
}, },
components: {
tagName: 'span',
type: DataComponentTypes.variable,
dataResolver: { collectionId: keyRootData, path },
},
})[0]; })[0];
test('children reflect resolved value from dataResolver', () => { test('children reflect resolved value from dataResolver', () => {
wrapper.setDataResolver(createDataResolver('contentDataSource.blogs.data')); wrapper.setDataResolver(createDataResolver('contentDataSource.blogs.data'));
wrapper.resolverCurrentItem = 0; wrapper.setResolverCurrentItem(0);
const child = appendChildWithTitle(); const child = appendChildWithTitle();
expect(child.get('title')).toBe(blogsData[0].title); expect(child.get('title')).toBe(blogsData[0].title);
@ -113,7 +125,7 @@ describe('ComponentWrapper', () => {
test('children update collectionStateMap on wrapper.setDataResolver', () => { test('children update collectionStateMap on wrapper.setDataResolver', () => {
const child = appendChildWithTitle(); const child = appendChildWithTitle();
wrapper.setDataResolver(createDataResolver('contentDataSource.blogs.data')); wrapper.setDataResolver(createDataResolver('contentDataSource.blogs.data'));
wrapper.resolverCurrentItem = 0; wrapper.setResolverCurrentItem(0);
expect(child.get('title')).toBe(blogsData[0].title); expect(child.get('title')).toBe(blogsData[0].title);
@ -123,10 +135,32 @@ describe('ComponentWrapper', () => {
test('wrapper should handle objects as collection state', () => { test('wrapper should handle objects as collection state', () => {
wrapper.setDataResolver(createDataResolver('contentDataSource.productsById.data')); wrapper.setDataResolver(createDataResolver('contentDataSource.productsById.data'));
wrapper.resolverCurrentItem = 'product1'; wrapper.setResolverCurrentItem('product1');
const child = appendChildWithTitle('title'); const child = appendChildWithTitle('title');
expect(child.get('title')).toBe(productsById.product1.title); expect(child.get('title')).toBe(productsById.product1.title);
}); });
test('wrapper should handle default data source records', () => {
wrapper.setDataResolver(createDataResolver(blogDataSourceId));
const child = appendChildWithTitle('title');
expect(child.get('title')).toBe(blogsData[0].title);
expect(child.getInnerHTML()).toBe(`<span>${blogsData[0].title}</span>`);
const eventUpdate = jest.fn();
em.on(em.events.updateBefore, eventUpdate);
wrapper.setResolverCurrentItem(1);
expect(child.get('title')).toBe(blogsData[1].title);
expect(child.getInnerHTML()).toBe(`<span>${blogsData[1].title}</span>`);
wrapper.setResolverCurrentItem(blogsData[2].id);
expect(child.get('title')).toBe(blogsData[2].title);
expect(child.getInnerHTML()).toBe(`<span>${blogsData[2].title}</span>`);
// No update events are expected
expect(eventUpdate).toHaveBeenCalledTimes(0);
});
}); });
}); });

Loading…
Cancel
Save