@@ -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' ;
1218import { ExpressionParsingError } from '../../../src/expression/parsing_error' ;
13- import { Result } from '../../../src/util/result' ;
1419import { getGeometry } from '../../lib/geometry' ;
1520import { deepEqual , stripPrecision } from '../../lib/json-diff' ;
1621import { 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
2029type 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
3682const expressionTestFileNames = globSync ( '**/test.json' , { cwd : __dirname } ) ;
3783describe ( '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