Browse Source

Remove watching and serialization of dynamic traits

pull/6351/head
mohamedsalem401 2 years ago
parent
commit
9dc773aeda
  1. 43
      packages/core/src/dom_components/model/Component.ts
  2. 56
      packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts
  3. 2
      packages/core/src/trait_manager/model/Trait.ts
  4. 67
      packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap
  5. 441
      packages/core/test/specs/data_sources/model/TraitDataVariable.ts
  6. 239
      packages/core/test/specs/data_sources/model/conditional_variables/ConditionalTraits.ts
  7. 72
      packages/core/test/specs/data_sources/model/conditional_variables/__snapshots__/ConditionalTraits.ts.snap
  8. 103
      packages/core/test/specs/data_sources/serialization.ts

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

@ -53,6 +53,7 @@ import {
} from './SymbolUtils';
import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher';
import { DynamicValueWatcher } from './DynamicValueWatcher';
import { DynamicValueDefinition } from '../../data_sources/types';
export interface IComponent extends ExtractMethods<Component> {}
export interface DynamicWatchersOptions {
@ -74,7 +75,6 @@ export const keySymbol = '__symbol';
export const keySymbolOvrd = '__symbol_ovrd';
export const keyUpdate = ComponentsEvents.update;
export const keyUpdateInside = ComponentsEvents.updateInside;
export const dynamicAttrKey = 'attributes-dynamic-value';
/**
* The Component object represents a single node of our template structure, so when you update its properties the changes are
@ -355,15 +355,9 @@ export default class Component extends StyleableModel<ComponentProperties> {
options = optionsOrUndefined || options;
}
const areStaticAttributes = DynamicValueWatcher.areStaticValues(attributes);
let evaluatedAttributes: Partial<ComponentProperties>;
if (areStaticAttributes) {
evaluatedAttributes = attributes;
} else {
// @ts-ignore
const em = this.em || options.em;
evaluatedAttributes = ComponentDynamicValueWatcher.evaluateComponentDef(attributes, em);
}
// @ts-ignore
const em = this.em || options.em;
const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attributes, em);
const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource;
if (!shouldSkipWatcherUpdates) {
@ -962,7 +956,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
const event = 'change:traits';
this.off(event, this.initTraits);
this.__loadTraits();
const attrs = { ...this.get('attributes') };
const attrs: { [key: string]: string | DynamicValueDefinition } = {};
const traits = this.traits;
traits.each((trait) => {
const name = trait.getName();
@ -974,7 +968,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
if (name && value) attrs[name] = value;
}
});
traits.length && this.set('attributes', attrs);
traits.length && this.addAttributes(attrs);
this.on(event, this.initTraits);
changed && em && em.trigger('component:toggled');
return this;
@ -1581,12 +1575,11 @@ export default class Component extends StyleableModel<ComponentProperties> {
toJSON(opts: ObjectAny = {}): ComponentDefinition {
let obj = Model.prototype.toJSON.call(this, opts);
obj = { ...obj, ...this.componentDVListener.getDynamicPropsDefs() };
obj.attributes = this.componentDVListener.getAttributesDefsOrValues(this.getAttributes({ noClass: true }));
obj[dynamicAttrKey] = this.serializeDynamicTraits();
obj.attributes = this.componentDVListener.getAttributesDefsOrValues(this.getAttributes());
delete obj.componentDVListener;
delete obj.traits;
delete obj.attributes.class;
delete obj.toolbar;
delete obj.traits;
delete obj.status;
delete obj.open; // used in Layers
delete obj._undoexc;
@ -1610,26 +1603,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
return obj;
}
/**
* Serialize dynamic traits into an array of objects with name and value.
* @return {ObjectAny[]}
* @private
*/
private serializeDynamicTraits(): ObjectAny[] | undefined {
const dynamicTraitsObj = this.componentDVListener.getTraitsDefs();
const keys = Object.entries(dynamicTraitsObj);
if (keys.length === 0) return undefined;
return keys.map(([key, value]) => {
const traitJSON = this.getTrait(key).toJSON();
return {
...traitJSON,
name: key,
value,
};
});
}
/**
* Return an object containing only changed props
*/

56
packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts

@ -1,12 +1,11 @@
import { ObjectAny } from '../../common';
import EditorModel from '../../editor/model/Editor';
import Component, { dynamicAttrKey } from './Component';
import Component from './Component';
import { DynamicValueWatcher } from './DynamicValueWatcher';
export class ComponentDynamicValueWatcher {
private propertyWatcher: DynamicValueWatcher;
private attributeWatcher: DynamicValueWatcher;
private traitsWatcher: DynamicValueWatcher;
constructor(
private component: Component,
@ -14,7 +13,6 @@ export class ComponentDynamicValueWatcher {
) {
this.propertyWatcher = new DynamicValueWatcher(this.createPropertyUpdater(), em);
this.attributeWatcher = new DynamicValueWatcher(this.createAttributeUpdater(), em);
this.traitsWatcher = new DynamicValueWatcher(this.createTraitUpdater(), em);
}
private createPropertyUpdater() {
@ -29,44 +27,9 @@ export class ComponentDynamicValueWatcher {
};
}
private createTraitUpdater() {
return (key: string, value: any) => {
this.component.updateTrait(key, { value });
const trait = this.component.getTrait(key);
trait.setTargetValue(value);
};
}
static evaluateComponentDef(values: ObjectAny, em: EditorModel) {
const props = DynamicValueWatcher.getStaticValues(values, em);
if (values.attributes) {
props.attributes = DynamicValueWatcher.getStaticValues(values.attributes, em);
}
if (Array.isArray(values[dynamicAttrKey]) && values[dynamicAttrKey].length > 0) {
values.traits = values.traits ? [...values[dynamicAttrKey], ...values.traits] : values[dynamicAttrKey];
}
if (values.traits) {
const evaluatedTraitsValues = DynamicValueWatcher.getStaticValues(
values.traits.map((trait: any) => trait.value),
em,
);
props.traits = values.traits.map((trait: any, index: number) => ({
...trait,
value: evaluatedTraitsValues[index],
}));
}
return props;
}
watchComponentDef(values: ObjectAny) {
this.addProps(values);
this.addAttributes(values.attributes);
this.addTraits(values.traits);
}
addProps(props: ObjectAny) {
@ -85,18 +48,6 @@ export class ComponentDynamicValueWatcher {
this.attributeWatcher.addDynamicValues(attributes);
}
addTraits(traits: (string | ObjectAny)[]) {
const evaluatedTraits: { [key: string]: ObjectAny } = {};
traits?.forEach((trait: any) => {
if (typeof trait === 'object' && trait.name) {
evaluatedTraits[trait.name] = trait.value;
}
});
this.traitsWatcher.addDynamicValues(evaluatedTraits);
}
removeAttributes(attributes: string[]) {
this.attributeWatcher.removeListeners(attributes);
}
@ -105,10 +56,6 @@ export class ComponentDynamicValueWatcher {
return this.attributeWatcher.getSerializableValues(attributes);
}
getTraitsDefs() {
return this.traitsWatcher.getAllSerializableValues();
}
getPropsDefsOrValues(props: ObjectAny) {
return this.propertyWatcher.getSerializableValues(props);
}
@ -116,6 +63,5 @@ export class ComponentDynamicValueWatcher {
destroy() {
this.propertyWatcher.removeListeners();
this.attributeWatcher.removeListeners();
this.traitsWatcher.removeListeners();
}
}

2
packages/core/src/trait_manager/model/Trait.ts

@ -9,9 +9,7 @@ import TraitsEvents, { TraitGetValueOptions, TraitOption, TraitProperties, Trait
import TraitView from '../view/TraitView';
import Traits from './Traits';
import TraitDataVariable from '../../data_sources/model/TraitDataVariable';
import { DataVariableType } from '../../data_sources/model/DataVariable';
import DynamicVariableListenerManager from '../../data_sources/model/DataVariableListenerManager';
import { isDynamicValueDefinition } from '../../data_sources/model/utils';
/**
* @property {String} id Trait id, eg. `my-trait-id`.

67
packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap

@ -165,70 +165,3 @@ exports[`DataSource Serialization .getProjectData StyleDataVariable 1`] = `
"symbols": [],
}
`;
exports[`DataSource Serialization .getProjectData TraitDataVariable 1`] = `
{
"assets": [],
"dataSources": [],
"pages": [
{
"frames": [
{
"component": {
"components": [
{
"attributes": {
"value": "test-value",
},
"attributes-dynamic-value": [
{
"category": "",
"changeProp": false,
"default": "",
"id": "data-variable-id",
"label": "Value",
"name": "value",
"options": [],
"placeholder": "",
"step": 1,
"type": "text",
"unit": "",
"value": {
"defaultValue": "default",
"path": "test-input.id1.value",
"type": "data-variable",
},
},
],
"tagName": "input",
"void": true,
},
],
"docEl": {
"tagName": "html",
},
"head": {
"type": "head",
},
"stylable": [
"background",
"background-color",
"background-image",
"background-repeat",
"background-attachment",
"background-position",
"background-size",
],
"type": "wrapper",
},
"id": "data-variable-id",
},
],
"id": "data-variable-id",
"type": "main",
},
],
"styles": [],
"symbols": [],
}
`;

441
packages/core/test/specs/data_sources/model/TraitDataVariable.ts

@ -2,7 +2,6 @@ import Editor from '../../../../src/editor/model/Editor';
import DataSourceManager from '../../../../src/data_sources';
import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper';
import { DataVariableType } from '../../../../src/data_sources/model/DataVariable';
import { DataSourceProps } from '../../../../src/data_sources/types';
import { setupTestEditor } from '../../../common';
describe('TraitDataVariable', () => {
@ -18,350 +17,144 @@ describe('TraitDataVariable', () => {
em.destroy();
});
describe('text input component', () => {
test('component initializes data-variable value', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
},
],
})[0];
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('test-value');
expect(cmp?.getAttributes().value).toBe('test-value');
});
test('component initializes data-variable placeholder', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
label: 'Placeholder',
name: 'placeholder',
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
},
],
})[0];
const input = cmp.getEl();
expect(input?.getAttribute('placeholder')).toBe('test-value');
expect(cmp?.getAttributes().placeholder).toBe('test-value');
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'new-value' });
expect(input?.getAttribute('placeholder')).toBe('new-value');
expect(cmp?.getAttributes().placeholder).toBe('new-value');
});
test('component updates to defaultValue on record removal', () => {
const inputDataSource = {
id: 'test-input-removal',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
test('set component attribute to trait value if component has no value for the attribute', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
],
})[0];
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('test-value');
expect(cmp?.getAttributes().value).toBe('test-value');
const testDs = dsm.get(inputDataSource.id);
testDs.removeRecord('id1');
expect(input?.getAttribute('value')).toBe('default');
expect(cmp?.getAttributes().value).toBe('default');
});
test('component updates with data-variable value', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
'type',
{
type: 'text',
label: 'Value',
name: 'value',
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
},
],
})[0];
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('test-value');
expect(cmp?.getAttributes().value).toBe('test-value');
},
],
})[0];
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'new-value' });
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('test-value');
expect(cmp?.getAttributes().value).toBe('test-value');
expect(input?.getAttribute('value')).toBe('new-value');
expect(cmp?.getAttributes().value).toBe('new-value');
});
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'new-value' });
test('component initializes data-variable value for nested object', () => {
const inputDataSource = {
id: 'nested-input-data',
records: [
{
id: 'id1',
nestedObject: {
value: 'nested-value',
},
},
],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
value: {
type: DataVariableType,
defaultValue: 'default',
path: 'nested-input-data.id1.nestedObject.value',
},
},
],
})[0];
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('nested-value');
expect(cmp?.getAttributes().value).toBe('nested-value');
});
expect(input?.getAttribute('value')).toBe('new-value');
expect(cmp?.getAttributes().value).toBe('new-value');
});
describe('checkbox input component', () => {
test('component initializes and updates data-variable value', () => {
const inputDataSource = {
id: 'test-checkbox-datasource',
records: [{ id: 'id1', value: 'true' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
attributes: { type: 'checkbox', name: 'my-checkbox' },
traits: [
{
type: 'checkbox',
label: 'Checked',
name: 'checked',
value: {
type: 'data-variable',
defaultValue: 'false',
path: `${inputDataSource.id}.id1.value`,
},
valueTrue: 'true',
valueFalse: 'false',
test('set component prop to trait value if component has no value for the prop', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
changeProp: true,
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
],
})[0];
},
],
})[0];
const input = cmp.getEl() as HTMLInputElement;
expect(input?.checked).toBe(true);
expect(input?.getAttribute('checked')).toBe('true');
expect(cmp?.get('value')).toBe('test-value');
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'false' });
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'new-value' });
expect(input?.getAttribute('checked')).toBe('false');
// Not syncing - related to
// https://github.com/GrapesJS/grapesjs/discussions/5868
// https://github.com/GrapesJS/grapesjs/discussions/4415
// https://github.com/GrapesJS/grapesjs/pull/6095
// expect(input?.checked).toBe(false);
});
expect(cmp?.get('value')).toBe('new-value');
});
describe('image component', () => {
test('component initializes and updates data-variable value', () => {
const inputDataSource = {
id: 'test-image-datasource',
records: [{ id: 'id1', value: 'url-to-cat-image' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
type: 'image',
tagName: 'img',
traits: [
{
type: 'text',
name: 'src',
value: {
type: 'data-variable',
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
test('should keep component prop if component already has a value for the prop', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
attributes: {
value: 'existing-value',
},
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
changeProp: true,
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
],
})[0];
},
],
})[0];
const img = cmp.getEl() as HTMLImageElement;
expect(img?.getAttribute('src')).toBe('url-to-cat-image');
expect(cmp?.getAttributes().src).toBe('url-to-cat-image');
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('existing-value');
expect(cmp?.getAttributes().value).toBe('existing-value');
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'url-to-dog-image' });
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'new-value' });
expect(img?.getAttribute('src')).toBe('url-to-dog-image');
expect(cmp?.getAttributes().src).toBe('url-to-dog-image');
});
expect(input?.getAttribute('value')).toBe('existing-value');
expect(cmp?.getAttributes().value).toBe('existing-value');
});
describe('link component', () => {
test('component initializes and updates data-variable value', () => {
const inputDataSource = {
id: 'test-link-datasource',
records: [{ id: 'id1', value: 'url-to-cat-image' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
type: 'link',
tagName: 'a',
traits: [
{
type: 'text',
name: 'href',
value: {
type: 'data-variable',
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
test('should keep component prop if component already has a value for the prop', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'input',
value: 'existing-value',
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
changeProp: true,
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
],
components: [{ tagName: 'span', content: 'Link' }],
})[0];
},
],
})[0];
const link = cmp.getEl() as HTMLLinkElement;
expect(link?.href).toBe('http://localhost/url-to-cat-image');
expect(cmp?.getAttributes().href).toBe('url-to-cat-image');
expect(cmp?.get('value')).toBe('existing-value');
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'url-to-dog-image' });
expect(link?.href).toBe('http://localhost/url-to-dog-image');
expect(cmp?.getAttributes().href).toBe('url-to-dog-image');
});
});
describe('changeProp', () => {
test('component initializes and updates data-variable value using changeProp', () => {
const inputDataSource = {
id: 'test-change-prop-datasource',
records: [{ id: 'id1', value: 'I love grapes' }],
};
dsm.add(inputDataSource);
const cmp = cmpRoot.append({
tagName: 'div',
type: 'default',
traits: [
{
name: 'test-change-prop',
type: 'text',
changeProp: true,
value: {
type: DataVariableType,
defaultValue: 'default',
path: `${inputDataSource.id}.id1.value`,
},
},
],
})[0];
let property = cmp.get('test-change-prop');
expect(property).toBe('I love grapes');
expect(cmp.getAttributes()['test-change-prop']).toBe(undefined);
expect(cmp.getView()?.el.getAttribute('test-change-prop')).toBeNull();
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'I really love grapes' });
property = cmp.get('test-change-prop');
expect(property).toBe('I really love grapes');
expect(cmp.getAttributes()['test-change-prop']).toBe(undefined);
expect(cmp.getView()?.el.getAttribute('test-change-prop')).toBeNull();
});
test('should cover when changeProp trait value is not set', () => {
const cmp = cmpRoot.append({
tagName: 'div',
type: 'default',
'test-change-prop': 'initial-value',
traits: [
{
name: 'test-change-prop',
type: 'text',
changeProp: true,
},
],
})[0];
const testDs = dsm.get(inputDataSource.id);
testDs.getRecord('id1')?.set({ value: 'new-value' });
let property = cmp.get('test-change-prop');
expect(property).toBe('initial-value');
});
expect(cmp?.get('value')).toBe('existing-value');
});
});

239
packages/core/test/specs/data_sources/model/conditional_variables/ConditionalTraits.ts

@ -4,12 +4,12 @@ import { MissingConditionError } from '../../../../../src/data_sources/model/con
import { ConditionalVariableType } from '../../../../../src/data_sources/model/conditional_variables/DataCondition';
import { GenericOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/GenericOperator';
import { NumberOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/NumberOperator';
import Component, { dynamicAttrKey } from '../../../../../src/dom_components/model/Component';
import Component from '../../../../../src/dom_components/model/Component';
import ComponentWrapper from '../../../../../src/dom_components/model/ComponentWrapper';
import EditorModel from '../../../../../src/editor/model/Editor';
import { filterObjectForSnapshot, setupTestEditor } from '../../../../common';
describe('TraitConditionalVariable', () => {
describe('conditional traits', () => {
let editor: Editor;
let em: EditorModel;
let dsm: DataSourceManager;
@ -22,15 +22,21 @@ describe('TraitConditionalVariable', () => {
afterEach(() => {
em.destroy();
});
test('set component attribute to trait value if component has no value for the attribute', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
it('should add a trait with a condition evaluating to a string', () => {
const component = cmpRoot.append({
tagName: 'h1',
type: 'text',
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
name: 'title',
label: 'Value',
name: 'value',
value: {
type: ConditionalVariableType,
condition: {
@ -38,215 +44,115 @@ describe('TraitConditionalVariable', () => {
operator: NumberOperation.greaterThan,
right: -1,
},
ifTrue: 'Some title',
ifTrue: 'test-value',
},
},
],
})[0];
testComponentAttr(component, 'title', 'Some title');
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('test-value');
expect(cmp?.getAttributes().value).toBe('test-value');
});
it('should add a trait with a data-source condition', () => {
const dataSource = {
id: 'ds1',
records: [{ id: 'left_id', left: 'Name1' }],
test('set component prop to trait value if component has no value for the prop', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(dataSource);
dsm.add(inputDataSource);
const component = cmpRoot.append({
tagName: 'h1',
type: 'text',
const cmp = cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
name: 'title',
label: 'Value',
name: 'value',
changeProp: true,
value: {
type: ConditionalVariableType,
condition: {
left: {
type: DataVariableType,
path: 'ds1.left_id.left',
},
operator: GenericOperation.equals,
right: 'Name1',
left: 0,
operator: NumberOperation.greaterThan,
right: -1,
},
ifTrue: 'Valid name',
ifFalse: 'Invalid name',
ifTrue: 'test-value',
},
},
],
})[0];
testComponentAttr(component, 'title', 'Valid name');
expect(cmp?.get('value')).toBe('test-value');
});
it('should change trait value with changing data-source value', () => {
const dataSource = {
id: 'ds1',
records: [{ id: 'left_id', left: 'Name1' }],
test('should keep component prop if component already has a value for the prop', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(dataSource);
dsm.add(inputDataSource);
const component = cmpRoot.append({
tagName: 'h1',
type: 'text',
const cmp = cmpRoot.append({
tagName: 'input',
attributes: {
value: 'existing-value',
},
traits: [
'name',
{
type: 'text',
name: 'title',
label: 'Value',
name: 'value',
changeProp: true,
value: {
type: ConditionalVariableType,
condition: {
left: {
type: DataVariableType,
path: 'ds1.left_id.left',
},
operator: GenericOperation.equals,
right: 'Name1',
},
ifTrue: 'Correct name',
ifFalse: 'Incorrect name',
},
},
],
})[0];
testComponentAttr(component, 'title', 'Correct name');
dsm.get('ds1').getRecord('left_id')?.set('left', 'Different name');
testComponentAttr(component, 'title', 'Incorrect name');
});
it('should throw an error if no condition is passed in trait', () => {
expect(() => {
cmpRoot.append({
tagName: 'h1',
type: 'text',
traits: [
{
type: 'text',
name: 'invalidTrait',
value: {
type: ConditionalVariableType,
left: 0,
operator: NumberOperation.greaterThan,
right: -1,
},
ifTrue: 'existing-value',
},
],
});
}).toThrow(MissingConditionError);
});
it('should store traits with conditional values correctly', () => {
const conditionalTrait = {
type: ConditionalVariableType,
condition: {
left: 0,
operator: NumberOperation.greaterThan,
right: -1,
},
ifTrue: 'Positive',
};
cmpRoot.append({
tagName: 'h1',
type: 'text',
traits: [
{
type: 'text',
name: 'dynamicTrait',
value: conditionalTrait,
},
],
})[0];
const projectData = editor.getProjectData();
const snapshot = filterObjectForSnapshot(projectData);
expect(snapshot).toMatchSnapshot(``);
const page = projectData.pages[0];
const frame = page.frames[0];
const storedComponent = frame.component.components[0];
expect(storedComponent[dynamicAttrKey][0].value).toEqual(conditionalTrait);
const input = cmp.getEl();
expect(input?.getAttribute('value')).toBe('existing-value');
expect(cmp?.getAttributes().value).toBe('existing-value');
});
it('should load traits with conditional values correctly', () => {
const projectData = {
pages: [
{
frames: [
{
component: {
components: [
{
[dynamicAttrKey]: [
{
name: 'dynamicTrait',
value: {
condition: {
left: 0,
operator: '>',
right: -1,
},
ifTrue: 'Positive',
type: 'conditional-variable',
},
},
],
type: 'text',
},
],
type: 'wrapper',
},
},
],
type: 'main',
},
],
test('should keep component prop if component already has a value for the prop', () => {
const inputDataSource = {
id: 'test-input',
records: [{ id: 'id1', value: 'test-value' }],
};
dsm.add(inputDataSource);
editor.loadProjectData(projectData);
const components = editor.getComponents();
const component = components.models[0];
expect(component.getAttributes()).toEqual({ dynamicTrait: 'Positive' });
});
it('should be property on the component with `changeProp:true`', () => {
const dataSource = {
id: 'ds1',
records: [{ id: 'left_id', left: 'Name1' }],
};
dsm.add(dataSource);
const component = cmpRoot.append({
tagName: 'h1',
type: 'text',
const cmp = cmpRoot.append({
tagName: 'input',
value: 'existing-value',
traits: [
'name',
{
type: 'text',
name: 'title',
label: 'Value',
name: 'value',
changeProp: true,
value: {
type: ConditionalVariableType,
condition: {
left: {
type: DataVariableType,
path: 'ds1.left_id.left',
},
operator: GenericOperation.equals,
right: 'Name1',
left: 0,
operator: NumberOperation.greaterThan,
right: -1,
},
ifTrue: 'Correct name',
ifFalse: 'Incorrect name',
ifTrue: 'existing-value',
},
},
],
})[0];
// TODO: make dynamic values not to change the attributes if `changeProp:true`
// expect(component.getView()?.el.getAttribute('title')).toBeNull();
expect(component.get('title')).toBe('Correct name');
dsm.get('ds1').getRecord('left_id')?.set('left', 'Different name');
// expect(component.getView()?.el.getAttribute('title')).toBeNull();
expect(component.get('title')).toBe('Incorrect name');
});
it('should handle objects as traits (other than dynamic values)', () => {
@ -271,10 +177,3 @@ describe('TraitConditionalVariable', () => {
expect(component.getAttributes().title).toEqual(traitValue);
});
});
function testComponentAttr(component: Component, trait: string, value: string) {
expect(component).toBeDefined();
expect(component.getTrait(trait).get('value')).toBe(value);
expect(component.getAttributes()[trait]).toBe(value);
expect(component.getView()?.el.getAttribute(trait)).toBe(value);
}

72
packages/core/test/specs/data_sources/model/conditional_variables/__snapshots__/ConditionalTraits.ts.snap

@ -1,72 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TraitConditionalVariable should store traits with conditional values correctly 1`] = `
{
"assets": [],
"dataSources": [],
"pages": [
{
"frames": [
{
"component": {
"components": [
{
"attributes": {
"dynamicTrait": "Positive",
},
"attributes-dynamic-value": [
{
"category": "",
"changeProp": false,
"default": "",
"id": "data-variable-id",
"label": "",
"name": "dynamicTrait",
"options": [],
"placeholder": "",
"step": 1,
"type": "text",
"unit": "",
"value": {
"condition": {
"left": 0,
"operator": ">",
"right": -1,
},
"ifTrue": "Positive",
"type": "conditional-variable",
},
},
],
"tagName": "h1",
"type": "text",
},
],
"docEl": {
"tagName": "html",
},
"head": {
"type": "head",
},
"stylable": [
"background",
"background-color",
"background-image",
"background-repeat",
"background-attachment",
"background-position",
"background-size",
],
"type": "wrapper",
},
"id": "data-variable-id",
},
],
"id": "data-variable-id",
"type": "main",
},
],
"styles": [],
"symbols": [],
}
`;

103
packages/core/test/specs/data_sources/serialization.ts

@ -5,7 +5,6 @@ import { DataVariableType } from '../../../src/data_sources/model/DataVariable';
import EditorModel from '../../../src/editor/model/Editor';
import { ProjectData } from '../../../src/storage_manager';
import { filterObjectForSnapshot, setupTestEditor } from '../../common';
import { dynamicAttrKey } from '../../../src/dom_components/model/Component';
describe('DataSource Serialization', () => {
let editor: Editor;
let em: EditorModel;
@ -146,45 +145,6 @@ describe('DataSource Serialization', () => {
const snapshot = filterObjectForSnapshot(projectData);
expect(snapshot).toMatchSnapshot(``);
});
test('TraitDataVariable', () => {
const dataVariable = {
type: DataVariableType,
defaultValue: 'default',
path: `${traitDataSource.id}.id1.value`,
};
cmpRoot.append({
tagName: 'input',
traits: [
'name',
{
type: 'text',
label: 'Value',
name: 'value',
value: dataVariable,
},
],
})[0];
const projectData = editor.getProjectData();
const page = projectData.pages[0];
const frame = page.frames[0];
const component = frame.component.components[0];
expect(component).toHaveProperty(dynamicAttrKey);
expect(component[dynamicAttrKey][0]).toEqual(
expect.objectContaining({
name: 'value',
value: dataVariable,
}),
);
expect(component.attributes).toEqual({
value: 'test-value',
});
const snapshot = filterObjectForSnapshot(projectData);
expect(snapshot).toMatchSnapshot(``);
});
});
describe('.loadProjectData', () => {
@ -373,68 +333,5 @@ describe('DataSource Serialization', () => {
color: 'red',
});
});
test('TraitDataVariable', () => {
const componentProjectData: ProjectData = {
assets: [],
pages: [
{
frames: [
{
component: {
components: [
{
[dynamicAttrKey]: [
{
name: 'value',
value: {
path: 'test-input.id1.value',
type: 'data-variable',
defaultValue: 'default',
},
},
],
tagName: 'input',
void: true,
},
],
docEl: {
tagName: 'html',
},
head: {
type: 'head',
},
stylable: [
'background',
'background-color',
'background-image',
'background-repeat',
'background-attachment',
'background-position',
'background-size',
],
type: 'wrapper',
},
id: 'frameid',
},
],
id: 'pageid',
type: 'main',
},
],
styles: [],
symbols: [],
dataSources: [traitDataSource],
};
editor.loadProjectData(componentProjectData);
const components = editor.getComponents();
const component = components.models[0];
const value = component.getAttributes();
expect(value).toEqual({
value: 'test-value',
});
});
});
});

Loading…
Cancel
Save