diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 94958370..4b2ddd76 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -888,7 +888,9 @@ U.PathMixin = { const other = new (this instanceof U.Polyline ? U.Polyline : U.Polygon)( this.map, shape, - { geojson: { properties: properties } } + { + geojson: { properties }, + } ) this.datalayer.addLayer(other) other.edit() diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index b4df88cf..7383f74c 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -974,6 +974,8 @@ U.Map = L.Map.extend({ formData.append('settings', JSON.stringify(geojson)) const uri = this.urls.get('map_save', { map_id: this.options.umap_id }) const [data, response, error] = await this.server.post(uri, {}, formData) + // FIXME: login_required response will not be an error, so it will not + // stop code while it should if (!error) { let duration = 3000, alert = { content: L._('Map has been saved!'), level: 'info' } diff --git a/umap/static/umap/test/Polyline.js b/umap/static/umap/test/Polyline.js index 41b6e16c..06f5ee08 100644 --- a/umap/static/umap/test/Polyline.js +++ b/umap/static/umap/test/Polyline.js @@ -234,72 +234,6 @@ describe('U.Polyline', function () { }) }) - describe('#addShape', function () { - it('"add shape" control should not be visible by default', function () { - assert.notOk(qs('.umap-draw-polyline-multi')) - }) - - it('"add shape" control should be visible when editing a Polyline', function () { - var layer = new U.Polyline(this.map, [p2ll(100, 100), p2ll(100, 200)], { - datalayer: this.datalayer, - }).addTo(this.datalayer) - layer.edit() - assert.ok(qs('.umap-draw-polyline-multi')) - }) - - it('"add shape" control should extend the same multi', function () { - var layer = new U.Polyline(this.map, [p2ll(100, 100), p2ll(100, 200)], { - datalayer: this.datalayer, - }).addTo(this.datalayer) - layer.edit() - assert.notOk(layer.isMulti()) - happen.click(qs('.umap-draw-polyline-multi')) - happen.at('mousemove', 300, 300) - happen.at('click', 300, 300) - happen.at('mousemove', 350, 300) - happen.at('click', 350, 300) - happen.at('click', 350, 300) - assert.ok(layer.isMulti()) - assert.equal(this.datalayer._index.length, 1) - }) - }) - - describe('#transferShape', function () { - it('should transfer simple line shape to another line', function () { - var latlngs = [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - layer = new U.Polyline(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer), - other = new U.Polyline(this.map, [p2ll(200, 300), p2ll(300, 200)], { - datalayer: this.datalayer, - }).addTo(this.datalayer) - assert.ok(this.map.hasLayer(layer)) - layer.transferShape(p2ll(150, 150), other) - assert.equal(other._latlngs.length, 2) - assert.deepEqual(other._latlngs[1], latlngs) - assert.notOk(this.map.hasLayer(layer)) - }) - - it('should transfer multi line shape to another line', function () { - var latlngs = [ - [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - [p2ll(200, 300), p2ll(300, 200)], - ], - layer = new U.Polyline(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer), - other = new U.Polyline(this.map, [p2ll(250, 300), p2ll(350, 200)], { - datalayer: this.datalayer, - }).addTo(this.datalayer) - assert.ok(this.map.hasLayer(layer)) - layer.transferShape(p2ll(150, 150), other) - assert.equal(other._latlngs.length, 2) - assert.deepEqual(other._latlngs[1], latlngs[0]) - assert.ok(this.map.hasLayer(layer)) - assert.equal(layer._latlngs.length, 1) - }) - }) - describe('#mergeShapes', function () { it('should remove duplicated join point when merging', function () { var latlngs = [ @@ -349,54 +283,4 @@ describe('U.Polyline', function () { }) }) - describe('#isolateShape', function () { - it('should not allow to isolate simple line', function () { - var latlngs = [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - layer = new U.Polyline(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) - assert.equal(this.datalayer._index.length, 1) - assert.ok(this.map.hasLayer(layer)) - layer.isolateShape(p2ll(150, 150)) - assert.equal(layer._latlngs.length, 3) - assert.equal(this.datalayer._index.length, 1) - }) - - it('should isolate multipolyline shape', function () { - var latlngs = [ - [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - [[p2ll(200, 300), p2ll(300, 200)]], - ], - layer = new U.Polyline(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) - assert.equal(this.datalayer._index.length, 1) - assert.ok(this.map.hasLayer(layer)) - var other = layer.isolateShape(p2ll(150, 150)) - assert.equal(this.datalayer._index.length, 2) - assert.equal(other._latlngs.length, 3) - assert.deepEqual(other._latlngs, latlngs[0]) - assert.ok(this.map.hasLayer(layer)) - assert.ok(this.map.hasLayer(other)) - assert.equal(layer._latlngs.length, 1) - other.remove() - }) - }) - - describe('#clone', function () { - it('should clone polyline', function () { - var latlngs = [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - layer = new U.Polyline(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) - assert.equal(this.datalayer._index.length, 1) - other = layer.clone() - assert.ok(this.map.hasLayer(other)) - assert.equal(this.datalayer._index.length, 2) - // Must not be the same reference - assert.notEqual(layer._latlngs, other._latlngs) - assert.equal(L.Util.formatNum(layer._latlngs[0].lat), other._latlngs[0].lat) - assert.equal(L.Util.formatNum(layer._latlngs[0].lng), other._latlngs[0].lng) - }) - }) }) diff --git a/umap/tests/integration/test_drawing.py b/umap/tests/integration/test_drawing.py deleted file mode 100644 index d84d9b3d..00000000 --- a/umap/tests/integration/test_drawing.py +++ /dev/null @@ -1,121 +0,0 @@ -from playwright.sync_api import expect - - -def test_draw_polyline(page, live_server, tilelayer): - page.goto(f"{live_server.url}/en/map/new/") - - # Click on the Draw a line button on a new map. - create_line = page.locator(".leaflet-control-toolbar ").get_by_title( - "Draw a polyline" - ) - create_line.click() - - # Check no line is present by default. - # We target with the color, because there is also the drawing line guide (dash-array) - # around - lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") - guide = page.locator(".leaflet-overlay-pane > svg > g > path") - expect(lines).to_have_count(0) - expect(guide).to_have_count(0) - - # Click on the map, it will create a line. - map = page.locator("#map") - map.click(position={"x": 200, "y": 200}) - expect(lines).to_have_count(1) - expect(guide).to_have_count(1) - map.click(position={"x": 100, "y": 200}) - expect(lines).to_have_count(1) - expect(guide).to_have_count(1) - map.click(position={"x": 100, "y": 100}) - expect(lines).to_have_count(1) - expect(guide).to_have_count(1) - # Click again to finish - map.click(position={"x": 100, "y": 100}) - expect(lines).to_have_count(1) - expect(guide).to_have_count(0) - - -def test_clicking_esc_should_finish_line(page, live_server, tilelayer): - page.goto(f"{live_server.url}/en/map/new/") - - # Click on the Draw a line button on a new map. - create_line = page.locator(".leaflet-control-toolbar ").get_by_title( - "Draw a polyline" - ) - create_line.click() - - # Check no line is present by default. - # We target with the color, because there is also the drawing line guide (dash-array) - # around - lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") - guide = page.locator(".leaflet-overlay-pane > svg > g > path") - expect(lines).to_have_count(0) - expect(guide).to_have_count(0) - - # Click on the map, it will create a line. - map = page.locator("#map") - map.click(position={"x": 200, "y": 200}) - expect(lines).to_have_count(1) - expect(guide).to_have_count(1) - map.click(position={"x": 100, "y": 200}) - expect(lines).to_have_count(1) - expect(guide).to_have_count(1) - map.click(position={"x": 100, "y": 100}) - expect(lines).to_have_count(1) - expect(guide).to_have_count(1) - # Click ESC to finish - page.keyboard.press("Escape") - expect(lines).to_have_count(1) - expect(guide).to_have_count(0) - - -def test_clicking_esc_should_delete_line_if_empty(page, live_server, tilelayer): - page.goto(f"{live_server.url}/en/map/new/") - - # Click on the Draw a line button on a new map. - create_line = page.locator(".leaflet-control-toolbar ").get_by_title( - "Draw a polyline" - ) - create_line.click() - - # Check no line is present by default. - # We target with the color, because there is also the drawing line guide (dash-array) - # around - lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") - guide = page.locator(".leaflet-overlay-pane > svg > g > path") - expect(lines).to_have_count(0) - expect(guide).to_have_count(0) - - map = page.locator("#map") - map.click(position={"x": 200, "y": 200}) - # At this stage, the line as one element, it should not be created - # on pressing esc, as invalid - # Click ESC to finish - page.keyboard.press("Escape") - expect(lines).to_have_count(0) - expect(guide).to_have_count(0) - - -def test_clicking_esc_should_delete_line_if_invalid(page, live_server, tilelayer): - page.goto(f"{live_server.url}/en/map/new/") - - # Click on the Draw a line button on a new map. - create_line = page.locator(".leaflet-control-toolbar ").get_by_title( - "Draw a polyline" - ) - create_line.click() - - # Check no line is present by default. - # We target with the color, because there is also the drawing line guide (dash-array) - # around - lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") - guide = page.locator(".leaflet-overlay-pane > svg > g > path") - expect(lines).to_have_count(0) - expect(guide).to_have_count(0) - - # At this stage, the line as no element, it should not be created - # on pressing esc - # Click ESC to finish - page.keyboard.press("Escape") - expect(lines).to_have_count(0) - expect(guide).to_have_count(0) diff --git a/umap/tests/integration/test_polygon.py b/umap/tests/integration/test_polygon.py index 7dfbe96c..44cb3c7c 100644 --- a/umap/tests/integration/test_polygon.py +++ b/umap/tests/integration/test_polygon.py @@ -167,7 +167,7 @@ def test_can_draw_multi(live_server, page, tilelayer): expect(multi_button).to_be_hidden() polygons.first.click(button="right", position={"x": 10, "y": 10}) expect(page.get_by_role("link", name="Transform to lines")).to_be_hidden() - expect(page.get_by_role("link", name="Remove shape from multi")).to_be_hidden() + expect(page.get_by_role("link", name="Remove shape from the multi")).to_be_visible() def test_can_draw_hole(page, live_server, tilelayer): @@ -237,7 +237,7 @@ def test_can_transfer_shape_from_simple_polygon(live_server, page, tilelayer): expect(polygons).to_have_count(1) -def test_can_transfer_shape(live_server, page, tilelayer): +def test_can_extract_shape(live_server, page, tilelayer): page.goto(f"{live_server.url}/en/map/new/") polygons = page.locator(".leaflet-overlay-pane path") expect(polygons).to_have_count(0) @@ -314,7 +314,8 @@ def test_cannot_transfer_shape_to_marker(live_server, page, tilelayer): expect(extract_button).to_be_hidden() -def test_can_clone_polygon(live_server, page, tilelayer): +def test_can_clone_polygon(live_server, page, tilelayer, settings): + settings.UMAP_ALLOW_ANONYMOUS = True page.goto(f"{live_server.url}/en/map/new/") polygons = page.locator(".leaflet-overlay-pane path") expect(polygons).to_have_count(0) @@ -330,6 +331,10 @@ def test_can_clone_polygon(live_server, page, tilelayer): polygons.first.click(button="right") page.get_by_role("link", name="Clone this feature").click() expect(polygons).to_have_count(2) + data = save_and_get_json(page) + assert len(data["features"]) == 2 + assert data["features"][0]["geometry"]["type"] == "Polygon" + assert data["features"][0]["geometry"] == data["features"][1]["geometry"] def test_can_transform_polygon_to_line(live_server, page, tilelayer, settings): diff --git a/umap/tests/integration/test_polyline.py b/umap/tests/integration/test_polyline.py new file mode 100644 index 00000000..fe76d4b8 --- /dev/null +++ b/umap/tests/integration/test_polyline.py @@ -0,0 +1,325 @@ +import json +import re +from pathlib import Path + +import pytest +from playwright.sync_api import expect + +from umap.models import DataLayer + +pytestmark = pytest.mark.django_db + + +def save_and_get_json(page): + with page.expect_response(re.compile(r".*/datalayer/create/.*")): + page.get_by_role("button", name="Save").click() + datalayer = DataLayer.objects.last() + return json.loads(Path(datalayer.geojson.path).read_text()) + + +def test_draw_polyline(page, live_server, tilelayer): + page.goto(f"{live_server.url}/en/map/new/") + + # Click on the Draw a line button on a new map. + create_line = page.locator(".leaflet-control-toolbar ").get_by_title( + "Draw a polyline" + ) + create_line.click() + + # Check no line is present by default. + # We target with the color, because there is also the drawing line guide (dash-array) + # around + lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") + guide = page.locator(".leaflet-overlay-pane > svg > g > path") + expect(lines).to_have_count(0) + expect(guide).to_have_count(0) + + # Click on the map, it will create a line. + map = page.locator("#map") + map.click(position={"x": 200, "y": 200}) + expect(lines).to_have_count(1) + expect(guide).to_have_count(1) + map.click(position={"x": 100, "y": 200}) + expect(lines).to_have_count(1) + expect(guide).to_have_count(1) + map.click(position={"x": 100, "y": 100}) + expect(lines).to_have_count(1) + expect(guide).to_have_count(1) + # Click again to finish + map.click(position={"x": 100, "y": 100}) + expect(lines).to_have_count(1) + expect(guide).to_have_count(0) + + +def test_clicking_esc_should_finish_line(page, live_server, tilelayer): + page.goto(f"{live_server.url}/en/map/new/") + + # Click on the Draw a line button on a new map. + create_line = page.locator(".leaflet-control-toolbar ").get_by_title( + "Draw a polyline" + ) + create_line.click() + + # Check no line is present by default. + # We target with the color, because there is also the drawing line guide (dash-array) + # around + lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") + guide = page.locator(".leaflet-overlay-pane > svg > g > path") + expect(lines).to_have_count(0) + expect(guide).to_have_count(0) + + # Click on the map, it will create a line. + map = page.locator("#map") + map.click(position={"x": 200, "y": 200}) + expect(lines).to_have_count(1) + expect(guide).to_have_count(1) + map.click(position={"x": 100, "y": 200}) + expect(lines).to_have_count(1) + expect(guide).to_have_count(1) + map.click(position={"x": 100, "y": 100}) + expect(lines).to_have_count(1) + expect(guide).to_have_count(1) + # Click ESC to finish + page.keyboard.press("Escape") + expect(lines).to_have_count(1) + expect(guide).to_have_count(0) + + +def test_clicking_esc_should_delete_line_if_empty(page, live_server, tilelayer): + page.goto(f"{live_server.url}/en/map/new/") + + # Click on the Draw a line button on a new map. + create_line = page.locator(".leaflet-control-toolbar ").get_by_title( + "Draw a polyline" + ) + create_line.click() + + # Check no line is present by default. + # We target with the color, because there is also the drawing line guide (dash-array) + # around + lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") + guide = page.locator(".leaflet-overlay-pane > svg > g > path") + expect(lines).to_have_count(0) + expect(guide).to_have_count(0) + + map = page.locator("#map") + map.click(position={"x": 200, "y": 200}) + # At this stage, the line as one element, it should not be created + # on pressing esc, as invalid + # Click ESC to finish + page.keyboard.press("Escape") + expect(lines).to_have_count(0) + expect(guide).to_have_count(0) + + +def test_clicking_esc_should_delete_line_if_invalid(page, live_server, tilelayer): + page.goto(f"{live_server.url}/en/map/new/") + + # Click on the Draw a line button on a new map. + create_line = page.locator(".leaflet-control-toolbar ").get_by_title( + "Draw a polyline" + ) + create_line.click() + + # Check no line is present by default. + # We target with the color, because there is also the drawing line guide (dash-array) + # around + lines = page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']") + guide = page.locator(".leaflet-overlay-pane > svg > g > path") + expect(lines).to_have_count(0) + expect(guide).to_have_count(0) + + # At this stage, the line as no element, it should not be created + # on pressing esc + # Click ESC to finish + page.keyboard.press("Escape") + expect(lines).to_have_count(0) + expect(guide).to_have_count(0) + + +def test_can_draw_multi(live_server, page, tilelayer): + page.goto(f"{live_server.url}/en/map/new/") + lines = page.locator(".leaflet-overlay-pane path") + expect(lines).to_have_count(0) + add_shape = page.get_by_title("Add a line to the current multi") + expect(add_shape).to_be_hidden() + page.get_by_title("Draw a polyline").click() + map = page.locator("#map") + map.click(position={"x": 200, "y": 100}) + map.click(position={"x": 100, "y": 100}) + map.click(position={"x": 100, "y": 200}) + # Click again to finish + map.click(position={"x": 100, "y": 200}) + expect(add_shape).to_be_visible() + expect(lines).to_have_count(1) + add_shape.click() + map.click(position={"x": 250, "y": 250}) + map.click(position={"x": 200, "y": 250}) + map.click(position={"x": 200, "y": 200}) + # Click again to finish + map.click(position={"x": 200, "y": 200}) + expect(lines).to_have_count(1) + page.keyboard.press("Escape") + expect(add_shape).to_be_hidden() + lines.first.click(button="right", position={"x": 10, "y": 1}) + expect(page.get_by_role("link", name="Transform to polygon")).to_be_hidden() + expect(page.get_by_role("link", name="Remove shape from the multi")).to_be_visible() + + +def test_can_transfer_shape_from_simple_polyline(live_server, page, tilelayer): + page.goto(f"{live_server.url}/en/map/new/") + lines = page.locator(".leaflet-overlay-pane path") + expect(lines).to_have_count(0) + page.get_by_title("Draw a polyline").click() + map = page.locator("#map") + + # Draw a first line + map.click(position={"x": 200, "y": 100}) + map.click(position={"x": 100, "y": 100}) + map.click(position={"x": 100, "y": 200}) + # Click again to finish + map.click(position={"x": 100, "y": 200}) + expect(lines).to_have_count(1) + + # Draw another polygon + page.get_by_title("Draw a polyline").click() + map.click(position={"x": 250, "y": 250}) + map.click(position={"x": 200, "y": 250}) + map.click(position={"x": 200, "y": 200}) + # Click again to finish + map.click(position={"x": 200, "y": 200}) + expect(lines).to_have_count(2) + + # Now that polygon 2 is selected, right click on first one + # and transfer shape + lines.first.click(position={"x": 10, "y": 1}, button="right") + page.get_by_role("link", name="Transfer shape to edited feature").click() + expect(lines).to_have_count(1) + + +def test_can_transfer_shape_from_multi(live_server, page, tilelayer, settings): + settings.UMAP_ALLOW_ANONYMOUS = True + page.goto(f"{live_server.url}/en/map/new/") + lines = page.locator(".leaflet-overlay-pane path") + expect(lines).to_have_count(0) + page.get_by_title("Draw a polyline").click() + map = page.locator("#map") + + # Draw a multi line + map.click(position={"x": 200, "y": 100}) + map.click(position={"x": 100, "y": 100}) + map.click(position={"x": 100, "y": 200}) + # Click again to finish + map.click(position={"x": 100, "y": 200}) + expect(lines).to_have_count(1) + page.get_by_title("Add a line to the current multi").click() + map.click(position={"x": 250, "y": 250}) + map.click(position={"x": 200, "y": 250}) + map.click(position={"x": 200, "y": 200}) + # Click again to finish + map.click(position={"x": 200, "y": 200}) + expect(lines).to_have_count(1) + + # Draw another line + page.get_by_title("Draw a polyline").click() + map.click(position={"x": 350, "y": 350}) + map.click(position={"x": 300, "y": 350}) + map.click(position={"x": 300, "y": 300}) + # Click again to finish + map.click(position={"x": 300, "y": 300}) + expect(lines).to_have_count(2) + + # Now that polygon 2 is selected, right click on first one + # and transfer shape + lines.first.click(position={"x": 10, "y": 1}, button="right") + page.get_by_role("link", name="Transfer shape to edited feature").click() + expect(lines).to_have_count(2) + data = save_and_get_json(page) + # FIXME this should be a LineString, not MultiLineString + assert data["features"][0]["geometry"] == { + "coordinates": [ + [[-6.569824, 52.49616], [-7.668457, 52.49616], [-7.668457, 53.159947]] + ], + "type": "MultiLineString", + } + assert data["features"][1]["geometry"] == { + "coordinates": [ + [[-4.372559, 51.138001], [-5.471191, 51.138001], [-5.471191, 51.822198]], + [[-7.668457, 54.457267], [-9.865723, 54.457267], [-9.865723, 53.159947]], + ], + "type": "MultiLineString", + } + + +def test_can_extract_shape(live_server, page, tilelayer): + page.goto(f"{live_server.url}/en/map/new/") + lines = page.locator(".leaflet-overlay-pane path") + expect(lines).to_have_count(0) + page.get_by_title("Draw a polylin").click() + map = page.locator("#map") + map.click(position={"x": 200, "y": 100}) + map.click(position={"x": 100, "y": 100}) + map.click(position={"x": 100, "y": 200}) + # Click again to finish + map.click(position={"x": 100, "y": 200}) + expect(lines).to_have_count(1) + extract_button = page.get_by_role("link", name="Extract shape to separate feature") + expect(extract_button).to_be_hidden() + page.get_by_title("Add a line to the current multi").click() + map.click(position={"x": 250, "y": 250}) + map.click(position={"x": 200, "y": 250}) + map.click(position={"x": 200, "y": 200}) + # Click again to finish + map.click(position={"x": 200, "y": 200}) + expect(lines).to_have_count(1) + lines.first.click(position={"x": 10, "y": 1}, button="right") + extract_button.click() + expect(lines).to_have_count(2) + + +def test_can_clone_polyline(live_server, page, tilelayer, settings): + settings.UMAP_ALLOW_ANONYMOUS = True + page.goto(f"{live_server.url}/en/map/new/") + lines = page.locator(".leaflet-overlay-pane path") + expect(lines).to_have_count(0) + page.get_by_title("Draw a polyline").click() + map = page.locator("#map") + map.click(position={"x": 200, "y": 100}) + map.click(position={"x": 100, "y": 100}) + map.click(position={"x": 100, "y": 200}) + # Click again to finish + map.click(position={"x": 100, "y": 200}) + expect(lines).to_have_count(1) + lines.first.click(position={"x": 10, "y": 1}, button="right") + page.get_by_role("link", name="Clone this feature").click() + expect(lines).to_have_count(2) + data = save_and_get_json(page) + assert len(data["features"]) == 2 + assert data["features"][0]["geometry"]["type"] == "LineString" + assert data["features"][0]["geometry"] == data["features"][1]["geometry"] + assert data["features"][0]["properties"] == data["features"][1]["properties"] + + +def test_can_transform_polyline_to_polygon(live_server, page, tilelayer, settings): + settings.UMAP_ALLOW_ANONYMOUS = True + page.goto(f"{live_server.url}/en/map/new/") + paths = page.locator(".leaflet-overlay-pane path") + # Paths with fill + polygons = page.locator(".leaflet-overlay-pane path[fill='DarkBlue']") + expect(paths).to_have_count(0) + page.get_by_title("Draw a polyline").click() + map = page.locator("#map") + map.click(position={"x": 200, "y": 100}) + map.click(position={"x": 100, "y": 100}) + map.click(position={"x": 100, "y": 200}) + # Click again to finish + map.click(position={"x": 100, "y": 200}) + expect(paths).to_have_count(1) + expect(polygons).to_have_count(0) + paths.first.click(position={"x": 10, "y": 1}, button="right") + page.get_by_role("link", name="Transform to polygon").click() + expect(polygons).to_have_count(1) + expect(paths).to_have_count(1) + data = save_and_get_json(page) + assert len(data["features"]) == 1 + assert data["features"][0]["geometry"]["type"] == "Polygon"