umap-project 2.6.3__py3-none-any.whl → 2.7.0__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.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (137) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +64 -1
  3. umap/asgi.py +15 -0
  4. umap/context_processors.py +1 -0
  5. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/cs_CZ/LC_MESSAGES/django.po +96 -92
  7. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/de/LC_MESSAGES/django.po +19 -18
  9. umap/locale/en/LC_MESSAGES/django.po +47 -43
  10. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  11. umap/locale/es/LC_MESSAGES/django.po +134 -128
  12. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/fr/LC_MESSAGES/django.po +51 -47
  14. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/pt/LC_MESSAGES/django.po +64 -60
  16. umap/management/commands/clean_tilelayer.py +152 -0
  17. umap/management/commands/purge_purgatory.py +28 -0
  18. umap/models.py +27 -2
  19. umap/settings/base.py +3 -1
  20. umap/static/umap/base.css +4 -4
  21. umap/static/umap/css/contextmenu.css +6 -1
  22. umap/static/umap/css/icon.css +7 -2
  23. umap/static/umap/css/importers.css +4 -0
  24. umap/static/umap/img/16-white.svg +9 -2
  25. umap/static/umap/img/16.svg +1 -181
  26. umap/static/umap/img/24-white.svg +1 -0
  27. umap/static/umap/img/24.svg +1 -0
  28. umap/static/umap/img/importers/cadastrefr.svg +23 -0
  29. umap/static/umap/img/source/16-white.svg +10 -3
  30. umap/static/umap/img/source/16.svg +753 -197
  31. umap/static/umap/img/source/24-white.svg +3 -2
  32. umap/static/umap/img/source/24.svg +3 -2
  33. umap/static/umap/js/modules/autocomplete.js +7 -3
  34. umap/static/umap/js/modules/browser.js +54 -1
  35. umap/static/umap/js/modules/caption.js +16 -5
  36. umap/static/umap/js/modules/data/features.js +176 -2
  37. umap/static/umap/js/modules/data/layer.js +57 -40
  38. umap/static/umap/js/modules/formatter.js +3 -2
  39. umap/static/umap/js/modules/global.js +2 -0
  40. umap/static/umap/js/modules/importer.js +3 -0
  41. umap/static/umap/js/modules/importers/cadastrefr.js +62 -0
  42. umap/static/umap/js/modules/importers/communesfr.js +15 -3
  43. umap/static/umap/js/modules/permissions.js +123 -93
  44. umap/static/umap/js/modules/rendering/layers/classified.js +2 -0
  45. umap/static/umap/js/modules/rendering/ui.js +60 -213
  46. umap/static/umap/js/modules/share.js +1 -3
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/engine.js +371 -14
  49. umap/static/umap/js/modules/sync/hlc.js +106 -0
  50. umap/static/umap/js/modules/sync/updaters.js +18 -6
  51. umap/static/umap/js/modules/sync/websocket.js +1 -1
  52. umap/static/umap/js/modules/tableeditor.js +1 -1
  53. umap/static/umap/js/modules/ui/base.js +2 -2
  54. umap/static/umap/js/modules/ui/contextmenu.js +51 -18
  55. umap/static/umap/js/modules/urls.js +5 -1
  56. umap/static/umap/js/modules/utils.js +28 -4
  57. umap/static/umap/js/umap.controls.js +73 -52
  58. umap/static/umap/js/umap.core.js +3 -3
  59. umap/static/umap/js/umap.forms.js +3 -1
  60. umap/static/umap/js/umap.js +115 -124
  61. umap/static/umap/locale/br.js +13 -4
  62. umap/static/umap/locale/br.json +13 -4
  63. umap/static/umap/locale/ca.js +28 -15
  64. umap/static/umap/locale/ca.json +28 -15
  65. umap/static/umap/locale/cs_CZ.js +87 -78
  66. umap/static/umap/locale/cs_CZ.json +87 -78
  67. umap/static/umap/locale/de.js +17 -8
  68. umap/static/umap/locale/de.json +17 -8
  69. umap/static/umap/locale/en.js +13 -2
  70. umap/static/umap/locale/en.json +13 -2
  71. umap/static/umap/locale/es.js +330 -319
  72. umap/static/umap/locale/es.json +330 -319
  73. umap/static/umap/locale/eu.js +10 -3
  74. umap/static/umap/locale/eu.json +10 -3
  75. umap/static/umap/locale/fa_IR.js +11 -4
  76. umap/static/umap/locale/fa_IR.json +11 -4
  77. umap/static/umap/locale/fr.js +15 -4
  78. umap/static/umap/locale/fr.json +15 -4
  79. umap/static/umap/locale/hu.js +10 -3
  80. umap/static/umap/locale/hu.json +10 -3
  81. umap/static/umap/locale/pt.js +17 -8
  82. umap/static/umap/locale/pt.json +17 -8
  83. umap/static/umap/locale/pt_PT.js +13 -4
  84. umap/static/umap/locale/pt_PT.json +13 -4
  85. umap/static/umap/locale/zh_TW.js +13 -4
  86. umap/static/umap/locale/zh_TW.json +13 -4
  87. umap/static/umap/map.css +44 -29
  88. umap/static/umap/unittests/hlc.js +165 -0
  89. umap/static/umap/unittests/sync.js +321 -15
  90. umap/static/umap/unittests/utils.js +47 -0
  91. umap/static/umap/vars.css +2 -1
  92. umap/static/umap/vendors/colorbrewer/colorbrewer.js +309 -317
  93. umap/static/umap/vendors/dompurify/purify.es.js +15 -16
  94. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  95. umap/static/umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js +111 -80
  96. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js +2 -2
  97. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js.map +1 -1
  98. umap/static/umap/vendors/simple-statistics/simple-statistics.min.js +1 -1
  99. umap/static/umap/vendors/simple-statistics/simple-statistics.min.js.map +1 -1
  100. umap/templates/umap/css.html +0 -2
  101. umap/templates/umap/dashboard_menu.html +4 -2
  102. umap/templates/umap/js.html +0 -5
  103. umap/templates/umap/map_detail.html +2 -2
  104. umap/tests/fixtures/test_upload_data.csv +2 -2
  105. umap/tests/integration/test_anonymous_owned_map.py +1 -0
  106. umap/tests/integration/test_basics.py +1 -1
  107. umap/tests/integration/test_browser.py +69 -7
  108. umap/tests/integration/test_caption.py +3 -3
  109. umap/tests/integration/test_circles_layer.py +12 -0
  110. umap/tests/integration/test_datalayer.py +2 -1
  111. umap/tests/integration/test_draw_polygon.py +17 -9
  112. umap/tests/integration/test_draw_polyline.py +12 -8
  113. umap/tests/integration/test_edit_datalayer.py +5 -8
  114. umap/tests/integration/test_edit_map.py +2 -2
  115. umap/tests/integration/test_edit_marker.py +1 -1
  116. umap/tests/integration/test_facets_browser.py +3 -3
  117. umap/tests/integration/test_import.py +1 -0
  118. umap/tests/integration/test_map.py +1 -0
  119. umap/tests/integration/test_owned_map.py +1 -1
  120. umap/tests/integration/test_view_marker.py +63 -0
  121. umap/tests/integration/test_view_polygon.py +12 -12
  122. umap/tests/integration/test_websocket_sync.py +65 -3
  123. umap/tests/test_clean_tilelayer.py +83 -0
  124. umap/tests/test_datalayer.py +24 -0
  125. umap/tests/test_map_views.py +20 -0
  126. umap/tests/test_purge_purgatory.py +25 -0
  127. umap/tests/test_websocket_server.py +22 -0
  128. umap/urls.py +5 -1
  129. umap/views.py +6 -3
  130. umap/websocket_server.py +130 -27
  131. {umap_project-2.6.3.dist-info → umap_project-2.7.0.dist-info}/METADATA +18 -14
  132. {umap_project-2.6.3.dist-info → umap_project-2.7.0.dist-info}/RECORD +135 -127
  133. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css +0 -1
  134. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js +0 -7
  135. {umap_project-2.6.3.dist-info → umap_project-2.7.0.dist-info}/WHEEL +0 -0
  136. {umap_project-2.6.3.dist-info → umap_project-2.7.0.dist-info}/entry_points.txt +0 -0
  137. {umap_project-2.6.3.dist-info → umap_project-2.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,10 +5,10 @@ import pkg from 'chai'
5
5
  const { expect } = pkg
6
6
 
7
7
  import { MapUpdater } from '../js/modules/sync/updaters.js'
8
- import { SyncEngine } from '../js/modules/sync/engine.js'
8
+ import { SyncEngine, Operations } from '../js/modules/sync/engine.js'
9
9
 
10
10
  describe('SyncEngine', () => {
11
- it('should initialize methods even before start', function () {
11
+ it('should initialize methods even before start', () => {
12
12
  const engine = new SyncEngine({})
13
13
  engine.upsert()
14
14
  engine.update()
@@ -16,8 +16,8 @@ describe('SyncEngine', () => {
16
16
  })
17
17
  })
18
18
 
19
- describe('#dispatch', function () {
20
- it('should raise an error on unknown updater', function () {
19
+ describe('#dispatch', () => {
20
+ it('should raise an error on unknown updater', () => {
21
21
  const dispatcher = new SyncEngine({})
22
22
  expect(() => {
23
23
  dispatcher.dispatch({
@@ -27,7 +27,7 @@ describe('#dispatch', function () {
27
27
  })
28
28
  }).to.throw(Error)
29
29
  })
30
- it('should produce an error on malformated messages', function () {
30
+ it('should produce an error on malformated messages', () => {
31
31
  const dispatcher = new SyncEngine({})
32
32
  expect(() => {
33
33
  dispatcher.dispatch({
@@ -36,7 +36,7 @@ describe('#dispatch', function () {
36
36
  })
37
37
  }).to.throw(Error)
38
38
  })
39
- it('should raise an unknown operations', function () {
39
+ it('should raise an unknown operations', () => {
40
40
  const dispatcher = new SyncEngine({})
41
41
  expect(() => {
42
42
  dispatcher.dispatch({
@@ -47,55 +47,55 @@ describe('#dispatch', function () {
47
47
  })
48
48
 
49
49
  describe('Updaters', () => {
50
- describe('BaseUpdater', function () {
50
+ describe('BaseUpdater', () => {
51
51
  let updater
52
52
  let map
53
53
  let obj
54
54
 
55
- this.beforeEach(function () {
55
+ beforeEach(() => {
56
56
  map = {}
57
57
  updater = new MapUpdater(map)
58
58
  obj = {}
59
59
  })
60
- it('should be able to set object properties', function () {
60
+ it('should be able to set object properties', () => {
61
61
  let obj = {}
62
62
  updater.updateObjectValue(obj, 'foo', 'foo')
63
63
  expect(obj).deep.equal({ foo: 'foo' })
64
64
  })
65
65
 
66
- it('should be able to set object properties recursively on existing objects', function () {
66
+ it('should be able to set object properties recursively on existing objects', () => {
67
67
  let obj = { foo: {} }
68
68
  updater.updateObjectValue(obj, 'foo.bar', 'foo')
69
69
  expect(obj).deep.equal({ foo: { bar: 'foo' } })
70
70
  })
71
71
 
72
- it('should be able to set object properties recursively on deep objects', function () {
72
+ it('should be able to set object properties recursively on deep objects', () => {
73
73
  let obj = { foo: { bar: { baz: {} } } }
74
74
  updater.updateObjectValue(obj, 'foo.bar.baz.test', 'value')
75
75
  expect(obj).deep.equal({ foo: { bar: { baz: { test: 'value' } } } })
76
76
  })
77
77
 
78
- it('should be able to replace object properties recursively on deep objects', function () {
78
+ it('should be able to replace object properties recursively on deep objects', () => {
79
79
  let obj = { foo: { bar: { baz: { test: 'test' } } } }
80
80
  updater.updateObjectValue(obj, 'foo.bar.baz.test', 'value')
81
81
  expect(obj).deep.equal({ foo: { bar: { baz: { test: 'value' } } } })
82
82
  })
83
83
 
84
- it('should not set object properties recursively on non-existing objects', function () {
84
+ it('should not set object properties recursively on non-existing objects', () => {
85
85
  let obj = { foo: {} }
86
86
  updater.updateObjectValue(obj, 'bar.bar', 'value')
87
87
 
88
88
  expect(obj).deep.equal({ foo: {} })
89
89
  })
90
90
 
91
- it('should delete keys for undefined values', function () {
91
+ it('should delete keys for undefined values', () => {
92
92
  let obj = { foo: 'foo' }
93
93
  updater.updateObjectValue(obj, 'foo', undefined)
94
94
 
95
95
  expect(obj).deep.equal({})
96
96
  })
97
97
 
98
- it('should delete keys for undefined values, recursively', function () {
98
+ it('should delete keys for undefined values, recursively', () => {
99
99
  let obj = { foo: { bar: 'bar' } }
100
100
  updater.updateObjectValue(obj, 'foo.bar', undefined)
101
101
 
@@ -103,3 +103,309 @@ describe('Updaters', () => {
103
103
  })
104
104
  })
105
105
  })
106
+
107
+ describe('Operations', () => {
108
+ describe('haveSameContext', () => {
109
+ const createOperation = (overrides = {}) => ({
110
+ subject: 'feature',
111
+ metadata: {
112
+ id: 'UxNjQ',
113
+ layerId: '606d26bd-230f-4d3e-a2a7-0c3caed71548',
114
+ featureType: 'marker',
115
+ },
116
+ ...overrides,
117
+ })
118
+
119
+ it('should check if subject and metadata are the same', () => {
120
+ const op1 = createOperation()
121
+ const op2 = createOperation()
122
+ const op3 = createOperation({
123
+ subject: 'datalayer',
124
+ metadata: { id: '606d26bd-230f-4d3e-a2a7-0c3caed71548' },
125
+ })
126
+
127
+ expect(Operations.haveSameContext(op1, op2)).to.be.true
128
+ expect(Operations.haveSameContext(op1, op3)).to.be.false
129
+ expect(Operations.haveSameContext(op2, op3)).to.be.false
130
+ })
131
+
132
+ it('should check if the key matches if there is any provided', () => {
133
+ const op1 = createOperation({ key: 'properties.name' })
134
+ const op2 = createOperation({ key: 'properties.name' })
135
+ const op3 = createOperation({ key: 'geometry' })
136
+ const op4 = createOperation()
137
+
138
+ expect(Operations.haveSameContext(op1, op2)).to.be.true
139
+ expect(Operations.haveSameContext(op1, op3)).to.be.false
140
+ expect(Operations.haveSameContext(op1, op4)).to.be.true
141
+ expect(Operations.haveSameContext(op4, createOperation())).to.be.true
142
+ })
143
+
144
+ it('should use deep equality for subject and metadata', () => {
145
+ const op1 = createOperation({ metadata: { nested: { value: 1 } } })
146
+ const op2 = createOperation({ metadata: { nested: { value: 1 } } })
147
+ const op3 = createOperation({ metadata: { nested: { value: 2 } } })
148
+
149
+ expect(Operations.haveSameContext(op1, op2)).to.be.true
150
+ expect(Operations.haveSameContext(op1, op3)).to.be.false
151
+ })
152
+ })
153
+
154
+ describe('sort', () => {
155
+ it('should sort operations by timestamp', () => {
156
+ const operations = [
157
+ { hlc: '1727193550:44:id1' },
158
+ { hlc: '1727193549:42:id1' },
159
+ { hlc: '1727193551:43:id1' },
160
+ ]
161
+ const sorted = Operations.sort(operations)
162
+ expect(sorted).to.deep.equal([
163
+ { hlc: '1727193549:42:id1' },
164
+ { hlc: '1727193550:44:id1' },
165
+ { hlc: '1727193551:43:id1' },
166
+ ])
167
+ })
168
+
169
+ it('should sort operations by NN when timestamp is the same', () => {
170
+ const operations = [
171
+ { hlc: '1727193549:42:id1' },
172
+ { hlc: '1727193549:44:id1' },
173
+ { hlc: '1727193549:43:id1' },
174
+ ]
175
+ const sorted = Operations.sort(operations)
176
+ expect(sorted).to.deep.equal([
177
+ { hlc: '1727193549:42:id1' },
178
+ { hlc: '1727193549:43:id1' },
179
+ { hlc: '1727193549:44:id1' },
180
+ ])
181
+ })
182
+
183
+ it('should sort operations by id if other fields are equal', () => {
184
+ const operations = [
185
+ { hlc: '1727193549:42:id3' },
186
+ { hlc: '1727193549:42:id2' },
187
+ { hlc: '1727193549:42:id1' },
188
+ ]
189
+ const sorted = Operations.sort(operations)
190
+ expect(sorted).to.deep.equal([
191
+ { hlc: '1727193549:42:id1' },
192
+ { hlc: '1727193549:42:id2' },
193
+ { hlc: '1727193549:42:id3' },
194
+ ])
195
+ })
196
+ })
197
+
198
+ describe('addLocal', () => {
199
+ it('should add a local operation with a new hlc', () => {
200
+ const ops = new Operations()
201
+ const inputMessage = { verb: 'update', subject: 'test' }
202
+ const result = ops.addLocal(inputMessage)
203
+ expect(result).to.have.property('hlc')
204
+ expect(result.hlc).to.match(/^\d+:\d+:[^:]+$/)
205
+ expect(result).to.include(inputMessage)
206
+ })
207
+ })
208
+
209
+ describe('sorted', () => {
210
+ it('should return sorted operations', () => {
211
+ const ops = new Operations()
212
+ ops._operations = [{ hlc: '1727193549:43:id1' }, { hlc: '1727193549:42:id1' }]
213
+ const sorted = ops.sorted()
214
+ expect(sorted[0].hlc).to.equal('1727193549:42:id1')
215
+ expect(sorted[1].hlc).to.equal('1727193549:43:id1')
216
+ })
217
+ })
218
+
219
+ describe('shouldBypassOperation', () => {
220
+ let ops
221
+
222
+ beforeEach(() => {
223
+ ops = new Operations()
224
+ })
225
+
226
+ const createOperation = (overrides = {}) => ({
227
+ verb: 'update',
228
+ subject: 'feature',
229
+ metadata: {
230
+ id: 'UxNjQ',
231
+ layerId: '606d26bd-230f-4d3e-a2a7-0c3caed71548',
232
+ featureType: 'marker',
233
+ },
234
+ key: 'properties.name',
235
+ value: 'default',
236
+ hlc: '0000000000000:0:f4df51cc-7617-4bd4-8bd2-599cdf17da65',
237
+ ...overrides,
238
+ })
239
+
240
+ const createUpsertOperation = (overrides = {}) =>
241
+ createOperation({
242
+ verb: 'upsert',
243
+ key: undefined,
244
+ value: {
245
+ type: 'Feature',
246
+ geometry: {
247
+ coordinates: [0.439453, 48.04871],
248
+ type: 'Point',
249
+ },
250
+ properties: {},
251
+ id: 'UxNjQ',
252
+ },
253
+ ...overrides,
254
+ })
255
+
256
+ it('should return false if no local operation is newer', () => {
257
+ const remote = createUpsertOperation({ hlc: '1727184449050:44:id2' })
258
+ ops._operations = [
259
+ createOperation({
260
+ hlc: '1727184449010:0:f4df51cc-7617-4bd4-8bd2-599cdf17da65',
261
+ }),
262
+ createUpsertOperation({
263
+ hlc: '1727184449020:0:b4a221a0-7b62-4588-a6af-041b041006dc',
264
+ }),
265
+ ]
266
+
267
+ const result = ops.shouldBypassOperation(remote)
268
+ expect(result).to.be.false
269
+ })
270
+
271
+ it('should return true if a similar "delete" operation is newer', () => {
272
+ const remote = createOperation({
273
+ verb: 'delete',
274
+ metadata: { id: 'M1NTA', layerId: '1234', featureType: 'marker' },
275
+ hlc: '1:0:3f45b56f-f750-4b50-90d7-9ecce4b0cf53',
276
+ })
277
+
278
+ ops._operations = [
279
+ createOperation({
280
+ verb: 'delete',
281
+ metadata: { id: 'M1NTA', layerId: '1234', featureType: 'marker' },
282
+ hlc: '2:0:3f45b56f-f750-4b50-90d7-9ecce4b0cf53',
283
+ }),
284
+ ]
285
+
286
+ const result = ops.shouldBypassOperation(remote)
287
+ expect(result).to.be.true
288
+ })
289
+
290
+ describe('update', () => {
291
+ it('should check for related updates', () => {
292
+ ops._operations = [
293
+ createOperation({
294
+ value: 'y',
295
+ hlc: '1:0:f4df51cc-7617-4bd4-8bd2-599cdf17da65',
296
+ }),
297
+ createOperation({
298
+ value: 'youpi',
299
+ hlc: '9:0:f4df51cc-7617-4bd4-8bd2-599cdf17da65',
300
+ }),
301
+ ]
302
+
303
+ const remoteOperation = createOperation({
304
+ value: 'something else',
305
+ hlc: '0:0:f4df51cc-7617-4bd4-8bd2-599cdf17da65',
306
+ })
307
+
308
+ const result = ops.shouldBypassOperation(remoteOperation)
309
+ expect(result).to.be.true
310
+ })
311
+
312
+ it('should check for related deletes', () => {
313
+ ops._operations = [
314
+ {
315
+ verb: 'delete',
316
+ subject: 'feature',
317
+ metadata: {
318
+ id: 'M1NTA',
319
+ layerId: '123',
320
+ featureType: 'marker',
321
+ },
322
+ hlc: '1727196583562:0:3f45b56f-f750-4b50-90d7-9ecce4b0cf53',
323
+ key: undefined,
324
+ },
325
+ ]
326
+
327
+ const remoteOperation = createOperation({
328
+ metadata: { id: 'M1NTA', layerId: '123', featureType: 'marker' },
329
+ key: 'geometry',
330
+ value: { coordinates: [2.944336, 47.070122], type: 'Point' },
331
+ hlc: '0:0:3f45b56f-f750-4b50-90d7-9ecce4b0cf53',
332
+ })
333
+
334
+ const result = ops.shouldBypassOperation(remoteOperation)
335
+ expect(result).to.be.true
336
+ })
337
+ })
338
+
339
+ describe('upsert', () => {
340
+ it('should take precedence over updates (even if fresher)', () => {
341
+ ops._operations = [
342
+ createOperation({
343
+ value: 'youpi',
344
+ hlc: '1000000000000:0:f4df51cc-7617-4bd4-8bd2-599cdf17da65',
345
+ }),
346
+ ]
347
+
348
+ const remoteOperation = createUpsertOperation({
349
+ hlc: '0000000000000:0:b4a221a0-7b62-4588-a6af-041b041006dc',
350
+ })
351
+
352
+ const result = ops.shouldBypassOperation(remoteOperation)
353
+ expect(result).to.be.false
354
+ })
355
+ })
356
+
357
+ describe('delete', () => {
358
+ it('should check for the same delete', () => {
359
+ ops._operations = [
360
+ createOperation({
361
+ verb: 'delete',
362
+ metadata: { id: 'I3MDg', layerId: null, featureType: 'polygon' },
363
+ key: undefined,
364
+ hlc: '1:0:3f45b56f-f750-4b50-90d7-9ecce4b0cf53',
365
+ }),
366
+ ]
367
+
368
+ const remoteOperation = createOperation({
369
+ verb: 'delete',
370
+ metadata: { id: 'I3MDg', layerId: null, featureType: 'polygon' },
371
+ key: undefined,
372
+ hlc: '0:0:3f45b56f-f750-4b50-90d7-9ecce4b0cf53',
373
+ })
374
+
375
+ const result = ops.shouldBypassOperation(remoteOperation)
376
+ expect(result).to.be.true
377
+ })
378
+ })
379
+ })
380
+ describe('storeRemoteOperations', () => {
381
+ it('should store remote operations and update the local HLC', () => {
382
+ const ops = new Operations()
383
+ const remoteOps = [{ hlc: '1727193549:42:id2' }, { hlc: '1727193549:43:id2' }]
384
+ ops.storeRemoteOperations(remoteOps)
385
+ expect(ops._operations).to.deep.equal(remoteOps)
386
+ })
387
+ })
388
+
389
+ describe('getOperationsSince', () => {
390
+ it('should return operations since a given HLC', () => {
391
+ const ops = new Operations()
392
+ ops._operations = [
393
+ { hlc: '1727193549:42:id1' },
394
+ { hlc: '1727193549:43:id1' },
395
+ { hlc: '1727193549:44:id1' },
396
+ ]
397
+ const result = ops.getOperationsSince('1727193549:42:id1')
398
+ expect(result).to.deep.equal([
399
+ { hlc: '1727193549:43:id1' },
400
+ { hlc: '1727193549:44:id1' },
401
+ ])
402
+ })
403
+
404
+ it('should return all operations if no HLC is provided', () => {
405
+ const ops = new Operations()
406
+ ops._operations = [{ hlc: '1727193549:42:id1' }, { hlc: '1727193549:43:id1' }]
407
+ const result = ops.getOperationsSince()
408
+ expect(result).to.deep.equal(ops._operations)
409
+ })
410
+ })
411
+ })
@@ -747,6 +747,30 @@ describe('Utils', () => {
747
747
  })
748
748
  })
749
749
 
750
+ describe('#fieldInSchema', () => {
751
+ it('should return true if the field is in the schema', () => {
752
+ assert.equal(Utils.fieldInSchema('foo', { foo: {} }), true)
753
+ })
754
+ it('should return false if the field is not in the schema', () => {
755
+ assert.equal(Utils.fieldInSchema('foo', { bar: {} }), false)
756
+ })
757
+ it('should return false if the schema is not provided', () => {
758
+ assert.equal(Utils.fieldInSchema('foo', {}), false)
759
+ })
760
+ it('should return false if the field is undefined', () => {
761
+ assert.equal(Utils.fieldInSchema(undefined, {}), false)
762
+ })
763
+ // check that options. is removed
764
+ it('should remove options. from the field', () => {
765
+ assert.equal(Utils.fieldInSchema('options.foo', { foo: {} }), true)
766
+ })
767
+
768
+ // check that subfields are removed
769
+ it('should remove subfields from the field', () => {
770
+ assert.equal(Utils.fieldInSchema('options.foo.bar', { foo: { bar: {} } }), true)
771
+ })
772
+ })
773
+
750
774
  describe('#parseNaiveDate', () => {
751
775
  it('should parse a date', () => {
752
776
  assert.equal(
@@ -779,4 +803,27 @@ describe('Utils', () => {
779
803
  )
780
804
  })
781
805
  })
806
+
807
+ describe('#isObject', () => {
808
+ it('should return true for objects', () => {
809
+ assert.equal(Utils.isObject({}), true)
810
+ assert.equal(Utils.isObject({ foo: 'bar' }), true)
811
+ })
812
+
813
+ it('should return false for Array', () => {
814
+ assert.equal(Utils.isObject([]), false)
815
+ })
816
+
817
+ it('should return false on null', () => {
818
+ assert.equal(Utils.isObject(null), false)
819
+ })
820
+
821
+ it('should return false on undefined', () => {
822
+ assert.equal(Utils.isObject(undefined), false)
823
+ })
824
+
825
+ it('should return false on string', () => {
826
+ assert.equal(Utils.isObject(''), false)
827
+ })
828
+ })
782
829
  })
umap/static/umap/vars.css CHANGED
@@ -6,6 +6,7 @@
6
6
  --color-mediumGray: #3e4444;
7
7
  --color-darkGray: #323737;
8
8
  --color-light: white;
9
+ --color-dark: black;
9
10
  --color-limeGreen: #b9f5d2;
10
11
  --color-brightCyan: #46ece6;
11
12
  --color-lightCyan: #d4fbf9;
@@ -14,7 +15,7 @@
14
15
 
15
16
  --background-color: var(--color-light);
16
17
  --color-accent: var(--color-brightCyan);
17
- --text-color: black;
18
+ --text-color: var(--color-dark);
18
19
 
19
20
  /* Buttons. */
20
21
  --button-primary-background: var(--color-waterMint);