Free and Open source Web Builder Framework. Next generation tool for building templates without coding
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

794 lines
24 KiB

import { Component, CssRule, Property, PropertyComposite, PropertyStack } from '../../../../src';
import Editor from '../../../../src/editor/model/Editor';
describe('StyleManager properties logic', () => {
let obj: Editor['Styles'];
let em: Editor;
let domc: Editor['Components'];
let dv: Editor['Devices'];
let cssc: Editor['Css'];
let cmp: Component;
let rule1: CssRule;
const sectorTest = 'sector-test';
beforeEach(() => {
em = new Editor({
mediaCondition: 'max-width',
avoidInlineStyle: true,
styleManager: { custom: true },
});
domc = em.Components;
cssc = em.Css;
dv = em.Devices;
obj = em.Styles;
em.Pages.onLoad();
cmp = domc.addComponent('<div class="cls"></div>') as Component;
});
afterEach(() => {
em.destroy();
});
describe('Composite type', () => {
const propTest = 'padding';
const propATest = `${propTest}-top`;
const propBTest = `${propTest}-right`;
const propCTest = `${propTest}-bottom`;
const propDTest = `${propTest}-left`;
const propATestId = `${propATest}-sub`;
const propBTestId = `${propBTest}-sub`;
const propCTestId = `${propCTest}-sub`;
const propDTestId = `${propDTest}-sub`;
let compTypeProp: PropertyComposite;
let compTypePropInn: Property;
beforeEach(() => {
rule1 = cssc.addRules('.cls { color: red; }')[0];
obj.addSector(sectorTest, {
name: 'sector',
properties: [
{
extend: propTest,
// @ts-ignore
detached: true,
},
],
});
compTypeProp = obj.getProperty(sectorTest, propTest) as PropertyComposite;
compTypePropInn = compTypeProp.getProperty(propATestId)!;
em.setSelected(cmp);
obj.__upSel();
});
test('Property exists', () => {
expect(compTypeProp).toBeTruthy();
expect(compTypeProp.getProperties().length).toBe(4);
});
test('Has inner property', () => {
expect(compTypePropInn).toBeTruthy();
});
test('Property has no value', () => {
expect(compTypeProp.hasValue()).toBe(false);
expect(compTypePropInn.hasValue()).toBe(false);
});
test('Rule selected', () => {
expect(obj.getSelected()).toBe(rule1);
});
test('Properties correctly reflect on rule update', () => {
compTypeProp.set('detached', false);
rule1.setStyle({ padding: '1px 2px 3px 4px' });
obj.__upSel();
[
[propATestId, '1px'],
[propBTestId, '2px'],
[propCTestId, '3px'],
[propDTestId, '4px'],
].forEach((item) => {
expect(compTypeProp.getProperty(item[0])?.getFullValue()).toBe(item[1]);
});
});
test('Split value properly', () => {
expect(compTypeProp.__getSplitValue('1px 2px 3px 4px', { byName: true })).toEqual({
[propATest]: '1px',
[propBTest]: '2px',
[propCTest]: '3px',
[propDTest]: '4px',
});
expect(compTypeProp.__getSplitValue('1px 2px 3px 4px')).toEqual({
[propATestId]: '1px',
[propBTestId]: '2px',
[propCTestId]: '3px',
[propDTestId]: '4px',
});
expect(compTypeProp.__getSplitValue('1px 2px')).toEqual({
[propATestId]: '1px',
[propBTestId]: '2px',
[propCTestId]: '1px',
[propDTestId]: '2px',
});
expect(compTypeProp.__getSplitValue('100%')).toEqual({
[propATestId]: '100%',
[propBTestId]: '100%',
[propCTestId]: '100%',
[propDTestId]: '100%',
});
});
test('Updating the main property, the value is splitted properly', () => {
compTypeProp.upValue('1px 2px 3px 4px');
[
[propATest, '1px'],
[propBTest, '2px'],
[propCTest, '3px'],
[propDTest, '4px'],
].forEach((item) => {
expect(compTypeProp.getProperty(item[0])?.getFullValue()).toBe(item[1]);
});
compTypeProp.upValue('11px');
[
[propATest, '11px'],
[propBTest, '11px'],
[propCTest, '11px'],
[propDTest, '11px'],
].forEach((item) => {
expect(compTypeProp.getProperty(item[0])?.getFullValue()).toBe(item[1]);
});
obj.__upSel();
expect(rule1.getStyle()).toEqual({
color: 'red',
[propATest]: '11px',
[propBTest]: '11px',
[propCTest]: '11px',
[propDTest]: '11px',
});
compTypeProp.upValue('');
[
[propATest, ''],
[propBTest, ''],
[propCTest, ''],
[propDTest, ''],
].forEach((item) => {
expect(compTypeProp.getProperty(item[0])?.getFullValue()).toBe(item[1]);
});
});
test('getPropsFromStyle returns correct values', () => {
expect(
compTypeProp.__getPropsFromStyle(
{
[propTest]: '1px 2px 3px 4px',
[propCTest]: '33px',
[propBTest]: '22%',
},
{ byName: true },
),
).toEqual({
[propATest]: '1px',
[propBTest]: '22%',
[propCTest]: '33px',
[propDTest]: '4px',
});
// Split correctly props in 4 numeric values
expect(
compTypeProp.__getPropsFromStyle(
{
[propTest]: '111px',
[propCTest]: '33px',
[propBTest]: '22%',
},
{ byName: true },
),
).toEqual({
[propATest]: '111px',
[propBTest]: '22%',
[propCTest]: '33px',
[propDTest]: '111px',
});
// Resolves correctly with values from inner properties
expect(
compTypeProp.__getPropsFromStyle(
{
color: 'red',
[propCTest]: '33px',
},
{ byName: true },
),
).toEqual({
[propATest]: '',
[propBTest]: '',
[propCTest]: '33px',
[propDTest]: '',
});
// null if no properties are found
expect(compTypeProp.__getPropsFromStyle({ color: 'red' })).toEqual(null);
});
test('Custom fromStyle', () => {
compTypeProp.set('fromStyle', (style, { separator }) => {
const values = (style[propTest] as string).split(separator);
return {
[propATest]: values[0],
[propBTest]: values[1],
};
});
expect(
compTypeProp.__getPropsFromStyle({
[propTest]: 'rgba(valueA 1) rgba(value B)',
}),
).toEqual({
[propATest]: 'rgba(valueA 1)',
[propBTest]: 'rgba(value B)',
});
});
test('getStyleFromProps', () => {
rule1.setStyle({ padding: '1px 2px 3px 4px' });
obj.__upSel();
expect(compTypeProp.getStyleFromProps()).toEqual({
[propTest]: '',
[propATest]: '1px',
[propBTest]: '2px',
[propCTest]: '3px',
[propDTest]: '4px',
});
compTypeProp.set('detached', false);
expect(compTypeProp.getStyleFromProps()).toEqual({
[propTest]: '1px 2px 3px 4px',
[propATest]: '',
[propBTest]: '',
[propCTest]: '',
[propDTest]: '',
});
compTypeProp.set('detached', true);
rule1.setStyle({ [propATest]: '10px', [propBTest]: '20px' });
obj.__upSel();
expect(compTypeProp.getStyleFromProps()).toEqual({
[propTest]: '',
[propATest]: '10px',
[propBTest]: '20px',
[propCTest]: '',
[propDTest]: '',
});
});
test('On clear removes all values', () => {
rule1.setStyle({ padding: '1px 2px 3px 4px' });
obj.__upSel();
expect(compTypeProp.hasValue()).toBe(true);
compTypeProp.clear();
expect(compTypeProp.hasValue()).toBe(false);
expect(rule1.getStyle()).toEqual({});
});
test('Get the values from parent style', () => {
rule1.setStyle({
padding: '11px 22px',
'padding-left': '44px',
});
const rule2 = cssc.addRules(`
@media (max-width: 992px) {
.cls { color: red; }
}
`)[0];
dv.select('tablet');
obj.__upSel();
expect(obj.getSelected()).toBe(rule2);
expect(obj.getSelectedParents()).toEqual([rule1]);
expect(compTypeProp.hasValue()).toBe(true);
expect(compTypeProp.hasValue({ noParent: true })).toBe(false);
[
[propATest, '11px'],
[propBTest, '22px'],
[propCTest, '11px'],
[propDTest, '44px'],
].forEach((item) => {
const prop = compTypeProp.getProperty(item[0])!;
expect(prop.hasValue()).toBe(true);
expect(prop.hasValue({ noParent: true })).toBe(false);
expect(prop.getFullValue()).toBe(item[1]);
});
});
test('Parent styles are ignored if on the lower device', () => {
const rule2 = cssc.addRules(`
@media (max-width: 992px) {
.cls {
padding: 11px 22px;
padding-left: 44px;
}
}
`)[0];
dv.select('tablet');
obj.__upSel();
expect(obj.getSelected()).toBe(rule2);
expect(compTypeProp.hasValue({ noParent: true })).toBe(true);
[
[propATest, '11px'],
[propBTest, '22px'],
[propCTest, '11px'],
[propDTest, '44px'],
].forEach((item) => {
const prop = compTypeProp.getProperty(item[0])!;
expect(prop.getFullValue()).toBe(item[1]);
});
dv.select('desktop');
obj.__upSel();
expect(obj.getSelected()).toBe(rule1);
expect(obj.getSelectedParents()).toEqual([]);
[
[propATest, ''],
[propBTest, ''],
[propCTest, ''],
[propDTest, ''],
].forEach((item) => {
const prop = compTypeProp.getProperty(item[0])!;
expect(prop.hasValue()).toBe(false);
expect(prop.getFullValue()).toBe(item[1]);
});
});
test("Changing one inner rule (detached property) doesn't affect others", () => {
rule1.setStyle({
padding: '1px 2px',
'padding-left': '4px',
});
const rule2 = cssc.addRules(`
@media (max-width: 992px) {
.cls { padding-left: 44px }
}
`)[0];
dv.select('tablet');
obj.__upSel();
[
[propATest, '1px'],
[propBTest, '2px'],
[propCTest, '1px'],
].forEach((item) => {
const prop = compTypeProp.getProperty(item[0])!;
expect(prop.hasValue({ noParent: true })).toBe(false);
expect(prop.getFullValue()).toBe(item[1]);
});
const propD = compTypeProp.getProperty(propDTest)!;
expect(propD.hasValue({ noParent: true })).toBe(true);
expect(propD.getFullValue()).toBe('44px');
// By editing the last one, the rule should not take other values
propD.upValue('45px');
compTypeProp.getProperty(propATest)?.upValue('11px');
obj.__upSel();
expect(rule2.getStyle()).toEqual({
[propATest]: '11px',
[propDTest]: '45px',
});
// Other properties shouldn't change
[
[propBTest, '2px'],
[propCTest, '1px'],
].forEach((item) => {
const prop = compTypeProp.getProperty(item[0])!;
expect(prop.hasValue({ noParent: true })).toBe(false);
expect(prop.getFullValue()).toBe(item[1]);
});
});
test('getStyleFromProps with custom toStyle', () => {
rule1.setStyle({ padding: '1px 2px 3px 4px' });
obj.__upSel();
compTypeProp.set('toStyle', (values) => {
return {
[propTest]: `rgba(${values[propATestId]}, ${values[propBTestId]}, ${values[propDTestId]})`,
};
});
compTypeProp.set('detached', false);
expect(compTypeProp.getStyleFromProps()).toEqual({
[propTest]: 'rgba(1px, 2px, 4px)',
[propATest]: '',
[propBTest]: '',
[propCTest]: '',
[propDTest]: '',
});
});
test('Update on the rule reflects to the property correctly', () => {
rule1.setStyle({ padding: '1px 2px 3px 4px' });
obj.__upSel();
compTypeProp.getProperty(propCTest)?.upValue('50%');
[
[propATest, '1px'],
[propBTest, '2px'],
[propCTest, '50%'],
[propDTest, '4px'],
].forEach((item) => {
const prop = compTypeProp.getProperty(item[0])!;
expect(prop.getFullValue()).toBe(item[1]);
});
});
test('Update on properties reflects to the rule correctly', () => {
compTypeProp.set('detached', false);
rule1.setStyle({ padding: '1px 2px 3px 4px' });
obj.__upSel();
compTypeProp.getProperty(propCTest)?.upValue('50%');
obj.__upSel();
expect(rule1.getStyle()).toEqual({
padding: '1px 2px 50% 4px',
});
});
test('Updating inner property, it reflects on the rule', () => {
compTypePropInn.upValue('55%');
const style = rule1.getStyle();
const otherProps = Object.keys(style).filter((p) => p.indexOf('padding') >= 0 && p !== propATest);
expect(style[propATest]).toBe('55%');
expect(compTypeProp.hasValue()).toBe(true);
expect(compTypePropInn.hasValue()).toBe(true);
otherProps.forEach((prop) => {
expect(style[prop]).toBe('');
if (prop !== propTest) {
expect(compTypeProp.getProperty(prop)?.hasValue()).toBe(false);
}
});
});
});
describe('Stack type', () => {
const propTest = 'stack-prop';
const propATest = `${propTest}-a`;
const propBTest = `${propTest}-b`;
const propCTest = `${propTest}-c`;
const propsTest = [propATest, propBTest, propCTest];
const propStyleValue = 'valueA-1 valueB-1 valueC-1, valueA-2 valueB-2 valueC-2';
const propStyleExtValue = 'valueC-1-ext, valueC-2-ext';
let compTypeProp: PropertyStack;
let compTypePropInn: Property;
beforeEach(() => {
rule1 = cssc.addRules(`
.cls {
${propTest}: ${propStyleValue};
${propCTest}: ${propStyleExtValue};
}
`)[0];
obj.addSector(sectorTest, {
name: 'My sector',
properties: [
{
type: 'stack',
property: propTest,
emptyValue: '',
properties: propsTest.map((property) => ({ property })),
},
],
});
compTypeProp = obj.getProperty(sectorTest, propTest) as PropertyStack;
compTypePropInn = compTypeProp.getProperty(propATest)!;
em.setSelected(cmp);
obj.__upSel();
});
test('Property exists', () => {
expect(compTypeProp).toBeTruthy();
expect(compTypePropInn).toBeTruthy();
expect(compTypeProp.getProperties().length).toBe(3);
});
test('Has the right number of layers', () => {
expect(compTypeProp.getLayers().length).toBe(2);
});
test('Property has no value', () => {
expect(compTypeProp.hasValue()).toBe(true);
});
test('Has no selected layer', () => {
expect(compTypeProp.getSelectedLayer()).toBe(undefined);
});
test('getLayersFromStyle returns correct values', () => {
expect(
compTypeProp.__getLayersFromStyle({
[propTest]: 'valueA-1 valueB-1 valueC-1, valueA-2 valueB-2 valueC-2',
[propCTest]: 'valueC-1-ext, valueC-2-ext, valueC-3-ext',
[propBTest]: 'valueB-1-ext',
}),
).toEqual([
{
[propATest]: 'valueA-1',
[propBTest]: 'valueB-1-ext',
[propCTest]: 'valueC-1-ext',
},
{
[propATest]: 'valueA-2',
[propBTest]: 'valueB-2',
[propCTest]: 'valueC-2-ext',
},
{
[propCTest]: 'valueC-3-ext',
},
]);
});
test('getLayersFromStyle with empty values', () => {
expect(compTypeProp.__getLayersFromStyle({ [propTest]: '' })).toBe(null);
expect(compTypeProp.__getLayersFromStyle({ color: 'red' })).toBe(null);
});
test('Custom fromStyle', () => {
compTypeProp.set('fromStyle', (style, { separatorLayers }) => {
const layerValues = (style[propTest] as string).split(separatorLayers);
return layerValues.map((value) => ({ value }));
});
expect(
compTypeProp.__getLayersFromStyle({
[propTest]: 'rgba(valueA-1, valueB-1), rgba(valueA-2, valueB-2)',
}),
).toEqual([
{
value: 'rgba(valueA-1, valueB-1)',
},
{
value: 'rgba(valueA-2, valueB-2)',
},
]);
});
test('getStyleFromLayers', () => {
expect(compTypeProp.getStyleFromLayers()).toEqual({
[propTest]: 'valueA-1 valueB-1 valueC-1-ext, valueA-2 valueB-2 valueC-2-ext',
[propATest]: '',
[propBTest]: '',
[propCTest]: '',
});
});
test('getStyleFromLayers detached', () => {
compTypeProp.set('detached', true);
expect(compTypeProp.getStyleFromLayers()).toEqual({
[propTest]: '',
[propATest]: 'valueA-1, valueA-2',
[propBTest]: 'valueB-1, valueB-2',
[propCTest]: 'valueC-1-ext, valueC-2-ext',
});
});
test('Custom toStyle', () => {
compTypeProp.set('toStyle', (values) => {
return {
[propTest]: `rgba(${values[propATest]}, ${values[propBTest]}, ${values[propCTest]})`,
};
});
expect(compTypeProp.getStyleFromLayers()).toEqual({
[propTest]: 'rgba(valueA-1, valueB-1, valueC-1-ext), rgba(valueA-2, valueB-2, valueC-2-ext)',
[propATest]: '',
[propBTest]: '',
[propCTest]: '',
});
});
test('Layers has the right values', () => {
expect(compTypeProp.getLayer(0)?.getValues()).toEqual({
[propATest]: 'valueA-1',
[propBTest]: 'valueB-1',
[propCTest]: 'valueC-1-ext',
});
expect(compTypeProp.getLayer(1)?.getValues()).toEqual({
[propATest]: 'valueA-2',
[propBTest]: 'valueB-2',
[propCTest]: 'valueC-2-ext',
});
});
test('Updating the rule reflects in layers', () => {
rule1.setStyle({
[propTest]: 'valueAa-1 valueBb-1 valueCc-1, valueAa-2 valueBb-2 valueCc-2',
[propBTest]: 'valueB-1-ext, valueB-2-ext',
});
obj.__upSel();
expect(compTypeProp.getLayers().length).toBe(2);
expect(compTypeProp.getLayer(0)?.getValues()).toEqual({
[propATest]: 'valueAa-1',
[propBTest]: 'valueB-1-ext',
[propCTest]: 'valueCc-1',
});
expect(compTypeProp.getLayer(1)?.getValues()).toEqual({
[propATest]: 'valueAa-2',
[propBTest]: 'valueB-2-ext',
[propCTest]: 'valueCc-2',
});
});
test('Updating inner property, it reflects on the rule', () => {
expect(rule1.getStyle()).toEqual({
[propTest]: propStyleValue,
[propCTest]: propStyleExtValue,
});
compTypeProp.selectLayerAt(0);
compTypeProp.getProperty(propBTest)?.upValue('valueB-1-mod');
compTypeProp.selectLayerAt(1);
compTypeProp.getProperty(propCTest)?.upValue('valueC-2-mod');
expect(rule1.getStyle()).toEqual({
[propTest]: 'valueA-1 valueB-1-mod valueC-1-ext, valueA-2 valueB-2 valueC-2-mod',
});
});
test('Updating inner property (detached), it reflects on the rule', () => {
compTypeProp.set('detached', true);
compTypeProp.selectLayerAt(0);
compTypeProp.getProperty(propBTest)?.upValue('valueB-1-mod');
compTypeProp.selectLayerAt(1);
compTypeProp.getProperty(propCTest)?.upValue('valueC-2-mod');
expect(rule1.getStyle()).toEqual({
[propATest]: 'valueA-1, valueA-2',
[propBTest]: 'valueB-1-mod, valueB-2',
[propCTest]: 'valueC-1-ext, valueC-2-mod',
});
});
test('Removing layer, updates the rule', () => {
compTypeProp.removeLayerAt(1);
expect(rule1.getStyle()).toEqual({
[propTest]: 'valueA-1 valueB-1 valueC-1-ext',
});
});
test('Removing all layers', () => {
compTypeProp.removeLayerAt(1);
compTypeProp.removeLayerAt(0);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual({});
});
test('On clear removes all values', () => {
compTypeProp.addLayer();
compTypeProp.clear();
expect(compTypeProp.hasValue()).toBe(false);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual({});
});
test('On clear removes all values (detached)', () => {
compTypeProp.set('detached', true);
compTypeProp.clear();
expect(compTypeProp.hasValue()).toBe(false);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual({});
});
test('Get the values from parent style', () => {
const rule2 = cssc.addRules(`
@media (max-width: 992px) {
.cls { color: red; }
}
`)[0];
dv.select('tablet');
obj.__upSel();
expect(obj.getSelected()).toBe(rule2);
expect(obj.getSelectedParents()).toEqual([rule1]);
expect(compTypeProp.hasValue()).toBe(true);
expect(compTypeProp.hasValue({ noParent: true })).toBe(false);
expect(compTypeProp.getLayers().length).toBe(2);
});
test('Adding new layer, updates the rule', () => {
compTypeProp.addLayer(
{
[propATest]: 'valueA-new',
[propBTest]: 'valueB-new',
[propCTest]: 'valueC-new',
},
{ at: 0 },
);
expect(rule1.getStyle()).toEqual({
[propTest]: 'valueA-new valueB-new valueC-new, valueA-1 valueB-1 valueC-1-ext, valueA-2 valueB-2 valueC-2-ext',
});
});
test('Adding new layer, updates the rule (detached)', () => {
compTypeProp.set('detached', true);
compTypeProp.addLayer(
{
[propATest]: 'valueA-new AA',
[propBTest]: 'valueB-new BB',
[propCTest]: 'valueC-new CC',
},
{ at: 0 },
);
obj.__upSel();
expect(rule1.getStyle()).toEqual({
[propATest]: 'valueA-new AA, valueA-1, valueA-2',
[propBTest]: 'valueB-new BB, valueB-1, valueB-2',
[propCTest]: 'valueC-new CC, valueC-1-ext, valueC-2-ext',
});
// Check also the layers
const layers = compTypeProp.getLayers();
expect(layers.length).toBe(3);
const [layer1, layer2, layer3] = layers;
expect(layer1.getValues()).toEqual({
[propATest]: 'valueA-new AA',
[propBTest]: 'valueB-new BB',
[propCTest]: 'valueC-new CC',
});
expect(layer2.getValues()).toEqual({
[propATest]: 'valueA-1',
[propBTest]: 'valueB-1',
[propCTest]: 'valueC-1-ext',
});
expect(layer3.getValues()).toEqual({
[propATest]: 'valueA-2',
[propBTest]: 'valueB-2',
[propCTest]: 'valueC-2-ext',
});
});
describe('emptyValue', () => {
test('Removing all layers with empty value as string', () => {
compTypeProp.set('emptyValue', 'unset');
compTypeProp.removeLayerAt(1);
compTypeProp.removeLayerAt(0);
const res = { [propTest]: 'unset' };
expect(compTypeProp.isEmptyValueStyle(res)).toBe(true);
expect(compTypeProp.isEmptyValueStyle({})).toBe(false);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual(res);
});
test('Removing all layers with empty value as string (detached)', () => {
compTypeProp.set('emptyValue', 'unset'), compTypeProp.set('detached', true);
compTypeProp.removeLayerAt(1);
compTypeProp.removeLayerAt(0);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual({
[propATest]: 'unset',
[propBTest]: 'unset',
[propCTest]: 'unset',
});
});
test('Removing all layers with empty value as function', () => {
compTypeProp.set('emptyValue', () => ({
[propATest]: 'unset-a',
[propBTest]: 'unset-b',
})),
compTypeProp.removeLayerAt(1);
compTypeProp.removeLayerAt(0);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual({
[propATest]: 'unset-a',
[propBTest]: 'unset-b',
});
});
test('Removing all layers with empty value as function (detached)', () => {
compTypeProp.set('detached', true);
compTypeProp.set('emptyValue', () => ({
[propATest]: 'unset-a',
[propBTest]: 'unset-b',
[propCTest]: 'unset-c',
})),
compTypeProp.removeLayerAt(1);
compTypeProp.removeLayerAt(0);
expect(compTypeProp.getLayers().length).toBe(0);
expect(rule1.getStyle()).toEqual({
[propATest]: 'unset-a',
[propBTest]: 'unset-b',
[propCTest]: 'unset-c',
});
});
});
});
});