umap-project 2.7.3__py3-none-any.whl → 2.8.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. umap/__init__.py +1 -1
  2. umap/forms.py +4 -14
  3. umap/locale/am_ET/LC_MESSAGES/django.mo +0 -0
  4. umap/locale/am_ET/LC_MESSAGES/django.po +278 -151
  5. umap/locale/ar/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/ar/LC_MESSAGES/django.po +335 -141
  7. umap/locale/bg/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/bg/LC_MESSAGES/django.po +279 -152
  9. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/br/LC_MESSAGES/django.po +95 -79
  11. umap/locale/ca/LC_MESSAGES/django.mo +0 -0
  12. umap/locale/ca/LC_MESSAGES/django.po +85 -68
  13. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  14. umap/locale/cs_CZ/LC_MESSAGES/django.po +78 -66
  15. umap/locale/da/LC_MESSAGES/django.mo +0 -0
  16. umap/locale/da/LC_MESSAGES/django.po +280 -153
  17. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  18. umap/locale/de/LC_MESSAGES/django.po +80 -64
  19. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  20. umap/locale/el/LC_MESSAGES/django.po +82 -66
  21. umap/locale/en/LC_MESSAGES/django.po +73 -61
  22. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/es/LC_MESSAGES/django.po +75 -63
  24. umap/locale/et/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/et/LC_MESSAGES/django.po +280 -153
  26. umap/locale/eu/LC_MESSAGES/django.mo +0 -0
  27. umap/locale/eu/LC_MESSAGES/django.po +82 -66
  28. umap/locale/fa_IR/LC_MESSAGES/django.mo +0 -0
  29. umap/locale/fa_IR/LC_MESSAGES/django.po +80 -64
  30. umap/locale/fi/LC_MESSAGES/django.mo +0 -0
  31. umap/locale/fi/LC_MESSAGES/django.po +278 -151
  32. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  33. umap/locale/fr/LC_MESSAGES/django.po +75 -63
  34. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  35. umap/locale/gl/LC_MESSAGES/django.po +280 -153
  36. umap/locale/he/LC_MESSAGES/django.mo +0 -0
  37. umap/locale/he/LC_MESSAGES/django.po +281 -154
  38. umap/locale/hu/LC_MESSAGES/django.mo +0 -0
  39. umap/locale/hu/LC_MESSAGES/django.po +80 -64
  40. umap/locale/is/LC_MESSAGES/django.mo +0 -0
  41. umap/locale/is/LC_MESSAGES/django.po +280 -153
  42. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  43. umap/locale/it/LC_MESSAGES/django.po +82 -66
  44. umap/locale/ja/LC_MESSAGES/django.mo +0 -0
  45. umap/locale/ja/LC_MESSAGES/django.po +280 -153
  46. umap/locale/ko/LC_MESSAGES/django.mo +0 -0
  47. umap/locale/ko/LC_MESSAGES/django.po +280 -153
  48. umap/locale/lt/LC_MESSAGES/django.mo +0 -0
  49. umap/locale/lt/LC_MESSAGES/django.po +280 -153
  50. umap/locale/ms/LC_MESSAGES/django.mo +0 -0
  51. umap/locale/ms/LC_MESSAGES/django.po +82 -66
  52. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  53. umap/locale/nl/LC_MESSAGES/django.po +280 -153
  54. umap/locale/pl/LC_MESSAGES/django.mo +0 -0
  55. umap/locale/pl/LC_MESSAGES/django.po +82 -66
  56. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  57. umap/locale/pt/LC_MESSAGES/django.po +75 -63
  58. umap/locale/pt_BR/LC_MESSAGES/django.mo +0 -0
  59. umap/locale/pt_BR/LC_MESSAGES/django.po +280 -153
  60. umap/locale/pt_PT/LC_MESSAGES/django.mo +0 -0
  61. umap/locale/pt_PT/LC_MESSAGES/django.po +280 -153
  62. umap/locale/ru/LC_MESSAGES/django.mo +0 -0
  63. umap/locale/ru/LC_MESSAGES/django.po +280 -153
  64. umap/locale/sk_SK/LC_MESSAGES/django.mo +0 -0
  65. umap/locale/sk_SK/LC_MESSAGES/django.po +280 -153
  66. umap/locale/sl/LC_MESSAGES/django.mo +0 -0
  67. umap/locale/sl/LC_MESSAGES/django.po +280 -153
  68. umap/locale/sr/LC_MESSAGES/django.mo +0 -0
  69. umap/locale/sr/LC_MESSAGES/django.po +280 -153
  70. umap/locale/sv/LC_MESSAGES/django.mo +0 -0
  71. umap/locale/sv/LC_MESSAGES/django.po +81 -65
  72. umap/locale/th_TH/LC_MESSAGES/django.mo +0 -0
  73. umap/locale/th_TH/LC_MESSAGES/django.po +257 -185
  74. umap/locale/tr/LC_MESSAGES/django.mo +0 -0
  75. umap/locale/tr/LC_MESSAGES/django.po +280 -153
  76. umap/locale/uk_UA/LC_MESSAGES/django.mo +0 -0
  77. umap/locale/uk_UA/LC_MESSAGES/django.po +280 -153
  78. umap/locale/vi/LC_MESSAGES/django.mo +0 -0
  79. umap/locale/vi/LC_MESSAGES/django.po +278 -151
  80. umap/locale/zh/LC_MESSAGES/django.mo +0 -0
  81. umap/locale/zh/LC_MESSAGES/django.po +278 -151
  82. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  83. umap/locale/zh_TW/LC_MESSAGES/django.po +97 -81
  84. umap/management/commands/empty_trash.py +32 -0
  85. umap/management/commands/migrate_to_S3.py +29 -0
  86. umap/migrations/0023_alter_datalayer_uuid.py +19 -0
  87. umap/migrations/0024_alter_map_share_status.py +30 -0
  88. umap/migrations/0025_alter_datalayer_geojson.py +24 -0
  89. umap/models.py +68 -116
  90. umap/settings/base.py +22 -2
  91. umap/static/umap/base.css +3 -603
  92. umap/static/umap/content.css +5 -3
  93. umap/static/umap/css/bar.css +202 -0
  94. umap/static/umap/css/form.css +617 -0
  95. umap/static/umap/css/icon.css +21 -1
  96. umap/static/umap/css/popup.css +125 -0
  97. umap/static/umap/img/16-white.svg +16 -4
  98. umap/static/umap/img/16.svg +1 -1
  99. umap/static/umap/img/source/16-white.svg +46 -45
  100. umap/static/umap/img/source/16.svg +1 -753
  101. umap/static/umap/js/components/fragment.js +3 -1
  102. umap/static/umap/js/modules/browser.js +20 -19
  103. umap/static/umap/js/modules/caption.js +21 -22
  104. umap/static/umap/js/modules/data/features.js +101 -74
  105. umap/static/umap/js/modules/data/layer.js +157 -137
  106. umap/static/umap/js/modules/facets.js +9 -9
  107. umap/static/umap/js/modules/formatter.js +5 -5
  108. umap/static/umap/js/modules/global.js +4 -52
  109. umap/static/umap/js/modules/help.js +18 -21
  110. umap/static/umap/js/modules/importer.js +71 -39
  111. umap/static/umap/js/modules/importers/cadastrefr.js +4 -0
  112. umap/static/umap/js/modules/importers/geodatamine.js +3 -3
  113. umap/static/umap/js/modules/importers/overpass.js +5 -0
  114. umap/static/umap/js/modules/permissions.js +85 -87
  115. umap/static/umap/js/modules/rendering/layers/base.js +15 -15
  116. umap/static/umap/js/modules/rendering/layers/classified.js +1 -1
  117. umap/static/umap/js/modules/rendering/layers/cluster.js +1 -1
  118. umap/static/umap/js/modules/rendering/layers/heat.js +1 -1
  119. umap/static/umap/js/modules/rendering/map.js +390 -0
  120. umap/static/umap/js/modules/rendering/popup.js +10 -9
  121. umap/static/umap/js/modules/rendering/template.js +35 -12
  122. umap/static/umap/js/modules/rendering/ui.js +57 -12
  123. umap/static/umap/js/modules/rules.js +22 -25
  124. umap/static/umap/js/modules/saving.js +47 -0
  125. umap/static/umap/js/modules/schema.js +5 -0
  126. umap/static/umap/js/modules/share.js +21 -24
  127. umap/static/umap/js/modules/slideshow.js +24 -20
  128. umap/static/umap/js/modules/sync/updaters.js +7 -9
  129. umap/static/umap/js/modules/tableeditor.js +20 -19
  130. umap/static/umap/js/modules/ui/bar.js +196 -0
  131. umap/static/umap/js/modules/ui/panel.js +10 -9
  132. umap/static/umap/js/modules/umap.js +1668 -0
  133. umap/static/umap/js/modules/urls.js +2 -2
  134. umap/static/umap/js/modules/utils.js +20 -6
  135. umap/static/umap/js/umap.controls.js +74 -301
  136. umap/static/umap/js/umap.core.js +29 -50
  137. umap/static/umap/js/umap.forms.js +34 -27
  138. umap/static/umap/keycloak.png +0 -0
  139. umap/static/umap/locale/am_ET.js +26 -10
  140. umap/static/umap/locale/am_ET.json +26 -10
  141. umap/static/umap/locale/ar.js +26 -10
  142. umap/static/umap/locale/ar.json +26 -10
  143. umap/static/umap/locale/ast.js +26 -10
  144. umap/static/umap/locale/ast.json +26 -10
  145. umap/static/umap/locale/bg.js +26 -10
  146. umap/static/umap/locale/bg.json +26 -10
  147. umap/static/umap/locale/br.js +27 -20
  148. umap/static/umap/locale/br.json +27 -20
  149. umap/static/umap/locale/ca.js +32 -29
  150. umap/static/umap/locale/ca.json +32 -29
  151. umap/static/umap/locale/cs_CZ.js +24 -17
  152. umap/static/umap/locale/cs_CZ.json +24 -17
  153. umap/static/umap/locale/da.js +26 -10
  154. umap/static/umap/locale/da.json +26 -10
  155. umap/static/umap/locale/de.js +21 -14
  156. umap/static/umap/locale/de.json +21 -14
  157. umap/static/umap/locale/el.js +28 -12
  158. umap/static/umap/locale/el.json +28 -12
  159. umap/static/umap/locale/en.js +12 -9
  160. umap/static/umap/locale/en.json +12 -9
  161. umap/static/umap/locale/en_US.json +26 -10
  162. umap/static/umap/locale/es.js +16 -13
  163. umap/static/umap/locale/es.json +16 -13
  164. umap/static/umap/locale/et.js +26 -10
  165. umap/static/umap/locale/et.json +26 -10
  166. umap/static/umap/locale/eu.js +16 -9
  167. umap/static/umap/locale/eu.json +16 -9
  168. umap/static/umap/locale/fa_IR.js +16 -9
  169. umap/static/umap/locale/fa_IR.json +16 -9
  170. umap/static/umap/locale/fi.js +26 -10
  171. umap/static/umap/locale/fi.json +26 -10
  172. umap/static/umap/locale/fr.js +12 -9
  173. umap/static/umap/locale/fr.json +12 -9
  174. umap/static/umap/locale/gl.js +26 -10
  175. umap/static/umap/locale/gl.json +26 -10
  176. umap/static/umap/locale/he.js +26 -10
  177. umap/static/umap/locale/he.json +26 -10
  178. umap/static/umap/locale/hr.js +26 -10
  179. umap/static/umap/locale/hr.json +26 -10
  180. umap/static/umap/locale/hu.js +16 -9
  181. umap/static/umap/locale/hu.json +16 -9
  182. umap/static/umap/locale/id.js +26 -10
  183. umap/static/umap/locale/id.json +26 -10
  184. umap/static/umap/locale/is.js +26 -10
  185. umap/static/umap/locale/is.json +26 -10
  186. umap/static/umap/locale/it.js +26 -10
  187. umap/static/umap/locale/it.json +26 -10
  188. umap/static/umap/locale/ja.js +26 -10
  189. umap/static/umap/locale/ja.json +26 -10
  190. umap/static/umap/locale/ko.js +26 -10
  191. umap/static/umap/locale/ko.json +26 -10
  192. umap/static/umap/locale/lt.js +26 -10
  193. umap/static/umap/locale/lt.json +26 -10
  194. umap/static/umap/locale/ms.js +28 -12
  195. umap/static/umap/locale/ms.json +28 -12
  196. umap/static/umap/locale/nl.js +28 -12
  197. umap/static/umap/locale/nl.json +28 -12
  198. umap/static/umap/locale/no.js +26 -10
  199. umap/static/umap/locale/no.json +26 -10
  200. umap/static/umap/locale/pl.js +28 -12
  201. umap/static/umap/locale/pl.json +28 -12
  202. umap/static/umap/locale/pl_PL.json +26 -10
  203. umap/static/umap/locale/pt.js +16 -9
  204. umap/static/umap/locale/pt.json +16 -9
  205. umap/static/umap/locale/pt_BR.js +26 -10
  206. umap/static/umap/locale/pt_BR.json +26 -10
  207. umap/static/umap/locale/pt_PT.js +16 -9
  208. umap/static/umap/locale/pt_PT.json +16 -9
  209. umap/static/umap/locale/ro.js +26 -10
  210. umap/static/umap/locale/ro.json +26 -10
  211. umap/static/umap/locale/ru.js +26 -10
  212. umap/static/umap/locale/ru.json +26 -10
  213. umap/static/umap/locale/si.js +7 -7
  214. umap/static/umap/locale/si.json +7 -7
  215. umap/static/umap/locale/sk_SK.js +26 -10
  216. umap/static/umap/locale/sk_SK.json +26 -10
  217. umap/static/umap/locale/sl.js +26 -10
  218. umap/static/umap/locale/sl.json +26 -10
  219. umap/static/umap/locale/sr.js +26 -10
  220. umap/static/umap/locale/sr.json +26 -10
  221. umap/static/umap/locale/sv.js +27 -11
  222. umap/static/umap/locale/sv.json +27 -11
  223. umap/static/umap/locale/th_TH.js +28 -12
  224. umap/static/umap/locale/th_TH.json +28 -12
  225. umap/static/umap/locale/tr.js +26 -10
  226. umap/static/umap/locale/tr.json +26 -10
  227. umap/static/umap/locale/uk_UA.js +26 -10
  228. umap/static/umap/locale/uk_UA.json +26 -10
  229. umap/static/umap/locale/vi.js +26 -10
  230. umap/static/umap/locale/vi.json +26 -10
  231. umap/static/umap/locale/vi_VN.json +26 -10
  232. umap/static/umap/locale/zh.js +26 -10
  233. umap/static/umap/locale/zh.json +26 -10
  234. umap/static/umap/locale/zh_CN.json +26 -10
  235. umap/static/umap/locale/zh_TW.Big5.json +26 -10
  236. umap/static/umap/locale/zh_TW.js +34 -27
  237. umap/static/umap/locale/zh_TW.json +34 -27
  238. umap/static/umap/map.css +5 -364
  239. umap/static/umap/unittests/URLs.js +15 -15
  240. umap/static/umap/unittests/utils.js +23 -1
  241. umap/static/umap/vars.css +2 -0
  242. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +5 -1
  243. umap/storage.py +152 -0
  244. umap/templates/registration/login.html +7 -6
  245. umap/templates/umap/css.html +3 -0
  246. umap/templates/umap/js.html +1 -2
  247. umap/templates/umap/map_init.html +4 -5
  248. umap/templates/umap/user_dashboard.html +18 -19
  249. umap/tests/base.py +5 -1
  250. umap/tests/integration/conftest.py +2 -1
  251. umap/tests/integration/test_anonymous_owned_map.py +18 -10
  252. umap/tests/integration/test_browser.py +16 -1
  253. umap/tests/integration/test_dashboard.py +1 -1
  254. umap/tests/integration/test_edit_datalayer.py +18 -7
  255. umap/tests/integration/test_import.py +8 -5
  256. umap/tests/integration/test_optimistic_merge.py +31 -8
  257. umap/tests/integration/test_owned_map.py +22 -16
  258. umap/tests/integration/test_popup.py +44 -0
  259. umap/tests/integration/test_save.py +35 -0
  260. umap/tests/integration/test_view_marker.py +12 -0
  261. umap/tests/integration/test_view_polyline.py +257 -0
  262. umap/tests/integration/test_websocket_sync.py +81 -9
  263. umap/tests/test_datalayer.py +6 -7
  264. umap/tests/test_datalayer_s3.py +135 -0
  265. umap/tests/test_datalayer_views.py +28 -10
  266. umap/tests/test_empty_trash.py +34 -0
  267. umap/tests/test_map.py +12 -3
  268. umap/tests/test_map_views.py +69 -37
  269. umap/tests/test_views.py +53 -0
  270. umap/urls.py +3 -3
  271. umap/views.py +107 -76
  272. {umap_project-2.7.3.dist-info → umap_project-2.8.0a0.dist-info}/METADATA +16 -13
  273. {umap_project-2.7.3.dist-info → umap_project-2.8.0a0.dist-info}/RECORD +276 -262
  274. umap/management/commands/purge_purgatory.py +0 -28
  275. umap/static/umap/js/umap.js +0 -1903
  276. umap/tests/test_purge_purgatory.py +0 -25
  277. {umap_project-2.7.3.dist-info → umap_project-2.8.0a0.dist-info}/WHEEL +0 -0
  278. {umap_project-2.7.3.dist-info → umap_project-2.8.0a0.dist-info}/entry_points.txt +0 -0
  279. {umap_project-2.7.3.dist-info → umap_project-2.8.0a0.dist-info}/licenses/LICENSE +0 -0
@@ -49,6 +49,7 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
49
49
  "name": "test datalayer",
50
50
  "editMode": "advanced",
51
51
  "inCaption": True,
52
+ "id": str(datalayer.pk),
52
53
  }
53
54
 
54
55
  # Now navigate to this map from another tab
@@ -78,12 +79,14 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
78
79
  sleep(1)
79
80
  # No change after the save
80
81
  expect(marker_pane_p2).to_have_count(2)
81
- assert DataLayer.objects.get(pk=datalayer.pk).settings == {
82
+ datalayer_v2 = DataLayer.objects.get(pk=datalayer.pk)
83
+ assert datalayer_v2.settings == {
82
84
  "browsable": True,
83
85
  "displayOnLoad": True,
84
86
  "name": "test datalayer",
85
87
  "inCaption": True,
86
88
  "editMode": "advanced",
89
+ "id": str(datalayer.pk),
87
90
  }
88
91
 
89
92
  # Now create another marker in the first tab
@@ -94,7 +97,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
94
97
  save_p1.click()
95
98
  # Should now get the other marker too
96
99
  expect(marker_pane_p1).to_have_count(3)
97
- assert DataLayer.objects.get(pk=datalayer.pk).settings == {
100
+ datalayer_v3 = DataLayer.objects.get(pk=datalayer.pk)
101
+ assert datalayer_v3.settings == {
98
102
  "browsable": True,
99
103
  "displayOnLoad": True,
100
104
  "name": "test datalayer",
@@ -112,7 +116,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
112
116
  save_p1.click()
113
117
  sleep(1)
114
118
  # Should now get the other marker too
115
- assert DataLayer.objects.get(pk=datalayer.pk).settings == {
119
+ datalayer_v4 = DataLayer.objects.get(pk=datalayer.pk)
120
+ assert datalayer_v4.settings == {
116
121
  "browsable": True,
117
122
  "displayOnLoad": True,
118
123
  "name": "test datalayer",
@@ -132,7 +137,8 @@ def test_created_markers_are_merged(context, live_server, tilelayer):
132
137
  save_p2.click()
133
138
  sleep(1)
134
139
  # Should now get the other markers too
135
- assert DataLayer.objects.get(pk=datalayer.pk).settings == {
140
+ datalayer_v5 = DataLayer.objects.get(pk=datalayer.pk)
141
+ assert datalayer_v5.settings == {
136
142
  "browsable": True,
137
143
  "displayOnLoad": True,
138
144
  "name": "test datalayer",
@@ -276,21 +282,38 @@ def test_should_display_alert_on_conflict(context, live_server, datalayer, openm
276
282
  page_two = context.new_page()
277
283
  page_two.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
278
284
 
285
+ # Change name on page one and save
279
286
  page_one.locator(".leaflet-marker-icon").click(modifiers=["Shift"])
280
- page_one.locator('input[name="name"]').fill("new name")
287
+ page_one.locator('input[name="name"]').fill("name from page one")
281
288
  with page_one.expect_response(re.compile(r".*/datalayer/update/.*")):
282
289
  page_one.get_by_role("button", name="Save").click()
283
290
 
291
+ # Change name on page two and save
284
292
  page_two.locator(".leaflet-marker-icon").click(modifiers=["Shift"])
285
- page_two.locator('input[name="name"]').fill("custom name")
293
+ page_two.locator('input[name="name"]').fill("name from page two")
294
+
295
+ # Map should be in dirty status
296
+ expect(page_two.get_by_text("Cancel edits")).to_be_visible()
286
297
  with page_two.expect_response(re.compile(r".*/datalayer/update/.*")):
287
298
  page_two.get_by_role("button", name="Save").click()
299
+
300
+ # Make sure data is unchanged on the server
288
301
  saved = DataLayer.objects.last()
289
302
  data = json.loads(Path(saved.geojson.path).read_text())
290
- assert data["features"][0]["properties"]["name"] == "new name"
303
+ assert data["features"][0]["properties"]["name"] == "name from page one"
304
+
305
+ # We should have an alert with some actions
291
306
  expect(page_two.get_by_text("Whoops! Other contributor(s) changed")).to_be_visible()
307
+ # Map should still be in dirty status
308
+ expect(page_two.get_by_text("Cancel edits")).to_be_visible()
309
+
310
+ # Override data from page two
292
311
  with page_two.expect_response(re.compile(r".*/datalayer/update/.*")):
293
312
  page_two.get_by_text("Keep your changes and loose theirs").click()
313
+
314
+ # Make sure server has page two data
294
315
  saved = DataLayer.objects.last()
295
316
  data = json.loads(Path(saved.geojson.path).read_text())
296
- assert data["features"][0]["properties"]["name"] == "custom name"
317
+ assert data["features"][0]["properties"]["name"] == "name from page two"
318
+ # Map should not be in dirty status anymore
319
+ expect(page_two.get_by_text("Cancel edits")).to_be_hidden()
@@ -61,8 +61,12 @@ def test_owner_permissions_form(map, datalayer, live_server, login):
61
61
  edit_permissions = page.get_by_title("Update permissions and editors")
62
62
  expect(edit_permissions).to_be_visible()
63
63
  edit_permissions.click()
64
- select = page.locator(".umap-field-share_status select")
65
- expect(select).to_be_visible()
64
+ expect(page.locator(".umap-field-share_status select")).to_be_visible()
65
+ options = [
66
+ int(option.get_attribute("value"))
67
+ for option in page.locator(".umap-field-share_status select option").all()
68
+ ]
69
+ assert options == [Map.DRAFT, Map.PUBLIC, Map.OPEN, Map.PRIVATE]
66
70
  # expect(select).to_have_value(Map.PUBLIC) # Does not work
67
71
  owner_field = page.locator(".umap-field-owner")
68
72
  expect(owner_field).to_be_visible()
@@ -137,7 +141,7 @@ def test_owner_has_delete_map_button(map, live_server, login):
137
141
  delete.click()
138
142
  with page.expect_navigation():
139
143
  page.get_by_role("button", name="OK").click()
140
- assert Map.objects.all().count() == 0
144
+ assert Map.objects.get(pk=map.pk).share_status == Map.DELETED
141
145
 
142
146
 
143
147
  def test_editor_do_not_have_delete_map_button(map, live_server, login, user):
@@ -181,29 +185,31 @@ def test_can_change_perms_after_create(tilelayer, live_server, login, user):
181
185
  page.get_by_title("Manage layers").click()
182
186
  page.get_by_title("Add a layer").click()
183
187
  page.locator("input[name=name]").fill("Layer 1")
184
- save = page.get_by_role("button", name="Save")
185
- expect(save).to_be_visible()
188
+ expect(
189
+ page.get_by_role("button", name="Visibility: Draft (private)")
190
+ ).to_be_visible()
191
+ expect(page.get_by_role("button", name="Save", exact=True)).to_be_hidden()
186
192
  with page.expect_response(re.compile(r".*/map/create/")):
187
- save.click()
193
+ page.get_by_role("button", name="Save draft", exact=True).click()
188
194
  edit_permissions = page.get_by_title("Update permissions and editors")
189
195
  expect(edit_permissions).to_be_visible()
190
196
  edit_permissions.click()
191
- select = page.locator(".umap-field-share_status select")
192
- expect(select).to_be_visible()
193
- option = page.locator("select[name='share_status'] option:checked")
194
- expect(option).to_have_text("Everyone (public)")
195
- owner_field = page.locator(".umap-field-owner")
196
- expect(owner_field).to_be_visible()
197
- editors_field = page.locator(".umap-field-editors input")
198
- expect(editors_field).to_be_visible()
199
- datalayer_label = page.get_by_text('Who can edit "Layer 1"')
200
- expect(datalayer_label).to_be_visible()
197
+ expect(page.locator(".umap-field-share_status select")).to_be_visible()
198
+ expect(page.locator("select[name='share_status'] option:checked")).to_have_text(
199
+ "Draft (private)"
200
+ )
201
+ expect(page.locator(".umap-field-owner")).to_be_visible()
202
+ expect(page.locator(".umap-field-editors input")).to_be_visible()
203
+ expect(page.get_by_text('Who can edit "Layer 1"')).to_be_visible()
201
204
  options = page.locator(".datalayer-permissions select[name='edit_status'] option")
202
205
  expect(options).to_have_count(4)
203
206
  option = page.locator(
204
207
  ".datalayer-permissions select[name='edit_status'] option:checked"
205
208
  )
206
209
  expect(option).to_have_text("Inherit")
210
+ page.locator('select[name="share_status"]').select_option("1")
211
+ expect(page.get_by_role("button", name="Save draft", exact=True)).to_be_hidden()
212
+ expect(page.get_by_role("button", name="Save", exact=True)).to_be_visible()
207
213
 
208
214
 
209
215
  def test_can_change_owner(map, live_server, login, user):
@@ -0,0 +1,44 @@
1
+ import pytest
2
+ from playwright.sync_api import expect
3
+
4
+ from ..base import DataLayerFactory
5
+
6
+ pytestmark = pytest.mark.django_db
7
+
8
+ OSM_DATA = {
9
+ "type": "FeatureCollection",
10
+ "features": [
11
+ {
12
+ "type": "Feature",
13
+ "geometry": {"type": "Point", "coordinates": [2.49, 48.79]},
14
+ "properties": {
15
+ "amenity": "restaurant",
16
+ "cuisine": "italian",
17
+ "name": "A Casa di Nonna",
18
+ "panoramax": "d811b398-d930-4cf8-95a2-0c29c34d9fca",
19
+ "phone": "+33 1 48 89 54 12",
20
+ "takeaway:covid19": "yes",
21
+ "wheelchair": "no",
22
+ "id": "node/1130849864",
23
+ },
24
+ "id": "AzMjk",
25
+ },
26
+ ],
27
+ "_umap_options": {
28
+ "popupTemplate": "OSM",
29
+ },
30
+ }
31
+
32
+
33
+ def test_openstreetmap_popup(live_server, map, page):
34
+ DataLayerFactory(map=map, data=OSM_DATA)
35
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#18/48.79/2.49")
36
+ expect(page.locator(".umap-icon-active")).to_be_hidden()
37
+ page.locator(".leaflet-marker-icon").click()
38
+ expect(page.get_by_role("heading", name="A Casa di Nonna")).to_be_visible()
39
+ expect(page.get_by_text("+33 1 48 89 54 12")).to_be_visible()
40
+ img = page.locator(".umap-popup-content img")
41
+ expect(img).to_have_attribute(
42
+ "src",
43
+ "https://api.panoramax.xyz/api/pictures/d811b398-d930-4cf8-95a2-0c29c34d9fca/sd.jpg",
44
+ )
@@ -0,0 +1,35 @@
1
+ import re
2
+
3
+
4
+ def test_reseting_map_would_remove_from_save_queue(
5
+ live_server, openmap, page, datalayer
6
+ ):
7
+ page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
8
+ page.get_by_role("link", name="Edit map name and caption").click()
9
+ requests = []
10
+
11
+ def register_request(request):
12
+ if request.url.endswith(".png"):
13
+ return
14
+ requests.append((request.method, request.url))
15
+
16
+ page.on("request", register_request)
17
+ page.locator('input[name="name"]').click()
18
+ page.locator('input[name="name"]').fill("new name")
19
+ page.get_by_role("button", name="Cancel edits").click()
20
+ page.get_by_role("button", name="OK").click()
21
+ page.wait_for_timeout(500)
22
+ page.get_by_role("button", name="Edit").click()
23
+ page.get_by_role("link", name="Manage layers").click()
24
+ page.get_by_role("button", name="Edit", exact=True).click()
25
+ page.locator('input[name="name"]').click()
26
+ page.locator('input[name="name"]').fill("new datalayer name")
27
+ with page.expect_response(re.compile(".*/datalayer/update/.*")):
28
+ page.get_by_role("button", name="Save").click()
29
+ assert len(requests) == 1
30
+ assert requests == [
31
+ (
32
+ "POST",
33
+ f"{live_server.url}/en/map/{openmap.pk}/datalayer/update/{datalayer.pk}/",
34
+ ),
35
+ ]
@@ -57,6 +57,18 @@ def test_should_handle_locale_var_in_description(live_server, map, page):
57
57
  expect(link).to_have_attribute("href", "https://domain.org/?locale=en")
58
58
 
59
59
 
60
+ def test_should_use_custom_label_key_in_popup_default_template(live_server, map, page):
61
+ data = deepcopy(DATALAYER_DATA)
62
+ data["features"][0]["properties"] = {
63
+ "libellé": "my custom label",
64
+ }
65
+ data["_umap_options"] = {"labelKey": "libellé"}
66
+ DataLayerFactory(map=map, data=data)
67
+ page.goto(f"{live_server.url}{map.get_absolute_url()}")
68
+ page.locator(".leaflet-marker-icon").click()
69
+ expect(page.locator(".umap-popup h4")).to_have_text("my custom label")
70
+
71
+
60
72
  def test_should_display_tooltip_with_variable(live_server, map, page, bootstrap):
61
73
  map.settings["properties"]["showLabel"] = True
62
74
  map.settings["properties"]["labelKey"] = "Foo {name}"
@@ -1,3 +1,5 @@
1
+ from pathlib import Path
2
+
1
3
  import pytest
2
4
  from playwright.sync_api import expect
3
5
 
@@ -49,3 +51,258 @@ def test_should_open_popup_on_click(live_server, map, page, bootstrap):
49
51
  # Close popup
50
52
  page.locator("#map").click()
51
53
  expect(line).to_have_attribute("stroke-opacity", "0.5")
54
+
55
+
56
+ def test_can_use_measure_on_name(live_server, map, page):
57
+ data = {
58
+ "type": "FeatureCollection",
59
+ "features": [
60
+ {
61
+ "type": "Feature",
62
+ "properties": {"name": "linestring"},
63
+ "geometry": {
64
+ "type": "LineString",
65
+ "coordinates": [
66
+ [11.25, 53.585984],
67
+ [10.151367, 52.975108],
68
+ ],
69
+ },
70
+ },
71
+ {
72
+ "type": "Feature",
73
+ "properties": {"name": "multilinestring"},
74
+ "geometry": {
75
+ "type": "MultiLineString",
76
+ "coordinates": [[[8, 53], [13, 52]], [[12, 51], [15, 52]]],
77
+ },
78
+ },
79
+ ],
80
+ }
81
+ map.settings["properties"]["labelKey"] = "{name} ({measure})"
82
+ map.settings["properties"]["onLoadPanel"] = "databrowser"
83
+ map.save()
84
+ DataLayerFactory(map=map, data=data)
85
+ page.goto(f"{live_server.url}{map.get_absolute_url()}#6/10/50")
86
+ expect(page.get_by_text("linestring (99.7 km)")).to_be_visible()
87
+ expect(page.get_by_text("multilinestring (592 km)")).to_be_visible()
88
+
89
+
90
+ def test_can_use_gain_and_loss(live_server, map, page):
91
+ data = {
92
+ "type": "FeatureCollection",
93
+ "features": [
94
+ {
95
+ "type": "Feature",
96
+ "geometry": {
97
+ "type": "LineString",
98
+ "coordinates": [
99
+ [8.420888, 39.327819, 12.1],
100
+ [8.420915, 39.327745, 12.9],
101
+ [8.420906, 39.327543, 14.8],
102
+ [8.420876, 39.327312, 17],
103
+ [8.420754, 39.327023, 16.8],
104
+ [8.420609, 39.326776, 16.6],
105
+ [8.42051, 39.326467, 16.4],
106
+ [8.420409, 39.326254, 16.3],
107
+ [8.420367, 39.326108, 16.2],
108
+ [8.420287, 39.326044, 16.2],
109
+ [8.420294, 39.325794, 15.1],
110
+ [8.419974, 39.325549, 12.5],
111
+ [8.419887, 39.325508, 11.9],
112
+ [8.419702, 39.325343, 10.3],
113
+ [8.419574, 39.325289, 9.4],
114
+ [8.41943, 39.325183, 8.2],
115
+ [8.419393, 39.325127, 7.8],
116
+ [8.419297, 39.325108, 7.2],
117
+ [8.419179, 39.325014, 6.2],
118
+ [8.419179, 39.324979, 6.1],
119
+ [8.419179, 39.324979, 6.1],
120
+ [8.419182, 39.324653, 6],
121
+ [8.419129, 39.324508, 5.9],
122
+ [8.419074, 39.324482, 5.9],
123
+ [8.419007, 39.324362, 5.8],
124
+ [8.418957, 39.324324, 5.8],
125
+ [8.418944, 39.324291, 5.8],
126
+ [8.418947, 39.324233, 5.8],
127
+ [8.418967, 39.324196, 5.7],
128
+ [8.41895, 39.324126, 5.6],
129
+ [8.418838, 39.323996, 5.4],
130
+ [8.418814, 39.323774, 5],
131
+ [8.418931, 39.323546, 10.1],
132
+ [8.41896, 39.323444, 12.3],
133
+ [8.418971, 39.323232, 16.9],
134
+ [8.419068, 39.322974, 13.5],
135
+ [8.41914, 39.322946, 12.7],
136
+ [8.419245, 39.322895, 12.3],
137
+ [8.419342, 39.322857, 11.9],
138
+ [8.419492, 39.322855, 11.3],
139
+ [8.419591, 39.322768, 11.1],
140
+ [8.419672, 39.322724, 11],
141
+ [8.419776, 39.322704, 10.9],
142
+ [8.419823, 39.322711, 10.8],
143
+ [8.41971, 39.322631, 8.5],
144
+ [8.419668, 39.322581, 7.3],
145
+ [8.419653, 39.322537, 6.3],
146
+ [8.419628, 39.32243, 5.3],
147
+ [8.419629, 39.322384, 5.4],
148
+ [8.419656, 39.322346, 5.5],
149
+ [8.419703, 39.322304, 5.6],
150
+ [8.419802, 39.322305, 5.7],
151
+ [8.419868, 39.32228, 5.8],
152
+ [8.419994, 39.322223, 6.1],
153
+ [8.420066, 39.322174, 6.2],
154
+ [8.42013, 39.32211, 6.4],
155
+ [8.420142, 39.322013, 6.5],
156
+ [8.420167, 39.321924, 6.7],
157
+ [8.420188, 39.321788, 7],
158
+ [8.420269, 39.32165, 7.3],
159
+ [8.420342, 39.321625, 7.4],
160
+ [8.420427, 39.321609, 7.5],
161
+ [8.420645, 39.321582, 7.8],
162
+ [8.420753, 39.321699, 8.1],
163
+ [8.420881, 39.321801, 8.4],
164
+ [8.421082, 39.321898, 8.7],
165
+ [8.421184, 39.321868, 8.9],
166
+ [8.421351, 39.321877, 9.1],
167
+ [8.421451, 39.321834, 8.2],
168
+ [8.421545, 39.321811, 7.5],
169
+ [8.421815, 39.321691, 5.1],
170
+ [8.421877, 39.321632, 4.3],
171
+ [8.42196, 39.321602, 3.7],
172
+ [8.422083, 39.321621, 4.5],
173
+ [8.422462, 39.321579, 8.7],
174
+ [8.422875, 39.321691, 13.5],
175
+ [8.423069, 39.321732, 15.7],
176
+ [8.423231, 39.321687, 17.6],
177
+ [8.423405, 39.321726, 19.6],
178
+ [8.423626, 39.321719, 22.1],
179
+ [8.424103, 39.321776, 27.4],
180
+ [8.424214, 39.321746, 28.7],
181
+ [8.424402, 39.321632, 27.5],
182
+ [8.424486, 39.321559, 26.8],
183
+ [8.424524, 39.321501, 26.3],
184
+ [8.424916, 39.321097, 22.6],
185
+ [8.424969, 39.321007, 21.9],
186
+ [8.425387, 39.320766, 19],
187
+ [8.425489, 39.320645, 20.4],
188
+ [8.425621, 39.320556, 22.1],
189
+ [8.425699, 39.320574, 22.8],
190
+ [8.425774, 39.320511, 23.9],
191
+ [8.425831, 39.320336, 19.6],
192
+ [8.425822, 39.32021, 16.1],
193
+ [8.42575, 39.320086, 12.4],
194
+ [8.425788, 39.320004, 10],
195
+ [8.425946, 39.319822, 10.7],
196
+ [8.426039, 39.319781, 11],
197
+ [8.426264, 39.319776, 11.8],
198
+ [8.426305, 39.319718, 12],
199
+ [8.426362, 39.319433, 6.8],
200
+ [8.426414, 39.31936, 5.3],
201
+ [8.426422, 39.319161, 1.6],
202
+ [8.426449, 39.319085, 0.2],
203
+ [8.426568, 39.31904, 0.4],
204
+ [8.426608, 39.318981, 1.8],
205
+ [8.426714, 39.318923, 3.8],
206
+ [8.427072, 39.318862, 9.6],
207
+ [8.427204, 39.31886, 11.7],
208
+ [8.427359, 39.318896, 14.3],
209
+ [8.427434, 39.31895, 15.9],
210
+ [8.427519, 39.318968, 17.3],
211
+ [8.427558, 39.318972, 18.4],
212
+ [8.427616, 39.318991, 19.2],
213
+ [8.427685, 39.319082, 21.3],
214
+ [8.42773, 39.31921, 25.1],
215
+ [8.427914, 39.319306, 29.9],
216
+ [8.42849, 39.319358, 46.7],
217
+ [8.429645, 39.319309, 79.8],
218
+ [8.430532, 39.319314, 85],
219
+ [8.430582, 39.319297, 85.2],
220
+ [8.430808, 39.319195, 84.8],
221
+ [8.43098, 39.319076, 84.5],
222
+ [8.431138, 39.318999, 84.2],
223
+ [8.431283, 39.318985, 84],
224
+ [8.431288, 39.318946, 84.8],
225
+ [8.431373, 39.318825, 87.6],
226
+ [8.431411, 39.318796, 87.9],
227
+ [8.431678, 39.318827, 87.1],
228
+ [8.431773, 39.318752, 88.3],
229
+ [8.431854, 39.318552, 90.7],
230
+ [8.431832, 39.31847, 91.7],
231
+ [8.431959, 39.318284, 94.1],
232
+ [8.431968, 39.318034, 97],
233
+ [8.43207, 39.31791, 98.7],
234
+ [8.432118, 39.317736, 100.8],
235
+ [8.432263, 39.317582, 103],
236
+ [8.432291, 39.317263, 106.7],
237
+ [8.432279, 39.316848, 111.5],
238
+ [8.432358, 39.31652, 115.3],
239
+ [8.432327, 39.316486, 115.8],
240
+ [8.432336, 39.316437, 116.4],
241
+ [8.432287, 39.316409, 116.4],
242
+ [8.432257, 39.316391, 116.2],
243
+ [8.432179, 39.316251, 115.4],
244
+ [8.432156, 39.316183, 115.6],
245
+ [8.43223, 39.316138, 116.5],
246
+ [8.43223, 39.316043, 117.7],
247
+ [8.432274, 39.315918, 119.4],
248
+ [8.432254, 39.315799, 121],
249
+ [8.432377, 39.315713, 122.6],
250
+ [8.43248, 39.315542, 125.1],
251
+ [8.43257, 39.315451, 126],
252
+ [8.432652, 39.315352, 127],
253
+ [8.432724, 39.315291, 127.7],
254
+ [8.432779, 39.315208, 128.5],
255
+ [8.432863, 39.315109, 129.5],
256
+ [8.432945, 39.315038, 130.3],
257
+ [8.433002, 39.315014, 130.7],
258
+ [8.433092, 39.315007, 131.3],
259
+ [8.43318, 39.315012, 131.9],
260
+ [8.433295, 39.315111, 133],
261
+ [8.433346, 39.315126, 133.4],
262
+ [8.43338, 39.315113, 133.6],
263
+ [8.433404, 39.31509, 133.8],
264
+ [8.433408, 39.315058, 132.4],
265
+ [8.433422, 39.31498, 131.6],
266
+ [8.43347, 39.314876, 130.3],
267
+ [8.433562, 39.314771, 128.2],
268
+ [8.433817, 39.314543, 123],
269
+ [8.434004, 39.314434, 119.9],
270
+ [8.434262, 39.314344, 121.2],
271
+ [8.434729, 39.314236, 123.9],
272
+ [8.435085, 39.31388, 127.2],
273
+ [8.435254, 39.313808, 128.4],
274
+ [8.435388, 39.313779, 128.4],
275
+ [8.435547, 39.313774, 129.9],
276
+ [8.4357, 39.313825, 131.6],
277
+ [8.435802, 39.313882, 132.8],
278
+ [8.435866, 39.313979, 134.1],
279
+ [8.435902, 39.314064, 135.2],
280
+ [8.435973, 39.314103, 136],
281
+ [8.436062, 39.314107, 136.9],
282
+ [8.436143, 39.314101, 137.7],
283
+ [8.436215, 39.313989, 137.7],
284
+ [8.436162, 39.313752, 137.5],
285
+ [8.436209, 39.313402, 137.2],
286
+ [8.436413, 39.313182, 137],
287
+ [8.436559, 39.313127, 136.9],
288
+ [8.43664, 39.313115, 136.8],
289
+ [8.436682, 39.313058, 137.7],
290
+ [8.436798, 39.312789, 139.1],
291
+ [8.436706, 39.312486, 140.7],
292
+ [8.436703, 39.312448, 141],
293
+ ],
294
+ },
295
+ "properties": {"name": "some track"},
296
+ "id": "MzMTI",
297
+ }
298
+ ],
299
+ "_umap_options": {"popupContentTemplate": "{name}\n⭧ {gain} m\n⭨ {loss} m"},
300
+ }
301
+ DataLayerFactory(map=map, data=data)
302
+ page.goto(
303
+ f"{live_server.url}{map.get_absolute_url()}?onLoadPanel=databrowser#16/39.3201/8.4278"
304
+ )
305
+ # We can't make PW to click on the path, so let's use the browser for that
306
+ page.get_by_text("some track").click()
307
+ expect(page.get_by_text("⭧ 211 m")).to_be_visible()
308
+ expect(page.get_by_text("⭨ 82 m")).to_be_visible()
@@ -3,7 +3,7 @@ import re
3
3
  import pytest
4
4
  from playwright.sync_api import expect
5
5
 
6
- from umap.models import Map
6
+ from umap.models import DataLayer, Map
7
7
 
8
8
  from ..base import DataLayerFactory, MapFactory
9
9
 
@@ -161,7 +161,7 @@ def test_websocket_connection_can_sync_polygons(
161
161
 
162
162
  @pytest.mark.xdist_group(name="websockets")
163
163
  def test_websocket_connection_can_sync_map_properties(
164
- context, live_server, websocket_server, tilelayer
164
+ new_page, live_server, websocket_server, tilelayer
165
165
  ):
166
166
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
167
167
  map.settings["properties"]["syncEnabled"] = True
@@ -169,9 +169,9 @@ def test_websocket_connection_can_sync_map_properties(
169
169
  DataLayerFactory(map=map, data={})
170
170
 
171
171
  # Create two tabs
172
- peerA = context.new_page()
172
+ peerA = new_page()
173
173
  peerA.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
174
- peerB = context.new_page()
174
+ peerB = new_page()
175
175
  peerB.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
176
176
 
177
177
  # Name change is synced
@@ -193,7 +193,7 @@ def test_websocket_connection_can_sync_map_properties(
193
193
 
194
194
  @pytest.mark.xdist_group(name="websockets")
195
195
  def test_websocket_connection_can_sync_datalayer_properties(
196
- context, live_server, websocket_server, tilelayer
196
+ new_page, live_server, websocket_server, tilelayer
197
197
  ):
198
198
  map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
199
199
  map.settings["properties"]["syncEnabled"] = True
@@ -201,9 +201,9 @@ def test_websocket_connection_can_sync_datalayer_properties(
201
201
  DataLayerFactory(map=map, data={})
202
202
 
203
203
  # Create two tabs
204
- peerA = context.new_page()
204
+ peerA = new_page()
205
205
  peerA.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
206
- peerB = context.new_page()
206
+ peerB = new_page()
207
207
  peerB.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
208
208
 
209
209
  # Layer addition, name and type are synced
@@ -215,7 +215,7 @@ def test_websocket_connection_can_sync_datalayer_properties(
215
215
  peerA.locator("body").press("Escape")
216
216
 
217
217
  peerB.get_by_role("link", name="Manage layers").click()
218
- peerB.get_by_role("button", name="Edit").first.click()
218
+ peerB.locator(".panel.right").get_by_role("button", name="Edit").first.click()
219
219
  expect(peerB.locator('input[name="name"]')).to_have_value("synced layer!")
220
220
  expect(peerB.get_by_role("combobox")).to_have_value("Choropleth")
221
221
 
@@ -267,7 +267,7 @@ def test_websocket_connection_can_sync_cloned_polygons(
267
267
  b_polygon = peerB.locator("path")
268
268
 
269
269
  # Clone on peer B and save
270
- b_polygon.click(button="right")
270
+ b_polygon.click(button="right", delay=200)
271
271
  peerB.get_by_role("button", name="Clone this feature").click()
272
272
 
273
273
  expect(peerB.locator("path")).to_have_count(2)
@@ -343,3 +343,75 @@ def test_websocket_connection_can_sync_late_joining_peer(
343
343
 
344
344
  # Clean up: close edit mode
345
345
  peerB.locator("body").press("Escape")
346
+
347
+
348
+ @pytest.mark.xdist_group(name="websockets")
349
+ def test_should_sync_datalayers(new_page, live_server, websocket_server, tilelayer):
350
+ map = MapFactory(name="sync", edit_status=Map.ANONYMOUS)
351
+ map.settings["properties"]["syncEnabled"] = True
352
+ map.save()
353
+
354
+ assert not DataLayer.objects.count()
355
+
356
+ # Create two tabs
357
+ peerA = new_page("Page A")
358
+ peerA.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
359
+ peerB = new_page("Page B")
360
+ peerB.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
361
+
362
+ # Create a new layer from peerA
363
+ peerA.get_by_role("link", name="Manage layers").click()
364
+ peerA.get_by_role("button", name="Add a layer").click()
365
+
366
+ # Check layer has been sync to peerB
367
+ peerB.get_by_role("button", name="Open browser").click()
368
+ expect(peerB.get_by_text("Layer 1")).to_be_visible()
369
+
370
+ # Draw a marker in layer 1 from peerA
371
+ peerA.get_by_role("link", name="Draw a marker (Ctrl+M)").click()
372
+ peerA.locator("#map").click()
373
+
374
+ # Check marker is visible from peerB
375
+ expect(peerB.locator(".leaflet-marker-icon")).to_be_visible()
376
+
377
+ # Save layer to the server
378
+ with peerA.expect_response(re.compile(".*/datalayer/create/.*")):
379
+ peerA.get_by_role("button", name="Save").click()
380
+
381
+ assert DataLayer.objects.count() == 1
382
+
383
+ # Create another layer from peerA and draw a marker on it (without saving to server)
384
+ peerA.get_by_role("link", name="Manage layers").click()
385
+ peerA.get_by_role("button", name="Add a layer").click()
386
+ peerA.get_by_role("link", name="Draw a marker (Ctrl+M)").click()
387
+ peerA.locator("#map").click()
388
+
389
+ # Make sure this new marker is in Layer 2 for peerB
390
+ expect(peerB.get_by_text("Layer 2")).to_be_visible()
391
+ peerB.locator(".panel.left").get_by_role("button", name="Show/hide layer").nth(
392
+ 1
393
+ ).click()
394
+ expect(peerB.locator(".leaflet-marker-icon")).to_be_visible()
395
+
396
+ # Now draw a marker from peerB
397
+ peerB.get_by_role("link", name="Draw a marker (Ctrl+M)").click()
398
+ peerB.locator("#map").click()
399
+ peerB.locator('input[name="name"]').fill("marker from peerB")
400
+
401
+ # Save from peer B
402
+ with peerB.expect_response(re.compile(".*/datalayer/create/.*")):
403
+ peerB.get_by_role("button", name="Save").click()
404
+
405
+ assert DataLayer.objects.count() == 2
406
+
407
+ # Check this new marker is visible from peerA
408
+ peerA.get_by_role("button", name="Open browser").click()
409
+ peerA.locator(".panel.left").get_by_role("button", name="Show/hide layer").nth(
410
+ 1
411
+ ).click()
412
+
413
+ # Now peerA saves the layer 2 to the server
414
+ with peerA.expect_response(re.compile(".*/datalayer/update/.*")):
415
+ peerA.get_by_role("button", name="Save").click()
416
+
417
+ assert DataLayer.objects.count() == 2