Skip to content

Commit

Permalink
feat(terra-draw): add editable option for point mode to allow moving …
Browse files Browse the repository at this point in the history
…points whilst drawing (#443)
  • Loading branch information
JamesLMilner authored Jan 24, 2025
1 parent 581adc8 commit 9a85b89
Show file tree
Hide file tree
Showing 6 changed files with 472 additions and 31 deletions.
4 changes: 3 additions & 1 deletion packages/e2e/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ const example = {
},
},
}),
new TerraDrawPointMode(),
new TerraDrawPointMode({
editable: this.config?.includes("pointEditable"),
}),
new TerraDrawLineStringMode({
snapping: {
toCoordinate: this.config?.includes("snappingCoordinate"),
Expand Down
22 changes: 22 additions & 0 deletions packages/e2e/tests/leaflet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ test.describe("point mode", () => {

await expectPaths({ page, count: 3 });
});

test("mode can set with editable set to true and points can be moved", async ({
page,
}) => {
const mapDiv = await setupMap({
page,
configQueryParam: ["pointEditable"],
});
await changeMode({ page, mode });

await page.mouse.click(mapDiv.width / 2, mapDiv.height / 2);
await expectGroupPosition({ page, x: 633, y: 353 });

await page.mouse.move(mapDiv.width / 2, mapDiv.height / 2);
await page.mouse.down();
await page.mouse.move(mapDiv.width / 3, mapDiv.height / 3);
await page.mouse.up();

await expectPaths({ page, count: 1 });

await expectGroupPosition({ page, x: 419, y: 233 });
});
});

test.describe("linestring mode", () => {
Expand Down
1 change: 1 addition & 0 deletions packages/e2e/tests/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Page, expect } from "@playwright/test";
export const pageUrl = "http://localhost:3000/";

export type TestConfigOptions =
| "pointEditable"
| "validationSuccess"
| "validationFailure"
| "insertCoordinates"
Expand Down
1 change: 1 addition & 0 deletions packages/terra-draw/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export const SELECT_PROPERTIES = {
} as const;

export const COMMON_PROPERTIES = {
EDITED: "edited",
CLOSING_POINT: "closingPoint",
SNAPPING_POINT: "snappingPoint",
};
284 changes: 265 additions & 19 deletions packages/terra-draw/src/modes/point/point.mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ describe("TerraDrawPointMode", () => {

it("constructs with options", () => {
const pointMode = new TerraDrawPointMode({
cursors: {
create: "crosshair",
dragStart: "grabbing",
dragEnd: "crosshair",
},
styles: { pointOutlineColor: "#ffffff" },
});
expect(pointMode.styles).toStrictEqual({
Expand Down Expand Up @@ -180,7 +185,7 @@ describe("TerraDrawPointMode", () => {
});

describe("cleanUp", () => {
it("does nothing", () => {
it("does not throw", () => {
const pointMode = new TerraDrawPointMode();

expect(() => {
Expand All @@ -189,36 +194,241 @@ describe("TerraDrawPointMode", () => {
});
});

describe("onDrag", () => {
it("does nothing", () => {
const pointMode = new TerraDrawPointMode();
describe("onDragStart", () => {
it("does not set cursor on drag starting if editable false", () => {
const pointMode = new TerraDrawPointMode({
editable: false,
});

expect(() => {
pointMode.onDrag();
}).not.toThrow();
const mockConfig = MockModeConfig("point");

pointMode.register(mockConfig);

pointMode.onClick(MockCursorEvent({ lng: 0, lat: 0 }));

const setMapDraggability = jest.fn();
pointMode.onDragStart(
MockCursorEvent({ lng: 0, lat: 0 }),
setMapDraggability,
);

expect(mockConfig.setCursor).toHaveBeenCalledTimes(0);
expect(setMapDraggability).toHaveBeenCalledTimes(0);
});

it("sets the cursor on drag starting when editable true", () => {
const pointMode = new TerraDrawPointMode({
editable: true,
});

const mockConfig = MockModeConfig("point");

// Trigger the codepath which ignores none point geometries
mockConfig.store.create([
{
geometry: {
type: "Polygon",
coordinates: [
[
[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0, 0],
],
],
},
properties: { mode: "polygon" },
},
]);

mockConfig.store.create([
{
geometry: {
type: "Point",
coordinates: [0.1, 0.1],
},
properties: { mode: "point" },
},
]);

mockConfig.store.create([
{
geometry: {
type: "Point",
coordinates: [0.2, 0.2],
},
properties: { mode: "point" },
},
]);

pointMode.register(mockConfig);

pointMode.onClick(MockCursorEvent({ lng: 0, lat: 0 }));

const setMapDraggability = jest.fn();
pointMode.onDragStart(
MockCursorEvent({ lng: 0, lat: 0 }),
setMapDraggability,
);

expect(mockConfig.setCursor).toHaveBeenCalledTimes(1);
expect(setMapDraggability).toHaveBeenCalledWith(false);
});
});

describe("onDragStart", () => {
it("does nothing", () => {
const pointMode = new TerraDrawPointMode();
describe("onDrag", () => {
it("does nothing if nothing currently edited when editable true", () => {
const pointMode = new TerraDrawPointMode({
editable: true,
});
const mockConfig = MockModeConfig("point");

expect(() => {
pointMode.onDragStart();
}).not.toThrow();
pointMode.register(mockConfig);

const setMapDraggability = jest.fn();
pointMode.onDrag(MockCursorEvent({ lng: 0, lat: 0 }), setMapDraggability);

expect(mockConfig.onChange).toHaveBeenCalledTimes(0);
});

it("updates the point geometry on drag when editable true", () => {
const pointMode = new TerraDrawPointMode({
editable: true,
});

const mockConfig = MockModeConfig("point");

pointMode.register(mockConfig);

pointMode.onClick(MockCursorEvent({ lng: 0, lat: 0 }));

const setMapDraggability = jest.fn();
pointMode.onDragStart(
MockCursorEvent({ lng: 0, lat: 0 }),
setMapDraggability,
);

pointMode.onDrag(MockCursorEvent({ lng: 0, lat: 1 }), setMapDraggability);

expect(mockConfig.onChange).toHaveBeenCalledTimes(3);
expect(mockConfig.onChange).toHaveBeenNthCalledWith(
2,
[expect.any(String)],
"update",
);

// On finished called from onClick and is then only called after onDragEnd
expect(mockConfig.onFinish).toHaveBeenCalledTimes(1);
});

it("handles the falsy validation when editable true", () => {
let validations = 0;
const pointMode = new TerraDrawPointMode({
editable: true,
validation: () => {
validations++;
return {
valid: validations === 1,
};
},
});

const mockConfig = MockModeConfig("point");

pointMode.register(mockConfig);

pointMode.onClick(MockCursorEvent({ lng: 0, lat: 0 }));

const setMapDraggability = jest.fn();
pointMode.onDragStart(
MockCursorEvent({ lng: 0, lat: 0 }),
setMapDraggability,
);

pointMode.onDrag(MockCursorEvent({ lng: 0, lat: 1 }), setMapDraggability);

expect(mockConfig.onChange).toHaveBeenCalledTimes(1);
expect(mockConfig.onFinish).toHaveBeenCalledTimes(1);
});

it("handles the truthy validation when editable true", () => {
const pointMode = new TerraDrawPointMode({
editable: true,
validation: () => ({ valid: true }),
});

const mockConfig = MockModeConfig("point");

pointMode.register(mockConfig);

pointMode.onClick(MockCursorEvent({ lng: 0, lat: 0 }));

const setMapDraggability = jest.fn();
pointMode.onDragStart(
MockCursorEvent({ lng: 0, lat: 0 }),
setMapDraggability,
);

pointMode.onDrag(MockCursorEvent({ lng: 0, lat: 1 }), setMapDraggability);

expect(mockConfig.onChange).toHaveBeenCalledTimes(3);
// On finished called from onClick and is then only called after onDragEnd
expect(mockConfig.onFinish).toHaveBeenCalledTimes(1);
});
});

describe("onDragEnd", () => {
it("does nothing", () => {
const pointMode = new TerraDrawPointMode();
it("doesn't set the cursor on drag ending if nothing currently edited", () => {
const pointMode = new TerraDrawPointMode({
editable: true,
});

expect(() => {
pointMode.onDragEnd();
}).not.toThrow();
const mockConfig = MockModeConfig("point");

pointMode.register(mockConfig);

const setMapDraggability = jest.fn();
pointMode.onDragEnd(
MockCursorEvent({ lng: 0, lat: 0 }),
setMapDraggability,
);

expect(mockConfig.setCursor).toHaveBeenCalledTimes(0);
expect(setMapDraggability).toHaveBeenCalledTimes(0);
});
});

it("sets the cursor on drag ending", () => {
const pointMode = new TerraDrawPointMode({
editable: true,
});

const mockConfig = MockModeConfig("point");

pointMode.register(mockConfig);

pointMode.onClick(MockCursorEvent({ lng: 0, lat: 0 }));

const setMapDraggability = jest.fn();
pointMode.onDragStart(
MockCursorEvent({ lng: 0, lat: 0 }),
setMapDraggability,
);

pointMode.onDrag(MockCursorEvent({ lng: 1, lat: 0 }), setMapDraggability);

pointMode.onDragEnd(
MockCursorEvent({ lng: 1, lat: 0 }),
setMapDraggability,
);

expect(mockConfig.setCursor).toHaveBeenCalledTimes(2);
expect(mockConfig.setCursor).toHaveBeenNthCalledWith(1, "grabbing");
expect(mockConfig.setCursor).toHaveBeenNthCalledWith(2, "crosshair");
expect(setMapDraggability).toHaveBeenNthCalledWith(1, false);
expect(setMapDraggability).toHaveBeenNthCalledWith(2, true);
});
});
describe("styling", () => {
it("gets", () => {
const pointMode = new TerraDrawPointMode();
Expand Down Expand Up @@ -299,6 +509,42 @@ describe("TerraDrawPointMode", () => {
pointOutlineWidth: 2,
});
});

it("returns the correct styles for edited point", () => {
const pointMode = new TerraDrawPointMode({
editable: true,
styles: {
editedPointColor: "#222222",
editedPointWidth: 3,
editedPointOutlineColor: "#555555",
editedPointOutlineWidth: 3,
},
});

const mockConfig = MockModeConfig("point");

pointMode.register(mockConfig);

pointMode.onClick(MockCursorEvent({ lng: 0, lat: 0 }));

pointMode.onDragStart(MockCursorEvent({ lng: 0, lat: 0 }), jest.fn());

const id = mockConfig.onChange.mock.calls[0][0][0];

expect(
pointMode.styleFeature({
type: "Feature",
id,
geometry: { type: "Point", coordinates: [] },
properties: { mode: "point", edited: true },
}),
).toMatchObject({
pointColor: "#222222",
pointWidth: 3,
pointOutlineColor: "#555555",
pointOutlineWidth: 3,
});
});
});

describe("validateFeature", () => {
Expand Down
Loading

0 comments on commit 9a85b89

Please sign in to comment.