From 1a5c879c75de31bfaad617bab533fb04215b89b9 Mon Sep 17 00:00:00 2001 From: HarelM Date: Sat, 11 Jul 2026 15:33:31 +0300 Subject: [PATCH] Improve tests. --- e2e/layer-editor.spec.ts | 117 +++++++++++++++++++++++++-------------- e2e/modals.spec.ts | 36 ++++++------ e2e/playwright-helper.ts | 12 ++-- 3 files changed, 97 insertions(+), 68 deletions(-) diff --git a/e2e/layer-editor.spec.ts b/e2e/layer-editor.spec.ts index e5e86f19..aeaf0394 100644 --- a/e2e/layer-editor.spec.ts +++ b/e2e/layer-editor.spec.ts @@ -78,13 +78,12 @@ describe("layer editor", () => { }); test("the range slider adjusts min-zoom", async () => { + // The slider starts at 1 (set via the text input above); one step right lands on 2. await when.focus("min-zoom.input-range"); await when.typeKeys("{rightarrow}"); - await then( - get - .styleFromLocalStorage() - .then((style) => typeof style.layers.find((l: any) => l.id === "background:" + bgId).minzoom) - ).shouldEqual("number"); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id: "background:" + bgId, type: "background", minzoom: 2 }], + }); }); }); @@ -158,11 +157,9 @@ describe("layer editor", () => { test("typing a hex value updates the paint color", async () => { await when.setColorValue("background-color", "#ff0000"); - await then( - get - .styleFromLocalStorage() - .then((style) => style.layers.find((l: any) => l.id === "background:" + bgId).paint["background-color"]) - ).shouldEqual("#ff0000"); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id: "background:" + bgId, type: "background", paint: { "background-color": "#ff0000" } }], + }); }); }); @@ -188,21 +185,21 @@ describe("layer editor", () => { test("compound filter", async () => { const id = await when.modal.fillLayers({ type: "fill", layer: "example" }); await when.addFilter(); - await then( - get.styleFromLocalStorage().then((style) => style.layers.find((l: any) => l.id === id).filter) - ).shouldDeepNestedInclude(["all", ["==", "name", ""]]); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id, type: "fill", source: "example", filter: ["all", ["==", "name", ""]] }], + }); // Changing the operator updates the compound filter. await when.selectFilterOperator("!="); - await then( - get.styleFromLocalStorage().then((style) => style.layers.find((l: any) => l.id === id).filter) - ).shouldDeepNestedInclude(["all", ["!=", "name", ""]]); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id, type: "fill", source: "example", filter: ["all", ["!=", "name", ""]] }], + }); // A second filter item extends the compound filter. await when.addFilter(); - await then( - get.styleFromLocalStorage().then((style) => style.layers.find((l: any) => l.id === id).filter) - ).shouldDeepNestedInclude(["all", ["!=", "name", ""], ["==", "name", ""]]); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id, type: "fill", source: "example", filter: ["all", ["!=", "name", ""], ["==", "name", ""]] }], + }); }); }); @@ -210,21 +207,20 @@ describe("layer editor", () => { test("convert a property to a zoom function and add a stop", async () => { const id = await when.modal.fillLayers({ type: "circle", layer: "example" }); await when.makeZoomFunction("circle-radius"); - await then( - get.styleFromLocalStorage().then((style) => style.layers.find((l: any) => l.id === id).paint) - ).shouldDeepNestedInclude({ "circle-radius": { stops: [[6, 5], [10, 5]] } }); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id, type: "circle", source: "example", paint: { "circle-radius": { stops: [[6, 5], [10, 5]] } } }], + }); await when.addFunctionStop("circle-radius"); - await then( - get.styleFromLocalStorage().then((style) => style.layers.find((l: any) => l.id === id).paint) - ).shouldDeepNestedInclude({ "circle-radius": { stops: [[6, 5], [10, 5], [11, 5]] } }); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id, type: "circle", source: "example", paint: { "circle-radius": { stops: [[6, 5], [10, 5], [11, 5]] } } }], + }); + // Deleting the first stop leaves the rest. await when.deleteFunctionStop("circle-radius"); - await then( - get - .styleFromLocalStorage() - .then((style) => style.layers.find((l: any) => l.id === id).paint["circle-radius"].stops.length) - ).shouldEqual(2); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [{ id, type: "circle", source: "example", paint: { "circle-radius": { stops: [[10, 5], [11, 5]] } } }], + }); }); test("convert a property to a data function and edit stops", async () => { @@ -232,23 +228,58 @@ describe("layer editor", () => { // The property needs a value before it can be turned into a data function. await when.setValue("spec-field-input:circle-blur", "1"); await when.makeDataFunction("circle-blur"); - await then( - get.styleFromLocalStorage().then((style) => style.layers.find((l: any) => l.id === id).paint["circle-blur"].type) - ).shouldEqual("exponential"); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [ + { + id, + type: "circle", + source: "example", + paint: { + "circle-blur": { + property: "", + type: "exponential", + stops: [[{ zoom: 6, value: 0 }, 1], [{ zoom: 10, value: 0 }, 1]], + }, + }, + }, + ], + }); await when.addFunctionStop("circle-blur"); - await then( - get - .styleFromLocalStorage() - .then((style) => style.layers.find((l: any) => l.id === id).paint["circle-blur"].stops.length) - ).shouldEqual(3); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [ + { + id, + type: "circle", + source: "example", + paint: { + "circle-blur": { + property: "", + type: "exponential", + stops: [[{ zoom: 6, value: 0 }, 1], [{ zoom: 10, value: 0 }, 1], [{ zoom: 11, value: 0 }, 1]], + }, + }, + }, + ], + }); await when.deleteFunctionStop("circle-blur"); - await then( - get - .styleFromLocalStorage() - .then((style) => style.layers.find((l: any) => l.id === id).paint["circle-blur"].stops.length) - ).shouldEqual(2); + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + layers: [ + { + id, + type: "circle", + source: "example", + paint: { + "circle-blur": { + property: "", + type: "exponential", + stops: [[{ zoom: 10, value: 0 }, 1], [{ zoom: 11, value: 0 }, 1]], + }, + }, + }, + ], + }); }); }); diff --git a/e2e/modals.spec.ts b/e2e/modals.spec.ts index 3920918d..d212077d 100644 --- a/e2e/modals.spec.ts +++ b/e2e/modals.spec.ts @@ -101,8 +101,8 @@ describe("modals", () => { await when.select("modal:sources.add.scheme_type", "tms"); await when.click("modal:sources.add.add_source"); await when.wait(200); - await then(get.styleFromLocalStorage().then((style) => style.sources[sourceId])).shouldInclude({ - scheme: "tms", + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + sources: { [sourceId]: { scheme: "tms" } }, }); }); @@ -135,8 +135,8 @@ describe("modals", () => { await when.setValue("modal:sources.add.tile_size", "128"); await when.click("modal:sources.add.add_source"); await when.wait(200); - await then(get.styleFromLocalStorage().then((style) => style.sources[sourceId])).shouldInclude({ - tileSize: 128, + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + sources: { [sourceId]: { tileSize: 128 } }, }); }); }); @@ -221,8 +221,8 @@ describe("modals", () => { const apiKey = "testing123"; await when.setValue("modal:settings.maputnik:openmaptiles_access_token", apiKey); await when.click("modal:settings.name"); - await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({ - "maputnik:openmaptiles_access_token": apiKey, + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + metadata: { "maputnik:openmaptiles_access_token": apiKey }, }); }); @@ -230,8 +230,8 @@ describe("modals", () => { const apiKey = "testing123"; await when.setValue("modal:settings.maputnik:thunderforest_access_token", apiKey); await when.click("modal:settings.name"); - await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({ - "maputnik:thunderforest_access_token": apiKey, + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + metadata: { "maputnik:thunderforest_access_token": apiKey }, }); }); @@ -239,8 +239,8 @@ describe("modals", () => { const apiKey = "testing123"; await when.setValue("modal:settings.maputnik:stadia_access_token", apiKey); await when.click("modal:settings.name"); - await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({ - "maputnik:stadia_access_token": apiKey, + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + metadata: { "maputnik:stadia_access_token": apiKey }, }); }); @@ -248,29 +248,29 @@ describe("modals", () => { const apiKey = "testing123"; await when.setValue("modal:settings.maputnik:locationiq_access_token", apiKey); await when.click("modal:settings.name"); - await then(get.styleFromLocalStorage().then((style) => style.metadata)).shouldInclude({ - "maputnik:locationiq_access_token": apiKey, + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + metadata: { "maputnik:locationiq_access_token": apiKey }, }); }); test("style projection mercator", async () => { await when.select("modal:settings.projection", "mercator"); - await then(get.styleFromLocalStorage().then((style) => style.projection)).shouldInclude({ - type: "mercator", + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + projection: { type: "mercator" }, }); }); test("style projection globe", async () => { await when.select("modal:settings.projection", "globe"); - await then(get.styleFromLocalStorage().then((style) => style.projection)).shouldInclude({ - type: "globe", + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + projection: { type: "globe" }, }); }); test("style projection vertical-perspective", async () => { await when.select("modal:settings.projection", "vertical-perspective"); - await then(get.styleFromLocalStorage().then((style) => style.projection)).shouldInclude({ - type: "vertical-perspective", + await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ + projection: { type: "vertical-perspective" }, }); }); diff --git a/e2e/playwright-helper.ts b/e2e/playwright-helper.ts index db323865..0a815734 100644 --- a/e2e/playwright-helper.ts +++ b/e2e/playwright-helper.ts @@ -62,11 +62,12 @@ function isLocator(target: unknown): target is Locator { ); } -/** Asserts that every top-level key in `expected` deep-equals its counterpart in `actual`. */ +/** + * Asserts that `actual` recursively contains everything in `expected`: nested + * objects are matched as subsets, while arrays and primitives must match exactly. + */ function assertDeepNestedInclude(actual: any, expected: Record | unknown[]): void { - for (const key of Object.keys(expected)) { - expect(actual?.[key], `property "${key}"`).toEqual((expected as any)[key]); - } + expect(actual).toMatchObject(expected); } /** @@ -145,9 +146,6 @@ async function typeSequence(page: Page, text: string): Promise { tab: "Tab", home: "Home", rightarrow: "ArrowRight", - leftarrow: "ArrowLeft", - uparrow: "ArrowUp", - downarrow: "ArrowDown", }; for (let i = 0; i < tokens.length; i++) {