diff --git a/package.json b/package.json index 8827b8cf..2b322055 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "chai": "^3.3.0", "eslint": "^8.56.0", "eslint-plugin-compat": "^4.2.0", + "fetch-mock": "^9.11.0", "happen": "~0.1.3", "lebab": "^3.2.1", "mocha": "^10.2.0", diff --git a/umap/decorators.py b/umap/decorators.py index 005e4215..3ae667c5 100644 --- a/umap/decorators.py +++ b/umap/decorators.py @@ -60,17 +60,3 @@ def can_view_map(view_func): return view_func(request, *args, **kwargs) return wrapper - - -def jsonize_view(view_func): - @wraps(view_func) - def wrapper(request, *args, **kwargs): - response = view_func(request, *args, **kwargs) - response_kwargs = {} - if hasattr(response, "rendered_content"): - response_kwargs["html"] = response.rendered_content - if response.has_header("location"): - response_kwargs["redirect"] = response["location"] - return simple_json_response(**response_kwargs) - - return wrapper diff --git a/umap/models.py b/umap/models.py index d63f00fd..f8c31238 100644 --- a/umap/models.py +++ b/umap/models.py @@ -242,6 +242,13 @@ class Map(NamedModel): has_anonymous_cookie = False return has_anonymous_cookie + def can_delete(self, user=None, request=None): + if self.owner and user != self.owner: + return False + if not self.owner and not self.is_anonymous_owner(request): + return False + return True + def can_edit(self, user=None, request=None): """ Define if a user can edit or not the instance, according to his account diff --git a/umap/static/umap/content.css b/umap/static/umap/content.css index ccdb250f..55fb1c98 100644 --- a/umap/static/umap/content.css +++ b/umap/static/umap/content.css @@ -37,6 +37,21 @@ input:-moz-placeholder, :-moz-placeholder { /* **************** */ /* Login icons */ /* **************** */ +body.login { + width: 320px; + margin: auto; + text-align: center; +} +body.login header { + display: flex; + justify-content: center; +} +.login-grid { + display: grid; + justify-content: space-between; + grid-gap: 5px; + grid-template-columns: repeat(auto-fill,92px); +} .login-grid li, .login-grid a { display: inline-block; diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js index 51ba37d0..381b5497 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -1,8 +1,9 @@ import * as L from '../../vendors/leaflet/leaflet-src.esm.js' import URLs from './urls.js' +import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js' // Import modules and export them to the global scope. // For the not yet module-compatible JS out there. // Copy the leaflet module, it's expected by leaflet plugins to be writeable. window.L = { ...L } -window.umap = { URLs } +window.umap = { URLs, Request, ServerRequest, RequestError, HTTPError, NOKError } diff --git a/umap/static/umap/js/modules/request.js b/umap/static/umap/js/modules/request.js new file mode 100644 index 00000000..de1ce4b1 --- /dev/null +++ b/umap/static/umap/js/modules/request.js @@ -0,0 +1,155 @@ +// Uses `L._`` from Leaflet.i18n which we cannot import as a module yet +import { Evented, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js' + +export class RequestError extends Error {} + +export class HTTPError extends RequestError { + constructor(message) { + super(message) + this.name = this.constructor.name + } +} + +export class NOKError extends RequestError { + constructor(response) { + super(response.status) + this.response = response + this.status = response.status + this.name = this.constructor.name + } +} + +class BaseRequest { + async _fetch(method, uri, headers, data) { + let response + + try { + response = await fetch(uri, { + method: method, + mode: 'cors', + headers: headers, + body: data, + }) + } catch (error) { + console.error(error) + throw new HTTPError(error.message) + } + if (!response.ok) { + throw new NOKError(response) + } + + return response + } +} + +// Basic class to issue request +// It returns a response, or null in case of error +// In case of error, an alert is sent, but non 20X status are not handled +// The consumer must check the response status by hand +export class Request extends BaseRequest { + constructor(ui) { + super() + this.ui = ui + } + + async _fetch(method, uri, headers, data) { + const id = Math.random() + this.ui.fire('dataloading', { id: id }) + try { + const response = await BaseRequest.prototype._fetch.call( + this, + method, + uri, + headers, + data + ) + return response + } catch (error) { + if (error instanceof NOKError) return this._onNOK(error) + return this._onError(error) + } finally { + this.ui.fire('dataload', { id: id }) + } + } + + async get(uri, headers) { + return await this._fetch('GET', uri, headers) + } + + async post(uri, headers, data) { + return await this._fetch('POST', uri, headers, data) + } + + _onError(error) { + this.ui.alert({ content: L._('Problem in the response'), level: 'error' }) + } + + _onNOK(error) { + this._onError(error) + return error.response + } +} + +// Adds uMap specifics to requests handling +// like logging, CSRF, etc. +// It expects only json responses. +// Returns an array of three elements: [data, response, error] +// The consumer must check the error to proceed or not with using the data or response +export class ServerRequest extends Request { + async _fetch(method, uri, headers, data) { + // Add a flag so backend can know we are in ajax and adapt the response + // See is_ajax in utils.py + headers = headers || {} + headers['X-Requested-With'] = 'XMLHttpRequest' + return await Request.prototype._fetch.call(this, method, uri, headers, data) + } + + async post(uri, headers, data) { + const token = document.cookie.replace( + /(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/, + '$1' + ) + if (token) { + headers = headers || {} + headers['X-CSRFToken'] = token + } + const response = await Request.prototype.post.call(this, uri, headers, data) + return await this._as_json(response) + } + + async get(uri, headers) { + const response = await Request.prototype.get.call(this, uri, headers) + return await this._as_json(response) + } + + async _as_json(response) { + if (Array.isArray(response)) return response + try { + const data = await response.json() + if (data.info) { + this.ui.alert({ content: data.info, level: 'info' }) + this.ui.closePanel() + } else if (data.error) { + this.ui.alert({ content: data.error, level: 'error' }) + return this._onError(new Error(data.error)) + } + return [data, response, null] + } catch (error) { + return this._onError(error) + } + } + + _onError(error) { + return [{}, null, error] + } + + _onNOK(error) { + if (error.status === 403) { + this.ui.alert({ + content: message || L._('Action not allowed :('), + level: 'error', + }) + } + return [{}, error.response, error] + } +} diff --git a/umap/static/umap/js/umap.autocomplete.js b/umap/static/umap/js/umap.autocomplete.js index 1f9b65cb..cbc43153 100644 --- a/umap/static/umap/js/umap.autocomplete.js +++ b/umap/static/umap/js/umap.autocomplete.js @@ -13,7 +13,7 @@ L.U.AutoComplete = L.Class.extend({ initialize: function (el, options) { this.el = el const ui = new L.U.UI(document.querySelector('header')) - this.xhr = new L.U.Xhr(ui) + this.server = new window.umap.ServerRequest(ui) L.setOptions(this, options) let CURRENT = null try { @@ -158,21 +158,19 @@ L.U.AutoComplete = L.Class.extend({ } }, - search: function () { - const val = this.input.value + search: async function () { + let val = this.input.value if (val.length < this.options.minChar) { this.clear() return } if (`${val}` === `${this.CACHE}`) return else this.CACHE = val - this._do_search( - val, - function (data) { - this.handleResults(data.data) - }, - this + val = val.toLowerCase() + const [{ data }, response] = await this.server.get( + `/agnocomplete/AutocompleteUser/?q=${encodeURIComponent(val)}` ) + this.handleResults(data) }, createResult: function (item) { @@ -272,14 +270,6 @@ L.U.AutoComplete.Ajax = L.U.AutoComplete.extend({ label: option.innerHTML, } }, - - _do_search: function (val, callback, context) { - val = val.toLowerCase() - this.xhr.get(`/agnocomplete/AutocompleteUser/?q=${encodeURIComponent(val)}`, { - callback: callback, - context: context || this, - }) - }, }) L.U.AutoComplete.Ajax.SelectMultiple = L.U.AutoComplete.Ajax.extend({ diff --git a/umap/static/umap/js/umap.datalayer.permissions.js b/umap/static/umap/js/umap.datalayer.permissions.js index f77e086f..8ac2a2d7 100644 --- a/umap/static/umap/js/umap.datalayer.permissions.js +++ b/umap/static/umap/js/umap.datalayer.permissions.js @@ -51,19 +51,20 @@ L.U.DataLayerPermissions = L.Class.extend({ pk: this.datalayer.umap_id, }) }, - save: function () { + save: async function () { if (!this.isDirty) return this.datalayer.map.continueSaving() const formData = new FormData() formData.append('edit_status', this.options.edit_status) - this.datalayer.map.post(this.getUrl(), { - data: formData, - context: this, - callback: function (data) { - this.commit() - this.isDirty = false - this.datalayer.map.continueSaving() - }, - }) + const [data, response, error] = await this.datalayer.map.server.post( + this.getUrl(), + {}, + formData + ) + if (!error) { + this.commit() + this.isDirty = false + this.datalayer.map.continueSaving() + } }, commit: function () { diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index fabf2424..f2776ae8 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -685,7 +685,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ return !this.value() || this.value() === this.obj.getMap().options.default_iconUrl }, - showSymbolsTab: function () { + showSymbolsTab: async function () { this.openTab('symbols') this.searchInput = L.DomUtil.create('input', '', this.body) this.searchInput.type = 'search' @@ -695,13 +695,13 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ if (this.pictogram_list) { this.buildSymbolsList() } else { - this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, { - callback: (data) => { - this.pictogram_list = data.pictogram_list - this.buildSymbolsList() - }, - context: this, - }) + const [{ pictogram_list }, response, error] = await this.builder.map.server.get( + this.builder.map.options.urls.pictogram_list_json + ) + if (!error) { + this.pictogram_list = pictogram_list + this.buildSymbolsList() + } } }, diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index bf0f5f91..929c6009 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -34,7 +34,7 @@ L.Map.mergeOptions({ // we cannot rely on this because of the y is overriden by Leaflet // See https://github.com/Leaflet/Leaflet/pull/9201 // And let's remove this -y when this PR is merged and released. - demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, '-y': 181, r: '' }, + demoTileInfos: { 's': 'a', 'z': 9, 'x': 265, 'y': 181, '-y': 181, 'r': '' }, licences: [], licence: '', enableMarkerDraw: true, @@ -98,9 +98,10 @@ L.U.Map.include({ this.urls = new window.umap.URLs(this.options.urls) this.ui = new L.U.UI(this._container) - this.xhr = new L.U.Xhr(this.ui) - this.xhr.on('dataloading', (e) => this.fire('dataloading', e)) - this.xhr.on('dataload', (e) => this.fire('dataload', e)) + this.ui.on('dataloading', (e) => this.fire('dataloading', e)) + this.ui.on('dataload', (e) => this.fire('dataload', e)) + this.server = new window.umap.ServerRequest(this.ui) + this.request = new window.umap.Request(this.ui) this.initLoader() this.name = this.options.name @@ -183,7 +184,7 @@ L.U.Map.include({ // Needs locate control and hash to exist this.initCenter() this.handleLimitBounds() - this.initDatalayers() + this.initDataLayers() if (this.options.displayCaptionOnLoad) { // Retrocompat @@ -236,8 +237,6 @@ L.U.Map.include({ this._default_extent = true this.options.name = L._('Untitled map') this.options.editMode = 'advanced' - const datalayer = this.createDataLayer() - datalayer.connectToMap() this.enableEdit() let dataUrl = L.Util.queryString('dataUrl', null) const dataFormat = L.Util.queryString('dataFormat', 'geojson') @@ -273,8 +272,6 @@ L.U.Map.include({ this.options.onLoadPanel === 'datafilters' ) this.openFacet() - }) - this.onceDataLoaded(function () { const slug = L.Util.queryString('feature') if (slug && this.features_index[slug]) this.features_index[slug].view() if (L.Util.queryString('edit')) { @@ -419,56 +416,22 @@ L.U.Map.include({ if (this.options.scaleControl) this._controls.scale.addTo(this) }, - initDatalayers: function () { - for (let j = 0; j < this.options.datalayers.length; j++) { - this.createDataLayer(this.options.datalayers[j]) + initDataLayers: async function (datalayers) { + datalayers = datalayers || this.options.datalayers + for (const options of datalayers) { + this.createDataLayer(options) } - this.loadDatalayers() + await this.loadDataLayers() }, - loadDatalayers: function (force) { - const total = this.datalayers_index.length - // toload => datalayer metadata remaining to load (synchronous) - // dataToload => datalayer data remaining to load (asynchronous) - let toload = total, - dataToload = total - let datalayer - const loaded = () => { - this.datalayersLoaded = true - this.fire('datalayersloaded') - } - const decrementToLoad = () => { - toload-- - if (toload === 0) loaded() - } - const dataLoaded = () => { - this.dataLoaded = true - this.fire('dataloaded') - } - const decrementDataToLoad = () => { - dataToload-- - if (dataToload === 0) dataLoaded() - } - this.eachDataLayer(function (datalayer) { - if (force && !datalayer.hasDataLoaded()) { - datalayer.show() - } - if (datalayer.showAtLoad() || force) { - datalayer.onceLoaded(decrementToLoad) - } else { - decrementToLoad() - } - if (datalayer.showAtLoad() || force) { - datalayer.onceDataLoaded(decrementDataToLoad) - } else { - decrementDataToLoad({ sourceTarget: datalayer }) - } - }) - if (total === 0) { - // no datalayer - loaded() - dataLoaded() + loadDataLayers: async function () { + this.datalayersLoaded = true + this.fire('datalayersloaded') + for (const datalayer of Object.values(this.datalayers)) { + if (datalayer.showAtLoad()) await datalayer.show() } + this.dataloaded = true + this.fire('dataloaded') }, indexDatalayers: function () { @@ -501,7 +464,7 @@ L.U.Map.include({ onceDataLoaded: function (callback, context) { // Once datalayers **data** have been loaded - if (this.dataLoaded) { + if (this.dataloaded) { callback.call(context || this, this) } else { this.once('dataloaded', callback, context) @@ -835,7 +798,10 @@ L.U.Map.include({ self.isDirty = true } if (this._controls.tilelayersChooser) - this._controls.tilelayersChooser.openSwitcher({ callback: callback, className: 'dark' }) + this._controls.tilelayersChooser.openSwitcher({ + callback: callback, + className: 'dark', + }) }, manageDatalayers: function () { @@ -1083,7 +1049,7 @@ L.U.Map.include({ return properties }, - saveSelf: function () { + saveSelf: async function () { const geojson = { type: 'Feature', geometry: this.geometry(), @@ -1093,64 +1059,62 @@ L.U.Map.include({ formData.append('name', this.options.name) formData.append('center', JSON.stringify(this.geometry())) formData.append('settings', JSON.stringify(geojson)) - this.post(this.urls.get('map_save', { map_id: this.options.umap_id }), { - data: formData, - context: this, - callback: function (data) { - let duration = 3000, - alert = { content: L._('Map has been saved!'), level: 'info' } - if (!this.options.umap_id) { - alert.content = L._('Congratulations, your map has been created!') - this.options.umap_id = data.id - this.permissions.setOptions(data.permissions) - this.permissions.commit() - if ( - data.permissions && - data.permissions.anonymous_edit_url && - this.options.urls.map_send_edit_link - ) { - alert.duration = Infinity - alert.content = - L._( - 'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:' - ) + `
${data.permissions.anonymous_edit_url}` + const uri = this.urls.get('map_save', { map_id: this.options.umap_id }) + const [data, response, error] = await this.server.post(uri, {}, formData) + if (!error) { + let duration = 3000, + alert = { content: L._('Map has been saved!'), level: 'info' } + if (!this.options.umap_id) { + alert.content = L._('Congratulations, your map has been created!') + this.options.umap_id = data.id + this.permissions.setOptions(data.permissions) + this.permissions.commit() + if ( + data.permissions && + data.permissions.anonymous_edit_url && + this.options.urls.map_send_edit_link + ) { + alert.duration = Infinity + alert.content = + L._( + 'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:' + ) + `
${data.permissions.anonymous_edit_url}` - alert.actions = [ - { - label: L._('Send me the link'), - input: L._('Email'), - callback: this.sendEditLink, - callbackContext: this, + alert.actions = [ + { + label: L._('Send me the link'), + input: L._('Email'), + callback: this.sendEditLink, + callbackContext: this, + }, + { + label: L._('Copy link'), + callback: () => { + L.Util.copyToClipboard(data.permissions.anonymous_edit_url) + this.ui.alert({ + content: L._('Secret edit link copied to clipboard!'), + level: 'info', + }) }, - { - label: L._('Copy link'), - callback: () => { - L.Util.copyToClipboard(data.permissions.anonymous_edit_url) - this.ui.alert({ - content: L._('Secret edit link copied to clipboard!'), - level: 'info', - }) - }, - callbackContext: this, - }, - ] - } - } else if (!this.permissions.isDirty) { - // Do not override local changes to permissions, - // but update in case some other editors changed them in the meantime. - this.permissions.setOptions(data.permissions) - this.permissions.commit() + callbackContext: this, + }, + ] } - // Update URL in case the name has changed. - if (history && history.pushState) - history.pushState({}, this.options.name, data.url) - else window.location = data.url - alert.content = data.info || alert.content - this.once('saved', () => this.ui.alert(alert)) - this.ui.closePanel() - this.permissions.save() - }, - }) + } else if (!this.permissions.isDirty) { + // Do not override local changes to permissions, + // but update in case some other editors changed them in the meantime. + this.permissions.setOptions(data.permissions) + this.permissions.commit() + } + // Update URL in case the name has changed. + if (history && history.pushState) + history.pushState({}, this.options.name, data.url) + else window.location = data.url + alert.content = data.info || alert.content + this.once('saved', () => this.ui.alert(alert)) + this.ui.closePanel() + this.permissions.save() + } }, save: function () { @@ -1793,19 +1757,21 @@ L.U.Map.include({ return this.editTools.startPolygon() }, - del: function () { + del: async function () { if (confirm(L._('Are you sure you want to delete this map?'))) { const url = this.urls.get('map_delete', { map_id: this.options.umap_id }) - this.post(url) + const [data, response, error] = await this.server.post(url) + if (data.redirect) window.location = data.redirect } }, - clone: function () { + clone: async function () { if ( confirm(L._('Are you sure you want to clone this map and all its datalayers?')) ) { const url = this.urls.get('map_clone', { map_id: this.options.umap_id }) - this.post(url) + const [data, response, error] = await this.server.post(url) + if (data.redirect) window.location = data.redirect } }, @@ -1820,23 +1786,6 @@ L.U.Map.include({ this.loader.onAdd(this) }, - post: function (url, options) { - options = options || {} - options.listener = this - this.xhr.post(url, options) - }, - - get: function (url, options) { - options = options || {} - options.listener = this - this.xhr.get(url, options) - }, - - ajax: function (options) { - options.listener = this - this.xhr._ajax(options) - }, - initContextMenu: function () { this.contextmenu = new L.U.ContextMenu(this) this.contextmenu.enable() diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 7fafaea1..0aa138b3 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -581,8 +581,10 @@ L.U.DataLayer = L.Evented.extend({ this.backupOptions() this.connectToMap() this.permissions = new L.U.DataLayerPermissions(this) - if (this.showAtLoad()) this.show() - if (!this.umap_id) this.isDirty = true + if (!this.umap_id) { + if (this.showAtLoad()) this.show() + this.isDirty = true + } this.onceLoaded(function () { this.map.on('moveend', this.onMoveEnd, this) @@ -670,30 +672,28 @@ L.U.DataLayer = L.Evented.extend({ return this }, - fetchData: function () { + fetchData: async function () { if (!this.umap_id) return if (this._loading) return this._loading = true - this.map.get(this._dataUrl(), { - callback: function (geojson, response) { - this._last_modified = response.getResponseHeader('Last-Modified') - // FIXME: for now this property is set dynamically from backend - // And thus it's not in the geojson file in the server - // So do not let all options to be reset - // Fix is a proper migration so all datalayers settings are - // in DB, and we remove it from geojson flat files. - if (geojson._umap_options) { - geojson._umap_options.editMode = this.options.editMode - } - // In case of maps pre 1.0 still around - if (geojson._storage) geojson._storage.editMode = this.options.editMode - this.fromUmapGeoJSON(geojson) - this.backupOptions() - this.fire('loaded') - this._loading = false - }, - context: this, - }) + const [geojson, response, error] = await this.map.server.get(this._dataUrl()) + if (!error) { + this._last_modified = response.headers.get('last-modified') + // FIXME: for now this property is set dynamically from backend + // And thus it's not in the geojson file in the server + // So do not let all options to be reset + // Fix is a proper migration so all datalayers settings are + // in DB, and we remove it from geojson flat files. + if (geojson._umap_options) { + geojson._umap_options.editMode = this.options.editMode + } + // In case of maps pre 1.0 still around + if (geojson._storage) geojson._storage.editMode = this.options.editMode + this.fromUmapGeoJSON(geojson) + this.backupOptions() + this.fire('loaded') + this._loading = false + } }, fromGeoJSON: function (geojson) { @@ -744,23 +744,23 @@ L.U.DataLayer = L.Evented.extend({ return !((!isNaN(from) && zoom < from) || (!isNaN(to) && zoom > to)) }, - fetchRemoteData: function (force) { + fetchRemoteData: async function (force) { if (!this.isRemoteLayer()) return if (!this.options.remoteData.dynamic && this.hasDataLoaded() && !force) return if (!this.isVisible()) return let url = this.map.localizeUrl(this.options.remoteData.url) - if (this.options.remoteData.proxy) + if (this.options.remoteData.proxy) { url = this.map.proxyUrl(url, this.options.remoteData.ttl) - this.map.ajax({ - uri: url, - verb: 'GET', - callback: (raw) => { - this.clear() - this.rawToGeoJSON(raw, this.options.remoteData.format, (geojson) => - this.fromGeoJSON(geojson) - ) - }, - }) + } + const response = await this.map.request.get(url) + if (response && response.ok) { + this.clear() + this.rawToGeoJSON( + await response.text(), + this.options.remoteData.format, + (geojson) => this.fromGeoJSON(geojson) + ) + } }, onceLoaded: function (callback, context) { @@ -926,7 +926,7 @@ L.U.DataLayer = L.Evented.extend({ }) } this.map.ui.alert({ content: message, level: 'error', duration: 10000 }) - console.log(err) + console.error(err) } if (result && result.features.length) { callback(result) @@ -1062,13 +1062,12 @@ L.U.DataLayer = L.Evented.extend({ reader.onload = (e) => this.importRaw(e.target.result, type) }, - importFromUrl: function (url, type) { - url = this.map.localizeUrl(url) - this.map.xhr._ajax({ - verb: 'GET', - uri: url, - callback: (data) => this.importRaw(data, type), - }) + importFromUrl: async function (uri, type) { + uri = this.map.localizeUrl(uri) + const response = await this.map.request.get(uri) + if (response && response.ok) { + this.importRaw(await response.text(), type) + } }, getColor: function () { @@ -1385,8 +1384,8 @@ L.U.DataLayer = L.Evented.extend({ } }, - buildVersionsFieldset: function (container) { - const appendVersion = function (data) { + buildVersionsFieldset: async function (container) { + const appendVersion = (data) => { const date = new Date(parseInt(data.at, 10)) const content = `${date.toLocaleString(L.lang)} (${parseInt(data.size) / 1000}Kb)` const el = L.DomUtil.create('div', 'umap-datalayer-version', versionsContainer) @@ -1402,34 +1401,30 @@ L.U.DataLayer = L.Evented.extend({ } const versionsContainer = L.DomUtil.createFieldset(container, L._('Versions'), { - callback: function () { - this.map.xhr.get(this.getVersionsUrl(), { - callback: function (data) { - for (let i = 0; i < data.versions.length; i++) { - appendVersion.call(this, data.versions[i]) - } - }, - context: this, - }) + callback: async function () { + const [{ versions }, response, error] = await this.map.server.get( + this.getVersionsUrl() + ) + if (!error) versions.forEach(appendVersion) }, context: this, }) }, - restore: function (version) { + restore: async function (version) { if (!this.map.editEnabled) return if (!confirm(L._('Are you sure you want to restore this version?'))) return - this.map.xhr.get(this.getVersionUrl(version), { - callback: function (geojson) { - if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat. - if (geojson._umap_options) this.setOptions(geojson._umap_options) - this.empty() - if (this.isRemoteLayer()) this.fetchRemoteData() - else this.addData(geojson) - this.isDirty = true - }, - context: this, - }) + const [geojson, response, error] = await this.map.server.get( + this.getVersionUrl(version) + ) + if (!error) { + if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat. + if (geojson._umap_options) this.setOptions(geojson._umap_options) + this.empty() + if (this.isRemoteLayer()) this.fetchRemoteData() + else this.addData(geojson) + this.isDirty = true + } }, featuresToGeoJSON: function () { @@ -1438,9 +1433,9 @@ L.U.DataLayer = L.Evented.extend({ return features }, - show: function () { - if (!this.isLoaded()) this.fetchData() + show: async function () { this.map.addLayer(this.layer) + if (!this.isLoaded()) await this.fetchData() this.fire('show') }, @@ -1548,7 +1543,7 @@ L.U.DataLayer = L.Evented.extend({ return this.isReadOnly() || this.isRemoteLayer() }, - save: function () { + save: async function () { if (this.isDeleted) return this.saveDelete() if (!this.isLoaded()) { return @@ -1566,44 +1561,66 @@ L.U.DataLayer = L.Evented.extend({ map_id: this.map.options.umap_id, pk: this.umap_id, }) - this.map.post(saveUrl, { - data: formData, - callback: function (data, response) { - // Response contains geojson only if save has conflicted and conflicts have - // been resolved. So we need to reload to get extra data (saved from someone else) - if (data.geojson) { - this.clear() - this.fromGeoJSON(data.geojson) - delete data.geojson - } - this._geojson = geojson - this._last_modified = response.getResponseHeader('Last-Modified') - this.setUmapId(data.id) - this.updateOptions(data) - this.backupOptions() - this.connectToMap() - this._loaded = true - this.redraw() // Needed for reordering features - this.isDirty = false - this.permissions.save() - }, - context: this, - headers: this._last_modified - ? { 'If-Unmodified-Since': this._last_modified } - : {}, - }) + const headers = this._last_modified + ? { 'If-Unmodified-Since': this._last_modified } + : {} + await this._trySave(saveUrl, headers, formData) + this._geojson = geojson }, - saveDelete: function () { - const callback = function () { + _trySave: async function (url, headers, formData) { + const [data, response, error] = await this.map.server.post(url, headers, formData) + if (error) { + if (response && response.status === 412) { + const msg = L._( + 'Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.' + ) + const actions = [ + { + label: L._('Save anyway'), + callback: async () => { + // Save again, + // but do not pass If-Unmodified-Since this time + await this._trySave(url, {}, formData) + }, + }, + { + label: L._('Cancel'), + }, + ] + this.map.ui.alert({ + content: msg, + level: 'error', + duration: 100000, + actions: actions, + }) + } + } else { + // Response contains geojson only if save has conflicted and conflicts have + // been resolved. So we need to reload to get extra data (added by someone else) + if (data.geojson) { + this.clear() + this.fromGeoJSON(data.geojson) + delete data.geojson + } + this._last_modified = response.headers.get('last-modified') + this.setUmapId(data.id) + this.updateOptions(data) + this.backupOptions() + this.connectToMap() + this._loaded = true + this.redraw() // Needed for reordering features this.isDirty = false - this.map.continueSaving() + this.permissions.save() } - if (!this.umap_id) return callback.call(this) - this.map.xhr.post(this.getDeleteUrl(), { - callback: callback, - context: this, - }) + }, + + saveDelete: async function () { + if (this.umap_id) { + await this.map.server.post(this.getDeleteUrl()) + } + this.isDirty = false + this.map.continueSaving() }, getMap: function () { diff --git a/umap/static/umap/js/umap.permissions.js b/umap/static/umap/js/umap.permissions.js index 8945e18e..de5544fe 100644 --- a/umap/static/umap/js/umap.permissions.js +++ b/umap/static/umap/js/umap.permissions.js @@ -131,21 +131,19 @@ L.U.MapPermissions = L.Class.extend({ this.map.ui.openPanel({ data: { html: container }, className: 'dark' }) }, - attach: function () { - this.map.post(this.getAttachUrl(), { - callback: function () { - this.options.owner = this.map.options.user - this.map.ui.alert({ - content: L._('Map has been attached to your account'), - level: 'info', - }) - this.map.ui.closePanel() - }, - context: this, - }) + attach: async function () { + const [data, response, error] = await this.map.server.post(this.getAttachUrl()) + if (!error) { + this.options.owner = this.map.options.user + this.map.ui.alert({ + content: L._('Map has been attached to your account'), + level: 'info', + }) + this.map.ui.closePanel() + } }, - save: function () { + save: async function () { if (!this.isDirty) return this.map.continueSaving() const formData = new FormData() if (!this.isAnonymousMap() && this.options.editors) { @@ -159,16 +157,17 @@ L.U.MapPermissions = L.Class.extend({ formData.append('owner', this.options.owner && this.options.owner.id) formData.append('share_status', this.options.share_status) } - this.map.post(this.getUrl(), { - data: formData, - context: this, - callback: function (data) { - this.commit() - this.isDirty = false - this.map.continueSaving() - this.map.fire('postsync') - }, - }) + const [data, response, error] = await this.map.server.post( + this.getUrl(), + {}, + formData + ) + if (!error) { + this.commit() + this.isDirty = false + this.map.continueSaving() + this.map.fire('postsync') + } }, getUrl: function () { diff --git a/umap/static/umap/js/umap.xhr.js b/umap/static/umap/js/umap.xhr.js deleted file mode 100644 index 453579bb..00000000 --- a/umap/static/umap/js/umap.xhr.js +++ /dev/null @@ -1,304 +0,0 @@ -L.U.Xhr = L.Evented.extend({ - initialize: function (ui) { - this.ui = ui - }, - - _wrapper: function () { - let wrapper - if (window.XMLHttpRequest === undefined) { - wrapper = () => { - try { - return new window.ActiveXObject('Microsoft.XMLHTTP.6.0') - } catch (e1) { - try { - return new window.ActiveXObject('Microsoft.XMLHTTP.3.0') - } catch (e2) { - throw new Error('XMLHttpRequest is not supported') - } - } - } - } else { - wrapper = window.XMLHttpRequest - } - return new wrapper() - }, - - _ajax: function (settings) { - const xhr = this._wrapper(), - id = Math.random(), - self = this - this.fire('dataloading', { id: id }) - const loaded = () => { - self.fire('dataload', { id: id }) - } - - try { - xhr.open(settings.verb, settings.uri, true) - } catch (err) { - // Unknown protocol? - this.ui.alert({ - content: L._('Error while fetching {url}', { url: settings.uri }), - level: 'error', - }) - loaded() - return - } - - if ( - settings.uri.indexOf('http') !== 0 || - settings.uri.indexOf(window.location.origin) === 0 - ) { - // "X-" mode headers cause the request to be in preflight mode, - // we don"t want that by default for CORS requests - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') - } - if (settings.headers) { - for (const name in settings.headers) { - xhr.setRequestHeader(name, settings.headers[name]) - } - } - - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status == 200) { - settings.callback.call(settings.context || xhr, xhr.responseText, xhr) - } else if (xhr.status === 403) { - self.ui.alert({ - content: xhr.responseText || L._('Action not allowed :('), - level: 'error', - }) - } else if (xhr.status === 412) { - const msg = L._( - 'Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.' - ) - const actions = [ - { - label: L._('Save anyway'), - callback: function () { - delete settings.headers['If-Match'] - self._ajax(settings) - }, - callbackContext: self, - }, - { - label: L._('Cancel'), - }, - ] - self.ui.alert({ - content: msg, - level: 'error', - duration: 100000, - actions: actions, - }) - } else { - if (xhr.status === 0) { - // 0 === request cut by user or CORS - self.ui.alert({ - content: L._( - 'Issue reaching that URL (network problem or CORS protection): {url}', - { url: settings.uri } - ), - level: 'error', - }) - } else { - self.ui.alert({ content: L._('Problem in the response'), level: 'error' }) - } - } - loaded() - } - } - - try { - xhr.send(settings.data) - } catch (e) { - // Pass - loaded() - console.error('Bad Request', e) - } - }, - - // supports only JSON as response data type - _json: function (verb, uri, options) { - const args = arguments, - self = this - const default_options = { - async: true, - callback: null, - responseType: 'text', - data: null, - listen_form: null, // optional form to listen in default callback - } - const settings = L.Util.extend({}, default_options, options) - - if (verb === 'POST') { - // find a way not to make this django specific - const token = document.cookie.replace( - /(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/, - '$1' - ) - if (token) { - settings.headers = settings.headers || {} - settings.headers['X-CSRFToken'] = token - } - } - - const callback = function (responseText, response) { - let data - try { - data = JSON.parse(responseText) - } catch (err) { - console.log(err) - self.ui.alert({ - content: L._('Problem in the response format'), - level: 'error', - }) - return - } - if (data.errors) { - console.log(data.errors) - self.ui.alert({ content: L._('An error occured'), level: 'error' }) - } else if (data.login_required) { - // login_required should be an URL for the login form - if (settings.login_callback) settings.login_callback(data) - else self.login(data, args) - } else { - if (settings.callback) - L.bind(settings.callback, settings.context || this)(data, response) - else self.default_callback(data, settings, response) - } - } - - this._ajax({ - verb: verb, - uri: uri, - data: settings.data, - callback: callback, - headers: settings.headers, - listener: settings.listener, - }) - }, - - get: function (uri, options) { - this._json('GET', uri, options) - }, - - post: function (uri, options) { - this._json('POST', uri, options) - }, - - submit_form: function (form_id, options) { - if (typeof options === 'undefined') options = {} - const form = L.DomUtil.get(form_id) - const formData = new FormData(form) - if (options.extraFormData) formData.append(options.extraFormData) - options.data = formData - this.post(form.action, options) - return false - }, - - listen_form: function (form_id, options) { - const form = L.DomUtil.get(form_id), - self = this - if (!form) return - L.DomEvent.on(form, 'submit', L.DomEvent.stopPropagation) - .on(form, 'submit', L.DomEvent.preventDefault) - .on(form, 'submit', () => { - self.submit_form(form_id, options) - }) - }, - - listen_link: function (link_id, options) { - const link = L.DomUtil.get(link_id), - self = this - if (link) { - L.DomEvent.on(link, 'click', L.DomEvent.stop).on(link, 'click', () => { - if (options.confirm && !confirm(options.confirm)) { - return - } - self.get(link.href, options) - }) - } - }, - - default_callback: function (data, options) { - // default callback, to avoid boilerplate - if (data.redirect) { - const newPath = data.redirect - if (window.location.pathname == newPath) - window.location.reload() // Keep the hash, so the current view - else window.location = newPath - } else if (data.info) { - this.ui.alert({ content: data.info, level: 'info' }) - this.ui.closePanel() - } else if (data.error) { - this.ui.alert({ content: data.error, level: 'error' }) - } else if (data.html) { - const ui_options = { data: data } - let listen_options - if (options.className) ui_options.className = options.className - this.ui.openPanel(ui_options) - // To low boilerplate, if there is a form, listen it - if (options.listen_form) { - // Listen form again - listen_options = L.Util.extend({}, options, options.listen_form.options) - this.listen_form(options.listen_form.id, listen_options) - } - if (options.listen_link) { - for (let i = 0, l = options.listen_link.length; i < l; i++) { - // Listen link again - listen_options = L.Util.extend({}, options, options.listen_link[i].options) - this.listen_link(options.listen_link[i].id, listen_options) - } - } - } else if (options.success) { - // Success is called only if data contain no msg and no html - options.success(data) - } - }, - - login: function (data, args) { - // data.html: login form - // args: args of the first _json call, to call again at process end - const self = this - const proceed = () => { - self.ui.closePanel() - if (typeof args !== 'undefined') self._json.apply(self, args) - else self.default_callback(data, {}) - } - const ask_for_login = (data) => { - self.ui.openPanel({ data: data, className: 'login-panel' }) - self.listen_form('login_form', { - callback: function (data) { - if (data.html) ask_for_login(data) // Problem in the login - ask again - else proceed() - }, - }) - // Auth links - const links = document.getElementsByClassName('umap-login-popup') - Object.keys(links).forEach((el) => { - const link = links[el] - L.DomEvent.on(link, 'click', L.DomEvent.stop).on(link, 'click', () => { - self.ui.closePanel() - const win = window.open(link.href) - window.umap_proceed = () => { - proceed() - win.close() - } - }) - }) - } - if (data.login_required) { - this.get(data.login_required, { - callback: function (data) { - ask_for_login(data) - }, - }) - } else { - ask_for_login(data) - } - }, - - logout: function (url) { - this.get(url) - }, -}) diff --git a/umap/static/umap/test/Choropleth.js b/umap/static/umap/test/Choropleth.js index 1cd8c2ec..1178e85b 100644 --- a/umap/static/umap/test/Choropleth.js +++ b/umap/static/umap/test/Choropleth.js @@ -176,24 +176,26 @@ const POLYGONS = { ], } -describe('L.U.Choropleth', function () { +describe('L.U.Choropleth', () => { let path = '/map/99/datalayer/edit/62/', poly1, poly4, - poly9 + poly9, + map, + datalayer - before(function () { - this.server = sinon.fakeServer.create() - this.server.respondWith(/\/datalayer\/62\/\?.*/, JSON.stringify(POLYGONS)) - this.map = initMap({ umap_id: 99 }) - this.datalayer = this.map.getDataLayerByUmapId(62) - this.server.respond() - this.datalayer.options.type = 'Choropleth' - this.datalayer.options.choropleth = { + before(async () => { + fetchMock.mock(/\/datalayer\/62\/\?.*/, JSON.stringify(POLYGONS)) + map = initMap({ umap_id: 99 }) + const datalayer_options = defaultDatalayerData() + await map.initDataLayers([datalayer_options]) + datalayer = map.getDataLayerByUmapId(62) + datalayer.options.type = 'Choropleth' + datalayer.options.choropleth = { property: 'value', } enableEdit() - this.datalayer.eachLayer(function (layer) { + datalayer.eachLayer(function (layer) { if (layer.properties.name === 'number 1') { poly1 = layer } else if (layer.properties.name === 'number 4') { @@ -203,38 +205,38 @@ describe('L.U.Choropleth', function () { } }) }) - after(function () { - this.server.restore() + after(() => { + fetchMock.restore() resetMap() }) - describe('#init()', function () { - it('datalayer should have 9 features', function () { - assert.equal(this.datalayer._index.length, 9) + describe('#init()', () => { + it('datalayer should have 9 features', () => { + assert.equal(datalayer._index.length, 9) }) }) - describe('#compute()', function () { - it('choropleth should compute default colors', function () { - this.datalayer.resetLayer(true) + describe('#compute()', () => { + it('choropleth should compute default colors', () => { + datalayer.resetLayer(true) assert.deepEqual( - this.datalayer.layer.options.breaks, + datalayer.layer.options.breaks, [45, 673, 3829, 4900, 9898, 9898] ) assert.equal(poly1._path.attributes.fill.value, '#eff3ff') assert.equal(poly4._path.attributes.fill.value, '#bdd7e7') assert.equal(poly9._path.attributes.fill.value, '#3182bd') }) - it('can change brewer scheme', function () { - this.datalayer.options.choropleth.brewer = 'Reds' - this.datalayer.resetLayer(true) + it('can change brewer scheme', () => { + datalayer.options.choropleth.brewer = 'Reds' + datalayer.resetLayer(true) assert.equal(poly1._path.attributes.fill.value, '#fee5d9') assert.equal(poly4._path.attributes.fill.value, '#fcae91') assert.equal(poly9._path.attributes.fill.value, '#de2d26') }) - it('choropleth should allow to change steps', function () { - this.datalayer.options.choropleth.brewer = 'Blues' - this.datalayer.options.choropleth.classes = 6 - this.datalayer.resetLayer(true) + it('choropleth should allow to change steps', () => { + datalayer.options.choropleth.brewer = 'Blues' + datalayer.options.choropleth.classes = 6 + datalayer.resetLayer(true) assert.equal(poly1._path.attributes.fill.value, '#eff3ff') assert.equal(poly4._path.attributes.fill.value, '#c6dbef') assert.equal(poly9._path.attributes.fill.value, '#3182bd') diff --git a/umap/static/umap/test/Controls.js b/umap/static/umap/test/Controls.js deleted file mode 100644 index ff18cf74..00000000 --- a/umap/static/umap/test/Controls.js +++ /dev/null @@ -1,100 +0,0 @@ -describe('L.U.Controls', function () { - before(function () { - this.server = sinon.fakeServer.create() - this.server.respondWith( - /\/datalayer\/62\/\?.*/, - JSON.stringify(RESPONSES.datalayer62_GET) - ) - this.map = initMap({ umap_id: 99 }) - this.server.respond() - this.datalayer = this.map.getDataLayerByUmapId(62) - }) - after(function () { - this.server.restore() - resetMap() - }) - - describe('#databrowser()', function () { - let poly, marker, line - before(function () { - this.datalayer.eachLayer(function (layer) { - if (!poly && layer instanceof L.Polygon) { - poly = layer - } else if (!line && layer instanceof L.Polyline) { - line = layer - } else if (!marker && layer instanceof L.Marker) { - marker = layer - } - }) - }) - - it('should be opened at datalayer button click', function () { - var button = qs('.umap-browse-actions .umap-browse-link') - assert.ok(button) - happen.click(button) - assert.ok(qs('#umap-ui-container .umap-browse-data')) - }) - - it('should contain datalayer section', function () { - assert.ok(qs('#browse_data_datalayer_62')) - }) - - it("should contain datalayer's features list", function () { - assert.equal(qsa('#browse_data_datalayer_62 ul li').length, 3) - }) - - it('should sort feature in natural order', function () { - poly.properties.name = '9. a poly' - marker.properties.name = '1. a marker' - line.properties.name = '100. a line' - this.datalayer.reindex() - this.map.openBrowser() - const els = qsa('.umap-browse-features li') - assert.equal(els.length, 3) - assert.equal(els[0].textContent, '1. a marker') - assert.equal(els[1].textContent, '9. a poly') - assert.equal(els[2].textContent, '100. a line') - }) - - it("should redraw datalayer's features list at feature delete", function () { - var oldConfirm = window.confirm - window.confirm = function () { - return true - } - enableEdit() - happen.once(qs('path[fill="DarkBlue"]'), { type: 'contextmenu' }) - happen.click(qs('.leaflet-contextmenu .umap-delete')) - assert.equal(qsa('#browse_data_datalayer_62 ul li').length, 2) - window.confirm = oldConfirm - }) - - it("should redraw datalayer's features list on edit cancel", function () { - clickCancel() - happen.click(qs('.umap-browse-actions .umap-browse-link')) - assert.equal(qsa('#browse_data_datalayer_62 ul li').length, 3) - }) - }) - - describe('#exportPanel()', function () { - it('should be opened at datalayer button click', function () { - let button = qs('.leaflet-control-embed button') - assert.ok(button) - happen.click(button) - assert.ok(qs('#umap-ui-container .umap-share')) - }) - it('should update iframe link', function () { - let textarea = qs('.umap-share-iframe') - assert.ok(textarea) - console.log(textarea.textContent) - assert.include(textarea.textContent, 'src="') - assert.include(textarea.textContent, 'href="') - // We should ave both, once for iframe link, once for full screen - assert.include(textarea.textContent, 'scrollWheelZoom=true') - assert.include(textarea.textContent, 'scrollWheelZoom=false') - assert.notInclude(textarea.textContent, 'datalayers=62') - let switcher = qs('label[title="Keep current visible layers"]') - happen.click(switcher) - assert.include(textarea.textContent, 'datalayers=62') - }) - }) -}) diff --git a/umap/static/umap/test/DataLayer.js b/umap/static/umap/test/DataLayer.js index bc9c5742..580e117f 100644 --- a/umap/static/umap/test/DataLayer.js +++ b/umap/static/umap/test/DataLayer.js @@ -1,55 +1,54 @@ -describe('L.U.DataLayer', function () { - var path = '/map/99/datalayer/edit/62/' +describe('L.U.DataLayer', () => { + let path = '/map/99/datalayer/update/62/', + map, + datalayer - before(function () { - this.server = sinon.fakeServer.create() - this.server.respondWith( - /\/datalayer\/62\/\?.*/, - JSON.stringify(RESPONSES.datalayer62_GET) - ) - this.map = initMap({ umap_id: 99 }) - this.datalayer = this.map.getDataLayerByUmapId(62) - this.server.respond() + before(async () => { + fetchMock.mock(/\/datalayer\/62\/\?.*/, JSON.stringify(RESPONSES.datalayer62_GET)) + fetchMock.sticky('/map/99/update/settings/', { id: 99 }) + this.options = { + umap_id: 99, + } + MAP = map = initMap({ umap_id: 99 }) + const datalayer_options = defaultDatalayerData() + await map.initDataLayers([datalayer_options]) + datalayer = map.getDataLayerByUmapId(62) enableEdit() }) - after(function () { - this.server.restore() + after(() => { + fetchMock.restore() resetMap() }) - describe('#init()', function () { - it('should be added in datalayers index', function () { - assert.notEqual(this.map.datalayers_index.indexOf(this.datalayer), -1) + describe('#init()', () => { + it('should be added in datalayers index', () => { + assert.notEqual(map.datalayers_index.indexOf(datalayer), -1) }) }) - describe('#edit()', function () { + describe('#edit()', () => { var editButton, form, input, forceButton - it('row in control should be active', function () { + it('row in control should be active', () => { assert.notOk( - qs( - '.leaflet-control-browse #browse_data_toggle_' + - L.stamp(this.datalayer) + - '.off' - ) + qs('.leaflet-control-browse #browse_data_toggle_' + L.stamp(datalayer) + '.off') ) }) - it('should have edit button', function () { - editButton = qs('#browse_data_toggle_' + L.stamp(this.datalayer) + ' .layer-edit') + it('should have edit button', () => { + editButton = qs('#browse_data_toggle_' + L.stamp(datalayer) + ' .layer-edit') assert.ok(editButton) }) - it('should have toggle visibility element', function () { + it('should have toggle visibility element', () => { assert.ok(qs('.leaflet-control-browse i.layer-toggle')) }) - it('should exist only one datalayer', function () { + it('should exist only one datalayer', () => { assert.equal(qsa('.leaflet-control-browse i.layer-toggle').length, 1) }) - it('should build a form on edit button click', function () { + it('should build a form on edit button click', () => { happen.click(editButton) form = qs('form.umap-form') input = qs('form.umap-form input[name="name"]') @@ -57,97 +56,81 @@ describe('L.U.DataLayer', function () { assert.ok(input) }) - it('should update name on input change', function () { + it('should update name on input change', () => { var new_name = 'This is a new name' input.value = new_name happen.once(input, { type: 'input' }) - assert.equal(this.datalayer.options.name, new_name) + assert.equal(datalayer.options.name, new_name) }) - it('should have made datalayer dirty', function () { - assert.ok(this.datalayer.isDirty) - assert.notEqual(this.map.dirty_datalayers.indexOf(this.datalayer), -1) + it('should have made datalayer dirty', () => { + assert.ok(datalayer.isDirty) + assert.notEqual(map.dirty_datalayers.indexOf(datalayer), -1) }) - it('should have made Map dirty', function () { - assert.ok(this.map.isDirty) + it('should have made Map dirty', () => { + assert.ok(map.isDirty) }) - it('should call datalayer.save on save button click', function (done) { - sinon.spy(this.datalayer, 'save') - this.server.flush() - this.server.respondWith( - 'POST', - '/map/99/update/settings/', - JSON.stringify({ id: 99 }) - ) - this.server.respondWith( - 'POST', - '/map/99/datalayer/update/62/', - JSON.stringify(defaultDatalayerData()) - ) + it('should call datalayer.save on save button click', (done) => { + const postDatalayer = fetchMock.post(path, () => { + return defaultDatalayerData() + }) clickSave() - this.server.respond() - this.server.respond() - assert(this.datalayer.save.calledOnce) - this.datalayer.save.restore() - done() + window.setTimeout(() => { + assert(fetchMock.called(path)) + done() + }, 500) }) - it('should show alert if server respond 412', function () { + it('should show alert if server respond 412', (done) => { cleanAlert() - this.server.flush() - this.server.respondWith( - 'POST', - '/map/99/update/settings/', - JSON.stringify({ id: 99 }) - ) - this.server.respondWith('POST', '/map/99/datalayer/update/62/', [412, {}, '']) + fetchMock.restore() + fetchMock.post(path, 412) happen.click(editButton) input = qs('form.umap-form input[name="name"]') input.value = 'a new name' happen.once(input, { type: 'input' }) clickSave() - this.server.respond() - this.server.respond() - assert(L.DomUtil.hasClass(this.map._container, 'umap-alert')) - assert.notEqual(this.map.dirty_datalayers.indexOf(this.datalayer), -1) - forceButton = qs('#umap-alert-container .umap-action') - assert.ok(forceButton) + window.setTimeout(() => { + assert(L.DomUtil.hasClass(map._container, 'umap-alert')) + assert.notEqual(map.dirty_datalayers.indexOf(datalayer), -1) + const forceButton = qs('#umap-alert-container .umap-action') + assert.ok(forceButton) + done() + }, 500) }) - it('should save anyway on force save button click', function () { - sinon.spy(this.map, 'continueSaving') + it('should save anyway on force save button click', (done) => { + const forceButton = qs('#umap-alert-container .umap-action') + fetchMock.restore() + fetchMock.post(path, defaultDatalayerData) happen.click(forceButton) - this.server.flush() - this.server.respond( - 'POST', - '/map/99/datalayer/update/62/', - JSON.stringify(defaultDatalayerData()) - ) - assert.notOk(qs('#umap-alert-container .umap-action')) - assert(this.map.continueSaving.calledOnce) - this.map.continueSaving.restore() - assert.equal(this.map.dirty_datalayers.indexOf(this.datalayer), -1) + window.setTimeout(() => { + assert.notOk(qs('#umap-alert-container .umap-action')) + assert(fetchMock.called(path)) + assert.equal(map.dirty_datalayers.indexOf(datalayer), -1) + done() + }, 500) }) }) - describe('#save() new', function () { - var newLayerButton, form, input, newDatalayer, editButton, manageButton + describe('#save() new', () => { + let newLayerButton, form, input, newDatalayer, editButton, manageButton - it('should have a manage datalayers action', function () { + it('should have a manage datalayers action', () => { enableEdit() manageButton = qs('.manage-datalayers') assert.ok(manageButton) happen.click(manageButton) }) - it('should have a new layer button', function () { + it('should have a new layer button', () => { newLayerButton = qs('#umap-ui-container .add-datalayer') assert.ok(newLayerButton) }) - it('should build a form on new layer button click', function () { + it('should build a form on new layer button click', () => { happen.click(newLayerButton) form = qs('form.umap-form') input = qs('form.umap-form input[name="name"]') @@ -155,86 +138,72 @@ describe('L.U.DataLayer', function () { assert.ok(input) }) - it('should have an empty name', function () { + it('should have an empty name', () => { assert.notOk(input.value) }) - it('should have created a new datalayer', function () { - assert.equal(this.map.datalayers_index.length, 2) - newDatalayer = this.map.datalayers_index[1] + it('should have created a new datalayer', () => { + assert.equal(map.datalayers_index.length, 2) + newDatalayer = map.datalayers_index[1] }) - it('should have made Map dirty', function () { - assert.ok(this.map.isDirty) + it('should have made Map dirty', () => { + assert.ok(map.isDirty) }) - it('should update name on input change', function () { + it('should update name on input change', () => { var new_name = 'This is a new name' input.value = new_name happen.once(input, { type: 'input' }) assert.equal(newDatalayer.options.name, new_name) }) - it('should set umap_id on save callback', function () { + it('should set umap_id on save callback', async () => { assert.notOk(newDatalayer.umap_id) - this.server.flush() - this.server.respondWith( - 'POST', - '/map/99/update/settings/', - JSON.stringify({ id: 99 }) - ) - this.server.respondWith( - 'POST', - '/map/99/datalayer/create/', - JSON.stringify(defaultDatalayerData({ id: 63 })) - ) + fetchMock.post('/map/99/datalayer/create/', defaultDatalayerData({ id: 63 })) clickSave() - this.server.respond() - this.server.respond() // First respond will then trigger another Xhr request (continueSaving) - assert.equal(newDatalayer.umap_id, 63) + return new Promise((resolve) => { + window.setTimeout(() => { + assert.equal(newDatalayer.umap_id, 63) + resolve() + }, 1000) + }) }) - it('should have unset map dirty', function () { - assert.notOk(this.map.isDirty) + it('should have unset map dirty', () => { + assert.notOk(map.isDirty) }) - it('should have edit button', function () { + it('should have edit button', () => { editButton = qs('#browse_data_toggle_' + L.stamp(newDatalayer) + ' .layer-edit') assert.ok(editButton) }) - it('should call update if we edit again', function () { + it('should call update if we edit again', async () => { happen.click(editButton) - assert.notOk(this.map.isDirty) + assert.notOk(map.isDirty) input = qs('form.umap-form input[name="name"]') input.value = "a new name again but we don't care which" happen.once(input, { type: 'input' }) - assert.ok(this.map.isDirty) - var response = function (request) { - return request.respond( - 200, - {}, - JSON.stringify(defaultDatalayerData({ pk: 63 })) - ) + assert.ok(map.isDirty) + var response = () => { + return defaultDatalayerData({ pk: 63 }) } var spy = sinon.spy(response) - this.server.flush() - this.server.respondWith( - 'POST', - '/map/99/update/settings/', - JSON.stringify({ id: 99 }) - ) - this.server.respondWith('POST', '/map/99/datalayer/update/63/', spy) - clickSave() - this.server.respond() - this.server.respond() - assert.ok(spy.calledOnce) + fetchMock.post('/map/99/datalayer/update/63/', spy) + return new Promise((resolve) => { + clickSave() + window.setTimeout(() => { + assert.ok(spy.calledOnce) + resolve() + }, 1000) + }) }) }) - describe('#iconClassChange()', function () { - it('should change icon class', function () { - happen.click(qs('[data-id="' + this.datalayer._leaflet_id + '"] .layer-edit')) + describe('#iconClassChange()', () => { + it('should change icon class', () => { + happen.click(qs('[data-id="' + datalayer._leaflet_id + '"] .layer-edit')) changeSelectValue( qs('form#datalayer-advanced-properties select[name=iconClass]'), 'Circle' @@ -250,53 +219,53 @@ describe('L.U.DataLayer', function () { }) }) - describe('#show/hide', function () { - it('should hide features on hide', function () { + describe('#show/hide', () => { + it('should hide features on hide', () => { assert.ok(qs('div.umap-div-icon')) assert.ok(qs('path[fill="none"]')) - this.datalayer.hide() + datalayer.hide() assert.notOk(qs('div.umap-div-icon')) assert.notOk(qs('path[fill="none"]')) }) - it('should show features on show', function () { + it('should show features on show', () => { assert.notOk(qs('div.umap-div-icon')) assert.notOk(qs('path[fill="none"]')) - this.datalayer.show() + datalayer.show() assert.ok(qs('div.umap-div-icon')) assert.ok(qs('path[fill="none"]')) }) }) - describe('#clone()', function () { - it('should clone everything but the id and the name', function () { + describe('#clone()', () => { + it('should clone everything but the id and the name', () => { enableEdit() - var clone = this.datalayer.clone() + var clone = datalayer.clone() assert.notOk(clone.umap_id) - assert.notEqual(clone.options.name, this.datalayer.name) + assert.notEqual(clone.options.name, datalayer.name) assert.ok(clone.options.name) - assert.equal(clone.options.color, this.datalayer.options.color) - assert.equal(clone.options.stroke, this.datalayer.options.stroke) + assert.equal(clone.options.color, datalayer.options.color) + assert.equal(clone.options.stroke, datalayer.options.stroke) clone._delete() clickSave() }) }) - describe('#restore()', function () { + describe('#restore()', () => { var oldConfirm, - newConfirm = function () { + newConfirm = () => { return true } - before(function () { + before(() => { oldConfirm = window.confirm window.confirm = newConfirm }) - after(function () { + after(() => { window.confirm = oldConfirm }) - it('should restore everything', function () { + it('should restore everything', (done) => { enableEdit() var geojson = L.Util.CopyJSON(RESPONSES.datalayer62_GET) geojson.features.push({ @@ -309,33 +278,31 @@ describe('L.U.DataLayer', function () { properties: { _umap_options: {}, name: 'new point from restore' }, }) geojson._umap_options.color = 'Chocolate' - this.server.respondWith( - 'GET', - '/datalayer/62/olderversion.geojson', - JSON.stringify(geojson) - ) + fetchMock.get('/datalayer/62/olderversion.geojson', geojson) sinon.spy(window, 'confirm') - this.datalayer.restore('olderversion.geojson') - this.server.respond() - assert(window.confirm.calledOnce) - window.confirm.restore() - assert.equal(this.datalayer.umap_id, 62) - assert.ok(this.datalayer.isDirty) - assert.equal(this.datalayer._index.length, 4) - assert.ok(qs('path[fill="Chocolate"]')) + datalayer.restore('olderversion.geojson') + window.setTimeout(() => { + assert(window.confirm.calledOnce) + window.confirm.restore() + assert.equal(datalayer.umap_id, 62) + assert.ok(datalayer.isDirty) + assert.equal(datalayer._index.length, 4) + assert.ok(qs('path[fill="Chocolate"]')) + done() + }, 1000) }) - it('should revert anything on cancel click', function () { + it('should revert anything on cancel click', () => { clickCancel() - assert.equal(this.datalayer._index.length, 3) + assert.equal(datalayer._index.length, 3) assert.notOk(qs('path[fill="Chocolate"]')) }) }) - describe('#smart-options()', function () { + describe('#smart-options()', () => { let poly, marker - before(function () { - this.datalayer.eachLayer(function (layer) { + before(() => { + datalayer.eachLayer(function (layer) { if (!poly && layer instanceof L.Polygon) { poly = layer } @@ -345,39 +312,35 @@ describe('L.U.DataLayer', function () { }) }) - it('should parse color variable', function () { + it('should parse color variable', () => { let icon = qs('div.umap-div-icon .icon_container') poly.properties.mycolor = 'DarkGoldenRod' marker.properties.mycolor = 'DarkRed' marker.properties._umap_options.color = undefined assert.notOk(qs('path[fill="DarkGoldenRod"]')) assert.equal(icon.style.backgroundColor, 'olivedrab') - this.datalayer.options.color = '{mycolor}' - this.datalayer.options.fillColor = '{mycolor}' - this.datalayer.indexProperties(poly) - this.datalayer.indexProperties(marker) - this.datalayer.redraw() + datalayer.options.color = '{mycolor}' + datalayer.options.fillColor = '{mycolor}' + datalayer.indexProperties(poly) + datalayer.indexProperties(marker) + datalayer.redraw() icon = qs('div.umap-div-icon .icon_container') assert.equal(icon.style.backgroundColor, 'darkred') assert.ok(qs('path[fill="DarkGoldenRod"]')) }) }) - describe('#facet-search()', function () { - before(function () { - this.server.respondWith( - /\/datalayer\/63\/\?.*/, - JSON.stringify(RESPONSES.datalayer63_GET) - ) - this.map.options.facetKey = 'name' - this.map.createDataLayer(RESPONSES.datalayer63_GET._umap_options) - this.server.respond() + describe('#facet-search()', () => { + before(async () => { + fetchMock.get(/\/datalayer\/63\/\?.*/, RESPONSES.datalayer63_GET) + map.options.facetKey = 'name' + await map.initDataLayers([RESPONSES.datalayer63_GET._umap_options]) }) - it('should not impact non browsable layer', function () { + it('should not impact non browsable layer', () => { assert.ok(qs('path[fill="SteelBlue"]')) }) - it('should allow advanced filter', function () { - this.map.openFacet() + it('should allow advanced filter', () => { + map.openFacet() assert.ok(qs('div.umap-facet-search')) // This one if from the normal datalayer // it's name is "test", so it should be hidden @@ -390,104 +353,109 @@ describe('L.U.DataLayer', function () { assert.ok(qs('path[fill="SteelBlue"]')) happen.click(qs('input[data-value="name poly"]')) // Undo }) - it('should allow to control facet label', function () { - this.map.options.facetKey = 'name|Nom' - this.map.openFacet() + it('should allow to control facet label', () => { + map.options.facetKey = 'name|Nom' + map.openFacet() assert.ok(qs('div.umap-facet-search h5')) assert.equal(qs('div.umap-facet-search h5').textContent, 'Nom') }) }) - describe('#zoomEnd', function () { - it('should honour the fromZoom option', function () { - this.map.setZoom(6, { animate: false }) + describe('#zoomEnd', () => { + it('should honour the fromZoom option', () => { + map.setZoom(6, { animate: false }) assert.ok(qs('path[fill="none"]')) - this.datalayer.options.fromZoom = 6 - this.map.setZoom(5, { animate: false }) + datalayer.options.fromZoom = 6 + map.setZoom(5, { animate: false }) assert.notOk(qs('path[fill="none"]')) - this.map.setZoom(6, { animate: false }) + map.setZoom(6, { animate: false }) assert.ok(qs('path[fill="none"]')) }) - it('should honour the toZoom option', function () { - this.map.setZoom(6, { animate: false }) + it('should honour the toZoom option', () => { + map.setZoom(6, { animate: false }) assert.ok(qs('path[fill="none"]')) - this.datalayer.options.toZoom = 6 - this.map.setZoom(7, { animate: false }) + datalayer.options.toZoom = 6 + map.setZoom(7, { animate: false }) assert.notOk(qs('path[fill="none"]')) - this.map.setZoom(6, { animate: false }) + map.setZoom(6, { animate: false }) assert.ok(qs('path[fill="none"]')) }) }) - describe('#displayOnLoad', function () { - beforeEach(function () { - this.server.respondWith( - /\/datalayer\/64\/\?.*/, - JSON.stringify(RESPONSES.datalayer64_GET) - ) - this.datalayer = this.map.createDataLayer(RESPONSES.datalayer64_GET._umap_options) - // Force fetching the data, so to deal here with fake server - this.datalayer.fetchData() - this.server.respond() - this.map.setZoom(10, { animate: false }) + describe('#displayOnLoad', () => { + before(() => { + fetchMock.get(/\/datalayer\/64\/\?.*/, RESPONSES.datalayer64_GET) }) - afterEach(function () { - this.datalayer._delete() + beforeEach(async () => { + await map.initDataLayers([RESPONSES.datalayer64_GET._umap_options]) + datalayer = map.getDataLayerByUmapId(64) + map.setZoom(10, { animate: false }) }) - it('should not display layer at load', function () { + afterEach(() => { + datalayer._delete() + }) + + it('should not display layer at load', () => { assert.notOk(qs('path[fill="AliceBlue"]')) }) - it('should display on click', function () { - happen.click(qs(`[data-id='${L.stamp(this.datalayer)}'] .layer-toggle`)) - assert.ok(qs('path[fill="AliceBlue"]')) + it('should display on click', (done) => { + happen.click(qs(`[data-id='${L.stamp(datalayer)}'] .layer-toggle`)) + window.setTimeout(() => { + assert.ok(qs('path[fill="AliceBlue"]')) + done() + }, 500) }) - it('should not display on zoom', function () { - this.map.setZoom(9, { animate: false }) - assert.notOk(qs('path[fill="AliceBlue"]')) + it('should not display on zoom', (done) => { + map.setZoom(9, { animate: false }) + window.setTimeout(() => { + assert.notOk(qs('path[fill="AliceBlue"]')) + done() + }, 500) }) }) - describe('#delete()', function () { - var deleteLink, + describe('#delete()', () => { + let deleteLink, deletePath = '/map/99/datalayer/delete/62/' + before(() => { + datalayer = map.getDataLayerByUmapId(62) + }) - it('should have a delete link in update form', function () { + it('should have a delete link in update form', () => { enableEdit() - happen.click( - qs('#browse_data_toggle_' + L.stamp(this.datalayer) + ' .layer-edit') - ) + happen.click(qs('#browse_data_toggle_' + L.stamp(datalayer) + ' .layer-edit')) deleteLink = qs('button.delete_datalayer_button') assert.ok(deleteLink) }) - it('should delete features on datalayer delete', function () { + it('should delete features on datalayer delete', () => { happen.click(deleteLink) assert.notOk(qs('div.icon_container')) }) - it('should have set map dirty', function () { - assert.ok(this.map.isDirty) + it('should have set map dirty', () => { + assert.ok(map.isDirty) }) - it('should delete layer control row on delete', function () { + it('should delete layer control row on delete', () => { assert.notOk( - qs('.leaflet-control-browse #browse_data_toggle_' + L.stamp(this.datalayer)) + qs('.leaflet-control-browse #browse_data_toggle_' + L.stamp(datalayer)) ) }) - it('should be removed from map.datalayers_index', function () { - assert.equal(this.map.datalayers_index.indexOf(this.datalayer), -1) + it('should be removed from map.datalayers_index', () => { + assert.equal(map.datalayers_index.indexOf(datalayer), -1) }) - it('should be removed from map.datalayers', function () { - assert.notOk(this.map.datalayers[L.stamp(this.datalayer)]) + it('should be removed from map.datalayers', () => { + assert.notOk(map.datalayers[L.stamp(datalayer)]) }) - it('should be visible again on edit cancel', function () { + it('should be visible again on edit cancel', () => { clickCancel() assert.ok(qs('div.icon_container')) }) diff --git a/umap/static/umap/test/Feature.js b/umap/static/umap/test/Feature.js index 688e10e5..02261b90 100644 --- a/umap/static/umap/test/Feature.js +++ b/umap/static/umap/test/Feature.js @@ -1,21 +1,25 @@ describe('L.U.FeatureMixin', function () { - before(function () { - this.server = sinon.fakeServer.create() - this.server.respondWith( + let map, datalayer + before(async () => { + await fetchMock.mock( /\/datalayer\/62\/\?.*/, JSON.stringify(RESPONSES.datalayer62_GET) ) - this.map = initMap({ umap_id: 99 }) - this.datalayer = this.map.getDataLayerByUmapId(62) - this.server.respond() + this.options = { + umap_id: 99, + } + MAP = map = initMap({ umap_id: 99 }) + const datalayer_options = defaultDatalayerData() + await map.initDataLayers([datalayer_options]) + datalayer = map.getDataLayerByUmapId(62) }) after(function () { - this.server.restore() + fetchMock.restore() resetMap() }) describe('#edit()', function () { - var link + let link it('should have datalayer features created', function () { assert.equal( @@ -45,7 +49,7 @@ describe('L.U.FeatureMixin', function () { it('should take into account styles changes made in the datalayer', function () { happen.click( - qs('#browse_data_toggle_' + L.stamp(this.datalayer) + ' .layer-edit') + qs('#browse_data_toggle_' + L.stamp(datalayer) + ' .layer-edit') ) var colorInput = qs('form#datalayer-advanced-properties input[name=color]') changeInputValue(colorInput, 'DarkRed') @@ -101,7 +105,7 @@ describe('L.U.FeatureMixin', function () { it('should not override already set style on features', function () { happen.click( - qs('#browse_data_toggle_' + L.stamp(this.datalayer) + ' .layer-edit') + qs('#browse_data_toggle_' + L.stamp(datalayer) + ' .layer-edit') ) changeInputValue(qs('#umap-ui-container form input[name=color]'), 'Chocolate') assert.notOk(qs('path[fill="DarkBlue"]')) @@ -120,22 +124,22 @@ describe('L.U.FeatureMixin', function () { it('should set map.editedFeature on edit', function () { enableEdit() - assert.notOk(this.map.editedFeature) + assert.notOk(map.editedFeature) happen.click(qs('path[fill="DarkBlue"]')) happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit')) - assert.ok(this.map.editedFeature) + assert.ok(map.editedFeature) disableEdit() }) it('should reset map.editedFeature on panel open', function (done) { enableEdit() - assert.notOk(this.map.editedFeature) + assert.notOk(map.editedFeature) happen.click(qs('path[fill="DarkBlue"]')) happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit')) - assert.ok(this.map.editedFeature) - this.map.displayCaption() + assert.ok(map.editedFeature) + map.displayCaption() window.setTimeout(function () { - assert.notOk(this.map.editedFeature) + assert.notOk(map.editedFeature) disableEdit() done() }, 1001) // CSS transition time. @@ -155,7 +159,7 @@ describe('L.U.FeatureMixin', function () { }) } it('should generate a valid geojson', function () { - setFeatures(this.datalayer) + setFeatures(datalayer) assert.ok(poly) assert.deepEqual(poly.toGeoJSON().geometry, { type: 'Polygon', @@ -176,7 +180,7 @@ describe('L.U.FeatureMixin', function () { }) it('should remove empty _umap_options from exported geojson', function () { - setFeatures(this.datalayer) + setFeatures(datalayer) assert.ok(poly) assert.deepEqual(poly.toGeoJSON().properties, { name: 'name poly' }) assert.ok(marker) @@ -190,7 +194,7 @@ describe('L.U.FeatureMixin', function () { describe('#openPopup()', function () { let poly before(function () { - this.datalayer.eachLayer(function (layer) { + datalayer.eachLayer(function (layer) { if (!poly && layer instanceof L.Polygon) { poly = layer } @@ -233,8 +237,8 @@ describe('L.U.FeatureMixin', function () { }) it('should still highlight marker after hide() and show()', function () { - this.datalayer.hide() - this.datalayer.show() + datalayer.hide() + datalayer.show() happen.click(qs('div.leaflet-marker-icon')) assert.ok(qs('.umap-icon-active')) }) @@ -254,9 +258,9 @@ describe('L.U.FeatureMixin', function () { describe('#tooltip', function () { it('should have a tooltip when active and allow variables', function () { - this.map.options.showLabel = true - this.map.options.labelKey = 'Foo {name}' - this.datalayer.redraw() + map.options.showLabel = true + map.options.labelKey = 'Foo {name}' + datalayer.redraw() assert.equal( qs('.leaflet-tooltip-pane .leaflet-tooltip').textContent, 'Foo name poly' @@ -266,7 +270,7 @@ describe('L.U.FeatureMixin', function () { describe('#properties()', function () { it('should rename property', function () { - var poly = this.datalayer._lineToLayer({}, [ + var poly = datalayer._lineToLayer({}, [ [0, 0], [0, 1], [0, 2], @@ -278,7 +282,7 @@ describe('L.U.FeatureMixin', function () { }) it('should not create property when renaming', function () { - var poly = this.datalayer._lineToLayer({}, [ + var poly = datalayer._lineToLayer({}, [ [0, 0], [0, 1], [0, 2], @@ -289,7 +293,7 @@ describe('L.U.FeatureMixin', function () { }) it('should delete property', function () { - var poly = this.datalayer._lineToLayer({}, [ + var poly = datalayer._lineToLayer({}, [ [0, 0], [0, 1], [0, 2], @@ -305,7 +309,7 @@ describe('L.U.FeatureMixin', function () { var poly it('should filter on properties', function () { - poly = this.datalayer._lineToLayer({}, [ + poly = datalayer._lineToLayer({}, [ [0, 0], [0, 1], [0, 2], @@ -329,35 +333,6 @@ describe('L.U.FeatureMixin', function () { }) }) - describe('#quick-delete()', function () { - let poly, _confirm - before(function () { - _confirm = window.confirm - window.confirm = function (text) { - return true - } - - this.datalayer.eachLayer(function (layer) { - if (!poly && layer instanceof L.Polygon) { - poly = layer - } - }) - }) - - after(function () { - window.confirm = _confirm - }) - - it('should allow to delete from data browser', function () { - enableEdit() - assert.ok(qs('path[fill="DarkBlue"]')) - this.map.openBrowser() - happen.click(qs('.feature-delete')) - assert.notOk(qs('path[fill="DarkBlue"]')) - clickCancel() - }) - }) - describe('#changeDataLayer()', function () { it('should change style on datalayer select change', function () { enableEdit() diff --git a/umap/static/umap/test/Map.Export.js b/umap/static/umap/test/Map.Export.js index 8983c364..abdbcf81 100644 --- a/umap/static/umap/test/Map.Export.js +++ b/umap/static/umap/test/Map.Export.js @@ -1,26 +1,26 @@ describe('L.U.Map.Export', function () { - before(function () { - this.server = sinon.fakeServer.create() - this.server.respondWith( + let map + before(async () => { + await fetchMock.mock( /\/datalayer\/62\/\?.*/, JSON.stringify(RESPONSES.datalayer62_GET) ) this.options = { umap_id: 99, } - this.map = initMap({ umap_id: 99 }) - this.server.respond() - this.datalayer = this.map.getDataLayerByUmapId(62) + map = initMap({ umap_id: 99 }) + const datalayer_options = defaultDatalayerData() + await map.initDataLayers([datalayer_options]) }) after(function () { - this.server.restore() + fetchMock.restore() clickCancel() resetMap() }) describe('#formatters()', function () { it('should export to geojson', function () { - const { content, filetype, filename } = this.map.share.format('geojson') + const { content, filetype, filename } = map.share.format('geojson') assert.equal(filetype, 'application/json') assert.equal(filename, 'name_of_the_map.geojson') assert.deepEqual(JSON.parse(content), { @@ -86,7 +86,7 @@ describe('L.U.Map.Export', function () { }) it('should export to gpx', function () { - const { content, filetype, filename } = this.map.share.format('gpx') + const { content, filetype, filename } = map.share.format('gpx') assert.equal(filetype, 'application/gpx+xml') assert.equal(filename, 'name_of_the_map.gpx') const expected = @@ -95,7 +95,7 @@ describe('L.U.Map.Export', function () { }) it('should export to kml', function () { - const { content, filetype, filename } = this.map.share.format('kml') + const { content, filetype, filename } = map.share.format('kml') assert.equal(filetype, 'application/vnd.google-earth.kml+xml') assert.equal(filename, 'name_of_the_map.kml') const expected = diff --git a/umap/static/umap/test/Map.Init.js b/umap/static/umap/test/Map.Init.js deleted file mode 100644 index fa10e18a..00000000 --- a/umap/static/umap/test/Map.Init.js +++ /dev/null @@ -1,46 +0,0 @@ -describe('L.U.Map.initialize', function () { - afterEach(function () { - resetMap() - }) - - describe('Controls', function () { - it('should not show a minimap by default', function () { - this.map = initMap() - assert.notOk(qs('.leaflet-control-minimap')) - }) - - it('should show a minimap', function () { - this.map = initMap({ miniMap: true }) - assert.ok(qs('.leaflet-control-minimap')) - }) - }) - - describe('DefaultView', function () { - it('should set default view in default mode without data', function (done) { - this.map = initMap({ datalayers: [] }) - // Did not find a better way to wait for tiles to be actually loaded - window.setTimeout(() => { - assert.ok(qs('#map .leaflet-tile-pane img.leaflet-tile.leaflet-tile-loaded')) - done() - }, 1000) - }) - - it("should set default view in 'data' mode without data", function (done) { - this.map = initMap({ datalayers: [], defaultView: 'data' }) - // Did not find a better way to wait for tiles to be actually loaded - window.setTimeout(() => { - assert.ok(qs('#map .leaflet-tile-pane img.leaflet-tile.leaflet-tile-loaded')) - done() - }, 1000) - }) - - it("should set default view in 'latest' mode without data", function (done) { - this.map = initMap({ datalayers: [], defaultView: 'latest' }) - // Did not find a better way to wait for tiles to be actually loaded - window.setTimeout(() => { - assert.ok(qs('#map .leaflet-tile-pane img.leaflet-tile.leaflet-tile-loaded')) - done() - }, 1000) - }) - }) -}) diff --git a/umap/static/umap/test/Map.js b/umap/static/umap/test/Map.js index 12b64d73..5ba26ada 100644 --- a/umap/static/umap/test/Map.js +++ b/umap/static/umap/test/Map.js @@ -1,26 +1,27 @@ -describe('L.U.Map', function () { - before(function () { - this.server = sinon.fakeServer.create() - this.server.respondWith( +describe('L.U.Map', () => { + let map, datalayer + before(async () => { + await fetchMock.mock( /\/datalayer\/62\/\?.*/, JSON.stringify(RESPONSES.datalayer62_GET) ) this.options = { umap_id: 99, } - this.map = initMap({ umap_id: 99 }) - this.server.respond() - this.datalayer = this.map.getDataLayerByUmapId(62) + map = initMap({ umap_id: 99 }) + const datalayer_options = defaultDatalayerData() + await map.initDataLayers([datalayer_options]) + datalayer = map.getDataLayerByUmapId(62) }) - after(function () { - this.server.restore() + after(() => { + fetchMock.restore() clickCancel() resetMap() }) - describe('#init()', function () { + describe('#init()', () => { it('should be initialized', function () { - assert.equal(this.map.options.umap_id, 99) + assert.equal(map.options.umap_id, 99) }) it('should have created the edit button', function () { @@ -42,7 +43,7 @@ describe('L.U.Map', function () { it('should hide icon container div when hiding datalayer', function () { var el = qs( '.leaflet-control-browse #browse_data_toggle_' + - L.stamp(this.datalayer) + + L.stamp(datalayer) + ' .layer-toggle' ) happen.click(el) @@ -56,7 +57,7 @@ describe('L.U.Map', function () { }) it('should have only one datalayer in its index', function () { - assert.equal(this.map.datalayers_index.length, 1) + assert.equal(map.datalayers_index.length, 1) }) }) @@ -78,20 +79,20 @@ describe('L.U.Map', function () { var new_name = 'This is a new name' input.value = new_name happen.once(input, { type: 'input' }) - assert.equal(this.map.options.name, new_name) + assert.equal(map.options.name, new_name) }) it('should have made Map dirty', function () { - assert.ok(this.map.isDirty) + assert.ok(map.isDirty) }) it('should have added dirty class on map container', function () { - assert.ok(L.DomUtil.hasClass(this.map._container, 'umap-is-dirty')) + assert.ok(L.DomUtil.hasClass(map._container, 'umap-is-dirty')) }) }) describe('#delete()', function () { - var path = '/map/99/delete/', + let path = '/map/99/update/delete/', oldConfirm, newConfirm = function () { return true @@ -105,24 +106,22 @@ describe('L.U.Map', function () { window.confirm = oldConfirm }) - it('should ask for confirmation on delete link click', function () { - var button = qs('a.update-map-settings') + it('should ask for confirmation on delete link click', async function () { + let button = qs('a.update-map-settings') assert.ok(button, 'update map info button exists') happen.click(button) - var deleteLink = qs('button.umap-delete') + let deleteLink = qs('button.umap-delete') assert.ok(deleteLink, 'delete map button exists') sinon.spy(window, 'confirm') - this.server.respondWith('POST', path, JSON.stringify({ redirect: '#' })) + await fetchMock.post(path, { redirect: '#' }) happen.click(deleteLink) - this.server.respond() assert(window.confirm.calledOnce) window.confirm.restore() - }) }) describe('#importData()', function () { - var fileInput, textarea, submit, formatSelect, layerSelect, clearFlag + let fileInput, textarea, submit, formatSelect, layerSelect, clearFlag it('should build a form on click', function () { happen.click(qs('a.upload-data')) @@ -139,82 +138,82 @@ describe('L.U.Map', function () { }) it('should import geojson from textarea', function () { - this.datalayer.empty() - assert.equal(this.datalayer._index.length, 0) + datalayer.empty() + assert.equal(datalayer._index.length, 0) textarea.value = '{"type": "FeatureCollection", "features": [{"geometry": {"type": "Point", "coordinates": [6.922931671142578, 47.481161607175736]}, "type": "Feature", "properties": {"color": "", "name": "Chez R\u00e9my", "description": ""}}, {"geometry": {"type": "LineString", "coordinates": [[2.4609375, 48.88639177703194], [2.48291015625, 48.76343113791796], [2.164306640625, 48.719961222646276]]}, "type": "Feature", "properties": {"color": "", "name": "P\u00e9rif", "description": ""}}]}' changeSelectValue(formatSelect, 'geojson') happen.click(submit) - assert.equal(this.datalayer._index.length, 2) + assert.equal(datalayer._index.length, 2) }) it('should remove dot in property name', function () { - this.datalayer.empty() - assert.equal(this.datalayer._index.length, 0) + datalayer.empty() + assert.equal(datalayer._index.length, 0) textarea.value = '{"type": "FeatureCollection", "features": [{"geometry": {"type": "Point", "coordinates": [6.922931671142578, 47.481161607175736]}, "type": "Feature", "properties": {"color": "", "name": "Chez R\u00e9my", "A . in the name": ""}}, {"geometry": {"type": "LineString", "coordinates": [[2.4609375, 48.88639177703194], [2.48291015625, 48.76343113791796], [2.164306640625, 48.719961222646276]]}, "type": "Feature", "properties": {"color": "", "name": "P\u00e9rif", "with a dot.": ""}}]}' changeSelectValue(formatSelect, 'geojson') happen.click(submit) - assert.equal(this.datalayer._index.length, 2) - assert.ok(this.datalayer._propertiesIndex.includes('A _ in the name')) - assert.ok(this.datalayer._propertiesIndex.includes('with a dot_')) + assert.equal(datalayer._index.length, 2) + assert.ok(datalayer._propertiesIndex.includes('A _ in the name')) + assert.ok(datalayer._propertiesIndex.includes('with a dot_')) }) it('should import osm from textarea', function () { - this.datalayer.empty() + datalayer.empty() happen.click(qs('a.upload-data')) textarea = qs('.umap-upload textarea') submit = qs('.umap-upload input[type="button"]') formatSelect = qs('.umap-upload select[name="format"]') - assert.equal(this.datalayer._index.length, 0) + assert.equal(datalayer._index.length, 0) textarea.value = '{"version": 0.6,"generator": "Overpass API 0.7.55.4 3079d8ea","osm3s": {"timestamp_osm_base": "2018-09-22T05:26:02Z","copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."},"elements": [{"type": "node","id": 3619112991,"lat": 48.9352995,"lon": 2.3570684,"tags": {"information": "map","map_size": "city","map_type": "scheme","tourism": "information"}},{"type": "node","id": 3682500756,"lat": 48.9804426,"lon": 2.2719725,"tags": {"information": "map","level": "0","tourism": "information"}}]}' changeSelectValue(formatSelect, 'osm') happen.click(submit) - assert.equal(this.datalayer._index.length, 2) + assert.equal(datalayer._index.length, 2) assert.equal( - this.datalayer._layers[this.datalayer._index[0]].properties.tourism, + datalayer._layers[datalayer._index[0]].properties.tourism, 'information' ) }) it('should import kml from textarea', function () { - this.datalayer.empty() + datalayer.empty() happen.click(qs('a.upload-data')) textarea = qs('.umap-upload textarea') submit = qs('.umap-upload input[type="button"]') formatSelect = qs('.umap-upload select[name="format"]') - assert.equal(this.datalayer._index.length, 0) + assert.equal(datalayer._index.length, 0) textarea.value = kml_example changeSelectValue(formatSelect, 'kml') happen.click(submit) - assert.equal(this.datalayer._index.length, 3) + assert.equal(datalayer._index.length, 3) }) it('should import gpx from textarea', function () { - this.datalayer.empty() + datalayer.empty() happen.click(qs('a.upload-data')) textarea = qs('.umap-upload textarea') submit = qs('.umap-upload input[type="button"]') formatSelect = qs('.umap-upload select[name="format"]') - assert.equal(this.datalayer._index.length, 0) + assert.equal(datalayer._index.length, 0) textarea.value = gpx_example changeSelectValue(formatSelect, 'gpx') happen.click(submit) - assert.equal(this.datalayer._index.length, 2) + assert.equal(datalayer._index.length, 2) }) it('should import csv from textarea', function () { - this.datalayer.empty() + datalayer.empty() happen.click(qs('a.upload-data')) textarea = qs('.umap-upload textarea') submit = qs('.umap-upload input[type="button"]') formatSelect = qs('.umap-upload select[name="format"]') - assert.equal(this.datalayer._index.length, 0) + assert.equal(datalayer._index.length, 0) textarea.value = csv_example changeSelectValue(formatSelect, 'csv') happen.click(submit) - assert.equal(this.datalayer._index.length, 1) + assert.equal(datalayer._index.length, 1) }) it('should replace content if asked so', function () { @@ -224,52 +223,52 @@ describe('L.U.Map', function () { formatSelect = qs('.umap-upload select[name="format"]') clearFlag = qs('.umap-upload input[name="clear"]') clearFlag.checked = true - assert.equal(this.datalayer._index.length, 1) + assert.equal(datalayer._index.length, 1) textarea.value = csv_example changeSelectValue(formatSelect, 'csv') happen.click(submit) - assert.equal(this.datalayer._index.length, 1) + assert.equal(datalayer._index.length, 1) }) it('should import GeometryCollection from textarea', function () { - this.datalayer.empty() + datalayer.empty() textarea.value = '{"type": "GeometryCollection","geometries": [{"type": "Point","coordinates": [-80.66080570220947,35.04939206472683]},{"type": "Polygon","coordinates": [[[-80.66458225250244,35.04496519190309],[-80.66344499588013,35.04603679820616],[-80.66258668899536,35.045580049697556],[-80.66387414932251,35.044280059194946],[-80.66458225250244,35.04496519190309]]]},{"type": "LineString","coordinates": [[-80.66237211227417,35.05950973022538],[-80.66269397735596,35.0592638296087],[-80.66284418106079,35.05893010615862],[-80.66308021545409,35.05833291342246],[-80.66359519958496,35.057753281001425],[-80.66387414932251,35.05740198662245],[-80.66441059112549,35.05703312589789],[-80.66486120223999,35.056787217822475],[-80.66541910171509,35.05650617911516],[-80.66563367843628,35.05631296444281],[-80.66601991653441,35.055891403570705],[-80.66619157791138,35.05545227534804],[-80.66619157791138,35.05517123204622],[-80.66625595092773,35.05489018777713],[-80.6662130355835,35.054222703761525],[-80.6662130355835,35.05392409072499],[-80.66595554351807,35.05290528508858],[-80.66569805145262,35.052044560077285],[-80.66550493240356,35.0514824490509],[-80.665762424469,35.05048117920187],[-80.66617012023926,35.04972582715769],[-80.66651344299316,35.049286665781096],[-80.66692113876343,35.0485313026898],[-80.66700696945189,35.048215102112344],[-80.66707134246826,35.04777593261294],[-80.66704988479614,35.04738946150025],[-80.66696405410767,35.04698542156371],[-80.66681385040283,35.046353007216055],[-80.66659927368164,35.04596652937105],[-80.66640615463257,35.04561518428889],[-80.6659984588623,35.045193568195565],[-80.66552639007568,35.044877354697526],[-80.6649899482727,35.04454357245502],[-80.66449642181396,35.04417465365292],[-80.66385269165039,35.04387600387859],[-80.66303730010986,35.043717894732545]]}]}' formatSelect = qs('.umap-upload select[name="format"]') changeSelectValue(formatSelect, 'geojson') happen.click(submit) - assert.equal(this.datalayer._index.length, 3) + assert.equal(datalayer._index.length, 3) }) it('should import multipolygon', function () { - this.datalayer.empty() + datalayer.empty() textarea.value = '{"type": "Feature", "properties": { "name": "Some states" }, "geometry": { "type": "MultiPolygon", "coordinates": [[[[-109, 36], [-109, 40], [-102, 37], [-109, 36]], [[-108, 39], [-107, 37], [-104, 37], [-108, 39]]], [[[-119, 42], [-120, 39], [-114, 41], [-119, 42]]]] }}' changeSelectValue(formatSelect, 'geojson') happen.click(submit) - assert.equal(this.datalayer._index.length, 1) - var layer = this.datalayer.getFeatureByIndex(0) + assert.equal(datalayer._index.length, 1) + var layer = datalayer.getFeatureByIndex(0) assert.equal(layer._latlngs.length, 2) // Two shapes. assert.equal(layer._latlngs[0].length, 2) // Hole. }) it('should import multipolyline', function () { - this.datalayer.empty() + datalayer.empty() textarea.value = '{"type": "FeatureCollection", "features": [{ "type": "Feature", "properties": {}, "geometry": { "type": "MultiLineString", "coordinates": [[[-108, 46], [-113, 43]], [[-112, 45], [-115, 44]]] } }]}' changeSelectValue(formatSelect, 'geojson') happen.click(submit) - assert.equal(this.datalayer._index.length, 1) - var layer = this.datalayer.getFeatureByIndex(0) + assert.equal(datalayer._index.length, 1) + var layer = datalayer.getFeatureByIndex(0) assert.equal(layer._latlngs.length, 2) // Two shapes. }) it('should import raw umap data from textarea', function () { //Right now, the import function will try to save and reload. Stop this from happening. - var disabledSaveFunction = this.map.save - this.map.save = function () {} + var disabledSaveFunction = map.save + map.save = function () {} happen.click(qs('a.upload-data')) - var initialLayerCount = Object.keys(this.map.datalayers).length + var initialLayerCount = Object.keys(map.datalayers).length formatSelect = qs('.umap-upload select[name="format"]') textarea = qs('.umap-upload textarea') textarea.value = @@ -277,12 +276,12 @@ describe('L.U.Map', function () { formatSelect.value = 'umap' submit = qs('.umap-upload input[type="button"]') happen.click(submit) - assert.equal(Object.keys(this.map.datalayers).length, initialLayerCount + 2) - assert.equal(this.map.options.name, 'Imported map') + assert.equal(Object.keys(map.datalayers).length, initialLayerCount + 2) + assert.equal(map.options.name, 'Imported map') var foundFirstLayer = false var foundSecondLayer = false - for (var idx in this.map.datalayers) { - var datalayer = this.map.datalayers[idx] + for (var idx in map.datalayers) { + var datalayer = map.datalayers[idx] if (datalayer.options.name === 'Cities') { foundFirstLayer = true assert.equal(datalayer._index.length, 2) @@ -297,7 +296,7 @@ describe('L.U.Map', function () { }) it('should only import options on the whitelist (umap format import)', function () { - assert.equal(this.map.options.umap_id, 99) + assert.equal(map.options.umap_id, 99) }) it('should update title bar (umap format import)', function () { @@ -318,7 +317,7 @@ describe('L.U.Map', function () { it('should set the tilelayer (umap format import)', function () { assert.equal( - this.map.selected_tilelayer._url, + map.selected_tilelayer._url, 'http://{s}.tile.stamen.com/watercolor/{z}/{x}/{y}.jpg' ) }) @@ -327,14 +326,14 @@ describe('L.U.Map', function () { describe('#localizeUrl()', function () { it('should replace known variables', function () { assert.equal( - this.map.localizeUrl('http://example.org/{zoom}'), - 'http://example.org/' + this.map.getZoom() + map.localizeUrl('http://example.org/{zoom}'), + 'http://example.org/' + map.getZoom() ) }) it('should keep unknown variables', function () { assert.equal( - this.map.localizeUrl('http://example.org/{unkown}'), + map.localizeUrl('http://example.org/{unkown}'), 'http://example.org/{unkown}' ) }) diff --git a/umap/static/umap/test/Marker.js b/umap/static/umap/test/Marker.js index daf3d327..fc846b4c 100644 --- a/umap/static/umap/test/Marker.js +++ b/umap/static/umap/test/Marker.js @@ -1,20 +1,24 @@ -describe('L.U.Marker', function () { - before(function () { - this.server = sinon.fakeServer.create() - var datalayer_response = JSON.parse(JSON.stringify(RESPONSES.datalayer62_GET)) // Copy. +describe('L.U.Marker', () => { + let map, datalayer + before(async () => { + const datalayer_response = JSON.parse(JSON.stringify(RESPONSES.datalayer62_GET)) // Copy. datalayer_response._umap_options.iconClass = 'Drop' - this.server.respondWith(/\/datalayer\/62\/\?.*/, JSON.stringify(datalayer_response)) - this.map = initMap({ umap_id: 99 }) - this.datalayer = this.map.getDataLayerByUmapId(62) - this.server.respond() + await fetchMock.mock(/\/datalayer\/62\/\?.*/, datalayer_response) + this.options = { + umap_id: 99, + } + MAP = map = initMap({ umap_id: 99 }) + const datalayer_options = defaultDatalayerData() + await map.initDataLayers([datalayer_options]) + datalayer = map.getDataLayerByUmapId(62) }) - after(function () { - this.server.restore() + after(() => { + fetchMock.restore() resetMap() }) - describe('#iconClassChange()', function () { - it('should change icon class', function () { + describe('#iconClassChange()', () => { + it('should change icon class', () => { enableEdit() happen.click(qs('div.umap-drop-icon')) happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit')) @@ -35,8 +39,8 @@ describe('L.U.Marker', function () { }) }) - describe('#iconSymbolChange()', function () { - it('should change icon symbol', function () { + describe('#iconSymbolChange()', () => { + it('should change icon symbol', () => { enableEdit() happen.click(qs('div.umap-drop-icon')) happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit')) @@ -58,8 +62,8 @@ describe('L.U.Marker', function () { }) }) - describe('#iconClassChange()', function () { - it('should change icon class', function () { + describe('#iconClassChange()', () => { + it('should change icon class', () => { enableEdit() happen.click(qs('div.umap-drop-icon')) happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit')) @@ -80,15 +84,15 @@ describe('L.U.Marker', function () { }) }) - describe('#clone', function () { - it('should clone marker', function () { - var layer = new L.U.Marker(this.map, [10, 20], { - datalayer: this.datalayer, - }).addTo(this.datalayer) - assert.equal(this.datalayer._index.length, 4) + describe('#clone', () => { + it('should clone marker', () => { + var layer = new L.U.Marker(map, [10, 20], { + datalayer: datalayer, + }).addTo(datalayer) + assert.equal(datalayer._index.length, 4) other = layer.clone() - assert.ok(this.map.hasLayer(other)) - assert.equal(this.datalayer._index.length, 5) + assert.ok(map.hasLayer(other)) + assert.equal(datalayer._index.length, 5) // Must not be the same reference assert.notEqual(layer._latlng, other._latlng) assert.equal(L.Util.formatNum(layer._latlng.lat), other._latlng.lat) @@ -97,20 +101,20 @@ describe('L.U.Marker', function () { }) describe('#edit()', function (done) { - it('should allow changing coordinates manually', function () { - var layer = new L.U.Marker(this.map, [10, 20], { - datalayer: this.datalayer, - }).addTo(this.datalayer) + it('should allow changing coordinates manually', () => { + var layer = new L.U.Marker(map, [10, 20], { + datalayer: datalayer, + }).addTo(datalayer) enableEdit() layer.edit() changeInputValue(qs('form.umap-form input[name="lat"]'), '54.43') assert.equal(layer._latlng.lat, 54.43) }) - it('should not allow invalid latitude nor longitude', function () { - var layer = new L.U.Marker(this.map, [10, 20], { - datalayer: this.datalayer, - }).addTo(this.datalayer) + it('should not allow invalid latitude nor longitude', () => { + var layer = new L.U.Marker(map, [10, 20], { + datalayer: datalayer, + }).addTo(datalayer) enableEdit() layer.edit() changeInputValue(qs('form.umap-form input[name="lat"]'), '5443') diff --git a/umap/static/umap/test/Polygon.js b/umap/static/umap/test/Polygon.js index 3c53d0c3..1f4f45bf 100644 --- a/umap/static/umap/test/Polygon.js +++ b/umap/static/umap/test/Polygon.js @@ -1,14 +1,14 @@ describe('L.U.Polygon', function () { - var p2ll, map + var p2ll, map, datalayer before(function () { - this.map = map = initMap({ umap_id: 99 }) + map = initMap({ umap_id: 99 }) enableEdit() p2ll = function (x, y) { return map.containerPointToLatLng([x, y]) } - this.datalayer = this.map.createDataLayer() - this.datalayer.connectToMap() + datalayer = map.createDataLayer() + datalayer.connectToMap() }) after(function () { @@ -17,32 +17,32 @@ describe('L.U.Polygon', function () { }) afterEach(function () { - this.datalayer.empty() + datalayer.empty() }) describe('#isMulti()', function () { it('should return false for basic Polygon', function () { var layer = new L.U.Polygon( - this.map, + map, [ [1, 2], [3, 4], [5, 6], ], - { datalayer: this.datalayer } + { datalayer: datalayer } ) assert.notOk(layer.isMulti()) }) it('should return false for nested basic Polygon', function () { var latlngs = [[[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]]], - layer = new L.U.Polygon(this.map, latlngs, { datalayer: this.datalayer }) + layer = new L.U.Polygon(map, latlngs, { datalayer: datalayer }) assert.notOk(layer.isMulti()) }) it('should return false for simple Polygon with hole', function () { var layer = new L.U.Polygon( - this.map, + map, [ [ [1, 2], @@ -55,7 +55,7 @@ describe('L.U.Polygon', function () { [11, 12], ], ], - { datalayer: this.datalayer } + { datalayer: datalayer } ) assert.notOk(layer.isMulti()) }) @@ -77,7 +77,7 @@ describe('L.U.Polygon', function () { ], ], ] - var layer = new L.U.Polygon(this.map, latLngs, { datalayer: this.datalayer }) + var layer = new L.U.Polygon(map, latLngs, { datalayer: datalayer }) assert.ok(layer.isMulti()) }) @@ -103,7 +103,7 @@ describe('L.U.Polygon', function () { ], ], ] - var layer = new L.U.Polygon(this.map, latLngs, { datalayer: this.datalayer }) + var layer = new L.U.Polygon(map, latLngs, { datalayer: datalayer }) assert.ok(layer.isMulti()) }) }) @@ -120,27 +120,27 @@ describe('L.U.Polygon', function () { [[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]], [[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)]], ], - layer = new L.U.Polygon(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) + layer = new L.U.Polygon(map, latlngs, { + datalayer: datalayer, + }).addTo(datalayer) happen.once(layer._path, { type: 'contextmenu' }) assert.equal(qst('Remove shape from the multi'), 1) }) it('should not allow to remove shape when not multi', function () { var latlngs = [[[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]]], - layer = new L.U.Polygon(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) + layer = new L.U.Polygon(map, latlngs, { + datalayer: datalayer, + }).addTo(datalayer) happen.once(layer._path, { type: 'contextmenu' }) assert.notOk(qst('Remove shape from the multi')) }) it('should not allow to isolate shape when not multi', function () { var latlngs = [[[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]]], - layer = new L.U.Polygon(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) + layer = new L.U.Polygon(map, latlngs, { + datalayer: datalayer, + }).addTo(datalayer) happen.once(layer._path, { type: 'contextmenu' }) assert.notOk(qst('Extract shape to separate feature')) }) @@ -150,9 +150,9 @@ describe('L.U.Polygon', function () { [[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]], [[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)]], ], - layer = new L.U.Polygon(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) + layer = new L.U.Polygon(map, latlngs, { + datalayer: datalayer, + }).addTo(datalayer) happen.once(layer._path, { type: 'contextmenu' }) assert.ok(qst('Extract shape to separate feature')) }) @@ -162,9 +162,9 @@ describe('L.U.Polygon', function () { [[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]], [[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)]], ], - layer = new L.U.Polygon(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) + layer = new L.U.Polygon(map, latlngs, { + datalayer: datalayer, + }).addTo(datalayer) happen.once(layer._path, { type: 'contextmenu' }) assert.notOk(qst('Transform to lines')) }) @@ -176,26 +176,26 @@ describe('L.U.Polygon', function () { [p2ll(120, 150), p2ll(150, 180), p2ll(180, 120)], ], ], - layer = new L.U.Polygon(this.map, latlngs, { - datalayer: this.datalayer, - }).addTo(this.datalayer) + layer = new L.U.Polygon(map, latlngs, { + datalayer: datalayer, + }).addTo(datalayer) happen.once(layer._path, { type: 'contextmenu' }) assert.notOk(qst('Transform to lines')) }) it('should allow to transform to lines when not multi', function () { var latlngs = [[[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]]] - new L.U.Polygon(this.map, latlngs, { datalayer: this.datalayer }).addTo( - this.datalayer + new L.U.Polygon(map, latlngs, { datalayer: datalayer }).addTo( + datalayer ) happen.at('contextmenu', 150, 150) assert.equal(qst('Transform to lines'), 1) }) it('should not allow to transfer shape when not editedFeature', function () { - new L.U.Polygon(this.map, [p2ll(100, 150), p2ll(100, 200), p2ll(200, 150)], { - datalayer: this.datalayer, - }).addTo(this.datalayer) + new L.U.Polygon(map, [p2ll(100, 150), p2ll(100, 200), p2ll(200, 150)], { + datalayer: datalayer, + }).addTo(datalayer) happen.at('contextmenu', 110, 160) assert.equal(qst('Delete this feature'), 1) // Make sure we have right clicked on the polygon. assert.notOk(qst('Transfer shape to edited feature')) @@ -203,13 +203,13 @@ describe('L.U.Polygon', function () { it('should not allow to transfer shape when editedFeature is not a polygon', function () { var layer = new L.U.Polygon( - this.map, + map, [p2ll(100, 150), p2ll(100, 200), p2ll(200, 150)], - { datalayer: this.datalayer } - ).addTo(this.datalayer), - other = new L.U.Polyline(this.map, [p2ll(200, 250), p2ll(200, 300)], { - datalayer: this.datalayer, - }).addTo(this.datalayer) + { datalayer: datalayer } + ).addTo(datalayer), + other = new L.U.Polyline(map, [p2ll(200, 250), p2ll(200, 300)], { + datalayer: datalayer, + }).addTo(datalayer) other.edit() happen.once(layer._path, { type: 'contextmenu' }) assert.equal(qst('Delete this feature'), 1) // Make sure we have right clicked on the polygon. @@ -217,18 +217,18 @@ describe('L.U.Polygon', function () { }) it('should allow to transfer shape when another polygon is edited', function () { - this.datalayer.empty() + datalayer.empty() var layer = new L.U.Polygon( - this.map, + map, [p2ll(200, 300), p2ll(300, 200), p2ll(200, 100)], - { datalayer: this.datalayer } - ).addTo(this.datalayer) + { datalayer: datalayer } + ).addTo(datalayer) layer.edit() // This moves the map to put "other" at the center. var other = new L.U.Polygon( - this.map, + map, [p2ll(100, 150), p2ll(100, 200), p2ll(200, 150)], - { datalayer: this.datalayer } - ).addTo(this.datalayer) + { datalayer: datalayer } + ).addTo(datalayer) happen.once(other._path, { type: 'contextmenu' }) assert.equal(qst('Transfer shape to edited feature'), 1) layer.remove() @@ -242,19 +242,19 @@ describe('L.U.Polygon', function () { }) it('"add shape" control should be visible when editing a Polygon', function () { - var layer = new L.U.Polygon(this.map, [p2ll(100, 100), p2ll(100, 200)], { - datalayer: this.datalayer, - }).addTo(this.datalayer) + var layer = new L.U.Polygon(map, [p2ll(100, 100), p2ll(100, 200)], { + datalayer: datalayer, + }).addTo(datalayer) layer.edit() assert.ok(qs('.umap-draw-polygon-multi')) }) it('"add shape" control should extend the same multi', function () { var layer = new L.U.Polygon( - this.map, + map, [p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)], - { datalayer: this.datalayer } - ).addTo(this.datalayer) + { datalayer: datalayer } + ).addTo(datalayer) layer.edit() assert.notOk(layer.isMulti()) happen.click(qs('.umap-draw-polygon-multi')) @@ -264,26 +264,26 @@ describe('L.U.Polygon', function () { happen.at('click', 350, 300) happen.at('click', 350, 300) assert.ok(layer.isMulti()) - assert.equal(this.datalayer._index.length, 1) + assert.equal(datalayer._index.length, 1) }) }) describe('#transferShape', function () { it('should transfer simple polygon shape to another polygon', function () { var latlngs = [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - layer = new L.U.Polygon(this.map, latlngs, { datalayer: this.datalayer }).addTo( - this.datalayer + layer = new L.U.Polygon(map, latlngs, { datalayer: datalayer }).addTo( + datalayer ), other = new L.U.Polygon( - this.map, + map, [p2ll(200, 350), p2ll(200, 300), p2ll(300, 200)], - { datalayer: this.datalayer } - ).addTo(this.datalayer) - assert.ok(this.map.hasLayer(layer)) + { datalayer: datalayer } + ).addTo(datalayer) + assert.ok(map.hasLayer(layer)) layer.transferShape(p2ll(150, 150), other) assert.equal(other._latlngs.length, 2) assert.deepEqual(other._latlngs[1][0], latlngs) - assert.notOk(this.map.hasLayer(layer)) + assert.notOk(map.hasLayer(layer)) }) it('should transfer multipolygon shape to another polygon', function () { @@ -294,19 +294,19 @@ describe('L.U.Polygon', function () { ], [[p2ll(200, 300), p2ll(300, 200)]], ], - layer = new L.U.Polygon(this.map, latlngs, { datalayer: this.datalayer }).addTo( - this.datalayer + layer = new L.U.Polygon(map, latlngs, { datalayer: datalayer }).addTo( + datalayer ), other = new L.U.Polygon( - this.map, + map, [p2ll(200, 350), p2ll(200, 300), p2ll(300, 200)], - { datalayer: this.datalayer } - ).addTo(this.datalayer) - assert.ok(this.map.hasLayer(layer)) + { datalayer: datalayer } + ).addTo(datalayer) + assert.ok(map.hasLayer(layer)) layer.transferShape(p2ll(150, 150), other) assert.equal(other._latlngs.length, 2) assert.deepEqual(other._latlngs[1][0], latlngs[0][0]) - assert.ok(this.map.hasLayer(layer)) + assert.ok(map.hasLayer(layer)) assert.equal(layer._latlngs.length, 1) }) }) @@ -314,14 +314,14 @@ describe('L.U.Polygon', function () { describe('#isolateShape', function () { it('should not allow to isolate simple polygon', function () { var latlngs = [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - layer = new L.U.Polygon(this.map, latlngs, { datalayer: this.datalayer }).addTo( - this.datalayer + layer = new L.U.Polygon(map, latlngs, { datalayer: datalayer }).addTo( + datalayer ) - assert.equal(this.datalayer._index.length, 1) - assert.ok(this.map.hasLayer(layer)) + assert.equal(datalayer._index.length, 1) + assert.ok(map.hasLayer(layer)) layer.isolateShape(p2ll(150, 150)) assert.equal(layer._latlngs[0].length, 3) - assert.equal(this.datalayer._index.length, 1) + assert.equal(datalayer._index.length, 1) }) it('should isolate multipolygon shape', function () { @@ -332,17 +332,17 @@ describe('L.U.Polygon', function () { ], [[p2ll(200, 300), p2ll(300, 200)]], ], - layer = new L.U.Polygon(this.map, latlngs, { datalayer: this.datalayer }).addTo( - this.datalayer + layer = new L.U.Polygon(map, latlngs, { datalayer: datalayer }).addTo( + datalayer ) - assert.equal(this.datalayer._index.length, 1) - assert.ok(this.map.hasLayer(layer)) + assert.equal(datalayer._index.length, 1) + assert.ok(map.hasLayer(layer)) var other = layer.isolateShape(p2ll(150, 150)) - assert.equal(this.datalayer._index.length, 2) + assert.equal(datalayer._index.length, 2) assert.equal(other._latlngs.length, 2) assert.deepEqual(other._latlngs[0], latlngs[0][0]) - assert.ok(this.map.hasLayer(layer)) - assert.ok(this.map.hasLayer(other)) + assert.ok(map.hasLayer(layer)) + assert.ok(map.hasLayer(other)) assert.equal(layer._latlngs.length, 1) other.remove() }) @@ -351,13 +351,13 @@ describe('L.U.Polygon', function () { describe('#clone', function () { it('should clone polygon', function () { var latlngs = [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)], - layer = new L.U.Polygon(this.map, latlngs, { datalayer: this.datalayer }).addTo( - this.datalayer + layer = new L.U.Polygon(map, latlngs, { datalayer: datalayer }).addTo( + datalayer ) - assert.equal(this.datalayer._index.length, 1) + assert.equal(datalayer._index.length, 1) other = layer.clone() - assert.ok(this.map.hasLayer(other)) - assert.equal(this.datalayer._index.length, 2) + assert.ok(map.hasLayer(other)) + assert.equal(datalayer._index.length, 2) // Must not be the same reference assert.notEqual(layer._latlngs, other._latlngs) assert.equal(L.Util.formatNum(layer._latlngs[0][0].lat), other._latlngs[0][0].lat) diff --git a/umap/static/umap/test/TableEditor.js b/umap/static/umap/test/TableEditor.js index 5036a407..aa0578af 100644 --- a/umap/static/umap/test/TableEditor.js +++ b/umap/static/umap/test/TableEditor.js @@ -1,50 +1,54 @@ -describe('L.TableEditor', function () { - var path = '/map/99/datalayer/edit/62/' +describe('L.TableEditor', () => { + let path = '/map/99/datalayer/edit/62/', + datalayer - before(function () { - this.server = sinon.fakeServer.create() - this.server.respondWith( + before(async () => { + await fetchMock.mock( /\/datalayer\/62\/\?.*/, JSON.stringify(RESPONSES.datalayer62_GET) ) - this.map = initMap({ umap_id: 99 }) - this.datalayer = this.map.getDataLayerByUmapId(62) - this.server.respond() + this.options = { + umap_id: 99, + } + map = initMap({ umap_id: 99 }) + const datalayer_options = defaultDatalayerData() + await map.initDataLayers([datalayer_options]) + datalayer = map.getDataLayerByUmapId(62) enableEdit() }) - after(function () { + after(() => { + fetchMock.restore() clickCancel() - this.server.restore() resetMap() }) - describe('#open()', function () { + describe('#open()', () => { var button - it('should exist table click on edit mode', function () { + it('should exist table click on edit mode', () => { button = qs( - '#browse_data_toggle_' + L.stamp(this.datalayer) + ' .layer-table-edit' + '#browse_data_toggle_' + L.stamp(datalayer) + ' .layer-table-edit' ) expect(button).to.be.ok }) - it('should open table button click', function () { + it('should open table button click', () => { happen.click(button) expect(qs('#umap-ui-container div.table')).to.be.ok expect(qsa('#umap-ui-container div.table form').length).to.eql(3) // One per feature. expect(qsa('#umap-ui-container div.table input').length).to.eql(3) // One per feature and per property. }) }) - describe('#properties()', function () { + describe('#properties()', () => { var feature - before(function () { - var firstIndex = this.datalayer._index[0] - feature = this.datalayer._layers[firstIndex] + before(() => { + var firstIndex = datalayer._index[0] + feature = datalayer._layers[firstIndex] }) - it('should create new property column', function () { - var newPrompt = function () { + it('should create new property column', () => { + var newPrompt = () => { return 'newprop' } var oldPrompt = window.prompt @@ -56,7 +60,7 @@ describe('L.TableEditor', function () { window.prompt = oldPrompt }) - it('should populate feature property on fill', function () { + it('should populate feature property on fill', () => { var input = qs( 'form#umap-feature-properties_' + L.stamp(feature) + ' input[name=newprop]' ) @@ -64,8 +68,8 @@ describe('L.TableEditor', function () { expect(feature.properties.newprop).to.eql('the value') }) - it('should update property name on update click', function () { - var newPrompt = function () { + it('should update property name on update click', () => { + var newPrompt = () => { return 'newname' } var oldPrompt = window.prompt @@ -79,9 +83,9 @@ describe('L.TableEditor', function () { window.prompt = oldPrompt }) - it('should update property on delete click', function () { + it('should update property on delete click', () => { var oldConfirm, - newConfirm = function () { + newConfirm = () => { return true } oldConfirm = window.confirm diff --git a/umap/static/umap/test/_pre.js b/umap/static/umap/test/_pre.js index 30d407d3..85a7c8ea 100644 --- a/umap/static/umap/test/_pre.js +++ b/umap/static/umap/test/_pre.js @@ -213,7 +213,6 @@ function initMap(options) { }, }, } - default_options.properties.datalayers.push(defaultDatalayerData()) options = options || {} options.properties = L.extend({}, default_options.properties, options) options.geometry = { @@ -386,10 +385,6 @@ var RESPONSES = { }, } -sinon.fakeServer.flush = function () { - this.responses = [] -} - var kml_example = '' + '' + diff --git a/umap/static/umap/test/index.html b/umap/static/umap/test/index.html index d802ed73..6935dcbd 100644 --- a/umap/static/umap/test/index.html +++ b/umap/static/umap/test/index.html @@ -35,7 +35,6 @@ - @@ -76,6 +75,10 @@ + - @@ -95,7 +97,6 @@ - diff --git a/umap/templates/registration/login.html b/umap/templates/registration/login.html index c9edc372..d88bffe6 100644 --- a/umap/templates/registration/login.html +++ b/umap/templates/registration/login.html @@ -1,37 +1,51 @@ -{% load i18n %} -{% if ENABLE_ACCOUNT_LOGIN %} -
{% trans "Please log in with your account" %}
-
- {% if form.non_field_errors %} -
+ {% endif %} + +{% endblock content %} diff --git a/umap/templates/umap/branding.html b/umap/templates/umap/branding.html new file mode 100644 index 00000000..b6fb9931 --- /dev/null +++ b/umap/templates/umap/branding.html @@ -0,0 +1,3 @@ +

+ {{ SITE_NAME }} +

diff --git a/umap/templates/umap/content.html b/umap/templates/umap/content.html index a9daf056..ecabe18c 100644 --- a/umap/templates/umap/content.html +++ b/umap/templates/umap/content.html @@ -36,51 +36,26 @@ {% block bottom_js %} {{ block.super }} {% endblock bottom_js %} {% block footer %} diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index 26edc820..7d7a245f 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -36,7 +36,6 @@ - diff --git a/umap/templates/umap/navigation.html b/umap/templates/umap/navigation.html index fcc41fe0..f8d2dba9 100644 --- a/umap/templates/umap/navigation.html +++ b/umap/templates/umap/navigation.html @@ -1,9 +1,7 @@ {% load i18n %}