use ezu_translate::maplibre::{convert, ConvertOptions};
use serde_json::{json, Value};
fn convert_one(layer: Value) -> (Value, Vec<String>) {
let style = json!({
"version": 8,
"sources": {
"src": { "type": "vector", "tiles": ["https://example.com/{z}/{x}/{y}.pbf"] }
},
"layers": [layer],
});
let (recipe, report) = convert(&style, &ConvertOptions::default()).expect("conversion");
let text = serde_json::to_string(&recipe).unwrap();
ezu_style::Document::from_json(&text).expect("recipe parses as ezu Document");
(recipe, report.warnings)
}
fn features_node(recipe: &Value) -> &Value {
recipe["nodes"]
.as_object()
.unwrap()
.values()
.find(|n| n["op"] == "features")
.expect("a features node")
}
#[test]
fn expression_filter_emits_filter_expr_no_structured_filter() {
let expr = json!(["all", ["==", ["get", "class"], "primary"], ["has", "name"]]);
let (recipe, warnings) = convert_one(json!({
"id": "roads",
"type": "line",
"source": "src",
"source-layer": "transportation",
"filter": expr,
"paint": { "line-color": "#ff0000" },
}));
let feat = features_node(&recipe);
assert_eq!(
feat.get("filter-expr"),
Some(&json!([
"all",
["==", ["get", "class"], "primary"],
["has", "name"]
])),
);
assert!(
feat.get("filter").is_none(),
"expression filter must not also emit a structured `filter`: {feat}"
);
let joined = warnings.join("\n");
assert!(
!joined.contains("filter") && !joined.contains("unsupported"),
"expression filter should convert without warning, got:\n{joined}"
);
}
#[test]
fn legacy_filter_converts_to_a_filter_expr() {
let (recipe, warnings) = convert_one(json!({
"id": "roads",
"type": "line",
"source": "src",
"source-layer": "transportation",
"filter": ["==", "class", "primary"],
"paint": { "line-color": "#ff0000" },
}));
let feat = features_node(&recipe);
assert_eq!(
feat["filter-expr"],
json!(["==", ["get", "class"], "primary"])
);
assert!(
feat.get("filter").is_none(),
"legacy filter must not emit a structured `filter`: {feat}"
);
let joined = warnings.join("\n");
assert!(
!joined.contains("filter"),
"legacy filter should convert without warning, got:\n{joined}"
);
}
#[test]
fn legacy_none_filter_converts_and_excludes_matches() {
let (recipe, warnings) = convert_one(json!({
"id": "geolines",
"type": "line",
"source": "src",
"source-layer": "geolines",
"filter": ["none", ["==", "name", "International Date Line"]],
"paint": { "line-color": "#0000ff" },
}));
let feat = features_node(&recipe);
let expr = &feat["filter-expr"];
assert_eq!(expr[0], "!", "none must lower to a negation: {expr}");
let joined = warnings.join("\n");
assert!(
!joined.contains("filter"),
"legacy `none` should convert without warning, got:\n{joined}"
);
}
#[test]
fn data_driven_fill_with_expression_filter_carries_both() {
let (recipe, _warnings) = convert_one(json!({
"id": "areas",
"type": "fill",
"source": "src",
"source-layer": "landuse",
"filter": ["all", ["==", ["get", "class"], "park"]],
"paint": {
"fill-color": [
"match", ["get", "kind"],
"forest", "#00ff00",
"grass", "#88ff88",
"#cccccc"
]
},
}));
let nodes = recipe["nodes"].as_object().unwrap();
let feat = features_node(&recipe);
assert_eq!(
feat.get("filter-expr"),
Some(&json!(["all", ["==", ["get", "class"], "park"]])),
"features node should carry the layer's expression filter: {feat}"
);
let fill = nodes
.values()
.find(|n| n["op"] == "fill-solid")
.expect("a fill-solid node");
let fill_expr = fill
.get("fill-expr")
.expect("fill-solid should carry a fill-expr");
assert!(
serde_json::to_string(fill_expr).unwrap().contains("kind"),
"fill-expr should reference the driving property `kind`: {fill}"
);
}