mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-01 01:08:50 +01:00
140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
/**
|
|
* @vitest-environment node
|
|
*/
|
|
|
|
import type { StyleSpecification } from "maplibre-gl";
|
|
import { describe, expect, it } from "vitest";
|
|
import { finalizeMapStyle } from "../../../src/common/map/map-style-transforms";
|
|
|
|
// The label rewrite keys on the exact `text-field` @versatiles/style emits; a
|
|
// bump that changed it would silently ship local-only names again.
|
|
|
|
const layer = (id: string, layout?: unknown) => ({
|
|
id,
|
|
type: "symbol",
|
|
layout,
|
|
});
|
|
|
|
const style = () =>
|
|
({
|
|
sources: {
|
|
"versatiles-shortbread": {
|
|
type: "vector",
|
|
tiles: ["https://tiles.example.com/{z}/{x}/{y}"],
|
|
attribution: "upstream",
|
|
minzoom: 0,
|
|
maxzoom: 14,
|
|
},
|
|
},
|
|
layers: [
|
|
layer("label-place-city", { "text-field": ["get", "name"] }),
|
|
layer("label-street-primary", {
|
|
"symbol-placement": "line",
|
|
"text-field": ["get", "name"],
|
|
}),
|
|
layer("label-motorway-shield", { "text-field": "{ref}" }),
|
|
{ id: "water", type: "fill" },
|
|
],
|
|
}) as unknown as StyleSpecification;
|
|
|
|
// Just enough of the expression language for the expressions built here.
|
|
const evaluate = (expression: any, properties: Record<string, string>): any => {
|
|
if (!Array.isArray(expression)) {
|
|
return expression;
|
|
}
|
|
const [op, ...args] = expression;
|
|
switch (op) {
|
|
case "get":
|
|
return properties[args[0]];
|
|
case "has":
|
|
return args[0] in properties;
|
|
case "!":
|
|
return !evaluate(args[0], properties);
|
|
case "<":
|
|
return evaluate(args[0], properties) < evaluate(args[1], properties);
|
|
case "concat":
|
|
return args.map((arg) => evaluate(arg, properties)).join("");
|
|
case "format":
|
|
return args
|
|
.filter((_, i) => i % 2 === 0)
|
|
.map((arg) => evaluate(arg, properties))
|
|
.join("");
|
|
case "case":
|
|
for (let i = 0; i < args.length - 1; i += 2) {
|
|
if (evaluate(args[i], properties)) {
|
|
return evaluate(args[i + 1], properties);
|
|
}
|
|
}
|
|
return evaluate(args[args.length - 1], properties);
|
|
default:
|
|
throw new Error(`Unexpected operator ${op}`);
|
|
}
|
|
};
|
|
|
|
const textField = (spec: StyleSpecification, id: string) =>
|
|
(spec.layers.find((l) => l.id === id) as any).layout["text-field"];
|
|
|
|
describe("finalizeMapStyle", () => {
|
|
const finalized = finalizeMapStyle("colorful", style());
|
|
const city = textField(finalized, "label-place-city");
|
|
const street = textField(finalized, "label-street-primary");
|
|
|
|
// Whatever host the builder wrote has to be gone: it would be requested by
|
|
// every browser showing the map, straight past core's proxy.
|
|
it("repoints the source at the proxy's TileJSON", () => {
|
|
expect(finalized.sources["versatiles-shortbread"]).toEqual({
|
|
type: "vector",
|
|
url: "/api/map_tiles/tilejson.json",
|
|
});
|
|
});
|
|
|
|
it("refuses a style whose sources it does not recognize", () => {
|
|
const twoSources = style();
|
|
twoSources.sources.extra = { type: "vector", tiles: ["https://x/{z}"] };
|
|
|
|
expect(() => finalizeMapStyle("colorful", twoSources)).toThrow(
|
|
/expected exactly one/
|
|
);
|
|
});
|
|
|
|
it("leaves Latin names alone", () => {
|
|
expect(evaluate(city, { name: "Köln", name_en: "Cologne" })).toBe("Köln");
|
|
expect(evaluate(city, { name: "1er arrondissement" })).toBe(
|
|
"1er arrondissement"
|
|
);
|
|
});
|
|
|
|
it("adds the English name under a non-Latin one", () => {
|
|
expect(evaluate(city, { name: "Москва", name_en: "Moscow" })).toBe(
|
|
"Москва\nMoscow"
|
|
);
|
|
expect(evaluate(city, { name: "بيروت", name_en: "Beirut" })).toBe(
|
|
"بيروت\nBeirut"
|
|
);
|
|
expect(evaluate(city, { name: "東京", name_en: "Tokyo" })).toBe(
|
|
"東京\nTokyo"
|
|
);
|
|
});
|
|
|
|
it("sets the English line smaller", () => {
|
|
expect(city.at(-1)).toEqual(
|
|
expect.arrayContaining([["get", "name_en"], { "font-scale": 0.8 }])
|
|
);
|
|
});
|
|
|
|
it("falls back to the local name without an English one", () => {
|
|
expect(evaluate(city, { name: "בני ברק" })).toBe("בני ברק");
|
|
});
|
|
|
|
it("keeps street labels on one line", () => {
|
|
expect(
|
|
evaluate(street, { name: "شارع الحمرا", name_en: "Hamra Street" })
|
|
).toBe("شارع الحمرا (Hamra Street)");
|
|
});
|
|
|
|
it("does not touch other layers", () => {
|
|
expect(textField(finalized, "label-motorway-shield")).toBe("{ref}");
|
|
expect(finalized.layers[3]).toEqual(style().layers[3]);
|
|
});
|
|
});
|