From fbc55d3b19a42e3799176978f5184e4e7168008f Mon Sep 17 00:00:00 2001 From: sronveaux Date: Fri, 20 Sep 2024 09:54:18 +0200 Subject: [PATCH 1/4] Refactored Geocoder component --- src/components/geocoder/Geocoder.vue | 39 +++++--- src/components/geocoder/GeocoderController.js | 45 ++++----- src/components/geocoder/helpers/ajax.js | 96 ------------------- 3 files changed, 49 insertions(+), 131 deletions(-) delete mode 100644 src/components/geocoder/helpers/ajax.js diff --git a/src/components/geocoder/Geocoder.vue b/src/components/geocoder/Geocoder.vue index 72fa249f..3ff0050d 100644 --- a/src/components/geocoder/Geocoder.vue +++ b/src/components/geocoder/Geocoder.vue @@ -42,6 +42,7 @@ import { GeocoderController } from './GeocoderController'; import { applyTransform } from 'ol/extent'; import { getTransform, fromLonLat } from 'ol/proj'; import ViewAnimationUtil from '../../util/ViewAnimation'; +import axios from 'axios'; export default { name: 'wgu-geocoder-input', @@ -61,7 +62,7 @@ export default { }, data () { return { - results: [], + results: null, lastQueryStr: '', noFilter: true, search: null, @@ -98,19 +99,25 @@ export default { }, // Query by string - should return list of selection items (adresses) for ComboBox querySelections (queryStr) { + if (this.timeout) { + clearTimeout(this.timeout); + } this.timeout = setTimeout(() => { // Let Geocoder Provider do the query // items (item.text fields) will be shown in combobox dropdown suggestions this.trace(`geocoderController.query: ${queryStr}`); this.geocoderController.query(queryStr) .then(results => this.onQueryResults(results)) - .catch(err => this.onQueryError(err)) + .catch(err => { + if (!axios.isCancel(err)) { + this.onQueryError(err); + } + }); + this.timeout = null; }, this.queryDelay); }, onQueryResults (results) { // Handle query results from GeocoderController - this.timeout && clearTimeout(this.timeout); - this.timeout = null; this.results = null; if (!results || results.length === 0) { @@ -130,9 +137,9 @@ export default { watch: { // Input string value changed search (queryStr) { - if (this.timeout || this.selecting) { - // Query or selection in progress - this.trace('query or selection in progress...'); + if (this.selecting) { + // Selection in progress + this.trace('selection in progress...'); return; } if (!queryStr || queryStr.length === 0) { @@ -146,8 +153,10 @@ export default { queryStr = queryStr.trim(); // Only query if minimal number chars typed and querystring has changed - queryStr.length >= this.minChars && queryStr !== this.lastQueryStr && this.querySelections(queryStr); - this.lastQueryStr = queryStr; + if (queryStr.length >= this.minChars && queryStr !== this.lastQueryStr) { + this.querySelections(queryStr); + this.lastQueryStr = queryStr; + } }, // User has selected entry from suggested items selected (item) { @@ -160,7 +169,6 @@ export default { // Position Map on result const result = item.value; const mapProjection = this.map.getView().getProjection(); - const coords = fromLonLat([result.lon, result.lat], mapProjection); // Prefer zooming to bounding box if present in result if (Object.prototype.hasOwnProperty.call(result, 'boundingbox')) { @@ -170,6 +178,7 @@ export default { ViewAnimationUtil.to(this.map.getView(), extent); } else { // No bbox in result: center on lon/lat from result and zoom in + const coords = fromLonLat([result.lon, result.lat], mapProjection); ViewAnimationUtil.to(this.map.getView(), coords); } this.selecting = false; @@ -177,7 +186,15 @@ export default { }, mounted () { // Setup GeocoderController to which we delegate Provider and query-handling - this.geocoderController = new GeocoderController(this.provider, this.providerOptions, this); + this.geocoderController = new GeocoderController(this.provider, this.providerOptions); + }, + destroyed () { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + + this.geocoderController.destroy(); } } diff --git a/src/components/geocoder/GeocoderController.js b/src/components/geocoder/GeocoderController.js index 1266b529..510fe1b1 100644 --- a/src/components/geocoder/GeocoderController.js +++ b/src/components/geocoder/GeocoderController.js @@ -24,7 +24,7 @@ import { OpenStreetMap } from './providers/osm'; import { Photon } from './providers/photon'; import { OpenCage } from './providers/opencage'; -import { json } from './helpers/ajax'; +import axios from 'axios'; // Geocoder Provider types export const PROVIDERS = { @@ -41,10 +41,10 @@ export class GeocoderController { * @constructor * @param {String} providerName name of Provider. * @param {Object} options config options for Provider - * @param {Object} parent callback parent */ - constructor (providerName, options, parent) { + constructor (providerName, options) { this.options = options; + this.abortController = null; // Must have Provider class defined for name if (!Object.prototype.hasOwnProperty.call(PROVIDERS, providerName)) { @@ -53,10 +53,21 @@ export class GeocoderController { } // Create Provider from name via Factory Method this.provider = new PROVIDERS[providerName](); - this.parent = parent; + } + + destroy () { + if (this.abortController) { + this.abortController.abort(); + this.abortController = null; + } } async query (q) { + if (this.abortController) { + this.abortController.abort(); + } + this.abortController = new AbortController(); + const parameters = this.provider.getParameters({ query: q, key: this.options.key, @@ -65,28 +76,14 @@ export class GeocoderController { limit: this.options.limit }); - const ajax = { + const request = { + method: 'GET', url: parameters.url, - data: parameters.params - }; - - // Optional XHR with JSONP (Provider-specific) - if (parameters.callbackName) { - ajax.jsonp = true; - ajax.callbackName = parameters.callbackName; + params: parameters.params, + signal: this.abortController?.signal } - const response = await json(ajax); - return this.provider.handleResponse(response) - - // // Do the query via Ajax XHR, returning JSON. Async callback via Promise. - // json(ajax) - // .then(response => { - // // Call back parent with data formatted by Provider - // this.parent.onQueryResult(this.provider.handleResponse(response)); - // }) - // .catch(err => { - // this.parent.onQueryResult(undefined, err); - // }); + const response = await axios(request); + return this.provider.handleResponse(response.data) } } diff --git a/src/components/geocoder/helpers/ajax.js b/src/components/geocoder/helpers/ajax.js deleted file mode 100644 index 6e7ed38b..00000000 --- a/src/components/geocoder/helpers/ajax.js +++ /dev/null @@ -1,96 +0,0 @@ -// Code modified from: https://github.com/jonataswalker/ol-geocoder version: July 30, 2019 -// The MIT License (MIT) -// -// Copyright (c) 2015 Jonatas Walker -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -// TODO: may use Axios/Axios-jsonp -export function json (obj) { - return new Promise((resolve, reject) => { - const url = encodeUrlXhr(obj.url, obj.data); - const config = { - method: 'GET', - mode: 'cors', - credentials: 'same-origin' - }; - - if (obj.jsonp) { - jsonp(url, obj.callbackName, resolve); - } else { - fetch(url, config) - .then(r => r.json()) - .then(resolve) - .catch(reject); - } - }); -} - -function toQueryString (obj) { - return Object.keys(obj) - .reduce((a, k) => { - a.push( - typeof obj[k] === 'object' - ? toQueryString(obj[k]) - : `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}` - ); - return a; - }, []) - .join('&'); -} - -function encodeUrlXhr (url, data) { - if (data && typeof data === 'object') { - url += (/\?/.test(url) ? '&' : '?') + toQueryString(data); - } - return url; -} - -function jsonp (url, key, callback) { - // https://github.com/Fresheyeball/micro-jsonp/blob/master/src/jsonp.js - const head = document.head; - const script = document.createElement('script'); - // generate minimally unique name for callback function - const callbackName = 'f' + Math.round(Math.random() * Date.now()); - - // set request url - script.setAttribute( - 'src', - /* add callback parameter to the url - where key is the parameter key supplied - and callbackName is the parameter value */ - url + (url.indexOf('?') > 0 ? '&' : '?') + key + '=' + callbackName - ); - - /* place jsonp callback on window, - the script sent by the server should call this - function as it was passed as a url parameter */ - window[callbackName] = data => { - window[callbackName] = undefined; - - // clean up script tag created for request - setTimeout(() => head.removeChild(script), 0); - - // hand data back to the user - callback(data); - }; - - // actually make the request - head.appendChild(script); -} From 28dc0e8a0b358beb8a2b4ebf0cd47563fd0c0f49 Mon Sep 17 00:00:00 2001 From: sronveaux Date: Fri, 20 Sep 2024 09:54:57 +0200 Subject: [PATCH 2/4] Added axios-mock-adapter to make some unit test easier to write --- package-lock.json | 55 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 56 insertions(+) diff --git a/package-lock.json b/package-lock.json index 39d5ffbd..97caccc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@vue/cli-service": "~5.0.8", "@vue/eslint-config-standard": "^8.0.1", "@vue/test-utils": "^1.1.3", + "axios-mock-adapter": "^2.0.0", "babel-plugin-istanbul": "^6.1.1", "chai": "^4.3.10", "copy-dir": "^1.2.0", @@ -4852,6 +4853,42 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axios-mock-adapter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.0.0.tgz", + "integrity": "sha512-D/K0J5Zm6KvaMTnsWrBQZWLzKN9GxUFZEa0mx2qeEHXDeTugCoplWehy8y36dj5vuSjhe1u/Dol8cZ8lzzmDew==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, + "node_modules/axios-mock-adapter/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "dev": true, @@ -23041,6 +23078,24 @@ "proxy-from-env": "^1.1.0" } }, + "axios-mock-adapter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.0.0.tgz", + "integrity": "sha512-D/K0J5Zm6KvaMTnsWrBQZWLzKN9GxUFZEa0mx2qeEHXDeTugCoplWehy8y36dj5vuSjhe1u/Dol8cZ8lzzmDew==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + } + } + }, "babel-core": { "version": "7.0.0-bridge.0", "dev": true, diff --git a/package.json b/package.json index 9ba778df..e4313c5e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@vue/cli-service": "~5.0.8", "@vue/eslint-config-standard": "^8.0.1", "@vue/test-utils": "^1.1.3", + "axios-mock-adapter": "^2.0.0", "babel-plugin-istanbul": "^6.1.1", "chai": "^4.3.10", "copy-dir": "^1.2.0", From fb4e9d8393f2ce00a1d2b1b0781b2ec75e9ce73e Mon Sep 17 00:00:00 2001 From: sronveaux Date: Fri, 20 Sep 2024 09:55:31 +0200 Subject: [PATCH 3/4] Updated and refactored Geocoder component unit tests --- .../components/geocoder/Geocoder.spec.js | 245 +++++++++--------- 1 file changed, 123 insertions(+), 122 deletions(-) diff --git a/tests/unit/specs/components/geocoder/Geocoder.spec.js b/tests/unit/specs/components/geocoder/Geocoder.spec.js index e9a0a8ab..eb3709e9 100644 --- a/tests/unit/specs/components/geocoder/Geocoder.spec.js +++ b/tests/unit/specs/components/geocoder/Geocoder.spec.js @@ -1,23 +1,26 @@ import { shallowMount } from '@vue/test-utils'; -import Vuetify from 'vuetify' +import Vuetify from 'vuetify'; import Geocoder from '@/components/geocoder/Geocoder'; -import { OpenStreetMap } from '../../../../../src/components/geocoder/providers/osm'; -import { Photon } from '../../../../../src/components/geocoder/providers/photon'; -import { OpenCage } from '../../../../../src/components/geocoder/providers/opencage'; +import { OpenStreetMap } from '@/components/geocoder/providers/osm'; +import { Photon } from '@/components/geocoder/providers/photon'; +import { OpenCage } from '@/components/geocoder/providers/opencage'; import OlMap from 'ol/Map'; -import { fromLonLat } from 'ol/proj'; -// import * as sinon from 'sinon'; +import { applyTransform, getCenter } from 'ol/extent'; +import { getTransform } from 'ol/proj'; + +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; describe('geocoder/Geocoder.vue', () => { const vuetify = new Vuetify(); // Inspect the raw component options it('is defined', () => { - expect(typeof Geocoder).to.not.equal('undefined'); + expect(Geocoder).to.not.be.undefined; }); it('has a mounted hook', () => { - expect(typeof Geocoder.mounted).to.equal('function'); + expect(Geocoder.mounted).to.be.a('function'); }); describe('props', () => { @@ -30,9 +33,9 @@ describe('geocoder/Geocoder.vue', () => { it('has correct default props', () => { expect(comp.vm.icon).to.equal('search'); - expect(comp.vm.rounded).to.equal(true); - expect(comp.vm.autofocus).to.equal(true); - expect(comp.vm.persistentHint).to.equal(true); + expect(comp.vm.rounded).to.be.true; + expect(comp.vm.autofocus).to.be.true; + expect(comp.vm.persistentHint).to.be.true; }); }); @@ -48,11 +51,11 @@ describe('geocoder/Geocoder.vue', () => { }); it('has correct default data and Provider', () => { - expect(vm.hideSearch).to.equal(true); + expect(vm.hideSearch).to.be.true; expect(vm.minChars).to.equal(3); expect(vm.queryDelay).to.equal(300); - expect(vm.geocoderController !== undefined).to.equal(true); - expect(vm.geocoderController.provider instanceof OpenStreetMap).to.equal(true); + expect(vm.geocoderController).to.not.be.undefined; + expect(vm.geocoderController.provider instanceof OpenStreetMap).to.be.true; }); }); @@ -76,11 +79,11 @@ describe('geocoder/Geocoder.vue', () => { }); it('has correct configured data and Provider', () => { - expect(vm.hideSearch).to.equal(true); + expect(vm.hideSearch).to.be.true; expect(vm.minChars).to.equal(5); expect(vm.queryDelay).to.equal(200); - expect(vm.geocoderController !== undefined).to.equal(true); - expect(vm.geocoderController.provider instanceof Photon).to.equal(true); + expect(vm.geocoderController).to.not.be.undefined; + expect(vm.geocoderController.provider instanceof Photon).to.be.true; }); }); @@ -104,19 +107,21 @@ describe('geocoder/Geocoder.vue', () => { }); it('has correct configured data and Provider', () => { - expect(vm.hideSearch).to.equal(true); + expect(vm.hideSearch).to.be.true; expect(vm.minChars).to.equal(6); expect(vm.queryDelay).to.equal(200); - expect(vm.geocoderController !== undefined).to.equal(true); - expect(vm.geocoderController.provider instanceof OpenCage).to.equal(true); + expect(vm.geocoderController).to.not.be.undefined; + expect(vm.geocoderController.provider instanceof OpenCage).to.be.true; }); }); describe('methods - search', () => { let comp; let vm; + let axiosMock; let onQueryResultsSpy; let onQueryErrorSpy; + const osmURL = 'https://nominatim.openstreetmap.org/search'; const fetchResults = JSON.stringify([ { lon: '7.0928944', @@ -157,13 +162,20 @@ describe('geocoder/Geocoder.vue', () => { country_code: 'de' } } - ]) - // let fakeXhr; - // let clock; - // let requests = []; + ]); + let clock; const queryString = 'Heerstraße 52 bonn'; let selectionItems; + function applyAxiosMock (error = false) { + axiosMock = new MockAdapter(axios); + if (!error) { + axiosMock.onGet(osmURL).reply(200, fetchResults); + } else { + axiosMock.onGet(osmURL).networkError(); + } + }; + beforeEach(() => { const moduleProps = { target: 'toolbar', @@ -176,113 +188,103 @@ describe('geocoder/Geocoder.vue', () => { }); vm = comp.vm; - onQueryResultsSpy = sinon.replace(vm, 'onQueryResults', sinon.fake(vm.onQueryResults)) - onQueryErrorSpy = sinon.replace(vm, 'onQueryError', sinon.fake(vm.onQueryError)) - // TODO: Sinon Fake XMLHttpRequest and Fake Timers did not work for us... - // clock = sinon.useFakeTimers(); - // global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); - // global.XMLHttpRequest.onCreate = function (xhr) { - // requests.push(xhr); - // }; + onQueryResultsSpy = sinon.replace(vm, 'onQueryResults', sinon.fake(vm.onQueryResults)); + onQueryErrorSpy = sinon.replace(vm, 'onQueryError', sinon.fake(vm.onQueryError)); + clock = sinon.useFakeTimers(); }); it('functions are implemented', () => { - expect(typeof vm.toggle).to.equal('function'); - expect(typeof vm.querySelections).to.equal('function'); - expect(typeof vm.onQueryResults).to.equal('function'); + expect(vm.toggle).to.be.a('function'); + expect(vm.querySelections).to.be.a('function'); + expect(vm.onQueryResults).to.be.a('function'); }); it('GeoCoderController calls remote service', done => { vm.geocoderController.query(queryString).then(results => { - expect(results === undefined).to.equal(false); - expect(results.length > 0).to.equal(true); - expect(results[0].address.road === 'Heerstraße').to.equal(true); + expect(results).to.not.be.undefined; + expect(results).to.not.be.empty; + expect(results[0].address.road).to.be('Heerstraße'); done(); }); }); - it('search watcher assigns last query string', done => { - sinon.replace(window, 'fetch', sinon.fake.resolves(new Response(fetchResults))) + it('search watcher assigns last query string', async () => { + applyAxiosMock(); vm.search = queryString; - vm.$nextTick(() => { - setTimeout(function () { - expect(vm.lastQueryStr === queryString).to.equal(true); - done(); - }, 50) - }); + await clock.tickAsync(200); + + expect(vm.lastQueryStr === queryString).to.equal(true); }); - it('search watcher query with results', done => { - sinon.replace(window, 'fetch', sinon.fake.resolves(new Response(fetchResults))) + it('search watcher query with results', async () => { + applyAxiosMock(); vm.search = queryString; - vm.$nextTick(() => { - expect(vm.lastQueryStr === queryString).to.equal(true); - - // We do a timeout, to beat setTimeout() with async query in Geocoder - // TODO find a more elegant way. sinon.useFakeTimers did not work for us. - setTimeout(function () { - expect(vm.results === undefined).to.equal(false); - expect(vm.results.length > 0).to.equal(true); - expect(vm.results[0].address.road === 'Heerstraße').to.equal(true); - - // Items from query result should be assigned to combobox - const comboBox = comp.findComponent({ name: 'v-combobox' }); - selectionItems = comboBox.vnode.data.attrs.items; - expect(selectionItems === undefined).to.equal(false); - expect(selectionItems.length === vm.results.length).to.equal(true); - done(); - }, 50); - }); + await clock.tickAsync(200); + + expect(vm.results).to.not.be.undefined; + expect(vm.results).to.not.be.empty; + expect(vm.results[0].address.road).to.equal('Heerstraße'); + + // Items from query result should be assigned to combobox + const comboBox = comp.findComponent({ name: 'v-combobox' }); + selectionItems = comboBox.vnode.data.attrs.items; + expect(selectionItems).to.not.be.undefined; + expect(selectionItems).to.have.length(vm.results.length); }); - it('select items watcher assigns result and zooms/centers Map at result', done => { + it('select items watcher assigns result and zooms/centers Map at result', async () => { + applyAxiosMock(); + vm.map = new OlMap(); + vm.search = queryString; + await clock.tickAsync(200); + + const comboBox = comp.findComponent({ name: 'v-combobox' }); + selectionItems = comboBox.vnode.data.attrs.items; vm.selected = selectionItems[0]; - vm.$nextTick(() => { - // Map center should be at coordinates from selected item - const mapCenter = vm.map.getView().getCenter(); - - // Map may have different projection than WGS84 - const coords = fromLonLat( - [selectionItems[0].value.lon, selectionItems[0].value.lat], - vm.map.getView().getProjection()); - expect(mapCenter[0] === coords[0]); - expect(mapCenter[1] === coords[1]); - done(); - }); + await vm.$nextTick(); + + // Map center should be at coordinates from selected item + const mapCenter = vm.map.getView().getCenter(); + + // Map may have different projection than WGS84 + const extent = applyTransform( + selectionItems[0].value.boundingbox, + getTransform('EPSG:4326', vm.map.getView().getProjection())); + const coords = getCenter(extent); + + expect(mapCenter[0]).to.equal(coords[0]); + expect(mapCenter[1]).to.equal(coords[1]); }); - it('calls onQueryResults if fetch successful', done => { - sinon.replace(window, 'fetch', sinon.fake.resolves(new Response(fetchResults))) + it('calls onQueryResults if fetch successful', async () => { + applyAxiosMock(); vm.search = queryString; - vm.$nextTick(() => { - setTimeout(function () { - expect(onQueryResultsSpy).to.have.been.called; - done(); - }, 50); - }); - }) + await clock.tickAsync(200); + + expect(onQueryResultsSpy).to.have.been.called; + }); - it('calls onQueryError if error during fetch', done => { - sinon.replace(window, 'fetch', sinon.fake.rejects()) + it('calls onQueryError if error during fetch', async () => { + applyAxiosMock(true); vm.search = queryString; - vm.$nextTick(() => { - setTimeout(function () { - expect(onQueryErrorSpy).to.have.been.called; - done(); - }, 50); - }); - }) + await clock.tickAsync(200); + + expect(onQueryErrorSpy).to.have.been.called; + }); afterEach(function () { - sinon.restore() + if (axiosMock) { + axiosMock.restore(); + } + // Like before we must clean up when tampering with globals. - // global.XMLHttpRequest.restore(); - // clock.restore(); + clock.restore(); + sinon.restore(); }); }); @@ -299,45 +301,44 @@ describe('geocoder/Geocoder.vue', () => { it('toggle show/hides search input', () => { vm.toggle(); - expect(vm.hideSearch).to.equal(false); + expect(vm.hideSearch).to.be.false; vm.toggle(); - expect(vm.hideSearch).to.equal(true); + expect(vm.hideSearch).to.be.true; }); - it('button click should toggle search input visibility', done => { + it('button click should toggle search input visibility', async () => { // Two subwidgets const button = comp.findComponent({ name: 'v-btn' }); const comboBox = comp.findComponent({ name: 'v-combobox' }); // Initial state - expect(vm.hideSearch).to.equal(true); + expect(vm.hideSearch).to.be.true; expect(comboBox.attributes('hidden')).to.equal('true'); // Make visible button.vm.$emit('click'); - vm.$nextTick(() => { - expect(vm.hideSearch).to.equal(false); - // So looks like the 'hidden' attr is simply removed/added through toggle()! - expect(comboBox.attributes('hidden')).to.equal(undefined); - - // And hide - button.vm.$emit('click'); - vm.$nextTick(() => { - expect(vm.hideSearch).to.equal(true); - expect(comboBox.attributes('hidden')).to.equal('true'); - done(); - }); - }); + await vm.$nextTick(); + + expect(vm.hideSearch).to.be.false; + // So looks like the 'hidden' attr is simply removed/added through toggle()! + expect(comboBox.attributes('hidden')).to.be.undefined; + + // And hide + button.vm.$emit('click'); + await vm.$nextTick(); + + expect(vm.hideSearch).to.be.true; + expect(comboBox.attributes('hidden')).to.equal('true'); }); - it('search input string should trigger search', done => { + it('search input string should trigger search', async () => { const queryString = 'heerstrasse 52 bonn'; + // Trigger watcher for search input string in combobox vm.search = queryString; - vm.$nextTick(() => { - expect(vm.lastQueryStr).to.equal(queryString); - done(); - }); + await vm.$nextTick(); + + expect(vm.lastQueryStr).to.equal(queryString); }); }); }); From 66f312b71b9c3ee494b332805dfebf1bb039e0bf Mon Sep 17 00:00:00 2001 From: sronveaux Date: Fri, 20 Sep 2024 14:11:32 +0200 Subject: [PATCH 4/4] Corrected error in Geocoder unit test --- tests/unit/specs/components/geocoder/Geocoder.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/specs/components/geocoder/Geocoder.spec.js b/tests/unit/specs/components/geocoder/Geocoder.spec.js index eb3709e9..d1fa0467 100644 --- a/tests/unit/specs/components/geocoder/Geocoder.spec.js +++ b/tests/unit/specs/components/geocoder/Geocoder.spec.js @@ -203,7 +203,7 @@ describe('geocoder/Geocoder.vue', () => { vm.geocoderController.query(queryString).then(results => { expect(results).to.not.be.undefined; expect(results).to.not.be.empty; - expect(results[0].address.road).to.be('Heerstraße'); + expect(results[0].address.road).to.equal('Heerstraße'); done(); }); });