From 9dc773aedaa2fb6267a34178b0825db663d6bb67 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Fri, 27 Dec 2024 06:20:05 +0200 Subject: [PATCH] Remove watching and serialization of dynamic traits --- .../src/dom_components/model/Component.ts | 43 +- .../model/ComponentDynamicValueWatcher.ts | 56 +-- .../core/src/trait_manager/model/Trait.ts | 2 - .../__snapshots__/serialization.ts.snap | 67 --- .../data_sources/model/TraitDataVariable.ts | 441 +++++------------- .../ConditionalTraits.ts | 239 +++------- .../__snapshots__/ConditionalTraits.ts.snap | 72 --- .../test/specs/data_sources/serialization.ts | 103 ---- 8 files changed, 195 insertions(+), 828 deletions(-) delete mode 100644 packages/core/test/specs/data_sources/model/conditional_variables/__snapshots__/ConditionalTraits.ts.snap diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index c59408d44..926aa2feb 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/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 {} 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 { options = optionsOrUndefined || options; } - const areStaticAttributes = DynamicValueWatcher.areStaticValues(attributes); - let evaluatedAttributes: Partial; - 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 { 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 { 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 { 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 { 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 */ diff --git a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts b/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts index a78e2a50f..23e0b2b4c 100644 --- a/packages/core/src/dom_components/model/ComponentDynamicValueWatcher.ts +++ b/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(); } } diff --git a/packages/core/src/trait_manager/model/Trait.ts b/packages/core/src/trait_manager/model/Trait.ts index 20e33153b..497a0741d 100644 --- a/packages/core/src/trait_manager/model/Trait.ts +++ b/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`. diff --git a/packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap b/packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap index 73692160d..2a8343c87 100644 --- a/packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap +++ b/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": [], -} -`; diff --git a/packages/core/test/specs/data_sources/model/TraitDataVariable.ts b/packages/core/test/specs/data_sources/model/TraitDataVariable.ts index 4778c73ed..f2ec24e05 100644 --- a/packages/core/test/specs/data_sources/model/TraitDataVariable.ts +++ b/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'); }); }); diff --git a/packages/core/test/specs/data_sources/model/conditional_variables/ConditionalTraits.ts b/packages/core/test/specs/data_sources/model/conditional_variables/ConditionalTraits.ts index fa7f78d9c..d5f2cac3b 100644 --- a/packages/core/test/specs/data_sources/model/conditional_variables/ConditionalTraits.ts +++ b/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); -} diff --git a/packages/core/test/specs/data_sources/model/conditional_variables/__snapshots__/ConditionalTraits.ts.snap b/packages/core/test/specs/data_sources/model/conditional_variables/__snapshots__/ConditionalTraits.ts.snap deleted file mode 100644 index cc410caa3..000000000 --- a/packages/core/test/specs/data_sources/model/conditional_variables/__snapshots__/ConditionalTraits.ts.snap +++ /dev/null @@ -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": [], -} -`; diff --git a/packages/core/test/specs/data_sources/serialization.ts b/packages/core/test/specs/data_sources/serialization.ts index 923773fb0..db2a03de5 100644 --- a/packages/core/test/specs/data_sources/serialization.ts +++ b/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', - }); - }); }); });