@maplibre/maplibre-gl-style-spec
Version:
a specification for maplibre styles
1,584 lines (1,425 loc) • 45.9 kB
text/typescript
import {expectToMatchColor} from '../../test/lib/util';
import {createFunction} from './index';
import {Color} from '../expression/types/color';
import {Formatted} from '../expression/types/formatted';
import {Padding} from '../expression/types/padding';
import {NumberArray} from '../expression/types/number_array';
import {ColorArray} from '../expression/types/color_array';
import {describe, test, expect} from 'vitest';
describe('binary search', () => {
test('will eventually terminate.', () => {
const f = createFunction(
{
stops: [
[9, 10],
[17, 11],
[17, 11],
[18, 13]
],
base: 2
},
{
type: 'number',
'property-type': 'data-constant',
expression: {
interpolated: true,
parameters: ['zoom']
}
}
).evaluate;
expect(f({zoom: 17}, undefined)).toBe(11);
});
});
describe('exponential function', () => {
test('is the default for interpolated properties', () => {
const f = createFunction(
{
stops: [
[1, 2],
[3, 6]
],
base: 2
},
{
type: 'number',
'property-type': 'data-constant',
expression: {
interpolated: true,
parameters: ['zoom']
}
}
).evaluate;
expect(f({zoom: 2}, undefined)).toBeCloseTo(30 / 9);
});
test('base', () => {
const f = createFunction(
{
type: 'exponential',
stops: [
[1, 2],
[3, 6]
],
base: 2
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toBeCloseTo(2);
expect(f({zoom: 1}, undefined)).toBeCloseTo(2);
expect(f({zoom: 2}, undefined)).toBeCloseTo(30 / 9);
expect(f({zoom: 3}, undefined)).toBeCloseTo(6);
expect(f({zoom: 4}, undefined)).toBeCloseTo(6);
});
test('one stop', () => {
const f = createFunction(
{
type: 'exponential',
stops: [[1, 2]]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toBe(2);
expect(f({zoom: 1}, undefined)).toBe(2);
expect(f({zoom: 2}, undefined)).toBe(2);
});
test('two stops', () => {
const f = createFunction(
{
type: 'exponential',
stops: [
[1, 2],
[3, 6]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toBe(2);
expect(f({zoom: 1}, undefined)).toBe(2);
expect(f({zoom: 2}, undefined)).toBe(4);
expect(f({zoom: 3}, undefined)).toBe(6);
expect(f({zoom: 4}, undefined)).toBe(6);
});
test('three stops', () => {
const f = createFunction(
{
type: 'exponential',
stops: [
[1, 2],
[3, 6],
[5, 10]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toBe(2);
expect(f({zoom: 1}, undefined)).toBe(2);
expect(f({zoom: 2}, undefined)).toBe(4);
expect(f({zoom: 2.5}, undefined)).toBe(5);
expect(f({zoom: 3}, undefined)).toBe(6);
expect(f({zoom: 4}, undefined)).toBe(8);
expect(f({zoom: 4.5}, undefined)).toBe(9);
expect(f({zoom: 5}, undefined)).toBe(10);
expect(f({zoom: 6}, undefined)).toBe(10);
});
test('four stops', () => {
const f = createFunction(
{
type: 'exponential',
stops: [
[1, 2],
[3, 6],
[5, 10],
[7, 14]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toBe(2);
expect(f({zoom: 1}, undefined)).toBe(2);
expect(f({zoom: 2}, undefined)).toBe(4);
expect(f({zoom: 2.5}, undefined)).toBe(5);
expect(f({zoom: 3}, undefined)).toBe(6);
expect(f({zoom: 3.5}, undefined)).toBe(7);
expect(f({zoom: 4}, undefined)).toBe(8);
expect(f({zoom: 4.5}, undefined)).toBe(9);
expect(f({zoom: 5}, undefined)).toBe(10);
expect(f({zoom: 6}, undefined)).toBe(12);
expect(f({zoom: 6.5}, undefined)).toBe(13);
expect(f({zoom: 7}, undefined)).toBe(14);
expect(f({zoom: 8}, undefined)).toBe(14);
});
test('many stops', () => {
const stops = [
[2, 100],
[55, 200],
[132, 300],
[607, 400],
[1287, 500],
[1985, 600],
[2650, 700],
[3299, 800],
[3995, 900],
[4927, 1000],
[7147, 10000], // 10
[10028, 100000], // 11
[12889, 1000000],
[40000, 10000000]
];
const f = createFunction(
{
type: 'exponential',
stops
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 2}, undefined)).toBeCloseTo(100); //, 1e-6);
expect(f({zoom: 20}, undefined)).toBeCloseTo(133.9622641509434);
expect(f({zoom: 607}, undefined)).toBeCloseTo(400);
expect(f({zoom: 680}, undefined)).toBeCloseTo(410.7352941176471);
expect(f({zoom: 4927}, undefined)).toBeCloseTo(1000); //86
expect(f({zoom: 7300}, undefined)).toBeCloseTo(14779.590419993057);
expect(f({zoom: 10000}, undefined)).toBeCloseTo(99125.30371398819);
expect(f({zoom: 20000}, undefined)).toBeCloseTo(3360628.527166095);
expect(f({zoom: 40000}, undefined)).toBeCloseTo(10000000);
});
test('color', () => {
const f = createFunction(
{
type: 'exponential',
stops: [
[1, 'red'],
[11, 'blue']
]
},
{
type: 'color'
}
).evaluate;
expectToMatchColor(f({zoom: 0}, undefined), 'rgb(100% 0% 0% / 1)');
expectToMatchColor(f({zoom: 5}, undefined), 'rgb(60% 0% 40% / 1)');
expectToMatchColor(f({zoom: 11}, undefined), 'rgb(0% 0% 100% / 1)');
});
test('color hcl colorspace', () => {
const f = createFunction(
{
type: 'exponential',
colorSpace: 'hcl',
stops: [
[0, 'rgb(36 98 36 / 0.6)'],
[10, 'hsl(222,73%,32%)']
]
},
{
type: 'color'
}
).evaluate;
expectToMatchColor(f({zoom: 0}, undefined), 'rgb(14.12% 38.43% 14.12% / .6)', 4);
expectToMatchColor(f({zoom: 5}, undefined), 'rgb(0.00% 35.13% 43.84% / 0.8)', 4);
expectToMatchColor(f({zoom: 10}, undefined), 'rgb(8.64% 22.66% 55.36% / 1)', 4);
});
test('color lab colorspace', () => {
const f = createFunction(
{
type: 'exponential',
colorSpace: 'lab',
stops: [
[0, '#0009'],
[10, 'rgba(0,255,255,1)']
]
},
{
type: 'color'
}
).evaluate;
expectToMatchColor(f({zoom: 0}, undefined), 'rgb(0% 0% 0% / .6)');
expectToMatchColor(f({zoom: 5}, undefined), 'rgb(14.15% 46.74% 46.63% / 0.8)', 4);
expectToMatchColor(f({zoom: 10}, undefined), 'rgb(0% 100% 100% / 1)');
});
test('color invalid colorspace', () => {
expect(() => {
createFunction(
{
type: 'exponential',
colorSpace: 'invalid',
stops: [
[1, [0, 0, 0, 1]],
[10, [0, 1, 1, 1]]
]
},
{
type: 'color'
}
);
}).toThrow('Unknown color space: "invalid"');
});
test('exponential interpolation of colorArray', () => {
const f = createFunction(
{
type: 'exponential',
stops: [
[1, ['red', 'blue']],
[11, ['blue', 'red']]
]
},
{
type: 'colorArray'
}
).evaluate;
expect(f({zoom: 5}, undefined).values).toHaveLength(2);
expectToMatchColor(f({zoom: 0}, undefined).values[0], 'rgb(100% 0% 0% / 1)');
expectToMatchColor(f({zoom: 0}, undefined).values[1], 'rgb(0% 0% 100% / 1)');
expectToMatchColor(f({zoom: 5}, undefined).values[0], 'rgb(60% 0% 40% / 1)');
expectToMatchColor(f({zoom: 5}, undefined).values[1], 'rgb(40% 0% 60% / 1)');
expectToMatchColor(f({zoom: 11}, undefined).values[0], 'rgb(0% 0% 100% / 1)');
expectToMatchColor(f({zoom: 11}, undefined).values[1], 'rgb(100% 0% 0% / 1)');
});
test('exponential interpolation of colorArray (hcl colorspace)', () => {
const f = createFunction(
{
type: 'exponential',
colorSpace: 'hcl',
stops: [
[0, 'rgb(36 98 36 / 0.6)'],
[10, 'hsl(222,73%,32%)']
]
},
{
type: 'colorArray'
}
).evaluate;
expect(f({zoom: 5}, undefined).values).toHaveLength(1);
expectToMatchColor(f({zoom: 0}, undefined).values[0], 'rgb(14.12% 38.43% 14.12% / .6)', 4);
expectToMatchColor(f({zoom: 5}, undefined).values[0], 'rgb(0.00% 35.13% 43.84% / 0.8)', 4);
expectToMatchColor(f({zoom: 10}, undefined).values[0], 'rgb(8.64% 22.66% 55.36% / 1)', 4);
});
test('exponential interpolation of colorArray (lab colorspace)', () => {
const f = createFunction(
{
type: 'exponential',
colorSpace: 'lab',
stops: [
[0, '#0009'],
[10, 'rgba(0,255,255,1)']
]
},
{
type: 'colorArray'
}
).evaluate;
expect(f({zoom: 5}, undefined).values).toHaveLength(1);
expectToMatchColor(f({zoom: 0}, undefined).values[0], 'rgb(0% 0% 0% / .6)');
expectToMatchColor(f({zoom: 5}, undefined).values[0], 'rgb(14.15% 46.74% 46.63% / 0.8)', 4);
expectToMatchColor(f({zoom: 10}, undefined).values[0], 'rgb(0% 100% 100% / 1)');
});
test('exponential interpolation of colorArray (invalid colorspace)', () => {
expect(() => {
createFunction(
{
type: 'exponential',
colorSpace: 'invalid',
stops: [
[1, [0, 0, 0, 1]],
[10, [0, 1, 1, 1]]
]
},
{
type: 'colorArray'
}
);
}).toThrow('Unknown color space: "invalid"');
});
test('exponential interpolation of numberArray', () => {
const f = createFunction(
{
type: 'exponential',
colorSpace: 'lab',
stops: [
[0, [1, 2]],
[10, [3, 4]]
]
},
{
type: 'numberArray'
}
).evaluate;
expect(f({zoom: 5}, undefined).values).toHaveLength(2);
expect(f({zoom: 0}, undefined).values).toEqual([1, 2]);
expect(f({zoom: 5}, undefined).values).toEqual([2, 3]);
expect(f({zoom: 10}, undefined).values).toEqual([3, 4]);
});
test('interpolation mutation avoidance', () => {
const params = {
type: 'exponential',
colorSpace: 'lab',
stops: [
[1, [0, 0, 0, 1]],
[10, [0, 1, 1, 1]]
]
};
const paramsCopy = JSON.parse(JSON.stringify(params));
createFunction(params, {
type: 'color'
});
expect(params).toEqual(paramsCopy);
});
test('exponential interpolation of padding', () => {
const f = createFunction(
{
type: 'exponential',
stops: [
[1, 2],
[11, [2, 5, 2, 7]]
]
},
{
type: 'padding'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toEqual(new Padding([2, 2, 2, 2]));
expect(f({zoom: 5}, undefined)).toEqual(new Padding([2, 3.2, 2, 4]));
expect(f({zoom: 11}, undefined)).toEqual(new Padding([2, 5, 2, 7]));
});
test('property present', () => {
const f = createFunction(
{
property: 'foo',
type: 'exponential',
stops: [
[0, 0],
[1, 2]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 1}})).toBe(2);
});
test('property absent, function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'exponential',
stops: [
[0, 0],
[1, 2]
],
default: 3
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe(3);
});
test('property absent, spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'exponential',
stops: [
[0, 0],
[1, 2]
]
},
{
type: 'number',
default: 3
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe(3);
});
test('property type mismatch, function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'exponential',
stops: [
[0, 0],
[1, 2]
],
default: 3
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe(3);
});
test('property type mismatch, spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'exponential',
stops: [
[0, 0],
[1, 2]
]
},
{
type: 'string',
default: 3
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe(3);
});
test('zoom-and-property function, one stop', () => {
const f = createFunction(
{
type: 'exponential',
property: 'prop',
stops: [[{zoom: 1, value: 1}, 2]]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, {properties: {prop: 0}})).toBe(2);
expect(f({zoom: 1}, {properties: {prop: 0}})).toBe(2);
expect(f({zoom: 2}, {properties: {prop: 0}})).toBe(2);
expect(f({zoom: 0}, {properties: {prop: 1}})).toBe(2);
expect(f({zoom: 1}, {properties: {prop: 1}})).toBe(2);
expect(f({zoom: 2}, {properties: {prop: 1}})).toBe(2);
expect(f({zoom: 0}, {properties: {prop: 2}})).toBe(2);
expect(f({zoom: 1}, {properties: {prop: 2}})).toBe(2);
expect(f({zoom: 2}, {properties: {prop: 2}})).toBe(2);
});
test('zoom-and-property function, two stops', () => {
const f = createFunction(
{
type: 'exponential',
property: 'prop',
base: 1,
stops: [
[{zoom: 1, value: 0}, 0],
[{zoom: 1, value: 2}, 4],
[{zoom: 3, value: 0}, 0],
[{zoom: 3, value: 2}, 12]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, {properties: {prop: 1}})).toBe(2);
expect(f({zoom: 1}, {properties: {prop: 1}})).toBe(2);
expect(f({zoom: 2}, {properties: {prop: 1}})).toBe(4);
expect(f({zoom: 3}, {properties: {prop: 1}})).toBe(6);
expect(f({zoom: 4}, {properties: {prop: 1}})).toBe(6);
expect(f({zoom: 2}, {properties: {prop: -1}})).toBe(0);
expect(f({zoom: 2}, {properties: {prop: 0}})).toBe(0);
expect(f({zoom: 2}, {properties: {prop: 2}})).toBe(8);
expect(f({zoom: 2}, {properties: {prop: 3}})).toBe(8);
});
test('zoom-and-property function, three stops', () => {
const f = createFunction(
{
type: 'exponential',
property: 'prop',
base: 1,
stops: [
[{zoom: 1, value: 0}, 0],
[{zoom: 1, value: 2}, 4],
[{zoom: 3, value: 0}, 0],
[{zoom: 3, value: 2}, 12],
[{zoom: 5, value: 0}, 0],
[{zoom: 5, value: 2}, 20]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, {properties: {prop: 1}})).toBe(2);
expect(f({zoom: 1}, {properties: {prop: 1}})).toBe(2);
expect(f({zoom: 2}, {properties: {prop: 1}})).toBe(4);
});
test('zoom-and-property function, two stops, fractional zoom', () => {
const f = createFunction(
{
type: 'exponential',
property: 'prop',
base: 1,
stops: [
[{zoom: 1.9, value: 0}, 4],
[{zoom: 2.1, value: 0}, 8]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 1.9}, {properties: {prop: 1}})).toBe(4);
expect(f({zoom: 2}, {properties: {prop: 1}})).toBe(6);
expect(f({zoom: 2.1}, {properties: {prop: 1}})).toBe(8);
});
test('zoom-and-property function, four stops, integer and fractional zooms', () => {
const f = createFunction(
{
type: 'exponential',
property: 'prop',
base: 1,
stops: [
[{zoom: 1, value: 0}, 0],
[{zoom: 1.5, value: 0}, 1],
[{zoom: 2, value: 0}, 10],
[{zoom: 2.5, value: 0}, 20]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 1}, {properties: {prop: 0}})).toBe(0);
expect(f({zoom: 1.5}, {properties: {prop: 0}})).toBe(1);
expect(f({zoom: 2}, {properties: {prop: 0}})).toBe(10);
expect(f({zoom: 2.5}, {properties: {prop: 0}})).toBe(20);
});
test('zoom-and-property function, no default', () => {
// This can happen for fill-outline-color, where the spec has no default.
const f = createFunction(
{
type: 'exponential',
property: 'prop',
base: 1,
stops: [
[{zoom: 0, value: 1}, 'red'],
[{zoom: 1, value: 1}, 'red']
]
},
{
type: 'color'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBeUndefined();
expect(f({zoom: 0.5}, {properties: {}})).toBeUndefined();
expect(f({zoom: 1}, {properties: {}})).toBeUndefined();
});
test('zoom-and-property function, color', () => {
const f = createFunction(
{
type: 'exponential',
property: 'prop',
stops: [
[{zoom: 0, value: 0}, 'red'],
[{zoom: 1, value: 0}, 'blue'],
[{zoom: 0, value: 1}, 'lime'],
[{zoom: 1, value: 1}, 'magenta']
]
},
{
type: 'color'
}
).evaluate;
expect(f({zoom: 0.0}, {properties: {prop: 0}})).toEqual(new Color(1, 0, 0, 1));
expect(f({zoom: 1.0}, {properties: {prop: 0}})).toEqual(new Color(0, 0, 1, 1));
expect(f({zoom: 0.5}, {properties: {prop: 1}})).toEqual(new Color(0.5, 0.5, 0.5, 1));
});
test('zoom-and-property function, padding', () => {
const f = createFunction(
{
type: 'exponential',
property: 'prop',
stops: [
[{zoom: 0, value: 0}, [2]],
[{zoom: 1, value: 0}, [4]],
[{zoom: 0, value: 1}, [6]],
[{zoom: 1, value: 1}, [8]]
]
},
{
type: 'padding'
}
).evaluate;
expect(f({zoom: 0.0}, {properties: {prop: 0}})).toEqual(new Padding([2, 2, 2, 2]));
expect(f({zoom: 1.0}, {properties: {prop: 0}})).toEqual(new Padding([4, 4, 4, 4]));
expect(f({zoom: 0.5}, {properties: {prop: 1}})).toEqual(new Padding([7, 7, 7, 7]));
});
});
describe('interval function', () => {
test('is the default for non-interpolated properties', () => {
const f = createFunction(
{
stops: [
[-1, 11],
[0, 111]
]
},
{
type: 'number',
'property-type': 'data-constant',
expression: {
interpolated: false,
parameters: ['zoom']
}
}
).evaluate;
expect(f({zoom: -1.5}, undefined)).toBe(11);
expect(f({zoom: -0.5}, undefined)).toBe(11);
expect(f({zoom: 0}, undefined)).toBe(111);
expect(f({zoom: 0.5}, undefined)).toBe(111);
});
test('one stop', () => {
const f = createFunction(
{
type: 'interval',
stops: [[0, 11]]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: -0.5}, undefined)).toBe(11);
expect(f({zoom: 0}, undefined)).toBe(11);
expect(f({zoom: 0.5}, undefined)).toBe(11);
});
test('two stops', () => {
const f = createFunction(
{
type: 'interval',
stops: [
[-1, 11],
[0, 111]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: -1.5}, undefined)).toBe(11);
expect(f({zoom: -0.5}, undefined)).toBe(11);
expect(f({zoom: 0}, undefined)).toBe(111);
expect(f({zoom: 0.5}, undefined)).toBe(111);
});
test('three stops', () => {
const f = createFunction(
{
type: 'interval',
stops: [
[-1, 11],
[0, 111],
[1, 1111]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: -1.5}, undefined)).toBe(11);
expect(f({zoom: -0.5}, undefined)).toBe(11);
expect(f({zoom: 0}, undefined)).toBe(111);
expect(f({zoom: 0.5}, undefined)).toBe(111);
expect(f({zoom: 1}, undefined)).toBe(1111);
expect(f({zoom: 1.5}, undefined)).toBe(1111);
});
test('four stops', () => {
const f = createFunction(
{
type: 'interval',
stops: [
[-1, 11],
[0, 111],
[1, 1111],
[2, 11111]
]
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: -1.5}, undefined)).toBe(11);
expect(f({zoom: -0.5}, undefined)).toBe(11);
expect(f({zoom: 0}, undefined)).toBe(111);
expect(f({zoom: 0.5}, undefined)).toBe(111);
expect(f({zoom: 1}, undefined)).toBe(1111);
expect(f({zoom: 1.5}, undefined)).toBe(1111);
expect(f({zoom: 2}, undefined)).toBe(11111);
expect(f({zoom: 2.5}, undefined)).toBe(11111);
});
test('color', () => {
const f = createFunction(
{
type: 'interval',
stops: [
[1, 'red'],
[11, 'blue']
]
},
{
type: 'color'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toEqual(new Color(1, 0, 0, 1));
expect(f({zoom: 10}, undefined)).toEqual(new Color(1, 0, 0, 1));
expect(f({zoom: 11}, undefined)).toEqual(new Color(0, 0, 1, 1));
});
test('padding', () => {
const f = createFunction(
{
type: 'interval',
stops: [
[1, 2],
[11, 4]
]
},
{
type: 'padding'
}
).evaluate;
expect(f({zoom: 0}, undefined)).toEqual(new Padding([2, 2, 2, 2]));
expect(f({zoom: 10}, undefined)).toEqual(new Padding([2, 2, 2, 2]));
expect(f({zoom: 11}, undefined)).toEqual(new Padding([4, 4, 4, 4]));
});
test('property present', () => {
const f = createFunction(
{
property: 'foo',
type: 'interval',
stops: [
[0, 'bad'],
[1, 'good'],
[2, 'bad']
]
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 1.5}})).toBe('good');
});
test('property absent, function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'interval',
stops: [
[0, 'zero'],
[1, 'one'],
[2, 'two']
],
default: 'default'
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe('default');
});
test('property absent, spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'interval',
stops: [
[0, 'zero'],
[1, 'one'],
[2, 'two']
]
},
{
type: 'string',
default: 'default'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe('default');
});
test('property type mismatch, function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'interval',
stops: [
[0, 'zero'],
[1, 'one'],
[2, 'two']
],
default: 'default'
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe('default');
});
test('property type mismatch, spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'interval',
stops: [
[0, 'zero'],
[1, 'one'],
[2, 'two']
]
},
{
type: 'string',
default: 'default'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'string'}})).toBe('default');
});
});
describe('categorical function', () => {
test('string', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 'bad'],
[1, 'good'],
[2, 'bad']
]
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 0}})).toBe('bad');
expect(f({zoom: 0}, {properties: {foo: 1}})).toBe('good');
expect(f({zoom: 0}, {properties: {foo: 2}})).toBe('bad');
});
test('string function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 'zero'],
[1, 'one'],
[2, 'two']
],
default: 'default'
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe('default');
expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('default');
});
test('string zoom-and-property function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [[{zoom: 0, value: 'bar'}, 'zero']],
default: 'default'
},
{
type: 'string',
function: 'interval'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe('default');
expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('default');
expect(f({zoom: 0}, {properties: {foo: 'bar'}})).toBe('zero');
});
test('strict type checking', () => {
const numberKeys = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 'zero'],
[1, 'one'],
[2, 'two']
],
default: 'default'
},
{
type: 'string'
}
).evaluate;
const stringKeys = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
['0', 'zero'],
['1', 'one'],
['2', 'two'],
['true', 'yes'],
['false', 'no']
],
default: 'default'
},
{
type: 'string'
}
).evaluate;
expect(numberKeys(0 as any, {properties: {foo: '0'}})).toBe('default');
expect(numberKeys(0 as any, {properties: {foo: '1'}})).toBe('default');
expect(numberKeys(0 as any, {properties: {foo: false}})).toBe('default');
expect(numberKeys(0 as any, {properties: {foo: true}})).toBe('default');
expect(stringKeys(0 as any, {properties: {foo: 0}})).toBe('default');
expect(stringKeys(0 as any, {properties: {foo: 1}})).toBe('default');
expect(stringKeys(0 as any, {properties: {foo: false}})).toBe('default');
expect(stringKeys(0 as any, {properties: {foo: true}})).toBe('default');
});
test('string spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 'zero'],
[1, 'one'],
[2, 'two']
]
},
{
type: 'string',
default: 'default'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe('default');
expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('default');
});
test('color', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 'red'],
[1, 'blue']
]
},
{
type: 'color'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 0}})).toEqual(new Color(1, 0, 0, 1));
expect(f({zoom: 1}, {properties: {foo: 1}})).toEqual(new Color(0, 0, 1, 1));
});
test('color function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 'red'],
[1, 'blue']
],
default: 'lime'
},
{
type: 'color'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(0, 1, 0, 1));
expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Color(0, 1, 0, 1));
});
test('color spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 'red'],
[1, 'blue']
]
},
{
type: 'color',
default: 'lime'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(0, 1, 0, 1));
expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Color(0, 1, 0, 1));
});
test('padding', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 2],
[1, 4]
]
},
{
type: 'padding'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 0}})).toEqual(new Padding([2, 2, 2, 2]));
expect(f({zoom: 1}, {properties: {foo: 1}})).toEqual(new Padding([4, 4, 4, 4]));
});
test('padding function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 2],
[1, 4]
],
default: 6
},
{
type: 'padding'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([6, 6, 6, 6]));
expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Padding([6, 6, 6, 6]));
});
test('padding spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[0, 2],
[1, 4]
]
},
{
type: 'padding',
default: 6
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([6, 6, 6, 6]));
expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Padding([6, 6, 6, 6]));
});
test('boolean', () => {
const f = createFunction(
{
property: 'foo',
type: 'categorical',
stops: [
[true, 'true'],
[false, 'false']
]
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: true}})).toBe('true');
expect(f({zoom: 0}, {properties: {foo: false}})).toBe('false');
});
});
describe('identity function', () => {
test('number', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'number'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 1}})).toBe(1);
});
test('number function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity',
default: 1
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe(1);
});
test('number spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'string',
default: 1
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toBe(1);
});
test('color', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'color'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'red'}})).toEqual(new Color(1, 0, 0, 1));
expect(f({zoom: 1}, {properties: {foo: 'blue'}})).toEqual(new Color(0, 0, 1, 1));
});
test('color function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity',
default: 'red'
},
{
type: 'color'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(1, 0, 0, 1));
});
test('color spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'color',
default: 'red'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Color(1, 0, 0, 1));
});
test('color invalid', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'color',
default: 'red'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'invalid'}})).toEqual(new Color(1, 0, 0, 1));
});
test('padding', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'padding'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new Padding([3, 3, 3, 3]));
expect(f({zoom: 1}, {properties: {foo: [3, 4]}})).toEqual(new Padding([3, 4, 3, 4]));
});
test('padding function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity',
default: [1, 2, 3, 4]
},
{
type: 'padding'
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([1, 2, 3, 4]));
});
test('padding spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'padding',
default: [1, 2, 3, 4]
}
).evaluate;
expect(f({zoom: 0}, {properties: {}})).toEqual(new Padding([1, 2, 3, 4]));
});
test('padding invalid', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'padding',
default: [1, 2, 3, 4]
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'invalid'}})).toEqual(new Padding([1, 2, 3, 4]));
});
test('colorArray identity', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'colorArray'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'red'}})).toEqual(new ColorArray([Color.red]));
expect(f({zoom: 1}, {properties: {foo: ['black', 'white']}})).toEqual(
new ColorArray([Color.black, Color.white])
);
});
test('numberArray identity', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'numberArray'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 3}})).toEqual(new NumberArray([3]));
expect(f({zoom: 1}, {properties: {foo: [3, 4]}})).toEqual(new NumberArray([3, 4]));
});
test('property type mismatch, function default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity',
default: 'default'
},
{
type: 'string'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 0}})).toBe('default');
});
test('property type mismatch, spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'string',
default: 'default'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 0}})).toBe('default');
});
test('valid enum', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'enum',
values: {
bar: {}
},
default: 'def'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'bar'}})).toBe('bar');
});
test('invalid enum, spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'enum',
values: {
bar: {}
},
default: 'def'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'baz'}})).toBe('def');
});
test('invalid type for enum, spec default', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'enum',
values: {
bar: {}
},
default: 'def'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 3}})).toBe('def');
});
test('formatted', () => {
const f = createFunction(
{
property: 'foo',
type: 'identity'
},
{
type: 'formatted'
}
).evaluate;
expect(f({zoom: 0}, {properties: {foo: 'foo'}})).toEqual(Formatted.fromString('foo'));
expect(f({zoom: 1}, {properties: {foo: 'bar'}})).toEqual(Formatted.fromString('bar'));
expect(f({zoom: 2}, {properties: {foo: 2}})).toEqual(Formatted.fromString('2'));
expect(f({zoom: 3}, {properties: {foo: true}})).toEqual(Formatted.fromString('true'));
});
});
test('unknown function', () => {
expect(() =>
createFunction(
{
type: 'nonesuch',
stops: [[]]
},
{
type: 'string'
}
)
).toThrow(/Unknown function type "nonesuch"/);
});
describe('kind', () => {
test('camera', () => {
const f = createFunction(
{
stops: [[1, 1]]
},
{
type: 'number'
}
);
expect(f.kind).toBe('camera');
});
test('source', () => {
const f = createFunction(
{
stops: [[1, 1]],
property: 'mapbox'
},
{
type: 'number'
}
);
expect(f.kind).toBe('source');
});
test('composite', () => {
const f = createFunction(
{
stops: [[{zoom: 1, value: 1}, 1]],
property: 'mapbox'
},
{
type: 'number'
}
);
expect(f.kind).toBe('composite');
});
});