import {describe, expectTypeOf, test} from 'vitest'; import type {ExpressionInputType, ExpressionSpecification} from '../types.g'; describe('Distance expression', () => { describe('Invalid expression', () => { test('missing geometry typecheck', () => { expectTypeOf<['distance']>().not.toExtend(); }); test('invalid geometry typecheck', () => { expectTypeOf<['distance', {type: 'Nope!'}]>().not.toExtend(); }); test('expression as geometry typecheck', () => { expectTypeOf< ['distance', ['literal', {type: 'MultiPoint'; coordinates: [[3, 3], [3, 4]]}]] >().not.toExtend(); }); }); describe('valid expression', () => { test('multi point geometry typecheck', () => { expectTypeOf< ['distance', {type: 'MultiPoint'; coordinates: [[3, 3], [3, 4]]}] >().toExtend(); }); test('multi line geometry typecheck', () => { expectTypeOf< ['distance', {type: 'MultiLineString'; coordinates: [[[3, 3], [3, 4]]]}] >().toExtend(); }); test('multi polygon geometry typecheck', () => { expectTypeOf< [ 'distance', { type: 'MultiPolygon'; coordinates: [[[[3, 3], [3, 4], [4, 4], [4, 3], [3, 3]]]]; } ] >().toExtend(); }); }); }); describe('"array" expression', () => { test('type requires an expression as the input value', () => { expectTypeOf<['array', 1, 2, 3]>().not.toExtend(); expectTypeOf<['array', [1, 2, 3]]>().not.toExtend(); expectTypeOf<['array', 'number', [1, 2, 3]]>().not.toExtend(); expectTypeOf<['array', 'number', 3, [1, 2, 3]]>().not.toExtend(); expectTypeOf<['array', ['literal', []]]>().toExtend(); expectTypeOf< ['array', 'number', ['literal', [1, 2, 3]]] >().toExtend(); expectTypeOf< ['array', 'number', typeof Number.MAX_SAFE_INTEGER, ['get', 'arr']] >().toExtend(); }); test('type requires either "string", "number", or "boolean" as the asserted type', () => { expectTypeOf<['array', 0, ['literal', []]]>().not.toExtend(); expectTypeOf<['array', '0', ['literal', []]]>().not.toExtend(); expectTypeOf< ['array', ['literal', 'number'], ['literal', []]] >().not.toExtend(); expectTypeOf<['array', 'string', ['literal', []]]>().toExtend(); expectTypeOf<['array', 'number', ['literal', []]]>().toExtend(); expectTypeOf<['array', 'boolean', ['literal', []]]>().toExtend(); }); test('type requires a number literal as the asserted length', () => { expectTypeOf< ['array', 'string', '0', ['literal', []]] >().not.toExtend(); expectTypeOf< ['array', 'string', ['literal', 0], ['literal', []]] >().not.toExtend(); expectTypeOf<['array', 'string', 0, ['literal', []]]>().toExtend(); expectTypeOf< ['array', 'string', 2, ['literal', ['one', 'two']]] >().toExtend(); }); }); describe('"format" expression', () => { test('type rejects bare string arrays in the "text-font" style override', () => { expectTypeOf< ['format', 'foo', {'text-font': ['Helvetica', 'Arial']}] >().not.toExtend(); }); test('type accepts expression which scales text', () => { expectTypeOf< ['format', ['get', 'title'], {'font-scale': 0.8}] >().toExtend(); }); test('type requires either "bottom", "center", or "top" as the vertical alignment', () => { expectTypeOf< ['format', 'foo', {'vertical-align': 'middle'}] >().not.toExtend(); }); test('type accepts expression which aligns a text section vertically', () => { expectTypeOf< ['format', 'foo', {'vertical-align': 'top'}] >().toExtend(); }); test('type accepts expression which aligns an image vertically', () => { expectTypeOf< ['format', ['image', 'bar'], {'vertical-align': 'bottom'}] >().toExtend(); }); test('type accepts expression which applies multiple style overrides', () => { expectTypeOf< ['format', 'foo', {'font-scale': 0.8; 'text-color': '#fff'}] >().toExtend(); }); test('type accepts expression which applies default styles with an empty overrides object', () => { expectTypeOf<['format', ['downcase', 'BaR'], {}]>().toExtend(); }); }); describe('"image" expression', () => { test('type requires a string as the image name argument', () => { expectTypeOf<['image', true]>().not.toExtend(); expectTypeOf<['image', 123]>().not.toExtend(); }); test('type accepts expression which returns an image with a string literal as the image name', () => { expectTypeOf<['image', 'foo']>().toExtend(); }); test('type accepts an expression which returns an image with an expression as the image name', () => { expectTypeOf<['image', ['concat', 'foo', 'bar']]>().toExtend(); }); }); describe('"typeof" expression', () => { test('type requires a value argument', () => { expectTypeOf<['typeof']>().not.toExtend(); }); test('type rejects a second argument', () => { expectTypeOf<['typeof', true, 42]>().not.toExtend(); }); test('type accepts expression which returns a string describing the type of the given literal value', () => { expectTypeOf<['typeof', true]>().toExtend(); }); test('type accepts expression which returns a string describing the type of the given expression value', () => { expectTypeOf< ['typeof', ['concat', 'foo', ['to-string', 0]]] >().toExtend(); }); }); describe('"feature-state" expression', () => { test('type accepts expression which retrieves the feature state with a string literal argument', () => { expectTypeOf<['feature-state', 'foo']>().toExtend(); }); test('type accepts expression which retrieves the feature state with an expression argument', () => { expectTypeOf<['feature-state', ['get', 'feat-prop']]>().toExtend(); }); }); describe('"get" expression', () => { test('type requires an expression as the object argument if provided', () => { expectTypeOf<['get', 'prop', {prop: 4}]>().not.toExtend(); }); test('type accepts expression which retrieves a property value from the given object argument', () => { expectTypeOf<['get', 'prop', ['literal', {prop: 4}]]>().toExtend(); }); }); describe('"global-state" expression', () => { test('type requires a property argument', () => { expectTypeOf<['global-state']>().not.toExtend(); }); test('type requires a string literal as the property argument', () => { expectTypeOf< ['global-state', ['concat', 'pr', 'op']] >().not.toExtend(); }); test('type rejects a second argument', () => { expectTypeOf<['global-state', 'foo', 'bar']>().not.toExtend(); }); test('type accepts expression which evaluates a global state property', () => { expectTypeOf<['global-state', 'foo']>().toExtend(); }); }); describe('"has" expression', () => { test('type requires an expression as the object argument if provided', () => { expectTypeOf<['has', 'prop', {prop: 4}]>().not.toExtend(); }); test('type accepts expression which checks whether a property exists in the given object argument', () => { expectTypeOf<['has', 'prop', ['literal', {prop: 4}]]>().toExtend(); }); }); describe('"at" expression', () => { test('type accepts expression which retrieves the item at the specified index in the given array', () => { expectTypeOf<['at', 2, ['literal', [1, 2, 3]]]>().toExtend(); }); }); describe('"in" expression', () => { test('type requires a needle', () => { expectTypeOf<['in']>().not.toExtend(); }); test('type requires a haystack', () => { expectTypeOf<['in', 'a']>().not.toExtend(); }); test('type rejects a third argument', () => { expectTypeOf<['in', 'a', 'abc', 1]>().not.toExtend(); }); test('type requires a string or array as the haystack', () => { expectTypeOf<['in', 't', true]>().not.toExtend(); }); test('type accepts expression which finds a substring in a string', () => { expectTypeOf<['in', 'b', 'abc']>().toExtend(); }); test('type accepts expression which finds a non-literal substring in a string', () => { expectTypeOf< ['in', ['downcase', 'C'], ['concat', 'ab', 'cd']] >().toExtend(); }); test('type accepts expression which finds an element in an array', () => { expectTypeOf<['in', 2, ['literal', [1, 2, 3]]]>().toExtend(); }); test('type accepts expression which finds a non-literal element in an array', () => { expectTypeOf< ['in', ['*', 2, 5], ['literal', [1, 10, 100]]] >().toExtend(); }); }); describe('"index-of" expression', () => { test('type requires a needle', () => { expectTypeOf<['index-of']>().not.toExtend(); }); test('type requires a haystack', () => { expectTypeOf<['index-of', 'a']>().not.toExtend(); }); test('type rejects a fourth argument', () => { expectTypeOf<['index-of', 'a', 'abc', 1, 8]>().not.toExtend(); }); test('type requires a string or array as the haystack', () => { expectTypeOf<['index-of', 't', true]>().not.toExtend(); }); test('type accepts expression which finds a substring in a string', () => { expectTypeOf<['index-of', 'b', 'abc']>().toExtend(); }); test('type accepts expression which finds a non-literal substring in a string', () => { expectTypeOf< ['index-of', ['downcase', 'C'], ['concat', 'ab', 'cd']] >().toExtend(); }); test('type accepts expression which starts looking for the substring at a start index', () => { expectTypeOf<['index-of', 'a', 'abc', 1]>().toExtend(); }); test('type accepts expression which starts looking for the substring at a non-literal start index', () => { expectTypeOf<['index-of', 'c', 'abc', ['-', 0, 1]]>().toExtend(); }); test('type accepts expression which finds an element in an array', () => { expectTypeOf<['index-of', 2, ['literal', [1, 2, 3]]]>().toExtend(); }); test('type accepts expression which finds a non-literal element in an array', () => { expectTypeOf< ['index-of', ['*', 2, 5], ['literal', [1, 10, 100]]] >().toExtend(); }); test('type accepts expression which starts looking for the element at a start index', () => { expectTypeOf< ['index-of', 1, ['literal', [1, 2, 3]], 1] >().toExtend(); }); test('type accepts expression which starts looking for the element at a non-literal start index', () => { expectTypeOf< ['index-of', 2, ['literal', [1, 2, 3]], ['+', 0, -1, 2]] >().toExtend(); }); }); describe('"length" expression', () => { test('type requires an argument', () => { expectTypeOf<['length']>().not.toExtend(); }); test('type requires a string or array as the argument', () => { expectTypeOf<['length', true]>().not.toExtend(); }); test('type rejects a second argument', () => { expectTypeOf<['length', 'abc', 'def']>().not.toExtend(); }); test('type accepts expression which measures a string', () => { expectTypeOf<['length', 'abc']>().toExtend(); }); test('type accepts expression which measures an array', () => { expectTypeOf<['length', ['literal', [1, 2, 3]]]>().toExtend(); }); }); describe('"slice" expression', () => { test('type requires an input argument', () => { expectTypeOf<['slice']>().not.toExtend(); }); test('type requires a start index argument', () => { expectTypeOf<['slice', 'abc']>().not.toExtend(); }); test('type rejects a fourth argument', () => { expectTypeOf<['slice', 'abc', 0, 1, 8]>().not.toExtend(); }); test('type requires a string or array as the input argument', () => { expectTypeOf<['slice', true, 0]>().not.toExtend(); }); test('type requires a number as the start index argument', () => { expectTypeOf<['slice', 'abc', true]>().not.toExtend(); }); test('type accepts expression which slices a string', () => { expectTypeOf<['slice', 'abc', 1]>().toExtend(); }); test('type accepts expression which slices a string by a given range', () => { expectTypeOf<['slice', 'abc', 1, 1]>().toExtend(); }); test('type accepts expression which slices an array', () => { expectTypeOf<['slice', ['literal', [1, 2, 3]], 1]>().toExtend(); }); test('type accepts expression which slices an array by a given range', () => { expectTypeOf<['slice', ['literal', [1, 2, 3]], 1, 1]>().toExtend(); }); }); describe('"split" expression', () => { test('type requires an input argument', () => { expectTypeOf<['split']>().not.toExtend(); }); test('type requires a separator argument', () => { expectTypeOf<['split', '1+2+3']>().not.toExtend(); }); test('type requires a string as the input argument', () => { expectTypeOf<['split', true, '+']>().not.toExtend(); }); test('type requires a string as the separator argument', () => { expectTypeOf<['split', '1+2+3', true]>().not.toExtend(); }); test('type accepts expression which splits a string', () => { expectTypeOf<['split', '1+2+3', '+']>().toExtend(); }); }); describe('"join" expression', () => { test('type requires an input argument', () => { expectTypeOf<['join']>().not.toExtend(); }); test('type requires a separator argument', () => { expectTypeOf< ['join', ['literal', ['1', '2', '3']]] >().not.toExtend(); }); test('type requires a string as the separator argument', () => { expectTypeOf< ['join', ['literal', ['1', '2', '3']], true] >().not.toExtend(); }); test('type accepts expression which joins an array', () => { expectTypeOf< ['join', ['literal', ['1', '2', '3']], '+'] >().toExtend(); }); }); describe('comparison expressions', () => { describe('"!=" expression', () => { test('type accepts expression which compares against literal null value', () => { expectTypeOf< ['!=', null, ['get', 'nonexistent-prop']] >().toExtend(); }); test('allows the third argument to be a collator', () => { expectTypeOf< ['!=', null, ['get', 'nonexistent-prop'], ['collator', {locale: 'mi-NZ'}]] >().toExtend(); }); test('allows the third argument to be a var', () => { expectTypeOf< [ 'let', 'myVariable', ['collator', {'diacritic-sensitive': true}], ['!=', 'münchen', 'munchen', ['var', 'myVariable']] ] >().toExtend(); }); test('does not allow the third argument to be any other expression type', () => { expectTypeOf< ['!=', 'münchen', 'munchen', ['!', true]] >().not.toExtend(); }); }); describe('"==" expression', () => { test('type accepts expression which compares expression input against literal input', () => { expectTypeOf< ['==', ['get', 'MILITARYAIRPORT'], 1] >().toExtend(); }); test('type accepts expression which compares against literal null value', () => { expectTypeOf< ['==', null, ['get', 'nonexistent-prop']] >().toExtend(); }); test('allows the third argument to be a collator', () => { expectTypeOf< ['==', null, ['get', 'nonexistent-prop'], ['collator', {locale: 'mi-NZ'}]] >().toExtend(); }); test('allows the third argument to be a var', () => { expectTypeOf< [ 'let', 'myVariable', ['collator', {'diacritic-sensitive': true}], ['==', 'münchen', 'munchen', ['var', 'myVariable']] ] >().toExtend(); }); test('does not allow the third argument to be any other expression type', () => { expectTypeOf< ['==', 'münchen', 'munchen', ['!', true]] >().not.toExtend(); }); }); describe('"<" expression', () => { test('type rejects boolean input', () => { expectTypeOf<['<', -1, true]>().not.toExtend(); }); test('allows the third argument to be a collator', () => { expectTypeOf< ['<', 'ä', 'a', ['collator', {locale: 'sv'}]] >().toExtend(); }); test('allows the third argument to be a var', () => { expectTypeOf< [ 'let', 'myVariable', ['collator', {locale: 'sv'}], ['<', 'ä', 'a', ['var', 'myVariable']] ] >().toExtend(); }); test('does not allow the third argument to be any other expression type', () => { expectTypeOf< ['<', 'ä', 'a', ['get', 'prop']] >().not.toExtend(); }); }); describe('"<=" expression', () => { test('type rejects boolean input', () => { expectTypeOf<['<=', 0, true]>().not.toExtend(); }); }); describe('">" expression', () => { test('type rejects boolean input', () => { expectTypeOf<['>', 1, true]>().not.toExtend(); }); }); describe('">=" expression', () => { test('type rejects boolean input', () => { expectTypeOf<['>=', 1, true]>().not.toExtend(); }); }); }); describe('"any" expression', () => { test('type accepts expression which has no arguments', () => { expectTypeOf<['any']>().toExtend(); }); }); describe('"case" expression', () => { test('type accepts expression which returns the string output of the first matching condition', () => { expectTypeOf< [ 'case', ['==', ['get', 'CAPITAL'], 1], 'city-capital', ['>=', ['get', 'POPULATION'], 1000000], 'city-1M', ['>=', ['get', 'POPULATION'], 500000], 'city-500k', ['>=', ['get', 'POPULATION'], 100000], 'city-100k', 'city' ] >().toExtend(); }); test('type accepts expression which returns the evaluated output of the first matching condition', () => { expectTypeOf< [ 'case', ['has', 'point_count'], ['interpolate', ['linear'], ['get', 'point_count'], 2, '#ccc', 10, '#444'], ['has', 'priorityValue'], ['interpolate', ['linear'], ['get', 'priorityValue'], 0, '#ff9', 1, '#f66'], '#fcaf3e' ] >().toExtend(); }); test('type accepts expression which has literal null output', () => { expectTypeOf< ['case', false, ['get', 'prop'], true, null, 'fallback'] >().toExtend(); }); test('type accepts expression which has literal null fallback', () => { expectTypeOf<['case', false, ['get', 'prop'], null]>().toExtend(); }); }); describe('"match" expression', () => { test('type requires label to be string literal, number literal, string literal array, or number literal array', () => { expectTypeOf< ['match', 4, true, 'matched', 'fallback'] >().not.toExtend(); expectTypeOf< ['match', 4, [true], 'matched', 'fallback'] >().not.toExtend(); expectTypeOf< ['match', 4, [4, '4'], 'matched', 'fallback'] >().not.toExtend(); expectTypeOf< ['match', 4, ['literal', [4]], 'matched', 'fallback'] >().not.toExtend(); }); test('type accepts expression which matches number input against number label', () => { expectTypeOf< ['match', 2, [0], 'o1', 1, 'o2', 2, 'o3', 'fallback'] >().toExtend(); }); test('type accepts expression which matches string input against string label', () => { expectTypeOf< ['match', 'c', 'a', 'o1', ['b'], 'o2', 'c', 'o3', 'fallback'] >().toExtend(); }); test('type accepts expression which matches number input against number array label', () => { expectTypeOf< ['match', 2, 0, 'o1', [1, 2, 3], 'o2', 'fallback'] >().toExtend(); }); test('type accepts expression which matches string input against string array label', () => { expectTypeOf< ['match', 'c', 'a', 'o1', ['b', 'c', 'd'], 'o2', 'fallback'] >().toExtend(); }); test('type accepts expression which has a non-literal input', () => { expectTypeOf< ['match', ['get', 'TYPE'], ['ADIZ', 'AMA', 'AWY'], true, false] >().toExtend(); }); test('type accepts expression which has an expression output', () => { expectTypeOf< ['match', ['get', 'id'], 'exampleID', ['get', 'iconNameFocused'], ['get', 'iconName']] >().toExtend(); }); test('type accepts expression which has literal null output', () => { expectTypeOf< ['match', 1, 0, ['get', 'prop'], 1, null, 'fallback'] >().toExtend(); }); }); describe('"within" expression', () => { test('type requires a GeoJSON input', () => { expectTypeOf<['within']>().not.toExtend(); }); test('type rejects an expression as input', () => { expectTypeOf< ['within', ['literal', {type: 'Polygon'; coordinates: []}]] >().not.toExtend(); }); test('type rejects a second argument', () => { expectTypeOf< ['within', {type: 'Polygon'; coordinates: []}, 'second arg'] >().not.toExtend(); }); test('type accepts expression which checks if feature fully contained within input GeoJSON geometry', () => { expectTypeOf< [ 'within', { type: 'Polygon'; coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]; } ] >().toExtend(); }); }); describe('interpolation expressions', () => { describe('linear interpolation type', () => { test('type works with "interpolate" expression', () => { expectTypeOf< ['interpolate', ['linear'], ['zoom'], 0, 10, 1, 20] >().toExtend(); }); }); describe('exponential interpolation type', () => { test('type requires a number literal as the base argument', () => { expectTypeOf< ['interpolate', ['exponential', ['+', 0.1, 0.4]], ['zoom'], 0, 10, 1, 100] >().not.toExtend(); }); test('type works with "interpolate" expression', () => { expectTypeOf< ['interpolate', ['exponential', 1.1], ['zoom'], 0, 10, 1, 20] >().toExtend(); }); }); describe('cubic-bezier interpolation type', () => { test('type requires four numeric literal control point arguments', () => { expectTypeOf< [ 'interpolate', ['cubic-bezier', 0.4, 0, ['literal', 0.6], 1], ['zoom'], 2, 0, 8, 100 ] >().not.toExtend(); }); test('type rejects a fifth control point argument', () => { expectTypeOf< ['interpolate', ['cubic-bezier', 0.4, 0, 0.6, 1, 0.8], ['zoom'], 2, 0, 8, 100] >().not.toExtend(); }); test('type works with "interpolate" expression', () => { expectTypeOf< ['interpolate', ['cubic-bezier', 0.4, 0, 0.6, 1], ['zoom'], 0, 0, 10, 100] >().toExtend(); }); }); describe('"interpolate" expression', () => { test('type requires stop outputs to be a number, color, number array, color array, or projection', () => { expectTypeOf< ['interpolate', ['linear'], ['zoom'], 0, false, 2, 1024] >().not.toExtend(); expectTypeOf< [ 'interpolate', ['linear'], ['zoom'], 0, [10, 20, 30], 0.5, [20, 30, 40], 1, [30, 40, 50] ] >().not.toExtend(); expectTypeOf< ['interpolate', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}] >().not.toExtend(); }); test('type accepts expression which interpolates with feature property input', () => { expectTypeOf< [ 'interpolate', ['linear'], ['get', 'point_count'], 2, ['/', 2, ['get', 'point_count']], 10, ['*', 4, ['get', 'point_count']] ] >().toExtend(); }); test('type accepts expression which interpolates between number outputs', () => { expectTypeOf< ['interpolate', ['linear'], ['zoom'], 0, 0, 0.5, ['*', 2, 5], 1, 100] >().toExtend(); }); test('type accepts expression which interpolates between color outputs', () => { expectTypeOf< ['interpolate', ['linear'], ['zoom'], 2, 'white', 4, 'black'] >().toExtend(); }); test('type accepts expression which interpolates between number array outputs', () => { expectTypeOf< [ 'interpolate', ['linear'], ['zoom'], 8, ['literal', [2, 3]], 10, ['literal', [4, 5]] ] >().toExtend(); }); test('type accepts expression which interpolates between color array outputs', () => { expectTypeOf< [ 'interpolate', ['linear'], ['zoom'], 8, ['literal', ['white', 'black']], 10, ['literal', ['black', 'white']] ] >().toExtend(); }); test('type accepts expression which interpolates between projection outputs', () => { expectTypeOf< ['interpolate', ['linear'], ['zoom'], 8, 'vertical-perspective', 10, 'mercator'] >().toExtend(); }); }); describe('"interpolate-hcl" expression', () => { test('type requires stop outputs to be a color', () => { expectTypeOf< ['interpolate-hcl', ['linear'], ['zoom'], 0, false, 2, 1024] >().not.toExtend(); expectTypeOf< [ 'interpolate-hcl', ['linear'], ['zoom'], 0, [10, 20, 30], 0.5, [20, 30, 40], 1, [30, 40, 50] ] >().not.toExtend(); expectTypeOf< ['interpolate-hcl', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}] >().not.toExtend(); }); test('type accepts expression which interpolates between color outputs', () => { expectTypeOf< ['interpolate-hcl', ['linear'], ['zoom'], 2, 'white', 4, 'black'] >().toExtend(); }); test('type accepts expression which interpolates between color array outputs', () => { expectTypeOf< [ 'interpolate-hcl', ['linear'], ['zoom'], 8, ['literal', ['white', 'black']], 10, ['literal', ['black', 'white']] ] >().toExtend(); }); test('type accepts expression which interpolates between non-literal color array outputs', () => { // eslint-disable-next-line const obj = {'colors-8': ['white', 'black'], 'colors-10': ['black', 'white']}; expectTypeOf< [ 'interpolate-hcl', ['linear'], ['zoom'], 8, ['get', 'colors-8', ['literal', typeof obj]], 10, ['get', 'colors-10', ['literal', typeof obj]] ] >().toExtend(); }); }); describe('"interpolate-lab" expression', () => { test('type requires stop outputs to be a color', () => { expectTypeOf< ['interpolate-lab', ['linear'], ['zoom'], 0, false, 2, 1024] >().not.toExtend(); expectTypeOf< [ 'interpolate-lab', ['linear'], ['zoom'], 0, [10, 20, 30], 0.5, [20, 30, 40], 1, [30, 40, 50] ] >().not.toExtend(); expectTypeOf< ['interpolate-lab', ['linear'], ['zoom'], 0, {prop: 'foo'}, 2, {prop: 'bar'}] >().not.toExtend(); }); test('type accepts expression which interpolates between color outputs', () => { expectTypeOf< ['interpolate-lab', ['linear'], ['zoom'], 2, 'white', 4, 'black'] >().toExtend(); }); test('type accepts expression which interpolates between color array outputs', () => { expectTypeOf< [ 'interpolate-lab', ['linear'], ['zoom'], 8, ['literal', ['white', 'black']], 10, ['literal', ['black', 'white']] ] >().toExtend(); }); test('type accepts expression which interpolates between non-literal color array outputs', () => { // eslint-disable-next-line const obj = {'colors-8': ['white', 'black'], 'colors-10': ['black', 'white']}; expectTypeOf< [ 'interpolate-lab', ['linear'], ['zoom'], 8, ['get', 'colors-8', ['literal', typeof obj]], 10, ['get', 'colors-10', ['literal', typeof obj]] ] >().toExtend(); }); }); }); describe('"step" expression', () => { test('type accepts expression which outputs stepped numbers', () => { expectTypeOf< ['step', ['get', 'point_count'], 0.6, 50, 0.7, 200, 0.8] >().toExtend(); }); test('type accepts expression which outputs stepped colors', () => { expectTypeOf< ['step', ['get', 'point_count'], '#ddd', 50, '#eee', 200, '#fff'] >().toExtend(); }); test('type accepts expression which outputs stepped projections', () => { expectTypeOf< ['step', ['zoom'], 'vertical-perspective', 10, 'mercator'] >().toExtend(); }); test('type accepts expression which outputs stepped multi-input projections', () => { expectTypeOf< [ 'step', ['zoom'], ['literal', ['vertical-perspective', 'mercator', 0.5]], 10, 'mercator' ] >().toExtend(); }); }); describe('"e" expression', () => { test('type rejects any arguments', () => { expectTypeOf<['e', 2]>().not.toExtend(); }); test('type accepts expression which returns the mathematical constant e', () => { expectTypeOf<['e']>().toExtend(); }); }); describe('nonexistent operators', () => { test('ExpressionSpecification type does not contain "ExpressionSpecification" expression', () => { type ExpressionSpecificationExpression = Extract< ExpressionSpecification, ['ExpressionSpecification', ...any[]] >; expectTypeOf().not.toExtend(); }); }); test('ExpressionSpecification type supports common variable insertion patterns', () => { // Checks the ability for the ExpressionSpecification type to allow arguments to be provided via constants (as opposed to in-line). // As in most cases the styling is read from JSON, these are rather optional tests. // eslint-disable-next-line const colorStops = [0, 'red', 0.5, 'green', 1, 'blue']; expectTypeOf< ['interpolate', ['linear'], ['line-progress'], ...typeof colorStops] >().toExtend(); expectTypeOf< ['interpolate-hcl', ['linear'], ['line-progress'], ...typeof colorStops] >().toExtend(); expectTypeOf< ['interpolate-lab', ['linear'], ['line-progress'], ...typeof colorStops] >().toExtend(); // eslint-disable-next-line const [firstOutput, ...steps] = ['#df2d43', 50, '#df2d43', 200, '#df2d43']; expectTypeOf< ['step', ['get', 'point_count'], typeof firstOutput, ...typeof steps] >().toExtend(); // eslint-disable-next-line const strings = ['first', 'second', 'third']; expectTypeOf<['concat', ...typeof strings]>().toExtend(); // eslint-disable-next-line const values: (ExpressionInputType | ExpressionSpecification)[] = [ ['get', 'name'], ['get', 'code'], 'NONE' ]; // type is necessary! expectTypeOf<['coalesce', ...typeof values]>().toExtend(); });