Skip to content

Commit 2af178e

Browse files
authored
Improve typings in expression integration test file (#1172)
* impove typings in expression integration test file * annotate evaluateExpression return type * change map over inputs to for loop
1 parent a42dfb4 commit 2af178e

File tree

1 file changed

+153
-105
lines changed

1 file changed

+153
-105
lines changed

test/integration/expression/expression.test.ts

Lines changed: 153 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -7,180 +7,228 @@ import {
77
convertFunction,
88
toString,
99
ICanonicalTileID,
10-
StylePropertyExpression
10+
StylePropertyExpression,
11+
StylePropertySpecification,
12+
ZoomConstantExpression,
13+
StyleExpression,
14+
GlobalProperties,
15+
Feature,
16+
ZoomDependentExpression
1117
} from '../../../src/index';
1218
import {ExpressionParsingError} from '../../../src/expression/parsing_error';
13-
import {Result} from '../../../src/util/result';
1419
import {getGeometry} from '../../lib/geometry';
1520
import {deepEqual, stripPrecision} from '../../lib/json-diff';
1621
import {describe, expect, test} from 'vitest';
1722

18-
const DECIMAL_SIGNIFICANT_FIGURES = 6;
23+
const DECIMAL_SIGNIFICANT_FIGURES = 6;
24+
25+
type Mutable<T> = {
26+
-readonly [K in keyof T]: T[K];
27+
};
1928

2029
type ExpressionFixture = {
21-
propertySpec: any;
30+
propertySpec?: Partial<StylePropertySpecification>;
2231
expression: any[];
23-
inputs:any[];
24-
expected: {
25-
compiled?: {
26-
result?: any;
27-
isFeatureConstant?: any;
28-
isZoomConstant?: any;
29-
type?: any;
32+
inputs?: FixtureInput[];
33+
expected?: FixtureResult;
34+
};
35+
36+
type FixtureInput = [
37+
Partial<GlobalProperties> & {
38+
availableImages?: string[];
39+
canonicalID?: {
40+
z: number;
41+
x: number;
42+
y: number;
3043
};
31-
outputs? : any;
32-
serialized?: any;
33-
};
34-
}
44+
},
45+
{
46+
properties?: Record<string, any>;
47+
featureState?: Record<string, any>;
48+
id?: any;
49+
geometry?: GeoJSON.Point | GeoJSON.MultiPoint | GeoJSON.LineString | GeoJSON.MultiLineString | GeoJSON.Polygon | GeoJSON.MultiPolygon;
50+
},
51+
];
52+
53+
type FixtureResult = FixtureErrorResult | FixtureSuccessResult;
54+
type FixtureErrorResult = {
55+
compiled: CompilationErrorResult;
56+
};
57+
type FixtureSuccessResult = {
58+
compiled: CompilationSuccessResult;
59+
outputs: EvaluationOutput[];
60+
};
61+
62+
type CompilationErrorResult = {
63+
result: 'error';
64+
errors: {
65+
key: string;
66+
error: string;
67+
}[];
68+
};
69+
type CompilationSuccessResult = {
70+
result: 'success';
71+
isFeatureConstant: boolean;
72+
isZoomConstant: boolean;
73+
type: string;
74+
};
75+
76+
type EvaluationOutput = EvaluationErrorOutput | EvaluationSuccessOutput;
77+
type EvaluationErrorOutput = {
78+
error: string;
79+
};
80+
type EvaluationSuccessOutput = any;
3581

3682
const expressionTestFileNames = globSync('**/test.json', {cwd: __dirname});
3783
describe('expression', () => {
3884

3985
for (const expressionTestFileName of expressionTestFileNames) {
4086
test(expressionTestFileName, () => {
4187

42-
const fixture = JSON.parse(fs.readFileSync(path.join(__dirname, expressionTestFileName), 'utf8'));
88+
const fixturePath = path.join(__dirname, expressionTestFileName);
89+
const fixture: ExpressionFixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
4390

44-
const result = evaluateFixture(fixture);
91+
const spec = getCompletePropertySpec(fixture.propertySpec);
92+
const result = evaluateFixture(fixture, spec);
4593

4694
if (process.env.UPDATE) {
47-
fixture.expected = {
48-
compiled: result.compiled,
49-
outputs: stripPrecision(result.outputs, DECIMAL_SIGNIFICANT_FIGURES),
50-
};
51-
52-
delete fixture.metadata;
95+
fixture.expected = isFixtureErrorResult(result) ?
96+
result :
97+
{
98+
compiled: result.compiled,
99+
outputs: stripPrecision(result.outputs, DECIMAL_SIGNIFICANT_FIGURES),
100+
};
101+
if (fixture.propertySpec) {
102+
fixture.propertySpec = spec;
103+
}
53104

54-
const fname = path.join(__dirname, expressionTestFileName);
55-
fs.writeFileSync(fname, JSON.stringify(fixture, null, 2));
105+
fs.writeFileSync(fixturePath, JSON.stringify(fixture, null, 2));
56106
return;
57107
}
58108

59-
const expected = fixture.expected;
109+
const expected = fixture.expected as FixtureResult;
110+
60111
const compileOk = deepEqual(result.compiled, expected.compiled, DECIMAL_SIGNIFICANT_FIGURES);
61-
62-
const evalOk = compileOk && deepEqual(result.outputs, expected.outputs, DECIMAL_SIGNIFICANT_FIGURES);
63-
64112
try {
65113
expect(compileOk).toBeTruthy();
66114
} catch {
67115
throw new Error(`Compilation Failed:\nExpected ${JSON.stringify(expected.compiled)}\nResult ${JSON.stringify(result.compiled)}`);
68116
}
69-
117+
118+
const resultOutputs = (result as any).outputs;
119+
const expectedOutputs = (expected as any).outputs;
120+
const evalOk = compileOk && deepEqual(resultOutputs, expectedOutputs, DECIMAL_SIGNIFICANT_FIGURES);
70121
try {
71122
expect(evalOk).toBeTruthy();
72123
} catch {
73-
throw new Error(`Evaluation Failed:\nExpected ${JSON.stringify(expected.outputs)}\nResult ${JSON.stringify(result.outputs)}`);
124+
throw new Error(`Evaluation Failed:\nExpected ${JSON.stringify(expectedOutputs)}\nResult ${JSON.stringify(resultOutputs)}`);
74125
}
75126

76127
});
77128
}
78129
});
79130

80-
function evaluateFixture(fixture: ExpressionFixture) {
81-
const spec = fixture.propertySpec || {};
82-
131+
function getCompletePropertySpec(propertySpec: ExpressionFixture['propertySpec']) {
132+
const spec = propertySpec === undefined ? {} : {...propertySpec};
83133
if (!spec['property-type']) {
84134
spec['property-type'] = 'data-driven';
85135
}
86-
87136
if (!spec['expression']) {
88137
spec['expression'] = {
89138
'interpolated': true,
90-
'parameters': ['zoom', 'feature']
139+
'parameters': ['zoom', 'feature'],
91140
};
92141
}
142+
return spec as StylePropertySpecification;
143+
}
93144

145+
function evaluateFixture(fixture: ExpressionFixture, spec: StylePropertySpecification): FixtureResult {
94146
const expression = isFunction(fixture.expression) ?
95147
createPropertyExpression(convertFunction(fixture.expression, spec), spec) :
96148
createPropertyExpression(fixture.expression, spec);
97149

98-
const result: { compiled: any; outputs?: any } = {
99-
compiled: getCompilationResult(expression)
100-
};
101-
102-
if (result.compiled.result !== 'error') {
103-
result.outputs = evaluateExpression(fixture, expression);
150+
if (expression.result === 'error') {
151+
return {
152+
compiled: getCompilationErrorResult(expression.value),
153+
};
104154
}
105-
106-
return result;
155+
return {
156+
compiled: getCompilationSuccessResult(expression.value),
157+
outputs: fixture.inputs === undefined ? [] : evaluateExpression(fixture.inputs, expression.value),
158+
};
107159
}
108160

109-
function getCompilationResult(expression: Result<StylePropertyExpression, ExpressionParsingError[]>) {
110-
const compilationResult = {} as any;
111-
if (expression.result === 'error') {
112-
compilationResult.result = 'error';
113-
compilationResult.errors = expression.value.map((err) => ({
161+
function getCompilationErrorResult(parsingErrors: ExpressionParsingError[]): CompilationErrorResult {
162+
return {
163+
result: 'error',
164+
errors: parsingErrors.map((err) => ({
114165
key: err.key,
115-
error: err.message
116-
}));
117-
return compilationResult;
118-
}
119-
120-
const expressionValue = expression.value;
121-
const type = (expressionValue as any)._styleExpression.expression.type; // :scream:
122-
123-
compilationResult.result = 'success';
124-
compilationResult.isFeatureConstant = expressionValue.kind === 'constant' || expressionValue.kind === 'camera';
125-
compilationResult.isZoomConstant = expressionValue.kind === 'constant' || expressionValue.kind === 'source';
126-
compilationResult.type = toString(type);
127-
128-
return compilationResult;
166+
error: err.message,
167+
})),
168+
};
129169
}
130170

131-
function evaluateExpression(fixture: ExpressionFixture, expression: Result<StylePropertyExpression, ExpressionParsingError[]>) {
132-
133-
let availableImages: any[];
134-
let canonical: ICanonicalTileID | null;
171+
function getCompilationSuccessResult(expression: StylePropertyExpression): CompilationSuccessResult {
172+
const kind = expression.kind;
173+
const type = getStylePropertyExpressionType(expression);
174+
return {
175+
result: 'success',
176+
isFeatureConstant: kind === 'constant' || kind ==='camera',
177+
isZoomConstant: kind === 'constant' || kind === 'source',
178+
type: toString(type),
179+
};
180+
}
135181

136-
const evaluationResult: any[] = [];
182+
function evaluateExpression(inputs: FixtureInput[], expression: StylePropertyExpression): EvaluationOutput[] {
183+
const type = getStylePropertyExpressionType(expression);
184+
const outputs: EvaluationOutput[] = [];
137185

138-
const expressionValue = expression.value;
139-
const type = (expressionValue as any)._styleExpression.expression.type; // :scream:
186+
for (const input of inputs) {
187+
const {availableImages, canonicalID} = input[0];
188+
const {featureState, geometry, id, properties} = input[1];
140189

141-
for (const input of fixture.inputs || []) {
142-
try {
143-
const feature: {
144-
properties: any;
145-
id?: any;
146-
type?: any;
147-
} = {properties: input[1].properties || {}};
148-
const featureState = input[1].featureState ?? {};
149-
availableImages = input[0].availableImages || [];
150-
if ('canonicalID' in input[0]) {
151-
const id = input[0].canonicalID;
152-
canonical = {z: id.z, x: id.x, y: id.y} as any;
190+
const canonical = (canonicalID ?? null) as ICanonicalTileID | null;
191+
const feature: Partial<Mutable<Feature>> = {
192+
properties: properties ?? {},
193+
};
194+
if (id !== undefined) {
195+
feature.id = id;
196+
}
197+
if (geometry !== undefined) {
198+
if (canonical !== null) {
199+
getGeometry(feature, geometry, canonical);
153200
} else {
154-
canonical = null;
155-
}
156-
157-
if ('id' in input[1]) {
158-
feature.id = input[1].id;
159-
}
160-
if ('geometry' in input[1]) {
161-
if (canonical !== null) {
162-
getGeometry(feature, input[1].geometry, canonical);
163-
} else {
164-
feature.type = input[1].geometry.type;
165-
}
201+
feature.type = geometry.type;
166202
}
203+
}
167204

168-
let value = expressionValue.evaluateWithoutErrorHandling(input[0], feature, featureState, canonical, availableImages);
169-
205+
try {
206+
let value = (expression as ZoomConstantExpression<any> | ZoomDependentExpression<any>).evaluateWithoutErrorHandling(
207+
input[0] as GlobalProperties,
208+
feature as Feature,
209+
featureState ?? {},
210+
canonical as ICanonicalTileID,
211+
availableImages ?? [],
212+
);
170213
if (type.kind === 'color') {
171214
value = [value.r, value.g, value.b, value.a];
172215
}
173-
evaluationResult.push(value);
216+
outputs.push(value);
174217
} catch (error) {
175-
if (error.name === 'ExpressionEvaluationError') {
176-
evaluationResult.push({error: error.toJSON()});
177-
} else {
178-
evaluationResult.push({error: error.message});
179-
}
218+
outputs.push({
219+
error: error.name === 'ExpressionEvaluationError' ?
220+
error.toJSON() :
221+
error.message,
222+
});
180223
}
181224
}
225+
return outputs;
226+
}
182227

183-
if (fixture.inputs) {
184-
return evaluationResult;
185-
}
228+
function getStylePropertyExpressionType(expression: StylePropertyExpression) {
229+
return ((expression as any)._styleExpression as StyleExpression).expression.type;
230+
}
231+
232+
function isFixtureErrorResult(fixtureResult: FixtureResult): fixtureResult is FixtureErrorResult {
233+
return fixtureResult.compiled.result === 'error';
186234
}

0 commit comments

Comments
 (0)