Files
openlayers/test/spec/ol/style/expressions.test.js
Olivier Guyot e3f7d29bb2 Expressions / add utilities for using strings in GLSL & more type checking
It is now possible to specify a type hint when parsing an expression, which
helps determine the output value type.

When no single output type can be inferred, an error is thrown.

For strings, every literal value will be replaced by a number and a map of
these associations will be kept in the parsing context, which is passed
recursively.
2019-10-25 14:55:44 +02:00

264 lines
10 KiB
JavaScript

import {
arrayToGlsl, colorToGlsl,
expressionToGlsl,
getValueType, isTypeUnique,
numberToGlsl, stringToGlsl,
ValueTypes
} from '../../../../src/ol/style/expressions.js';
describe('ol.style.expressions', function() {
describe('numberToGlsl', function() {
it('does a simple transform when a fraction is present', function() {
expect(numberToGlsl(1.3456)).to.eql('1.3456');
});
it('adds a fraction separator when missing', function() {
expect(numberToGlsl(1)).to.eql('1.0');
expect(numberToGlsl(2.0)).to.eql('2.0');
});
});
describe('arrayToGlsl', function() {
it('outputs numbers with dot separators', function() {
expect(arrayToGlsl([1, 0, 3.45, 0.8888])).to.eql('vec4(1.0, 0.0, 3.45, 0.8888)');
expect(arrayToGlsl([3, 4])).to.eql('vec2(3.0, 4.0)');
});
it('throws on invalid lengths', function() {
let thrown = false;
try {
arrayToGlsl([3]);
} catch (e) {
thrown = true;
}
try {
arrayToGlsl([3, 2, 1, 0, -1]);
} catch (e) {
thrown = true;
}
expect(thrown).to.be(true);
});
});
describe('colorToGlsl', function() {
it('normalizes color and outputs numbers with dot separators', function() {
expect(colorToGlsl([100, 0, 255])).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 1.0)');
expect(colorToGlsl([100, 0, 255, 1])).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 1.0)');
});
it('handles colors in string format', function() {
expect(colorToGlsl('red')).to.eql('vec4(1.0, 0.0, 0.0, 1.0)');
expect(colorToGlsl('#00ff99')).to.eql('vec4(0.0, 1.0, 0.6, 1.0)');
expect(colorToGlsl('rgb(100, 0, 255)')).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 1.0)');
expect(colorToGlsl('rgba(100, 0, 255, 0.3)')).to.eql('vec4(0.39215686274509803, 0.0, 1.0, 0.3)');
});
});
describe('stringToGlsl', function() {
let context;
beforeEach(function() {
context = {
stringLiteralsMap: {}
};
});
it('maps input string to stable numbers', function() {
expect(stringToGlsl(context, 'abcd')).to.eql('0.0');
expect(stringToGlsl(context, 'defg')).to.eql('1.0');
expect(stringToGlsl(context, 'hijk')).to.eql('2.0');
expect(stringToGlsl(context, 'abcd')).to.eql('0.0');
expect(stringToGlsl(context, 'def')).to.eql('3.0');
});
});
describe('isTypeUnique', function() {
it('return true if only one value type', function() {
expect(isTypeUnique(ValueTypes.NUMBER)).to.eql(true);
expect(isTypeUnique(ValueTypes.STRING)).to.eql(true);
expect(isTypeUnique(ValueTypes.COLOR)).to.eql(true);
});
it('return false if several value types', function() {
expect(isTypeUnique(ValueTypes.NUMBER | ValueTypes.COLOR)).to.eql(false);
expect(isTypeUnique(ValueTypes.ANY)).to.eql(false);
});
it('return false if no value type', function() {
expect(isTypeUnique(ValueTypes.NUMBER & ValueTypes.COLOR)).to.eql(false);
});
});
describe('getValueType', function() {
it('correctly analyzes a literal value', function() {
expect(getValueType(1234)).to.eql(ValueTypes.NUMBER);
expect(getValueType([1, 2, 3, 4])).to.eql(ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY);
expect(getValueType([1, 2, 3])).to.eql(ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY);
expect(getValueType([1, 2])).to.eql(ValueTypes.NUMBER_ARRAY);
expect(getValueType([1, 2, 3, 4, 5])).to.eql(ValueTypes.NUMBER_ARRAY);
expect(getValueType('yellow')).to.eql(ValueTypes.COLOR | ValueTypes.STRING);
expect(getValueType('#113366')).to.eql(ValueTypes.COLOR | ValueTypes.STRING);
expect(getValueType('rgba(252,171,48,0.62)')).to.eql(ValueTypes.COLOR | ValueTypes.STRING);
expect(getValueType('abcd')).to.eql(ValueTypes.STRING);
expect(getValueType(true)).to.eql(ValueTypes.BOOLEAN);
});
it('throws on an unsupported type (object)', function(done) {
try {
getValueType(new Object());
} catch (e) {
done();
}
done(true);
});
it('throws on an unsupported type (mixed array)', function(done) {
try {
getValueType([1, true, 'aa']);
} catch (e) {
done();
}
done(true);
});
it('correctly analyzes operator return types', function() {
expect(getValueType(['get', 'myAttr'])).to.eql(ValueTypes.ANY);
expect(getValueType(['var', 'myValue'])).to.eql(ValueTypes.ANY);
expect(getValueType(['time'])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['+', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['-', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['/', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['*', ['get', 'size'], 12])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['clamp', ['get', 'attr2'], ['get', 'attr3'], 20])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['stretch', ['get', 'size'], 10, 100, 4, 8])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['pow', 10, 2])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['mod', ['time'], 10])).to.eql(ValueTypes.NUMBER);
expect(getValueType(['>', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
expect(getValueType(['>=', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
expect(getValueType(['<', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
expect(getValueType(['<=', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
expect(getValueType(['==', 10, ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
expect(getValueType(['between', ['get', 'attr4'], -4.0, 5.0])).to.eql(ValueTypes.BOOLEAN);
expect(getValueType(['!', ['get', 'attr4']])).to.eql(ValueTypes.BOOLEAN);
expect(getValueType(['interpolate', ['get', 'attr4'], [255, 255, 255, 1], 'transparent'])).to.eql(ValueTypes.COLOR);
});
});
describe('expressionToGlsl', function() {
let context;
beforeEach(function() {
context = {
variables: [],
attributes: [],
stringLiteralsMap: {}
};
});
it('correctly converts expressions to GLSL', function() {
expect(expressionToGlsl(context, ['get', 'myAttr'])).to.eql('a_myAttr');
expect(expressionToGlsl(context, ['var', 'myValue'])).to.eql('u_myValue');
expect(expressionToGlsl(context, ['time'])).to.eql('u_time');
expect(expressionToGlsl(context, ['+', ['*', ['get', 'size'], 0.001], 12])).to.eql('((a_size * 0.001) + 12.0)');
expect(expressionToGlsl(context, ['/', ['-', ['get', 'size'], 20], 100])).to.eql('((a_size - 20.0) / 100.0)');
expect(expressionToGlsl(context, ['clamp', ['get', 'attr2'], ['get', 'attr3'], 20])).to.eql('clamp(a_attr2, a_attr3, 20.0)');
expect(expressionToGlsl(context, ['stretch', ['get', 'size'], 10, 100, 4, 8])).to.eql('((clamp(a_size, 10.0, 100.0) - 10.0) * ((8.0 - 4.0) / (100.0 - 10.0)) + 4.0)');
expect(expressionToGlsl(context, ['pow', ['mod', ['time'], 10], 2])).to.eql('pow(mod(u_time, 10.0), 2.0)');
expect(expressionToGlsl(context, ['>', 10, ['get', 'attr4']])).to.eql('(10.0 > a_attr4)');
expect(expressionToGlsl(context, ['>=', 10, ['get', 'attr4']])).to.eql('(10.0 >= a_attr4)');
expect(expressionToGlsl(context, ['<', 10, ['get', 'attr4']])).to.eql('(10.0 < a_attr4)');
expect(expressionToGlsl(context, ['<=', 10, ['get', 'attr4']])).to.eql('(10.0 <= a_attr4)');
expect(expressionToGlsl(context, ['==', 10, ['get', 'attr4']])).to.eql('(10.0 == a_attr4)');
expect(expressionToGlsl(context, ['between', ['get', 'attr4'], -4.0, 5.0])).to.eql('(a_attr4 >= -4.0 && a_attr4 <= 5.0)');
expect(expressionToGlsl(context, ['!', ['get', 'attr4']])).to.eql('(!a_attr4)');
expect(expressionToGlsl(context, ['interpolate', ['get', 'attr4'], [255, 255, 255, 1], 'transparent'])).to.eql(
'mix(vec4(1.0, 1.0, 1.0, 1.0), vec4(0.0, 0.0, 0.0, 0.0), a_attr4)');
});
it('correctly adapts output for fragment shaders', function() {
context.inFragmentShader = true;
expect(expressionToGlsl(context, ['get', 'myAttr'])).to.eql('v_myAttr');
});
it('correctly adapts output for fragment shaders', function() {
expressionToGlsl(context, ['get', 'myAttr']);
expressionToGlsl(context, ['var', 'myVar']);
expressionToGlsl(context, ['clamp', ['get', 'attr2'], ['get', 'attr2'], ['get', 'myAttr']]);
expressionToGlsl(context, ['*', ['get', 'attr2'], ['var', 'myVar']]);
expressionToGlsl(context, ['*', ['get', 'attr3'], ['var', 'myVar2']]);
expect(context.attributes).to.eql(['myAttr', 'attr2', 'attr3']);
expect(context.variables).to.eql(['myVar', 'myVar2']);
});
it('gives precedence to the string type unless asked otherwise', function() {
expect(expressionToGlsl(context, 'lightgreen')).to.eql('0.0');
expect(expressionToGlsl(context, 'lightgreen', ValueTypes.COLOR)).to.eql(
'vec4(0.5647058823529412, 0.9333333333333333, 0.5647058823529412, 1.0)');
});
it('throws on unsupported types for operators', function() {
let thrown = false;
try {
expressionToGlsl(context, ['var', 1234]);
} catch (e) {
thrown = true;
}
try {
expressionToGlsl(context, ['<', 0, 'aa']);
} catch (e) {
thrown = true;
}
try {
expressionToGlsl(context, ['+', true, ['get', 'attr']]);
} catch (e) {
thrown = true;
}
try {
expressionToGlsl(context, ['interpolate', ['get', 'attr4'], 1, '#3344FF']);
} catch (e) {
thrown = true;
}
expect(thrown).to.be(true);
});
it('throws with the wrong number of arguments', function() {
let thrown = false;
try {
expressionToGlsl(context, ['var', 1234, 456]);
} catch (e) {
thrown = true;
}
try {
expressionToGlsl(context, ['<', 4]);
} catch (e) {
thrown = true;
}
try {
expressionToGlsl(context, ['+']);
} catch (e) {
thrown = true;
}
expect(thrown).to.be(true);
});
it('throws on invalid expressions', function() {
let thrown = false;
try {
expressionToGlsl(context, true);
} catch (e) {
thrown = true;
}
try {
expressionToGlsl(context, [123, 456]);
} catch (e) {
thrown = true;
}
try {
expressionToGlsl(context, null);
} catch (e) {
thrown = true;
}
expect(thrown).to.be(true);
});
});
});