vega2ol 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ /**
2
+ * vega2ol - Vega expression to OpenLayers expression transpiler
3
+ */
4
+ export { vega2ol, Vega2OLError, VegaNode } from './main.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * vega2ol - Vega expression to OpenLayers expression transpiler
3
+ */
4
+ export { vega2ol, Vega2OLError } from './main.js';
package/dist/main.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { ExpressionValue } from "ol/expr/expression.js";
2
+ /**
3
+ * OpenLayers expression types
4
+ * OL expressions are arrays: ['operator', arg1, arg2, ...] or ['case', condition, value, ...]
5
+ */
6
+ /**
7
+ * Vega AST node types (simplified)
8
+ */
9
+ interface VegaNode {
10
+ type: string;
11
+ [key: string]: any;
12
+ }
13
+ /**
14
+ * Operator mapping from Vega operators to OpenLayers operators
15
+ */
16
+ export declare const OPERATOR_MAPPING: Record<string, string>;
17
+ /**
18
+ * Constants mapping from Vega constants to OpenLayers constants
19
+ */
20
+ export declare const CONSTANTS_MAPPING: Record<string, number>;
21
+ /**
22
+ * Function mapping from Vega functions to OpenLayers functions
23
+ */
24
+ export declare const FUNCTION_MAPPING: Record<string, string>;
25
+ /**
26
+ * Custom error class for Vega2OL transpilation errors
27
+ */
28
+ export declare class Vega2OLError extends Error {
29
+ constructor(message: string);
30
+ }
31
+ /**
32
+ * Main transpiler function
33
+ * @param vegaExpressionStr - A Vega expression string (e.g., "datum.value > 100 ? 'red' : 'blue'")
34
+ * @returns OpenLayers expression array
35
+ */
36
+ export declare function vega2ol(vegaExpressionStr: string): ExpressionValue;
37
+ export { ExpressionValue as OLExpression, VegaNode };
package/dist/main.js ADDED
@@ -0,0 +1,286 @@
1
+ import { parseExpression } from "vega-expression";
2
+ /**
3
+ * Operator mapping from Vega operators to OpenLayers operators
4
+ */
5
+ export const OPERATOR_MAPPING = {
6
+ // Comparison operators
7
+ "==": "==",
8
+ "!=": "!=",
9
+ "<": "<",
10
+ "<=": "<=",
11
+ ">": ">",
12
+ ">=": ">=",
13
+ // Logical operators
14
+ "&&": "all",
15
+ "||": "any",
16
+ // Arithmetic operators
17
+ "+": "+",
18
+ "-": "-",
19
+ "*": "*",
20
+ "/": "/",
21
+ "%": "%",
22
+ };
23
+ /**
24
+ * Constants mapping from Vega constants to OpenLayers constants
25
+ */
26
+ export const CONSTANTS_MAPPING = {
27
+ PI: Math.PI,
28
+ E: Math.E,
29
+ LN2: Math.LN2,
30
+ LN10: Math.LN10,
31
+ LOG2E: Math.LOG2E,
32
+ LOG10E: Math.LOG10E,
33
+ SQRT1_2: Math.SQRT1_2,
34
+ SQRT2: Math.SQRT2,
35
+ MIN_VALUE: Number.MIN_VALUE,
36
+ MAX_VALUE: Number.MAX_VALUE,
37
+ };
38
+ /**
39
+ * Function mapping from Vega functions to OpenLayers functions
40
+ */
41
+ export const FUNCTION_MAPPING = {
42
+ // Math functions
43
+ abs: "abs",
44
+ floor: "floor",
45
+ ceil: "ceil",
46
+ round: "round",
47
+ sqrt: "sqrt",
48
+ pow: "^",
49
+ sin: "sin",
50
+ cos: "cos",
51
+ atan: "atan",
52
+ atan2: "atan",
53
+ clamp: "clamp",
54
+ rgb: "rgb",
55
+ };
56
+ /**
57
+ * Custom error class for Vega2OL transpilation errors
58
+ */
59
+ export class Vega2OLError extends Error {
60
+ constructor(message) {
61
+ const fullMessage = `${message}, note that only a subset of Vega is supported`;
62
+ super(fullMessage);
63
+ this.name = "Vega2OLError";
64
+ }
65
+ }
66
+ /**
67
+ * Visitor class to convert Vega AST to OpenLayers expressions
68
+ */
69
+ class VegaToOLVisitor {
70
+ constructor() {
71
+ this.scope = {};
72
+ }
73
+ isNegativeOne(node) {
74
+ // Case 1: Literal -1
75
+ if (node.type === "Literal" && node.value === -1) {
76
+ return true;
77
+ }
78
+ // Case 2: UnaryExpression: -1
79
+ if (node.type === "UnaryExpression" &&
80
+ node.operator === "-" &&
81
+ node.argument?.type === "Literal" &&
82
+ node.argument.value === 1) {
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+ visit(node) {
88
+ if (!node || !node.type) {
89
+ throw new Vega2OLError("Invalid AST node");
90
+ }
91
+ const method = `visit${node.type}`;
92
+ if (typeof this[method] === "function") {
93
+ return this[method](node);
94
+ }
95
+ throw new Vega2OLError(`Unsupported ${node.type} node`);
96
+ }
97
+ // Literal values
98
+ visitLiteral(node) {
99
+ const { value } = node;
100
+ if (value === null)
101
+ return null;
102
+ if (typeof value === "boolean")
103
+ return value;
104
+ if (typeof value === "number")
105
+ return value;
106
+ if (typeof value === "string")
107
+ return value;
108
+ throw new Vega2OLError(`Unsupported literal type: ${typeof value}`);
109
+ }
110
+ // Member access: datum.value, datum['value']
111
+ visitMemberExpression(node) {
112
+ const { object, property } = node;
113
+ if (object.type === "Identifier" && object.name === "datum") {
114
+ if (typeof property === "string") {
115
+ return ["get", property];
116
+ }
117
+ // For computed properties - if property is an Identifier with 'name'
118
+ if (property.type === "Identifier" && property.name) {
119
+ return ["get", property.name];
120
+ }
121
+ // For computed properties like datum[variable]
122
+ return ["get", this.visit(property)];
123
+ }
124
+ throw new Vega2OLError(`Unsupported member access`);
125
+ }
126
+ isIndexOfCall(node) {
127
+ return (node.type === "CallExpression" &&
128
+ node.callee?.type === "Identifier" &&
129
+ node.callee.name.toLowerCase() === "indexof");
130
+ }
131
+ // Binary operations: a + b, a > b, etc.
132
+ visitBinaryExpression(node) {
133
+ const { operator, left, right } = node;
134
+ // Special handling for indexOf comparisons
135
+ if (operator === "!=" || operator === "==") {
136
+ let call = null;
137
+ if (this.isIndexOfCall(left) && this.isNegativeOne(right)) {
138
+ call = left;
139
+ }
140
+ else if (this.isIndexOfCall(right) && this.isNegativeOne(left)) {
141
+ call = right;
142
+ }
143
+ if (call) {
144
+ const [arrayArg, valueArg] = call.arguments || [];
145
+ if (arrayArg && valueArg) {
146
+ const arrayOL = this.visit(arrayArg);
147
+ const valueOL = this.visit(valueArg);
148
+ const inExpr = [
149
+ "in",
150
+ valueOL,
151
+ ["literal", arrayOL],
152
+ ];
153
+ // indexof(...) != -1 → is in
154
+ if (operator === "!=") {
155
+ return inExpr;
156
+ }
157
+ // indexof(...) == -1 → is NOT in
158
+ if (operator === "==") {
159
+ return ["!", inExpr];
160
+ }
161
+ }
162
+ }
163
+ }
164
+ const leftOL = this.visit(left);
165
+ const rightOL = this.visit(right);
166
+ const olOp = OPERATOR_MAPPING[operator];
167
+ if (!olOp) {
168
+ throw new Vega2OLError(`Unsupported binary operator: ${operator}`);
169
+ }
170
+ return [olOp, leftOL, rightOL];
171
+ }
172
+ // Unary operations: !x, -x, +x
173
+ visitUnaryExpression(node) {
174
+ const { operator, argument } = node;
175
+ const argOL = this.visit(argument);
176
+ if (operator === "!") {
177
+ return ["!", argOL];
178
+ }
179
+ if (operator === "-") {
180
+ return ["*", argOL, -1];
181
+ }
182
+ if (operator === "+") {
183
+ return argOL;
184
+ }
185
+ throw new Vega2OLError(`Unsupported unary operator: ${operator}`);
186
+ }
187
+ // Conditional: test ? consequent : alternate
188
+ visitConditionalExpression(node) {
189
+ const { test, consequent, alternate } = node;
190
+ const testOL = this.visit(test);
191
+ const consequentOL = this.visit(consequent);
192
+ const alternateOL = this.visit(alternate);
193
+ // OL 'case' expression: ['case', condition1, value1, condition2, value2, ..., defaultValue]
194
+ return ["case", testOL, consequentOL, alternateOL];
195
+ }
196
+ // Function calls: floor(x), ceil(y), etc.
197
+ visitCallExpression(node) {
198
+ const { callee, arguments: args } = node;
199
+ let funcName;
200
+ if (callee.type === "Identifier") {
201
+ funcName = callee.name;
202
+ }
203
+ else if (callee.type === "MemberExpression" &&
204
+ callee.object.name === "Math") {
205
+ funcName = callee.property;
206
+ }
207
+ else {
208
+ throw new Vega2OLError(`Unsupported function call`);
209
+ }
210
+ if (this.isIndexOfCall(node)) {
211
+ throw new Vega2OLError(`indexof is only supported in the pattern 'indexof(array, value) != -1' (or '== -1'), ` +
212
+ `it cannot be used as a standalone expression in OpenLayers`);
213
+ }
214
+ // rgb(r, g, b[, opacity]) → OL ['color', r, g, b, a].
215
+ if (funcName.toLowerCase() === "rgb" &&
216
+ (args.length === 3 || args.length === 4)) {
217
+ const [r, g, b, a] = args;
218
+ return [
219
+ "color",
220
+ this.visit(r),
221
+ this.visit(g),
222
+ this.visit(b),
223
+ a ? this.visit(a) : 1,
224
+ ];
225
+ }
226
+ const olFunc = FUNCTION_MAPPING[funcName.toLowerCase()];
227
+ if (!olFunc) {
228
+ throw new Vega2OLError(`Unsupported function: ${funcName}`);
229
+ }
230
+ const olArgs = args.map((arg) => this.visit(arg));
231
+ return [olFunc, ...olArgs];
232
+ }
233
+ // Array literal: [1, 2, 3]
234
+ visitArrayExpression(node) {
235
+ const { elements } = node;
236
+ return elements.map((elem) => this.visit(elem));
237
+ }
238
+ // Logical expressions
239
+ visitLogicalExpression(node) {
240
+ const { operator, left, right } = node;
241
+ const leftOL = this.visit(left);
242
+ const rightOL = this.visit(right);
243
+ const olOp = OPERATOR_MAPPING[operator];
244
+ if (!olOp) {
245
+ throw new Vega2OLError(`Unsupported logical operator: ${operator}`);
246
+ }
247
+ return [olOp, leftOL, rightOL];
248
+ }
249
+ // Identifier/Variable: x, PI, etc.
250
+ visitIdentifier(node) {
251
+ const { name } = node;
252
+ // Check if it's in the current scope
253
+ if (name in this.scope) {
254
+ return this.scope[name];
255
+ }
256
+ if (name in CONSTANTS_MAPPING) {
257
+ return CONSTANTS_MAPPING[name];
258
+ }
259
+ // Return as string (might be a function or field)
260
+ return name;
261
+ }
262
+ }
263
+ /**
264
+ * Main transpiler function
265
+ * @param vegaExpressionStr - A Vega expression string (e.g., "datum.value > 100 ? 'red' : 'blue'")
266
+ * @returns OpenLayers expression array
267
+ */
268
+ export function vega2ol(vegaExpressionStr) {
269
+ if (typeof vegaExpressionStr !== "string") {
270
+ throw new Vega2OLError("Input must be a string");
271
+ }
272
+ try {
273
+ // Parse Vega expression to AST
274
+ const ast = parseExpression(vegaExpressionStr);
275
+ // Visit the AST and convert to OL expression
276
+ const visitor = new VegaToOLVisitor();
277
+ const olExpression = visitor.visit(ast);
278
+ return olExpression;
279
+ }
280
+ catch (error) {
281
+ if (error instanceof Vega2OLError) {
282
+ throw error;
283
+ }
284
+ throw new Vega2OLError(`Failed to parse expression: ${error.message}`);
285
+ }
286
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,319 @@
1
+ import { vega2ol, Vega2OLError } from "../main.js";
2
+ // Simple assertion helper
3
+ function assertEqual(actual, expected, message) {
4
+ const actualStr = JSON.stringify(actual);
5
+ const expectedStr = JSON.stringify(expected);
6
+ if (actualStr === expectedStr) {
7
+ console.log(` āœ… ${message}`);
8
+ return true;
9
+ }
10
+ else {
11
+ console.log(` āŒ ${message}`);
12
+ console.log(` Expected: ${expectedStr}`);
13
+ console.log(` Got: ${actualStr}`);
14
+ return false;
15
+ }
16
+ }
17
+ // Assert that vega2ol throws a Vega2OLError for the given expression
18
+ async function assertThrows(expr, message, messageContains) {
19
+ try {
20
+ const result = await vega2ol(expr);
21
+ console.log(` āŒ ${message}`);
22
+ console.log(` Expected a Vega2OLError, but got result: ${JSON.stringify(result)}`);
23
+ return false;
24
+ }
25
+ catch (error) {
26
+ if (!(error instanceof Vega2OLError)) {
27
+ console.log(` āŒ ${message}`);
28
+ console.log(` Expected Vega2OLError, got: ${error.constructor.name}: ${error.message}`);
29
+ return false;
30
+ }
31
+ if (messageContains && !error.message.includes(messageContains)) {
32
+ console.log(` āŒ ${message}`);
33
+ console.log(` Error message did not contain "${messageContains}"`);
34
+ console.log(` Got message: ${error.message}`);
35
+ return false;
36
+ }
37
+ console.log(` āœ… ${message}`);
38
+ return true;
39
+ }
40
+ }
41
+ // Run tests
42
+ async function runTests() {
43
+ console.log("šŸš€ Testing vega2ol transpiler\n");
44
+ let passed = 0;
45
+ let failed = 0;
46
+ try {
47
+ // Test 1: Literal values
48
+ console.log("šŸ“‹ Literal Values");
49
+ if (assertEqual(await vega2ol("42"), 42, "Number literal"))
50
+ passed++;
51
+ else
52
+ failed++;
53
+ if (assertEqual(await vega2ol("'hello'"), "hello", "String literal"))
54
+ passed++;
55
+ else
56
+ failed++;
57
+ if (assertEqual(await vega2ol("true"), true, "Boolean literal"))
58
+ passed++;
59
+ else
60
+ failed++;
61
+ // Test 2: Member access
62
+ console.log("\nšŸ“‹ Member Access (datum.field)");
63
+ if (assertEqual(await vega2ol("datum.value"), ["get", "value"], "Simple datum access"))
64
+ passed++;
65
+ else
66
+ failed++;
67
+ if (assertEqual(await vega2ol("datum.population"), ["get", "population"], "Datum field access"))
68
+ passed++;
69
+ else
70
+ failed++;
71
+ // Test 3: Comparison operators
72
+ console.log("\nšŸ“‹ Comparison Operators");
73
+ if (assertEqual(await vega2ol("datum.value > 100"), [">", ["get", "value"], 100], "Greater than operator"))
74
+ passed++;
75
+ else
76
+ failed++;
77
+ if (assertEqual(await vega2ol("datum.value == 100"), ["==", ["get", "value"], 100], "Equality operator"))
78
+ passed++;
79
+ else
80
+ failed++;
81
+ // Test 4: Ternary/Conditional (the main example from docs)
82
+ console.log("\nšŸ“‹ Conditional Expressions");
83
+ if (assertEqual(await vega2ol("datum.value > 100 ? 'red' : 'blue'"), ["case", [">", ["get", "value"], 100], "red", "blue"], "Simple ternary expression"))
84
+ passed++;
85
+ else
86
+ failed++;
87
+ // Test 5: Complex nested conditional
88
+ if (assertEqual(await vega2ol("datum.value > 100 ? 'red' : datum.value > 50 ? 'yellow' : 'green'"), [
89
+ "case",
90
+ [">", ["get", "value"], 100],
91
+ "red",
92
+ ["case", [">", ["get", "value"], 50], "yellow", "green"],
93
+ ], "Nested ternary expression"))
94
+ passed++;
95
+ else
96
+ failed++;
97
+ // Test 6: Logical operators
98
+ console.log("\nšŸ“‹ Logical Operators");
99
+ if (assertEqual(await vega2ol("datum.x > 0 && datum.y < 100"), ["all", [">", ["get", "x"], 0], ["<", ["get", "y"], 100]], "AND operator"))
100
+ passed++;
101
+ else
102
+ failed++;
103
+ if (assertEqual(await vega2ol("datum.a > 1 || datum.b < 5"), ["any", [">", ["get", "a"], 1], ["<", ["get", "b"], 5]], "OR operator"))
104
+ passed++;
105
+ else
106
+ failed++;
107
+ // Test 7: Arithmetic operators
108
+ console.log("\nšŸ“‹ Arithmetic Operators");
109
+ if (assertEqual(await vega2ol("datum.a + datum.b"), ["+", ["get", "a"], ["get", "b"]], "Addition operator"))
110
+ passed++;
111
+ else
112
+ failed++;
113
+ if (assertEqual(await vega2ol("datum.a * datum.b"), ["*", ["get", "a"], ["get", "b"]], "Multiplication operator"))
114
+ passed++;
115
+ else
116
+ failed++;
117
+ // Test 8: Unary operations
118
+ console.log("\nšŸ“‹ Unary Operations");
119
+ if (assertEqual(await vega2ol("!datum.active"), ["!", ["get", "active"]], "Negation operator"))
120
+ passed++;
121
+ else
122
+ failed++;
123
+ if (assertEqual(await vega2ol("-datum.value"), ["*", ["get", "value"], -1], "Unary minus"))
124
+ passed++;
125
+ else
126
+ failed++;
127
+ // Test 9: Function calls
128
+ console.log("\nšŸ“‹ Function Calls");
129
+ if (assertEqual(await vega2ol("floor(datum.value)"), ["floor", ["get", "value"]], "Floor function"))
130
+ passed++;
131
+ else
132
+ failed++;
133
+ if (assertEqual(await vega2ol("ceil(datum.value)"), ["ceil", ["get", "value"]], "ceil()"))
134
+ passed++;
135
+ else
136
+ failed++;
137
+ if (assertEqual(await vega2ol("round(datum.value)"), ["round", ["get", "value"]], "round()"))
138
+ passed++;
139
+ else
140
+ failed++;
141
+ if (assertEqual(await vega2ol("abs(datum.value)"), ["abs", ["get", "value"]], "abs()"))
142
+ passed++;
143
+ else
144
+ failed++;
145
+ if (assertEqual(await vega2ol("sqrt(datum.value)"), ["sqrt", ["get", "value"]], "sqrt()"))
146
+ passed++;
147
+ else
148
+ failed++;
149
+ if (assertEqual(await vega2ol("sin(datum.value)"), ["sin", ["get", "value"]], "sin()"))
150
+ passed++;
151
+ else
152
+ failed++;
153
+ if (assertEqual(await vega2ol("cos(datum.value)"), ["cos", ["get", "value"]], "cos()"))
154
+ passed++;
155
+ else
156
+ failed++;
157
+ if (assertEqual(await vega2ol("pow(datum.base, 2)"), ["^", ["get", "base"], 2], "pow() maps to OL's '^' operator, not a 'pow' function"))
158
+ passed++;
159
+ else
160
+ failed++;
161
+ if (assertEqual(await vega2ol("atan(datum.value)"), ["atan", ["get", "value"]], "atan() single-arg form"))
162
+ passed++;
163
+ else
164
+ failed++;
165
+ if (assertEqual(await vega2ol("atan2(datum.x, datum.y)"), ["atan", ["get", "x"], ["get", "y"]], "atan2() two-arg form"))
166
+ passed++;
167
+ else
168
+ failed++;
169
+ if (assertEqual(await vega2ol("clamp(datum.value, 0, 100)"), ["clamp", ["get", "value"], 0, 100], "clamp() preserves (value, min, max) arg order"))
170
+ passed++;
171
+ else
172
+ failed++;
173
+ if (assertEqual(await vega2ol("rgb(255, 0, 0)"), ["color", 255, 0, 0, 1], "rgb() with 3 numeric literals, default opacity"))
174
+ passed++;
175
+ else
176
+ failed++;
177
+ if (assertEqual(await vega2ol("rgb(255, 0, 0, 0.5)"), ["color", 255, 0, 0, 0.5], "rgb() with explicit opacity"))
178
+ passed++;
179
+ else
180
+ failed++;
181
+ if (assertEqual(await vega2ol("rgb(datum.r, datum.g, datum.b)"), ["color", ["get", "r"], ["get", "g"], ["get", "b"], 1], "rgb() with dynamic field args, default opacity"))
182
+ passed++;
183
+ else
184
+ failed++;
185
+ if (assertEqual(await vega2ol("rgb(sqrt(50), 0, 0, 0.5)"), ["color", ["sqrt", 50], 0, 0, 0.5], "rgb() with nested function call as a channel arg"))
186
+ passed++;
187
+ else
188
+ failed++;
189
+ // Test 10: Real-world example - Color mapping
190
+ console.log("\nšŸ“‹ Real-world Examples");
191
+ if (assertEqual(await vega2ol("datum.population > 1000000 ? '#FF0000' : datum.population > 500000 ? '#FFA500' : '#FFFF00'"), [
192
+ "case",
193
+ [">", ["get", "population"], 1000000],
194
+ "#FF0000",
195
+ ["case", [">", ["get", "population"], 500000], "#FFA500", "#FFFF00"],
196
+ ], "Color mapping by population size"))
197
+ passed++;
198
+ else
199
+ failed++;
200
+ // Test 11: Array expressions
201
+ console.log("\nšŸ“‹ Array Expressions");
202
+ if (assertEqual(await vega2ol("[1, 2, 3]"), [1, 2, 3], "Array literal"))
203
+ passed++;
204
+ else
205
+ failed++;
206
+ // Test 12: Constants
207
+ console.log("\nšŸ“‹ Vega Constants");
208
+ if (assertEqual(await vega2ol("PI"), Math.PI, "PI constant"))
209
+ passed++;
210
+ else
211
+ failed++;
212
+ // Test 13: Binary expressions
213
+ console.log("\nšŸ“‹ Binary Expressions");
214
+ if (assertEqual(await vega2ol("indexof(['USA', 'India'], datum.country) != -1"), ["in", ["get", "country"], ["literal", ["USA", "India"]]], "indexOf with != -1 (is in)"))
215
+ passed++;
216
+ else
217
+ failed++;
218
+ if (assertEqual(await vega2ol("-1 != indexof(['USA', 'India'], datum.country)"), ["in", ["get", "country"], ["literal", ["USA", "India"]]], "reverse indexOf with != -1 (is in)"))
219
+ passed++;
220
+ else
221
+ failed++;
222
+ if (assertEqual(await vega2ol("indexof(['hot', 'warm'], datum.temperature) != -1 ? 'red' : 'blue'"), [
223
+ "case",
224
+ ["in", ["get", "temperature"], ["literal", ["hot", "warm"]]],
225
+ "red",
226
+ "blue",
227
+ ], "indexOf in ternary (if in)"))
228
+ passed++;
229
+ else
230
+ failed++;
231
+ if (assertEqual(await vega2ol("indexof(['USA', 'India'], datum.country) == -1"), ["!", ["in", ["get", "country"], ["literal", ["USA", "India"]]]], "indexOf with == -1 (is not in)"))
232
+ passed++;
233
+ else
234
+ failed++;
235
+ if (assertEqual(await vega2ol("-1 == indexof(['USA', 'India'], datum.country)"), ["!", ["in", ["get", "country"], ["literal", ["USA", "India"]]]], "reverse indexOf with == -1 (is not in)"))
236
+ passed++;
237
+ else
238
+ failed++;
239
+ // Test 14: Unsupported / unknown function errors
240
+ console.log("\nšŸ“‹ Errors: Vega Functions With No OL Equivalent");
241
+ if (await assertThrows("upper(datum.name)", "upper() throws (no OL string case operator)", "Unsupported function"))
242
+ passed++;
243
+ else
244
+ failed++;
245
+ if (await assertThrows("lower(datum.name)", "lower() throws (no OL string case operator)", "Unsupported function"))
246
+ passed++;
247
+ else
248
+ failed++;
249
+ if (await assertThrows("toString(datum.value)", "toString() throws (no OL type coercion operator)", "Unsupported function"))
250
+ passed++;
251
+ else
252
+ failed++;
253
+ if (await assertThrows("isValid(datum.value)", "isValid() throws (no OL type-checking operator)", "Unsupported function"))
254
+ passed++;
255
+ else
256
+ failed++;
257
+ if (await assertThrows("length(datum.items)", "length() throws (no OL length operator)", "Unsupported function"))
258
+ passed++;
259
+ else
260
+ failed++;
261
+ if (await assertThrows("format(datum.value, ',.2f')", "format() throws (no OL number-formatting operator)", "Unsupported function"))
262
+ passed++;
263
+ else
264
+ failed++;
265
+ if (await assertThrows("now()", "now() throws (no OL date/time operators)", "Unsupported function"))
266
+ passed++;
267
+ else
268
+ failed++;
269
+ if (await assertThrows("min(datum.a, datum.b)", "min() throws (no OL aggregate min/max operator)", "Unsupported function"))
270
+ passed++;
271
+ else
272
+ failed++;
273
+ if (await assertThrows("tan(datum.value)", "tan() throws (OL only supports sin/cos/atan)", "Unsupported function"))
274
+ passed++;
275
+ else
276
+ failed++;
277
+ if (await assertThrows("indexof(datum.items, 'x')", "bare indexof() outside the != -1 / == -1 pattern throws a specific error", "indexof is only supported in the pattern"))
278
+ passed++;
279
+ else
280
+ failed++;
281
+ console.log("\nšŸ“‹ Errors: Names That Don't Exist In Vega Or OL");
282
+ if (await assertThrows("totallyMadeUpFunction(datum.value)", "Nonexistent function name throws", "Unsupported function"))
283
+ passed++;
284
+ else
285
+ failed++;
286
+ if (await assertThrows("fooBarBaz(1, 2, 3)", "Another nonexistent function name throws", "Unsupported function"))
287
+ passed++;
288
+ else
289
+ failed++;
290
+ // Test 15: Error handling
291
+ console.log("\nšŸ“‹ Error Handling");
292
+ try {
293
+ await vega2ol(null);
294
+ console.log("āŒ Should throw for null input");
295
+ failed++;
296
+ }
297
+ catch (error) {
298
+ if (error instanceof Vega2OLError) {
299
+ console.log(" āœ… Throws Vega2OLError for invalid input");
300
+ passed++;
301
+ }
302
+ else {
303
+ console.log("āŒ Wrong error type");
304
+ failed++;
305
+ }
306
+ }
307
+ }
308
+ catch (error) {
309
+ console.error("šŸ”„ Test suite error:", error);
310
+ failed++;
311
+ }
312
+ // Summary
313
+ console.log("\n" + "=".repeat(50));
314
+ console.log(`šŸ“Š Test Results: ${passed} passed, ${failed} failed`);
315
+ console.log("=".repeat(50));
316
+ return failed === 0 ? 0 : 1;
317
+ }
318
+ // Run the tests
319
+ runTests().then((code) => process.exit(code));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vega2ol",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Vega expression to Open-Layers expression transpiler",
5
5
  "keywords": [
6
6
  "vega",