mirror of
https://github.com/maputnik/editor.git
synced 2026-07-13 01:17:26 +00:00
Increase coverage to about 80%
This commit is contained in:
@@ -76,6 +76,16 @@ describe("layer editor", () => {
|
||||
layers: [{ id: "background:" + bgId, type: "background", minzoom: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
test("the range slider adjusts min-zoom", async () => {
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
describe("max-zoom", () => {
|
||||
@@ -145,6 +155,15 @@ describe("layer editor", () => {
|
||||
layers: [{ id: "background:" + bgId, type: "background" }],
|
||||
});
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
describe("opacity", () => {
|
||||
@@ -166,8 +185,71 @@ describe("layer editor", () => {
|
||||
});
|
||||
|
||||
describe("filter", () => {
|
||||
test.skip("expand/collapse", () => {});
|
||||
test.skip("compound filter", () => {});
|
||||
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", ""]]);
|
||||
|
||||
// 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", ""]]);
|
||||
|
||||
// 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", ""]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("functions", () => {
|
||||
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 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 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);
|
||||
});
|
||||
|
||||
test("convert a property to a data function and edit stops", async () => {
|
||||
const id = await when.modal.fillLayers({ type: "circle", layer: "example" });
|
||||
// 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 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 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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("layout", () => {
|
||||
|
||||
@@ -158,6 +158,56 @@ export class MaputnikDriver {
|
||||
await this.helper.when.typeText(value);
|
||||
},
|
||||
|
||||
makeZoomFunction: async (fieldName: string) => {
|
||||
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
|
||||
await container.scrollIntoViewIfNeeded();
|
||||
await container.locator(".maputnik-make-zoom-function").last().click({ force: true });
|
||||
},
|
||||
|
||||
makeDataFunction: async (fieldName: string) => {
|
||||
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
|
||||
await container.scrollIntoViewIfNeeded();
|
||||
await container.locator(".maputnik-make-data-function").click({ force: true });
|
||||
},
|
||||
|
||||
addFunctionStop: async (fieldName: string) => {
|
||||
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
|
||||
await container.locator(".maputnik-add-stop").first().click({ force: true });
|
||||
},
|
||||
|
||||
deleteFunctionStop: async (fieldName: string) => {
|
||||
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
|
||||
await container.locator(".maputnik-delete-stop").first().click({ force: true });
|
||||
},
|
||||
|
||||
addFilter: async () => {
|
||||
const button = this.helper.get.elementByTestId("layer-filter-button");
|
||||
await button.scrollIntoViewIfNeeded();
|
||||
await button.click({ force: true });
|
||||
},
|
||||
|
||||
selectFilterOperator: async (value: string) => {
|
||||
await this.helper.get.element(".maputnik-filter-editor-operator select").first().selectOption(value);
|
||||
},
|
||||
|
||||
deleteFirstActiveSource: async () => {
|
||||
await this.helper.get.element(".maputnik-active-source-type-editor-header-delete").first().click();
|
||||
},
|
||||
|
||||
setColorValue: async (fieldName: string, value: string) => {
|
||||
const input = this.helper.get.elementByTestId("spec-field:" + fieldName).locator(".maputnik-color");
|
||||
await input.fill(value);
|
||||
},
|
||||
|
||||
exportCreateHtml: async () => {
|
||||
await this.helper.get.element(".maputnik-modal-export-buttons button").last().click();
|
||||
},
|
||||
|
||||
exportSaveStyle: async () => {
|
||||
await this.helper.stubSaveFilePicker();
|
||||
await this.helper.get.element(".maputnik-modal-export-buttons button").first().click();
|
||||
},
|
||||
|
||||
waitForExampleFileResponse: () => this.helper.when.waitForResponse("example-style.json"),
|
||||
|
||||
/** Fill localStorage until we get a QuotaExceededError. */
|
||||
|
||||
+20
-3
@@ -64,8 +64,15 @@ describe("modals", () => {
|
||||
await then(get.elementByTestId("modal:export")).shouldNotExist();
|
||||
});
|
||||
|
||||
// TODO: Work out how to download a file and check the contents
|
||||
test.skip("download", () => {});
|
||||
test("download HTML and save the style", async () => {
|
||||
// Generate the standalone HTML export (triggers a file download).
|
||||
await when.exportCreateHtml();
|
||||
await then(get.elementByTestId("modal:export")).shouldExist();
|
||||
|
||||
// Saving the style closes the export modal.
|
||||
await when.exportSaveStyle();
|
||||
await then(get.elementByTestId("modal:export")).shouldNotExist();
|
||||
});
|
||||
});
|
||||
|
||||
describe("sources", () => {
|
||||
@@ -74,7 +81,17 @@ describe("modals", () => {
|
||||
await when.click("nav:sources");
|
||||
});
|
||||
|
||||
test.skip("active sources", () => {});
|
||||
test("active sources are listed and can be deleted", async () => {
|
||||
// The "both" style ships with active sources; reopen the modal against it.
|
||||
await when.setStyle("both");
|
||||
await when.click("nav:sources");
|
||||
const before = Object.keys(get.fixture("geojson-raster-style.json").sources).length;
|
||||
await when.deleteFirstActiveSource();
|
||||
await then(
|
||||
get.styleFromLocalStorage().then((style) => Object.keys(style.sources).length)
|
||||
).shouldEqual(before - 1);
|
||||
});
|
||||
|
||||
test.skip("public source", () => {});
|
||||
|
||||
test("add new source", async () => {
|
||||
|
||||
@@ -63,9 +63,9 @@ function isLocator(target: unknown): target is Locator {
|
||||
}
|
||||
|
||||
/** Asserts that every top-level key in `expected` deep-equals its counterpart in `actual`. */
|
||||
function assertDeepNestedInclude(actual: any, expected: Record<string, unknown>): void {
|
||||
function assertDeepNestedInclude(actual: any, expected: Record<string, unknown> | unknown[]): void {
|
||||
for (const key of Object.keys(expected)) {
|
||||
expect(actual?.[key], `property "${key}"`).toEqual(expected[key]);
|
||||
expect(actual?.[key], `property "${key}"`).toEqual((expected as any)[key]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export class Assertable<T> {
|
||||
}
|
||||
});
|
||||
|
||||
shouldDeepNestedInclude = (value: Record<string, unknown>) =>
|
||||
shouldDeepNestedInclude = (value: Record<string, unknown> | unknown[]) =>
|
||||
this.assertValue((actual) => assertDeepNestedInclude(actual, value));
|
||||
}
|
||||
|
||||
@@ -144,6 +144,10 @@ async function typeSequence(page: Page, text: string): Promise<void> {
|
||||
del: "Delete",
|
||||
tab: "Tab",
|
||||
home: "Home",
|
||||
rightarrow: "ArrowRight",
|
||||
leftarrow: "ArrowLeft",
|
||||
uparrow: "ArrowUp",
|
||||
downarrow: "ArrowDown",
|
||||
};
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
@@ -202,6 +206,15 @@ export class PlaywrightHelper {
|
||||
return new Query<T>(getter);
|
||||
}
|
||||
|
||||
/** Stubs the File System Access "save" picker so file saves complete headlessly. */
|
||||
public stubSaveFilePicker(): Promise<void> {
|
||||
return this.page.evaluate(() => {
|
||||
(window as any).showSaveFilePicker = async () => ({
|
||||
createWritable: async () => ({ write: async () => {}, close: async () => {} }),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Entry point for fluent assertions over a Locator or a value/Query. */
|
||||
public then = <T>(target: T): Assertable<T> => new Assertable(target);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user