Skip to content

Commit 01570f5

Browse files
authored
use global-state added to expression in constructor instead of the one passed as a parameter to evaluate function (#1267)
* prepare integration tests for `global-state` to define it as an object separate from expression parameters * use `global-state` added to expression in constructor instead of the one passed as a parameter to the `evaluate` function * changelog
1 parent ffc6ee0 commit 01570f5

File tree

18 files changed

+514
-37
lines changed

18 files changed

+514
-37
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
## main
22

33
### ✨ Features and improvements
4+
5+
- Change passing `global-state` to expression from the `evaluate` function to the constructor ([#1267](https://github.com/maplibre/maplibre-style-spec/pull/1267))
46
- _...Add new stuff here..._
57

68
### 🐞 Bug fixes

src/expression/expression.test.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('createPropertyExpression', () => {
3939
});
4040

4141
test('sets globalStateRefs', () => {
42-
const {value} = createPropertyExpression(['case', ['>', ['global-state','stateKey'], 0], 100, ['global-state', 'anotherStateKey']], {
42+
const {value} = createPropertyExpression(['case', ['>', ['global-state', 'stateKey'], 0], 100, ['global-state', 'anotherStateKey']], {
4343
type: 'number',
4444
'property-type': 'data-driven',
4545
expression: {
@@ -69,6 +69,38 @@ describe('evaluate expression', () => {
6969
expect(value.evaluate({globalState: {}, zoom: 10} as GlobalProperties)).toBe(42);
7070
expect(console.warn).not.toHaveBeenCalled();
7171
});
72+
73+
test('global state as expression property', () => {
74+
const {value} = createPropertyExpression(['global-state', 'x'], {
75+
type: null,
76+
default: 42,
77+
'property-type': 'data-driven',
78+
transition: false
79+
}, {x: 5}) as {value: StylePropertyExpression};
80+
81+
vi.spyOn(console, 'warn').mockImplementation(() => {});
82+
83+
expect(value.evaluate({globalState: {x: 15}, zoom: 10} as GlobalProperties)).toBe(5);
84+
expect(console.warn).not.toHaveBeenCalled();
85+
});
86+
87+
test('global state as expression property of zoom dependent expression', () => {
88+
const {value} = createPropertyExpression(['interpolate', ['linear'], ['zoom'], 10, ['global-state', 'x'], 20, 50], {
89+
type: 'number',
90+
default: 42,
91+
'property-type': 'data-driven',
92+
expression: {
93+
interpolated: true,
94+
parameters: ['zoom']
95+
}
96+
} as StylePropertySpecification, {x: 5}) as {value: StylePropertyExpression};
97+
98+
vi.spyOn(console, 'warn').mockImplementation(() => {});
99+
100+
expect(value.evaluate({globalState: {x: 15}, zoom: 10} as GlobalProperties)).toBe(5);
101+
expect(console.warn).not.toHaveBeenCalled();
102+
});
103+
72104
test('warns and falls back to default for invalid enum values', () => {
73105
const {value} = createPropertyExpression(['get', 'x'], {
74106
type: 'enum',
@@ -81,7 +113,7 @@ describe('evaluate expression', () => {
81113
}
82114
} as any as StylePropertySpecification) as {value: StylePropertyExpression};
83115

84-
vi.spyOn(console, 'warn').mockImplementation(() => { });
116+
vi.spyOn(console, 'warn').mockImplementation(() => {});
85117

86118
expect(value.kind).toBe('source');
87119

src/expression/index.ts

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,15 @@ export class StyleExpression {
7373
_defaultValue: Value;
7474
_warningHistory: {[key: string]: boolean};
7575
_enumValues: {[_: string]: any};
76+
readonly _globalState: Record<string, any>;
7677

77-
constructor(expression: Expression, propertySpec?: StylePropertySpecification | null) {
78+
constructor(expression: Expression, propertySpec?: StylePropertySpecification | null, globalState?: Record<string, any>) {
7879
this.expression = expression;
7980
this._warningHistory = {};
8081
this._evaluator = new EvaluationContext();
8182
this._defaultValue = propertySpec ? getDefaultValue(propertySpec) : null;
8283
this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null;
84+
this._globalState = globalState;
8385
}
8486

8587
evaluateWithoutErrorHandling(
@@ -90,6 +92,9 @@ export class StyleExpression {
9092
availableImages?: Array<string>,
9193
formattedSection?: FormattedSection
9294
): any {
95+
if (this._globalState) {
96+
globals = {...globals, globalState: this._globalState};
97+
}
9398
this._evaluator.globals = globals;
9499
this._evaluator.feature = feature;
95100
this._evaluator.featureState = featureState;
@@ -108,6 +113,9 @@ export class StyleExpression {
108113
availableImages?: Array<string>,
109114
formattedSection?: FormattedSection
110115
): any {
116+
if (this._globalState) {
117+
globals = {...globals, globalState: this._globalState};
118+
}
111119
this._evaluator.globals = globals;
112120
this._evaluator.feature = feature || null;
113121
this._evaluator.featureState = featureState || null;
@@ -150,7 +158,7 @@ export function isExpression(expression: unknown) {
150158
*
151159
* @private
152160
*/
153-
export function createExpression(expression: unknown, propertySpec?: StylePropertySpecification | null): Result<StyleExpression, Array<ExpressionParsingError>> {
161+
export function createExpression(expression: unknown, propertySpec?: StylePropertySpecification | null, globalState?: Record<string, any>): Result<StyleExpression, Array<ExpressionParsingError>> {
154162
const parser = new ParsingContext(expressions, isExpressionConstant, [], propertySpec ? getExpectedType(propertySpec) : undefined);
155163

156164
// For string-valued properties, coerce to string at the top level rather than asserting.
@@ -161,20 +169,22 @@ export function createExpression(expression: unknown, propertySpec?: StyleProper
161169
return error(parser.errors);
162170
}
163171

164-
return success(new StyleExpression(parsed, propertySpec));
172+
return success(new StyleExpression(parsed, propertySpec, globalState));
165173
}
166174

167175
export class ZoomConstantExpression<Kind extends EvaluationKind> {
168176
kind: Kind;
169177
isStateDependent: boolean;
170178
globalStateRefs: Set<string>;
171179
_styleExpression: StyleExpression;
180+
readonly _globalState: Record<string, any>;
172181

173-
constructor(kind: Kind, expression: StyleExpression) {
182+
constructor(kind: Kind, expression: StyleExpression, globalState?: Record<string, any>) {
174183
this.kind = kind;
175184
this._styleExpression = expression;
176185
this.isStateDependent = kind !== ('constant' as EvaluationKind) && !isStateConstant(expression.expression);
177186
this.globalStateRefs = findGlobalStateRefs(expression.expression);
187+
this._globalState = globalState;
178188
}
179189

180190
evaluateWithoutErrorHandling(
@@ -185,6 +195,9 @@ export class ZoomConstantExpression<Kind extends EvaluationKind> {
185195
availableImages?: Array<string>,
186196
formattedSection?: FormattedSection
187197
): any {
198+
if (this._globalState) {
199+
globals = {...globals, globalState: this._globalState};
200+
}
188201
return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection);
189202
}
190203

@@ -196,6 +209,9 @@ export class ZoomConstantExpression<Kind extends EvaluationKind> {
196209
availableImages?: Array<string>,
197210
formattedSection?: FormattedSection
198211
): any {
212+
if (this._globalState) {
213+
globals = {...globals, globalState: this._globalState};
214+
}
199215
return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection);
200216
}
201217
}
@@ -207,14 +223,16 @@ export class ZoomDependentExpression<Kind extends EvaluationKind> {
207223
globalStateRefs: Set<string>;
208224
_styleExpression: StyleExpression;
209225
interpolationType: InterpolationType;
226+
readonly _globalState: Record<string, any>;
210227

211-
constructor(kind: Kind, expression: StyleExpression, zoomStops: Array<number>, interpolationType?: InterpolationType) {
228+
constructor(kind: Kind, expression: StyleExpression, zoomStops: Array<number>, interpolationType?: InterpolationType, globalState?: Record<string, any>) {
212229
this.kind = kind;
213230
this.zoomStops = zoomStops;
214231
this._styleExpression = expression;
215232
this.isStateDependent = kind !== ('camera' as EvaluationKind) && !isStateConstant(expression.expression);
216233
this.globalStateRefs = findGlobalStateRefs(expression.expression);
217234
this.interpolationType = interpolationType;
235+
this._globalState = globalState;
218236
}
219237

220238
evaluateWithoutErrorHandling(
@@ -225,6 +243,9 @@ export class ZoomDependentExpression<Kind extends EvaluationKind> {
225243
availableImages?: Array<string>,
226244
formattedSection?: FormattedSection
227245
): any {
246+
if (this._globalState) {
247+
globals = {...globals, globalState: this._globalState};
248+
}
228249
return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection);
229250
}
230251

@@ -236,6 +257,9 @@ export class ZoomDependentExpression<Kind extends EvaluationKind> {
236257
availableImages?: Array<string>,
237258
formattedSection?: FormattedSection
238259
): any {
260+
if (this._globalState) {
261+
globals = {...globals, globalState: this._globalState};
262+
}
239263
return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection);
240264
}
241265

@@ -255,6 +279,7 @@ export function isZoomExpression(expression: any): expression is ZoomConstantExp
255279
export type ConstantExpression = {
256280
kind: 'constant';
257281
globalStateRefs: Set<string>;
282+
readonly _globalState: Record<string, any>;
258283
readonly evaluate: (
259284
globals: GlobalProperties,
260285
feature?: Feature,
@@ -268,6 +293,7 @@ export type SourceExpression = {
268293
kind: 'source';
269294
isStateDependent: boolean;
270295
globalStateRefs: Set<string>;
296+
readonly _globalState: Record<string, any>;
271297
readonly evaluate: (
272298
globals: GlobalProperties,
273299
feature?: Feature,
@@ -281,6 +307,7 @@ export type SourceExpression = {
281307
export type CameraExpression = {
282308
kind: 'camera';
283309
globalStateRefs: Set<string>;
310+
readonly _globalState: Record<string, any>;
284311
readonly evaluate: (
285312
globals: GlobalProperties,
286313
feature?: Feature,
@@ -297,6 +324,7 @@ export type CompositeExpression = {
297324
kind: 'composite';
298325
isStateDependent: boolean;
299326
globalStateRefs: Set<string>;
327+
readonly _globalState: Record<string, any>;
300328
readonly evaluate: (
301329
globals: GlobalProperties,
302330
feature?: Feature,
@@ -312,8 +340,8 @@ export type CompositeExpression = {
312340

313341
export type StylePropertyExpression = ConstantExpression | SourceExpression | CameraExpression | CompositeExpression;
314342

315-
export function createPropertyExpression(expressionInput: unknown, propertySpec: StylePropertySpecification): Result<StylePropertyExpression, Array<ExpressionParsingError>> {
316-
const expression = createExpression(expressionInput, propertySpec);
343+
export function createPropertyExpression(expressionInput: unknown, propertySpec: StylePropertySpecification, globalState?: Record<string, any>): Result<StylePropertyExpression, Array<ExpressionParsingError>> {
344+
const expression = createExpression(expressionInput, propertySpec, globalState);
317345
if (expression.result === 'error') {
318346
return expression;
319347
}
@@ -341,15 +369,15 @@ export function createPropertyExpression(expressionInput: unknown, propertySpec:
341369

342370
if (!zoomCurve) {
343371
return success(isFeatureConstantResult ?
344-
(new ZoomConstantExpression('constant', expression.value) as ConstantExpression) :
345-
(new ZoomConstantExpression('source', expression.value) as SourceExpression));
372+
(new ZoomConstantExpression('constant', expression.value, globalState) as ConstantExpression) :
373+
(new ZoomConstantExpression('source', expression.value, globalState) as SourceExpression));
346374
}
347375

348376
const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined;
349377

350378
return success(isFeatureConstantResult ?
351-
(new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType) as CameraExpression) :
352-
(new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType) as CompositeExpression));
379+
(new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType, globalState) as CameraExpression) :
380+
(new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType, globalState) as CompositeExpression));
353381
}
354382

355383
// serialization wrapper for old-style stop functions normalized to the
@@ -386,13 +414,14 @@ export class StylePropertyFunction<T> {
386414

387415
export function normalizePropertyExpression<T>(
388416
value: PropertyValueSpecification<T>,
389-
specification: StylePropertySpecification
417+
specification: StylePropertySpecification,
418+
globalState?: Record<string, any>
390419
): StylePropertyExpression {
391420
if (isFunction(value)) {
392421
return new StylePropertyFunction(value, specification) as any;
393422

394423
} else if (isExpression(value)) {
395-
const expression = createPropertyExpression(value, specification);
424+
const expression = createPropertyExpression(value, specification, globalState);
396425
if (expression.result === 'error') {
397426
// this should have been caught in validation
398427
throw new Error(expression.value.map(err => `${err.key}: ${err.message}`).join(', '));
@@ -416,6 +445,7 @@ export function normalizePropertyExpression<T>(
416445
}
417446
return {
418447
globalStateRefs: new Set<string>(),
448+
_globalState: null,
419449
kind: 'constant',
420450
evaluate: () => constant
421451
};

test/integration/expression/expression.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type ExpressionFixture = {
3030
propertySpec?: Partial<StylePropertySpecification>;
3131
expression: any[];
3232
inputs?: FixtureInput[];
33+
globalState?: Record<string, any>;
3334
expected?: FixtureResult;
3435
};
3536

@@ -144,8 +145,8 @@ function getCompletePropertySpec(propertySpec: ExpressionFixture['propertySpec']
144145

145146
function evaluateFixture(fixture: ExpressionFixture, spec: StylePropertySpecification): FixtureResult {
146147
const expression = isFunction(fixture.expression) ?
147-
createPropertyExpression(convertFunction(fixture.expression, spec), spec) :
148-
createPropertyExpression(fixture.expression, spec);
148+
createPropertyExpression(convertFunction(fixture.expression, spec), spec, fixture.globalState) :
149+
createPropertyExpression(fixture.expression, spec, fixture.globalState);
149150

150151
if (expression.result === 'error') {
151152
return {

test/integration/expression/tests/global-state/basic/test.json

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,11 @@
1010
[
1111
{},
1212
{}
13-
],
14-
[
15-
{
16-
"globalState": {}
17-
},
18-
{}
19-
],
20-
[
21-
{
22-
"globalState": {
23-
"x": 1
24-
}
25-
},
26-
{}
2713
]
2814
],
15+
"globalState": {
16+
"x": 1
17+
},
2918
"expected": {
3019
"compiled": {
3120
"result": "success",
@@ -34,12 +23,6 @@
3423
"type": "number"
3524
},
3625
"outputs": [
37-
{
38-
"error": "Expected value to be of type number, but found null instead."
39-
},
40-
{
41-
"error": "Expected value to be of type number, but found null instead."
42-
},
4326
1
4427
]
4528
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"expression": [
3+
"number",
4+
[
5+
"global-state",
6+
"x"
7+
]
8+
],
9+
"inputs": [
10+
[
11+
{},
12+
{}
13+
]
14+
],
15+
"globalState": {},
16+
"expected": {
17+
"compiled": {
18+
"result": "success",
19+
"isFeatureConstant": true,
20+
"isZoomConstant": true,
21+
"type": "number"
22+
},
23+
"outputs": [
24+
{
25+
"error": "Expected value to be of type number, but found null instead."
26+
}
27+
]
28+
}
29+
}

0 commit comments

Comments
 (0)