diff --git a/test/specs/data_sources/index.ts b/test/specs/data_sources/index.ts index b10d00c7c..3f60bdb2b 100644 --- a/test/specs/data_sources/index.ts +++ b/test/specs/data_sources/index.ts @@ -1,9 +1,7 @@ import Editor from '../../../src/editor/model/Editor'; import DataSourceManager from '../../../src/data_sources'; -import { DataSourceProps, DataSourcesEvents } from '../../../src/data_sources/types'; +import { DataSourceProps } from '../../../src/data_sources/types'; import ComponentWrapper from '../../../src/dom_components/model/ComponentWrapper'; -import ComponentDataVariable from '../../../src/data_sources/model/ComponentDataVariable'; -import { DataVariableType } from '../../../src/data_sources/model/DataVariable'; describe('DataSourceManager', () => { let em: Editor; @@ -35,270 +33,6 @@ describe('DataSourceManager', () => { expect(dsm).toBeTruthy(); }); - describe('Style', () => { - let fixtures: HTMLElement; - let cmpRoot: ComponentWrapper; - - beforeEach(() => { - document.body.innerHTML = '
'; - const { Pages, Components } = em; - Pages.onLoad(); - cmpRoot = Components.getWrapper()!; - const View = Components.getType('wrapper')!.view; - const wrapperEl = new View({ - model: cmpRoot, - config: { ...cmpRoot.config, em }, - }); - wrapperEl.render(); - fixtures = document.body.querySelector('#fixtures')!; - fixtures.appendChild(wrapperEl.el); - }); - - test('component initializes with data-variable style', () => { - const styleDataSource: DataSourceProps = { - id: 'colors-data', - records: [{ id: 'id1', color: 'red' }], - }; - dsm.add(styleDataSource); - - const cmp = cmpRoot.append({ - tagName: 'h1', - type: 'text', - content: 'Hello World', - style: { - color: { - type: DataVariableType, - value: 'black', - path: 'colors-data.id1.color', - }, - }, - })[0]; - - const style = cmp.getStyle(); - expect(style).toHaveProperty('color', 'red'); - }); - - test('component updates on style change', () => { - const styleDataSource: DataSourceProps = { - id: 'colors-data', - records: [{ id: 'id1', color: 'red' }], - }; - dsm.add(styleDataSource); - - const cmp = cmpRoot.append({ - tagName: 'h1', - type: 'text', - content: 'Hello World', - style: { - color: { - type: DataVariableType, - value: 'black', - path: 'colors-data.id1.color', - }, - }, - })[0]; - - const style = cmp.getStyle(); - expect(style).toHaveProperty('color', 'red'); - - const colorsDatasource = dsm.get('colors-data'); - colorsDatasource.getRecord('id1')?.set({ color: 'blue' }); - - const updatedStyle = cmp.getStyle(); - expect(updatedStyle).toHaveProperty('color', 'blue'); - }); - - test("should use default value if data source doesn't exist", () => { - const cmp = cmpRoot.append({ - tagName: 'h1', - type: 'text', - content: 'Hello World', - style: { - color: { - type: DataVariableType, - value: 'black', - path: 'unknown.id1.color', - }, - }, - })[0]; - - const style = cmp.getStyle(); - expect(style).toHaveProperty('color', 'black'); - }); - }); - - describe('Transformers', () => { - let fixtures: HTMLElement; - let cmpRoot: ComponentWrapper; - - beforeEach(() => { - document.body.innerHTML = '
'; - const { Pages, Components } = em; - Pages.onLoad(); - cmpRoot = Components.getWrapper()!; - const View = Components.getType('wrapper')!.view; - const wrapperEl = new View({ - model: cmpRoot, - config: { ...cmpRoot.config, em }, - }); - wrapperEl.render(); - fixtures = document.body.querySelector('#fixtures')!; - fixtures.appendChild(wrapperEl.el); - }); - - test('onRecordAdd', () => { - const testDataSource: DataSourceProps = { - id: 'test-data-source', - records: [], - transformers: { - onRecordAdd: ({ record }) => { - record.content = record.content.toUpperCase(); - return record; - }, - }, - }; - dsm.add(testDataSource); - - const cmp = cmpRoot.append({ - tagName: 'h1', - type: 'text', - components: [ - { - type: DataVariableType, - value: 'default', - path: 'test-data-source.id1.content', - }, - ], - })[0]; - - const ds = dsm.get('test-data-source'); - ds.addRecord({ id: 'id1', content: 'i love grapes' }); - - const el = cmp.getEl(); - expect(el?.innerHTML).toContain('I LOVE GRAPES'); - - const result = ds.getRecord('id1')?.get('content'); - expect(result).toBe('I LOVE GRAPES'); - }); - - test('onRecordSet', () => { - const testDataSource: DataSourceProps = { - id: 'test-data-source', - records: [], - transformers: { - onRecordSet: ({ id, key, value }) => { - if (key !== 'content') { - return value; - } - - if (typeof value !== 'string') { - throw new Error('Value must be a string'); - } - - return value.toUpperCase(); - }, - }, - }; - dsm.add(testDataSource); - - const cmp = cmpRoot.append({ - tagName: 'h1', - type: 'text', - components: [ - { - type: DataVariableType, - value: 'default', - path: 'test-data-source.id1.content', - }, - ], - })[0]; - - const ds = dsm.get('test-data-source'); - const dr = ds.addRecord({ id: 'id1', content: 'i love grapes' }); - - expect(() => dr.set('content', 123)).toThrowError('Value must be a string'); - - dr.set('content', 'I LOVE GRAPES'); - - const el = cmp.getEl(); - expect(el?.innerHTML).toContain('I LOVE GRAPES'); - - const result = ds.getRecord('id1')?.get('content'); - expect(result).toBe('I LOVE GRAPES'); - }); - - test('onRecordRead', () => { - const testDataSource: DataSourceProps = { - id: 'test-data-source', - records: [], - transformers: { - onRecordRead: ({ record }) => { - const content = record.get('content'); - - return record.set('content', content.toUpperCase(), { avoidTransformers: true }); - }, - }, - }; - dsm.add(testDataSource); - - const cmp = cmpRoot.append({ - tagName: 'h1', - type: 'text', - components: [ - { - type: DataVariableType, - value: 'default', - path: 'test-data-source.id1.content', - }, - ], - })[0]; - - const ds = dsm.get('test-data-source'); - ds.addRecord({ id: 'id1', content: 'i love grapes' }); - - const el = cmp.getEl(); - expect(el?.innerHTML).toContain('I LOVE GRAPES'); - - const result = ds.getRecord('id1')?.get('content'); - expect(result).toBe('I LOVE GRAPES'); - }); - - test('onRecordDelete', () => { - const testDataSource: DataSourceProps = { - id: 'test-data-source', - records: [], - transformers: { - onRecordDelete: ({ record }) => { - if (record.get('content') === 'i love grapes') { - throw new Error('Cannot delete record with content "i love grapes"'); - } - }, - }, - }; - dsm.add(testDataSource); - - const cmp = cmpRoot.append({ - tagName: 'h1', - type: 'text', - components: [ - { - type: DataVariableType, - value: 'default', - path: 'test-data-source.id1.content', - }, - ], - })[0]; - - const ds = dsm.get('test-data-source'); - ds.addRecord({ id: 'id1', content: 'i love grapes' }); - - let el = cmp.getEl(); - expect(el?.innerHTML).toContain('i love grapes'); - - expect(() => ds.removeRecord('id1')).toThrowError('Cannot delete record with content "i love grapes"'); - }); - }); - describe('Traits', () => { let fixtures: HTMLElement; let cmpRoot: ComponentWrapper; @@ -318,250 +52,28 @@ describe('DataSourceManager', () => { fixtures.appendChild(wrapperEl.el); }); - describe('input component', () => { - test('component initializes with trait data-variable on input component', () => { - const inputDataSource: DataSourceProps = { - 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, - value: 'default', - path: 'test-input.id1.value', - }, - }, - ], - })[0]; - - const input = cmp.getEl(); - expect(input?.getAttribute('value')).toBe('test-value'); - }); - - test('component updates with trait data-variable on input component', () => { - const inputDataSource: DataSourceProps = { - 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, - value: 'default', - path: 'test-input.id1.value', - }, - }, - ], - })[0]; - - const input = cmp.getEl(); - expect(input?.getAttribute('value')).toBe('test-value'); - - const testDs = dsm.get('test-input'); - testDs.getRecord('id1')?.set({ value: 'new-value' }); - - expect(input?.getAttribute('value')).toBe('new-value'); - }); - }); - }); - - test('add DataSource with records', () => { - const eventAdd = jest.fn(); - em.on(dsm.events.add, eventAdd); - const ds = addDataSource(); - expect(dsm.getAll().length).toBe(1); - expect(eventAdd).toBeCalledTimes(1); - expect(ds.getRecords().length).toBe(3); - }); - - test('get added DataSource', () => { - const ds = addDataSource(); - expect(dsm.get(dsTest.id)).toBe(ds); - }); - - test('remove DataSource', () => { - const event = jest.fn(); - em.on(dsm.events.remove, event); - const ds = addDataSource(); - dsm.remove('ds1'); - expect(dsm.getAll().length).toBe(0); - expect(event).toBeCalledTimes(1); - expect(event).toBeCalledWith(ds, expect.any(Object)); - }); - - describe('DataSource with DataVariable component', () => { - let fixtures: HTMLElement; - let cmpRoot: ComponentWrapper; - - const addDataVariable = (path = 'ds1.id1.name') => - cmpRoot.append({ - type: DataVariableType, - value: 'default', - path, - })[0]; - - beforeEach(() => { - document.body.innerHTML = '
'; - const { Pages, Components } = em; - Pages.onLoad(); - cmpRoot = Components.getWrapper()!; - const View = Components.getType('wrapper')!.view; - const wrapperEl = new View({ - model: cmpRoot, - config: { ...cmpRoot.config, em }, - }); - wrapperEl.render(); - fixtures = document.body.querySelector('#fixtures')!; - fixtures.appendChild(wrapperEl.el); - }); - - describe('Export', () => { - test('component exports properly with default value', () => { - const cmpVar = addDataVariable(); - expect(cmpVar.toHTML()).toBe('
default
'); - }); - - test('component exports properly with current value', () => { - addDataSource(); - const cmpVar = addDataVariable(); - expect(cmpVar.toHTML()).toBe('
Name1
'); - }); - - test('component exports properly with variable', () => { - addDataSource(); - const cmpVar = addDataVariable(); - expect(cmpVar.getInnerHTML({ keepVariables: true })).toBe('ds1.id1.name'); - }); - }); - - test('component is properly initiliazed with default value', () => { - const cmpVar = addDataVariable(); - expect(cmpVar.getEl()?.innerHTML).toBe('default'); - }); - - test('component is properly initiliazed with current value', () => { - addDataSource(); - const cmpVar = addDataVariable(); - expect(cmpVar.getEl()?.innerHTML).toBe('Name1'); - }); - - test('component is properly updating on its default value change', () => { - const cmpVar = addDataVariable(); - cmpVar.set({ value: 'none' }); - expect(cmpVar.getEl()?.innerHTML).toBe('none'); - }); - - test('component is properly updating on its path change', () => { - const eventFn1 = jest.fn(); - const eventFn2 = jest.fn(); + test('add DataSource with records', () => { + const eventAdd = jest.fn(); + em.on(dsm.events.add, eventAdd); const ds = addDataSource(); - const cmpVar = addDataVariable(); - const el = cmpVar.getEl()!; - const pathEvent = DataSourcesEvents.path; - - cmpVar.set({ path: 'ds1.id2.name' }); - expect(el.innerHTML).toBe('Name2'); - em.on(`${pathEvent}:ds1.id2.name`, eventFn1); - ds.getRecord('id2')?.set({ name: 'Name2-UP' }); - - cmpVar.set({ path: 'ds1[id3]name' }); - expect(el.innerHTML).toBe('Name3'); - em.on(`${pathEvent}:ds1.id3.name`, eventFn2); - ds.getRecord('id3')?.set({ name: 'Name3-UP' }); - - expect(el.innerHTML).toBe('Name3-UP'); - expect(eventFn1).toBeCalledTimes(1); - expect(eventFn2).toBeCalledTimes(1); + expect(dsm.getAll().length).toBe(1); + expect(eventAdd).toBeCalledTimes(1); + expect(ds.getRecords().length).toBe(3); }); - describe('DataSource changes', () => { - test('component is properly updating on data source add', () => { - const eventFn = jest.fn(); - em.on(DataSourcesEvents.add, eventFn); - const cmpVar = addDataVariable(); - const ds = addDataSource(); - expect(eventFn).toBeCalledTimes(1); - expect(eventFn).toBeCalledWith(ds, expect.any(Object)); - expect(cmpVar.getEl()?.innerHTML).toBe('Name1'); - }); - - test('component is properly updating on data source reset', () => { - addDataSource(); - const cmpVar = addDataVariable(); - const el = cmpVar.getEl()!; - expect(el.innerHTML).toBe('Name1'); - dsm.all.reset(); - expect(el.innerHTML).toBe('default'); - }); - - test('component is properly updating on data source remove', () => { - const eventFn = jest.fn(); - em.on(DataSourcesEvents.remove, eventFn); - const ds = addDataSource(); - const cmpVar = addDataVariable(); - const el = cmpVar.getEl()!; - dsm.remove('ds1'); - expect(eventFn).toBeCalledTimes(1); - expect(eventFn).toBeCalledWith(ds, expect.any(Object)); - expect(el.innerHTML).toBe('default'); - }); + test('get added DataSource', () => { + const ds = addDataSource(); + expect(dsm.get(dsTest.id)).toBe(ds); }); - describe('DataRecord changes', () => { - test('component is properly updating on record add', () => { - const ds = addDataSource(); - const cmpVar = addDataVariable('ds1[id4]name'); - const eventFn = jest.fn(); - em.on(`${DataSourcesEvents.path}:ds1.id4.name`, eventFn); - const newRecord = ds.addRecord({ id: 'id4', name: 'Name4' }); - expect(cmpVar.getEl()?.innerHTML).toBe('Name4'); - newRecord.set({ name: 'up' }); - expect(cmpVar.getEl()?.innerHTML).toBe('up'); - expect(eventFn).toBeCalledTimes(1); - }); - - test('component is properly updating on record change', () => { - const ds = addDataSource(); - const cmpVar = addDataVariable(); - const el = cmpVar.getEl()!; - ds.getRecord('id1')?.set({ name: 'Name1-UP' }); - expect(el.innerHTML).toBe('Name1-UP'); - }); - - test('component is properly updating on record remove', () => { - const ds = addDataSource(); - const cmpVar = addDataVariable(); - const el = cmpVar.getEl()!; - ds.removeRecord('id1'); - expect(el.innerHTML).toBe('default'); - }); - - test('component is properly updating on record reset', () => { - const ds = addDataSource(); - const cmpVar = addDataVariable(); - const el = cmpVar.getEl()!; - ds.records.reset(); - expect(el.innerHTML).toBe('default'); - }); + test('remove DataSource', () => { + const event = jest.fn(); + em.on(dsm.events.remove, event); + const ds = addDataSource(); + dsm.remove('ds1'); + expect(dsm.getAll().length).toBe(0); + expect(event).toBeCalledTimes(1); + expect(event).toBeCalledWith(ds, expect.any(Object)); }); }); }); diff --git a/test/specs/data_sources/model/ComponentDataVariable.ts b/test/specs/data_sources/model/ComponentDataVariable.ts new file mode 100644 index 000000000..7626fb711 --- /dev/null +++ b/test/specs/data_sources/model/ComponentDataVariable.ts @@ -0,0 +1,184 @@ +import Editor from '../../../../src/editor/model/Editor'; +import DataSourceManager from '../../../../src/data_sources'; +import { DataSourceProps, DataSourcesEvents } from '../../../../src/data_sources/types'; +import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper'; +import ComponentDataVariable from '../../../../src/data_sources/model/ComponentDataVariable'; +import { DataVariableType } from '../../../../src/data_sources/model/DataVariable'; + +describe('ComponentDataVariable', () => { + let em: Editor; + let dsm: DataSourceManager; + let fixtures: HTMLElement; + let cmpRoot: ComponentWrapper; + + const addDataVariable = (path = 'ds1.id1.name') => + cmpRoot.append({ + type: DataVariableType, + value: 'default', + path, + })[0]; + + const dsTest: DataSourceProps = { + id: 'ds1', + records: [ + { id: 'id1', name: 'Name1' }, + { id: 'id2', name: 'Name2' }, + { id: 'id3', name: 'Name3' }, + ], + }; + const addDataSource = () => dsm.add(dsTest); + + beforeEach(() => { + em = new Editor({ + mediaCondition: 'max-width', + avoidInlineStyle: true, + }); + dsm = em.DataSources; + document.body.innerHTML = '
'; + const { Pages, Components } = em; + Pages.onLoad(); + cmpRoot = Components.getWrapper()!; + const View = Components.getType('wrapper')!.view; + const wrapperEl = new View({ + model: cmpRoot, + config: { ...cmpRoot.config, em }, + }); + wrapperEl.render(); + fixtures = document.body.querySelector('#fixtures')!; + fixtures.appendChild(wrapperEl.el); + }); + + afterEach(() => { + em.destroy(); + }); + + describe('Export', () => { + test('component exports properly with default value', () => { + const cmpVar = addDataVariable(); + expect(cmpVar.toHTML()).toBe('
default
'); + }); + + test('component exports properly with current value', () => { + addDataSource(); + const cmpVar = addDataVariable(); + expect(cmpVar.toHTML()).toBe('
Name1
'); + }); + + test('component exports properly with variable', () => { + addDataSource(); + const cmpVar = addDataVariable(); + expect(cmpVar.getInnerHTML({ keepVariables: true })).toBe('ds1.id1.name'); + }); + }); + + test('component is properly initiliazed with default value', () => { + const cmpVar = addDataVariable(); + expect(cmpVar.getEl()?.innerHTML).toBe('default'); + }); + + test('component is properly initiliazed with current value', () => { + addDataSource(); + const cmpVar = addDataVariable(); + expect(cmpVar.getEl()?.innerHTML).toBe('Name1'); + }); + + test('component is properly updating on its default value change', () => { + const cmpVar = addDataVariable(); + cmpVar.set({ value: 'none' }); + expect(cmpVar.getEl()?.innerHTML).toBe('none'); + }); + + test('component is properly updating on its path change', () => { + const eventFn1 = jest.fn(); + const eventFn2 = jest.fn(); + const ds = addDataSource(); + const cmpVar = addDataVariable(); + const el = cmpVar.getEl()!; + const pathEvent = DataSourcesEvents.path; + + cmpVar.set({ path: 'ds1.id2.name' }); + expect(el.innerHTML).toBe('Name2'); + em.on(`${pathEvent}:ds1.id2.name`, eventFn1); + ds.getRecord('id2')?.set({ name: 'Name2-UP' }); + + cmpVar.set({ path: 'ds1[id3]name' }); + expect(el.innerHTML).toBe('Name3'); + em.on(`${pathEvent}:ds1.id3.name`, eventFn2); + ds.getRecord('id3')?.set({ name: 'Name3-UP' }); + + expect(el.innerHTML).toBe('Name3-UP'); + expect(eventFn1).toBeCalledTimes(1); + expect(eventFn2).toBeCalledTimes(1); + }); + + describe('DataSource changes', () => { + test('component is properly updating on data source add', () => { + const eventFn = jest.fn(); + em.on(DataSourcesEvents.add, eventFn); + const cmpVar = addDataVariable(); + const ds = addDataSource(); + expect(eventFn).toBeCalledTimes(1); + expect(eventFn).toBeCalledWith(ds, expect.any(Object)); + expect(cmpVar.getEl()?.innerHTML).toBe('Name1'); + }); + + test('component is properly updating on data source reset', () => { + addDataSource(); + const cmpVar = addDataVariable(); + const el = cmpVar.getEl()!; + expect(el.innerHTML).toBe('Name1'); + dsm.all.reset(); + expect(el.innerHTML).toBe('default'); + }); + + test('component is properly updating on data source remove', () => { + const eventFn = jest.fn(); + em.on(DataSourcesEvents.remove, eventFn); + const ds = addDataSource(); + const cmpVar = addDataVariable(); + const el = cmpVar.getEl()!; + dsm.remove('ds1'); + expect(eventFn).toBeCalledTimes(1); + expect(eventFn).toBeCalledWith(ds, expect.any(Object)); + expect(el.innerHTML).toBe('default'); + }); + }); + + describe('DataRecord changes', () => { + test('component is properly updating on record add', () => { + const ds = addDataSource(); + const cmpVar = addDataVariable('ds1[id4]name'); + const eventFn = jest.fn(); + em.on(`${DataSourcesEvents.path}:ds1.id4.name`, eventFn); + const newRecord = ds.addRecord({ id: 'id4', name: 'Name4' }); + expect(cmpVar.getEl()?.innerHTML).toBe('Name4'); + newRecord.set({ name: 'up' }); + expect(cmpVar.getEl()?.innerHTML).toBe('up'); + expect(eventFn).toBeCalledTimes(1); + }); + + test('component is properly updating on record change', () => { + const ds = addDataSource(); + const cmpVar = addDataVariable(); + const el = cmpVar.getEl()!; + ds.getRecord('id1')?.set({ name: 'Name1-UP' }); + expect(el.innerHTML).toBe('Name1-UP'); + }); + + test('component is properly updating on record remove', () => { + const ds = addDataSource(); + const cmpVar = addDataVariable(); + const el = cmpVar.getEl()!; + ds.removeRecord('id1'); + expect(el.innerHTML).toBe('default'); + }); + + test('component is properly updating on record reset', () => { + const ds = addDataSource(); + const cmpVar = addDataVariable(); + const el = cmpVar.getEl()!; + ds.records.reset(); + expect(el.innerHTML).toBe('default'); + }); + }); +}); diff --git a/test/specs/data_sources/model/StyleDataVariable.ts b/test/specs/data_sources/model/StyleDataVariable.ts new file mode 100644 index 000000000..44634be73 --- /dev/null +++ b/test/specs/data_sources/model/StyleDataVariable.ts @@ -0,0 +1,108 @@ +import Editor from '../../../../src/editor/model/Editor'; +import DataSourceManager from '../../../../src/data_sources'; +import { DataSourceProps } from '../../../../src/data_sources/types'; +import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper'; +import { DataVariableType } from '../../../../src/data_sources/model/DataVariable'; + +describe('StyleDataVariable', () => { + let em: Editor; + let dsm: DataSourceManager; + let fixtures: HTMLElement; + let cmpRoot: ComponentWrapper; + + beforeEach(() => { + em = new Editor({ + mediaCondition: 'max-width', + avoidInlineStyle: true, + }); + dsm = em.DataSources; + document.body.innerHTML = '
'; + const { Pages, Components } = em; + Pages.onLoad(); + cmpRoot = Components.getWrapper()!; + const View = Components.getType('wrapper')!.view; + const wrapperEl = new View({ + model: cmpRoot, + config: { ...cmpRoot.config, em }, + }); + wrapperEl.render(); + fixtures = document.body.querySelector('#fixtures')!; + fixtures.appendChild(wrapperEl.el); + }); + + afterEach(() => { + em.destroy(); + }); + + test('component initializes with data-variable style', () => { + const styleDataSource: DataSourceProps = { + id: 'colors-data', + records: [{ id: 'id1', color: 'red' }], + }; + dsm.add(styleDataSource); + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + content: 'Hello World', + style: { + color: { + type: DataVariableType, + value: 'black', + path: 'colors-data.id1.color', + }, + }, + })[0]; + + const style = cmp.getStyle(); + expect(style).toHaveProperty('color', 'red'); + }); + + test('component updates on style change', () => { + const styleDataSource: DataSourceProps = { + id: 'colors-data', + records: [{ id: 'id1', color: 'red' }], + }; + dsm.add(styleDataSource); + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + content: 'Hello World', + style: { + color: { + type: DataVariableType, + value: 'black', + path: 'colors-data.id1.color', + }, + }, + })[0]; + + const style = cmp.getStyle(); + expect(style).toHaveProperty('color', 'red'); + + const colorsDatasource = dsm.get('colors-data'); + colorsDatasource.getRecord('id1')?.set({ color: 'blue' }); + + const updatedStyle = cmp.getStyle(); + expect(updatedStyle).toHaveProperty('color', 'blue'); + }); + + test("should use default value if data source doesn't exist", () => { + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + content: 'Hello World', + style: { + color: { + type: DataVariableType, + value: 'black', + path: 'unknown.id1.color', + }, + }, + })[0]; + + const style = cmp.getStyle(); + expect(style).toHaveProperty('color', 'black'); + }); +}); diff --git a/test/specs/data_sources/model/TraitDataVariable.ts b/test/specs/data_sources/model/TraitDataVariable.ts new file mode 100644 index 000000000..0e22351f2 --- /dev/null +++ b/test/specs/data_sources/model/TraitDataVariable.ts @@ -0,0 +1,101 @@ +import Editor from '../../../../src/editor/model/Editor'; +import DataSourceManager from '../../../../src/data_sources'; +import { DataSourceProps } from '../../../../src/data_sources/types'; +import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper'; +import { DataVariableType } from '../../../../src/data_sources/model/DataVariable'; + +describe('TraitDataVariable', () => { + let em: Editor; + let dsm: DataSourceManager; + let fixtures: HTMLElement; + let cmpRoot: ComponentWrapper; + + beforeEach(() => { + em = new Editor({ + mediaCondition: 'max-width', + avoidInlineStyle: true, + }); + dsm = em.DataSources; + document.body.innerHTML = '
'; + const { Pages, Components } = em; + Pages.onLoad(); + cmpRoot = Components.getWrapper()!; + const View = Components.getType('wrapper')!.view; + const wrapperEl = new View({ + model: cmpRoot, + config: { ...cmpRoot.config, em }, + }); + wrapperEl.render(); + fixtures = document.body.querySelector('#fixtures')!; + fixtures.appendChild(wrapperEl.el); + }); + + afterEach(() => { + em.destroy(); + }); + + describe('input component', () => { + test('component initializes with trait data-variable on input component', () => { + const inputDataSource: DataSourceProps = { + 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, + value: 'default', + path: 'test-input.id1.value', + }, + }, + ], + })[0]; + + const input = cmp.getEl(); + expect(input?.getAttribute('value')).toBe('test-value'); + }); + + test('component updates with trait data-variable on input component', () => { + const inputDataSource: DataSourceProps = { + 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, + value: 'default', + path: 'test-input.id1.value', + }, + }, + ], + })[0]; + + const input = cmp.getEl(); + expect(input?.getAttribute('value')).toBe('test-value'); + + const testDs = dsm.get('test-input'); + testDs.getRecord('id1')?.set({ value: 'new-value' }); + + expect(input?.getAttribute('value')).toBe('new-value'); + }); + }); +}); diff --git a/test/specs/data_sources/transformers.ts b/test/specs/data_sources/transformers.ts new file mode 100644 index 000000000..00290e5f3 --- /dev/null +++ b/test/specs/data_sources/transformers.ts @@ -0,0 +1,188 @@ +import Editor from '../../../src/editor/model/Editor'; +import DataSourceManager from '../../../src/data_sources'; +import { DataSourceProps } from '../../../src/data_sources/types'; +import ComponentWrapper from '../../../src/dom_components/model/ComponentWrapper'; +import { DataVariableType } from '../../../src/data_sources/model/DataVariable'; + +describe('DataSource Transformers', () => { + let em: Editor; + let dsm: DataSourceManager; + let fixtures: HTMLElement; + let cmpRoot: ComponentWrapper; + + beforeEach(() => { + em = new Editor({ + mediaCondition: 'max-width', + avoidInlineStyle: true, + }); + dsm = em.DataSources; + document.body.innerHTML = '
'; + const { Pages, Components } = em; + Pages.onLoad(); + cmpRoot = Components.getWrapper()!; + const View = Components.getType('wrapper')!.view; + const wrapperEl = new View({ + model: cmpRoot, + config: { ...cmpRoot.config, em }, + }); + wrapperEl.render(); + fixtures = document.body.querySelector('#fixtures')!; + fixtures.appendChild(wrapperEl.el); + }); + + afterEach(() => { + em.destroy(); + }); + + test('onRecordAdd', () => { + const testDataSource: DataSourceProps = { + id: 'test-data-source', + records: [], + transformers: { + onRecordAdd: ({ record }) => { + record.content = record.content.toUpperCase(); + return record; + }, + }, + }; + dsm.add(testDataSource); + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + components: [ + { + type: DataVariableType, + value: 'default', + path: 'test-data-source.id1.content', + }, + ], + })[0]; + + const ds = dsm.get('test-data-source'); + ds.addRecord({ id: 'id1', content: 'i love grapes' }); + + const el = cmp.getEl(); + expect(el?.innerHTML).toContain('I LOVE GRAPES'); + + const result = ds.getRecord('id1')?.get('content'); + expect(result).toBe('I LOVE GRAPES'); + }); + + test('onRecordSet', () => { + const testDataSource: DataSourceProps = { + id: 'test-data-source', + records: [], + transformers: { + onRecordSet: ({ id, key, value }) => { + if (key !== 'content') { + return value; + } + + if (typeof value !== 'string') { + throw new Error('Value must be a string'); + } + + return value.toUpperCase(); + }, + }, + }; + dsm.add(testDataSource); + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + components: [ + { + type: DataVariableType, + value: 'default', + path: 'test-data-source.id1.content', + }, + ], + })[0]; + + const ds = dsm.get('test-data-source'); + const dr = ds.addRecord({ id: 'id1', content: 'i love grapes' }); + + expect(() => dr.set('content', 123)).toThrowError('Value must be a string'); + + dr.set('content', 'I LOVE GRAPES'); + + const el = cmp.getEl(); + expect(el?.innerHTML).toContain('I LOVE GRAPES'); + + const result = ds.getRecord('id1')?.get('content'); + expect(result).toBe('I LOVE GRAPES'); + }); + + test('onRecordRead', () => { + const testDataSource: DataSourceProps = { + id: 'test-data-source', + records: [], + transformers: { + onRecordRead: ({ record }) => { + const content = record.get('content'); + + return record.set('content', content.toUpperCase(), { avoidTransformers: true }); + }, + }, + }; + dsm.add(testDataSource); + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + components: [ + { + type: DataVariableType, + value: 'default', + path: 'test-data-source.id1.content', + }, + ], + })[0]; + + const ds = dsm.get('test-data-source'); + ds.addRecord({ id: 'id1', content: 'i love grapes' }); + + const el = cmp.getEl(); + expect(el?.innerHTML).toContain('I LOVE GRAPES'); + + const result = ds.getRecord('id1')?.get('content'); + expect(result).toBe('I LOVE GRAPES'); + }); + + test('onRecordDelete', () => { + const testDataSource: DataSourceProps = { + id: 'test-data-source', + records: [], + transformers: { + onRecordDelete: ({ record }) => { + if (record.get('content') === 'i love grapes') { + throw new Error('Cannot delete record with content "i love grapes"'); + } + }, + }, + }; + dsm.add(testDataSource); + + const cmp = cmpRoot.append({ + tagName: 'h1', + type: 'text', + components: [ + { + type: DataVariableType, + value: 'default', + path: 'test-data-source.id1.content', + }, + ], + })[0]; + + const ds = dsm.get('test-data-source'); + ds.addRecord({ id: 'id1', content: 'i love grapes' }); + + let el = cmp.getEl(); + expect(el?.innerHTML).toContain('i love grapes'); + + expect(() => ds.removeRecord('id1')).toThrowError('Cannot delete record with content "i love grapes"'); + }); +});