maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
110 lines (84 loc) • 4.53 kB
text/typescript
import {describe, test, expect, vi, afterEach} from 'vitest';
import {validateAndEmit, validateStyle, validateStyleAndEmit} from './validate_style.ts';
import {Evented, type ErrorEventType} from '../util/evented.ts';
import type {StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
class TestEmitter extends Evented<ErrorEventType> {}
afterEach(() => {
vi.restoreAllMocks();
});
/** An emitter collecting the messages of the {@link ErrorEvent}s fired at it, plus a `console.warn` spy. */
function setup() {
const emitter = new TestEmitter();
const fired: string[] = [];
emitter.on('error', ({error}) => fired.push(error.message));
return {emitter, fired, warn: vi.spyOn(console, 'warn').mockImplementation(() => {})};
}
describe('validateAndEmit', () => {
const key = 'layers.symbol.filter';
test('fires an error and reports failure for a filter the spec rejects', () => {
const {emitter, fired} = setup();
const hasErrors = validateAndEmit(emitter, validateStyle.filter, {
key,
value: ['all', ['==', ['get', 'class'], 'rail'], ['nope', 1]]
});
expect(hasErrors).toBe(true);
expect(fired).toEqual([expect.stringContaining('Unknown expression "nope"')]);
});
test('only warns for a filter that mixes legacy syntax into an expression, so the style keeps loading', () => {
const {emitter, fired, warn} = setup();
const hasErrors = validateAndEmit(emitter, validateStyle.filter, {
key,
value: ['all', ['==', ['get', 'class'], 'rail'], ['in', 'name', '']]
});
expect(hasErrors).toBe(false);
expect(fired).toEqual([]);
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Mixing deprecated filter syntax with expression syntax'));
});
test('still fails when an error accompanies a warning, emitting only the error', () => {
const {emitter, fired, warn} = setup();
// `!in` has no expression equivalent, so the spec reports it as an error on top of the
// warning about the mixing itself. A warning must not swallow that error.
const hasErrors = validateAndEmit(emitter, validateStyle.filter, {
key,
value: ['all', ['==', ['get', 'class'], 'rail'], ['!in', 'name', 'a']]
});
expect(hasErrors).toBe(true);
expect(fired).toEqual([expect.stringContaining('Unknown expression "!in"')]);
expect(warn).toHaveBeenCalledWith(expect.stringContaining('Mixing deprecated filter syntax with expression syntax'));
});
test('skips validation entirely when the validate option is false', () => {
const {emitter, fired} = setup();
expect(validateAndEmit(emitter, validateStyle.filter, {key, value: 'notafilter'}, {validate: false})).toBe(false);
expect(fired).toEqual([]);
});
});
describe('validateStyleAndEmit', () => {
const coordinates = [[0, 0], [1, 0], [1, 1], [0, 1]];
const styleWith = (sources: object, layers: object[] = []) =>
({version: 8, sources, layers}) as any as StyleSpecification;
test('ignores sources the spec has no schema for, which MapLibre renders anyway', () => {
const {emitter, fired} = setup();
// `canvas` is not in the spec at all, and `addSourceType` can register anything else.
const hasErrors = validateStyleAndEmit(emitter, styleWith({
canvas: {type: 'canvas', canvas: 'c', coordinates},
custom: {type: 'registered-at-runtime', url: 'https://example.com'}
}, [{id: 'l', type: 'raster', source: 'canvas'}]));
expect(hasErrors).toBe(false);
expect(fired).toEqual([]);
});
test('still fails on a source the spec does know', () => {
const {emitter, fired} = setup();
const hasErrors = validateStyleAndEmit(emitter, styleWith({v: {type: 'vector', tiles: 'not-an-array'}}));
expect(hasErrors).toBe(true);
expect(fired).toEqual([expect.stringContaining('sources.v.tiles')]);
});
test('still fails elsewhere in a style that also holds an ignored source', () => {
const {emitter, fired} = setup();
const hasErrors = validateStyleAndEmit(emitter, styleWith(
{canvas: {type: 'canvas', canvas: 'c', coordinates}},
[{id: 'l', type: 'not-a-layer-type', source: 'canvas'}]
));
expect(hasErrors).toBe(true);
expect(fired).toEqual([expect.stringContaining('layers[0]')]);
});
});