diff --git a/karma.conf.cjs b/karma.conf.cjs index f6becc4c8..f8c5cbc8c 100644 --- a/karma.conf.cjs +++ b/karma.conf.cjs @@ -19,7 +19,6 @@ module.exports = function (config) { files: [ 'node_modules/sinon/pkg/sinon.js', 'node_modules/happen/happen.js', - 'node_modules/fetch-mock/es5/client-bundle.js', { pattern: 'dist/rapid.js', included: true }, { pattern: 'dist/rapid.css', included: true }, { pattern: 'dist/**/*', included: false }, diff --git a/modules/main_dev.js b/modules/main_dev.js index 698b5b7e6..cd88498a7 100644 --- a/modules/main_dev.js +++ b/modules/main_dev.js @@ -22,6 +22,8 @@ window.Rapid.isDebug = true; // For dev build, we'll bundle additional things // that are useful for testing or debugging. +import fetchMock from 'fetch-mock'; +window.fetchMock = fetchMock; // Include rapid-sdk as a single `sdk` namespace. // (This works because we know there are no name conflicts) diff --git a/package-lock.json b/package-lock.json index bbe7a3e38..c4ddd2fd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,7 +71,7 @@ "esbuild": "^0.24.0", "esbuild-visualizer": "^0.6.0", "eslint": "^9.17.0", - "fetch-mock": "^9.11.0", + "fetch-mock": "^12.2.0", "gaze": "^1.1.3", "glob": "^10.4.5", "globals": "^15.13.0", @@ -342,17 +342,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.25.6", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.25.0", "dev": true, @@ -1269,6 +1258,13 @@ "version": "7946.0.14", "license": "MIT" }, + "node_modules/@types/glob-to-regexp": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@types/glob-to-regexp/-/glob-to-regexp-0.4.4.tgz", + "integrity": "sha512-nDKoaKJYbnn1MZxUY0cA1bPmmgZbg0cTq7Rh13d0KWYNOiKbqoR+2d89SnRPszGh7ROzSwZ/GOjZ4jPbmmZ6Eg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/gradient-parser": { "version": "0.1.5", "license": "MIT" @@ -2924,6 +2920,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "dev": true, @@ -3742,35 +3748,20 @@ "license": "MIT" }, "node_modules/fetch-mock": { - "version": "9.11.0", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.2.0.tgz", + "integrity": "sha512-XjgxM582kB0SzPOqH2UdGTwSqga8A8aBPjxcYr0wTeOlCWpZoK6zBrPzltECUTu6Zt3VTWafmKF599LN9BRN5Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.0.0", - "@babel/runtime": "^7.0.0", - "core-js": "^3.0.0", - "debug": "^4.1.1", - "glob-to-regexp": "^0.4.0", - "is-subset": "^0.1.1", - "lodash.isequal": "^4.5.0", - "path-to-regexp": "^2.2.1", - "querystring": "^0.2.0", - "whatwg-url": "^6.5.0" + "@types/glob-to-regexp": "^0.4.4", + "dequal": "^2.0.3", + "glob-to-regexp": "^0.4.1", + "is-subset-of": "^3.1.10", + "regexparam": "^3.0.0" }, "engines": { - "node": ">=4.0.0" - }, - "funding": { - "type": "charity", - "url": "https://www.justgiving.com/refugee-support-europe" - }, - "peerDependencies": { - "node-fetch": "*" - }, - "peerDependenciesMeta": { - "node-fetch": { - "optional": true - } + "node": ">=18.11.0" } }, "node_modules/fflate": { @@ -5024,10 +5015,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-subset": { - "version": "0.1.1", + "node_modules/is-subset-of": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/is-subset-of/-/is-subset-of-3.1.10.tgz", + "integrity": "sha512-avvaYgVmYWyaZ1NDFiv4y9JGkrE2je3op1Po4VYKKJKR8H2qVPsg1GZuuXl5elCTxTlwAIsrAjWAs4BVrISFRw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "typedescriptor": "3.0.2" + } }, "node_modules/is-symbol": { "version": "1.0.4", @@ -5835,11 +5832,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.keys": { "version": "3.1.2", "dev": true, @@ -5860,11 +5852,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.template": { "version": "3.6.2", "dev": true, @@ -7135,11 +7122,6 @@ "dev": true, "license": "ISC" }, - "node_modules/path-to-regexp": { - "version": "2.4.0", - "dev": true, - "license": "MIT" - }, "node_modules/path-type": { "version": "3.0.0", "dev": true, @@ -7559,14 +7541,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystring": { - "version": "0.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/quickselect": { "version": "2.0.0", "license": "ISC" @@ -7800,11 +7774,6 @@ "node": ">=0.10.0" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "dev": true, - "license": "MIT" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "dev": true, @@ -7822,6 +7791,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexparam": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", + "integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/remap-istanbul": { "version": "0.9.6", "dev": true, @@ -9068,22 +9047,6 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/tr46/node_modules/punycode": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/trim-newlines": { "version": "1.0.0", "dev": true, @@ -9204,6 +9167,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typedescriptor": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/typedescriptor/-/typedescriptor-3.0.2.tgz", + "integrity": "sha512-hyVbaCUd18UiXk656g/imaBLMogpdijIEpnhWYrSda9rhvO4gOU16n2nh7xG5lv/rjumnZzGOdz0CEGTmFe0fQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT" + }, "node_modules/typewise": { "version": "1.0.3", "license": "MIT", @@ -9458,25 +9429,10 @@ "pbf": "bin/pbf" } }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/wgs84": { "version": "0.0.0", "license": "BSD-2-Clause" }, - "node_modules/whatwg-url": { - "version": "6.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "node_modules/which": { "version": "4.0.0", "dev": true, diff --git a/package.json b/package.json index 5b28feb6e..099bc2cd0 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "esbuild": "^0.24.0", "esbuild-visualizer": "^0.6.0", "eslint": "^9.17.0", - "fetch-mock": "^9.11.0", + "fetch-mock": "^12.2.0", "gaze": "^1.1.3", "glob": "^10.4.5", "globals": "^15.13.0", diff --git a/test/browser/core/AssetSystem.test.js b/test/browser/core/AssetSystem.test.js index 40727b5b9..9c8325592 100644 --- a/test/browser/core/AssetSystem.test.js +++ b/test/browser/core/AssetSystem.test.js @@ -128,7 +128,7 @@ describe('AssetSystem', () => { }); it('returns a promise to fetch data if we do not already have the data', () => { - fetchMock.mock('/data/intro_graph.min.json', { + fetchMock.route(/\/data\/intro_graph\.min\.json/i, { body: JSON.stringify({ value: 'success' }), status: 200, headers: { 'Content-Type': 'application/json' } @@ -140,7 +140,7 @@ describe('AssetSystem', () => { .then(data => { expect(data).to.be.an('object'); expect(data.value).to.eql('success'); - fetchMock.resetHistory(); + fetchMock.removeRoutes().clearHistory(); }); }); diff --git a/test/browser/services/KartaviewService.test.js b/test/browser/services/KartaviewService.test.js index f4ab8779d..68c07f9d3 100644 --- a/test/browser/services/KartaviewService.test.js +++ b/test/browser/services/KartaviewService.test.js @@ -22,11 +22,15 @@ describe('KartaviewService', () => { beforeEach(() => { - fetchMock.reset(); + fetchMock.removeRoutes().clearHistory(); _kartaview = new Rapid.KartaviewService(new MockContext()); return _kartaview.initAsync(); }); + afterEach(() => { + fetchMock.removeRoutes().clearHistory(); + }); + describe('#initAsync', () => { it('initializes cache', () => { @@ -84,14 +88,14 @@ describe('KartaviewService', () => { totalFilteredItems: ['3'] }; - fetchMock.mock(new RegExp('/nearby-photos/'), { + fetchMock.route(/nearby-photos/, { body: JSON.stringify(nearbyResponse), status: 200, headers: { 'Content-Type': 'application/json' } }); _kartaview.on('loadedData', () => { - expect(fetchMock.calls().length).to.eql(1); // after /photo/?sequenceId=100 + expect(fetchMock.callHistory.calls().length).to.eql(1); // after /photo/?sequenceId=100 done(); }); @@ -133,7 +137,7 @@ describe('KartaviewService', () => { totalFilteredItems: ['3'] }; - fetchMock.mock(new RegExp('/nearby-photos/'), { + fetchMock.route(/nearby-photos/, { body: JSON.stringify(nearbyResponse), status: 200, headers: { 'Content-Type': 'application/json' } @@ -147,7 +151,7 @@ describe('KartaviewService', () => { window.setTimeout(() => { expect(spy.notCalled).to.be.ok; - expect(fetchMock.calls().length).to.eql(0); // no tile requests of any kind + expect(fetchMock.callHistory.calls().length).to.eql(0); // no tile requests of any kind done(); }, 20); }); diff --git a/test/browser/services/MapillaryService.test.js b/test/browser/services/MapillaryService.test.js index a6ecab0c3..a6797a377 100644 --- a/test/browser/services/MapillaryService.test.js +++ b/test/browser/services/MapillaryService.test.js @@ -21,7 +21,7 @@ describe('MapillaryService', () => { beforeEach(() => { - fetchMock.reset(); + fetchMock.removeRoutes().clearHistory(); _mapillary = new Rapid.MapillaryService(new MockContext()); // Mock function for retieving tile data.. The original expects a protobuffer vector tile. @@ -30,6 +30,10 @@ describe('MapillaryService', () => { return _mapillary.initAsync(); }); + afterEach(() => { + fetchMock.removeRoutes().clearHistory(); + }); + describe('#initAsync', () => { it('initializes cache', () => { @@ -171,14 +175,14 @@ describe('MapillaryService', () => { describe('#loadTiles', () => { it('fires loadedImages when image tiles are loaded', done => { - fetchMock.mock(new RegExp('/mly1(_computed)?_public/'), { + fetchMock.route(/mly1(_computed)?_public/, { body: '{"data":[]}', status: 200, headers: { 'Content-Type': 'application/json' } }); _mapillary.on('loadedImages', () => { - expect(fetchMock.calls().length).to.eql(1); + expect(fetchMock.callHistory.calls().length).to.eql(1); done(); }); @@ -188,7 +192,7 @@ describe('MapillaryService', () => { it('does not load tiles around Null Island', done => { const spy = sinon.spy(); - fetchMock.mock(new RegExp('/mly1(_computed)?_public/'), { + fetchMock.route(/mly1(_computed)?_public/, { body: '{"data":[]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -200,7 +204,7 @@ describe('MapillaryService', () => { window.setTimeout(() => { expect(spy.notCalled).to.be.ok; - expect(fetchMock.calls().length).to.eql(0); // no tile requests of any kind + expect(fetchMock.callHistory.calls().length).to.eql(0); // no tile requests of any kind done(); }, 20); }); diff --git a/test/browser/services/NominatimService.test.js b/test/browser/services/NominatimService.test.js index a219e7a0c..dca5a8f6c 100644 --- a/test/browser/services/NominatimService.test.js +++ b/test/browser/services/NominatimService.test.js @@ -15,20 +15,20 @@ describe('NominatimService', () => { } beforeEach(() => { - fetchMock.reset(); - fetchMock.mock(/reverse\?.*lat=48&lon=16/, { + fetchMock.removeRoutes().clearHistory(); + fetchMock.route(/reverse\?.*lat=48&lon=16/, { body: '{"address":{"country_code":"at"}}', status: 200, headers: { 'Content-Type': 'application/json' } }); - fetchMock.mock(/reverse\?.*lat=49&lon=17/, { + fetchMock.route(/reverse\?.*lat=49&lon=17/, { body: '{"address":{"country_code":"cz"}}', status: 200, headers: { 'Content-Type': 'application/json' } }); - fetchMock.mock(/reverse\?.*lat=1000&lon=1000/, { + fetchMock.route(/reverse\?.*lat=1000&lon=1000/, { body: '{"error":"Unable to geocode"}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -38,6 +38,10 @@ describe('NominatimService', () => { return nominatim.initAsync(); }); + afterEach(() => { + fetchMock.removeRoutes().clearHistory(); + }); + function parseQueryString(url) { return Rapid.sdk.utilStringQs(url.substring(url.indexOf('?'))); @@ -50,7 +54,7 @@ describe('NominatimService', () => { nominatim.countryCode([16, 48], callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16' } ); expect(callback.calledOnceWithExactly(null, 'at')).to.be.ok; @@ -66,17 +70,17 @@ describe('NominatimService', () => { nominatim.reverse([16, 48], callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16' } ); expect(callback.calledOnceWithExactly(null, { address: { country_code:'at' }})).to.be.ok; - fetchMock.resetHistory(); + fetchMock.clearHistory(); callback = sinon.spy(); nominatim.reverse([17, 49], callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { zoom: '13', format: 'json', addressdetails: '1', lat: '49', lon: '17' } ); expect(callback.calledOnceWithExactly(null, { address: { country_code:'cz' }})).to.be.ok; @@ -90,12 +94,12 @@ describe('NominatimService', () => { nominatim.reverse([16, 48], callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { zoom: '13', format: 'json', addressdetails: '1', lat: '48', lon: '16' } ); expect(callback.calledOnceWithExactly(null, { address: { country_code:'at' }})).to.be.ok; - fetchMock.resetHistory(); + fetchMock.clearHistory(); callback = sinon.spy(); nominatim.reverse([16.000001, 48.000001], callback); @@ -113,7 +117,7 @@ describe('NominatimService', () => { nominatim.reverse([1000, 1000], callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { zoom: '13', format: 'json', addressdetails: '1', lat: '1000', lon: '1000' } ); expect(callback.calledOnceWithExactly('Unable to geocode')).to.be.ok; @@ -126,7 +130,7 @@ describe('NominatimService', () => { describe('#search', () => { it('calls the given callback with the results of the search query', done => { const callback = sinon.spy(); - fetchMock.mock(/search/, { + fetchMock.route(/search/, { body: '[{"place_id":"158484588","osm_type":"relation","osm_id":"188022","boundingbox":["39.867005","40.1379593","-75.2802976","-74.9558313"],"lat":"39.9523993","lon":"-75.1635898","display_name":"Philadelphia, Philadelphia County, Pennsylvania, United States of America","class":"place","type":"city","importance":0.83238050437778}]', status: 200, headers: { 'Content-Type': 'application/json' } @@ -135,7 +139,7 @@ describe('NominatimService', () => { nominatim.search('philadelphia', callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql({q: 'philadelphia', format: 'json', limit: '10' }); + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql({q: 'philadelphia', format: 'json', limit: '10' }); expect(callback.calledOnce).to.be.ok; done(); }, 50); diff --git a/test/browser/services/OsmService.test.js b/test/browser/services/OsmService.test.js index 56af5011f..27a7afae2 100644 --- a/test/browser/services/OsmService.test.js +++ b/test/browser/services/OsmService.test.js @@ -22,7 +22,7 @@ describe('OsmService', () => { beforeEach(() => { spy = sinon.spy(); - fetchMock.reset(); + fetchMock.removeRoutes().clearHistory(); const capabilitiesJSON = `{ @@ -175,10 +175,10 @@ describe('OsmService', () => { }`; fetchMock - .mock(/api\/capabilities\.json/, { status: 200, body: capabilitiesJSON, headers: { 'Content-Type': 'application/json' } }) - .mock(/api\/capabilities(?!\.json)/, { status: 200, body: capabilitiesXML, headers: { 'Content-Type': 'application/xml' } }) - .mock(/user\/details\.json/, { status: 200, body: userJSON, headers: { 'Content-Type': 'application/json' } }) - .mock(/changesets\.json/, { status: 200, body: changesetJSON, headers: { 'Content-Type': 'application/json' } }); + .route(/api\/capabilities\.json/, { status: 200, body: capabilitiesJSON, headers: { 'Content-Type': 'application/json' } }) + .route(/api\/capabilities(?!\.json)/, { status: 200, body: capabilitiesXML, headers: { 'Content-Type': 'application/xml' } }) + .route(/user\/details\.json/, { status: 200, body: userJSON, headers: { 'Content-Type': 'application/json' } }) + .route(/changesets\.json/, { status: 200, body: changesetJSON, headers: { 'Content-Type': 'application/json' } }); _osm = new Rapid.OsmService(new MockContext()); @@ -189,7 +189,7 @@ describe('OsmService', () => { afterEach(() => { _osm.throttledReloadApiStatus.cancel(); - fetchMock.reset(); + fetchMock.removeRoutes().clearHistory(); }); @@ -322,7 +322,7 @@ describe('OsmService', () => { const okResponse = { status: 200, body: body, headers: { 'Content-Type': 'application/json' } }; it('returns an object', done => { - fetchMock.mock(/map\.json/, okResponse); + fetchMock.route(/map\.json/, okResponse); _osm.loadFromAPI(path, (err, result) => { expect(err).to.not.be.ok; @@ -336,21 +336,21 @@ describe('OsmService', () => { const badResponse = { status: 400, body: 'Bad Request', headers: { 'Content-Type': 'text/plain' } }; fetchMock - .mock((url, { headers }) => /map\.json/.test(url) && !!headers?.Authorization, badResponse) - .mock((url, { headers }) => /map\.json/.test(url) && !headers?.Authorization, okResponse); + .route(match => /map\.json/.test(match.url) && match.options.headers?.authorization, badResponse) + .route(match => /map\.json/.test(match.url) && !match.options.headers?.authorization, okResponse); loginAsync() .then(() => { - fetchMock.resetHistory(); + fetchMock.clearHistory(); _osm.loadFromAPI(path, (err, result) => { expect(err).to.be.not.ok; expect(typeof result).to.eql('object'); expect(_osm.authenticated()).to.be.not.ok; - const calls = fetchMock.calls(); + const calls = fetchMock.callHistory.calls(); expect(calls).to.have.lengthOf.at.least(2); // auth, unauth, capabilities - expect(calls[0][1]).to.have.nested.property('headers.Authorization'); - expect(calls[1][1]).to.not.have.nested.property('headers.Authorization'); + expect(calls[0].options.headers || {}).to.have.property('authorization'); + expect(calls[1].options.headers || {}).to.not.have.property('authorization'); done(); }); }); @@ -361,21 +361,21 @@ describe('OsmService', () => { const badResponse = { status: 401, body: 'Unauthorized', headers: { 'Content-Type': 'text/plain' } }; fetchMock - .mock((url, { headers }) => /map\.json/.test(url) && !!headers?.Authorization, badResponse) - .mock((url, { headers }) => /map\.json/.test(url) && !headers?.Authorization, okResponse); + .route(match => /map\.json/.test(match.url) && match.options.headers?.authorization, badResponse) + .route(match => /map\.json/.test(match.url) && !match.options.headers?.authorization, okResponse); loginAsync() .then(() => { - fetchMock.resetHistory(); + fetchMock.clearHistory(); _osm.loadFromAPI(path, (err, result) => { expect(err).to.be.not.ok; expect(typeof result).to.eql('object'); expect(_osm.authenticated()).to.be.not.ok; - const calls = fetchMock.calls(); + const calls = fetchMock.callHistory.calls(); expect(calls).to.have.lengthOf.at.least(2); // auth, unauth, capabilities - expect(calls[0][1]).to.have.nested.property('headers.Authorization'); - expect(calls[1][1]).to.not.have.nested.property('headers.Authorization'); + expect(calls[0].options.headers || {}).to.have.property('authorization'); + expect(calls[1].options.headers || {}).to.not.have.property('authorization'); done(); }); }); @@ -384,22 +384,22 @@ describe('OsmService', () => { it('retries an authenticated call unauthenticated if 403 Forbidden', done => { const badResponse = { status: 403, body: 'Forbidden', headers: { 'Content-Type': 'text/plain' } }; - fetchMock - .mock((url, { headers }) => /map\.json/.test(url) && !!headers?.Authorization, badResponse) - .mock((url, { headers }) => /map\.json/.test(url) && !headers?.Authorization, okResponse); + fetchMock + .route(match => /map\.json/.test(match.url) && match.options.headers?.authorization, badResponse) + .route(match => /map\.json/.test(match.url) && !match.options.headers?.authorization, okResponse); loginAsync() .then(() => { - fetchMock.resetHistory(); + fetchMock.clearHistory(); _osm.loadFromAPI(path, (err, result) => { expect(err).to.be.not.ok; expect(typeof result).to.eql('object'); expect(_osm.authenticated()).to.be.not.ok; - const calls = fetchMock.calls(); + const calls = fetchMock.callHistory.calls(); expect(calls).to.have.lengthOf.at.least(2); // auth, unauth, capabilities - expect(calls[0][1]).to.have.nested.property('headers.Authorization'); - expect(calls[1][1]).to.not.have.nested.property('headers.Authorization'); + expect(calls[0].options.headers || {}).to.have.property('authorization'); + expect(calls[1].options.headers || {}).to.not.have.property('authorization'); done(); }); }); @@ -423,7 +423,7 @@ describe('OsmService', () => { const partialResponse = { status: 200, body: partialBody, headers: { 'Content-Type': 'application/json' } }; it('returns a partial JSON error', done => { - fetchMock.mock(/map\.json/, partialResponse); + fetchMock.route(/map\.json/, partialResponse); _osm.loadFromAPI(path, err => { expect(err.message).to.eql('Partial JSON'); @@ -450,7 +450,7 @@ describe('OsmService', () => { }); it('calls callback when data tiles are loaded', done => { - fetchMock.mock(/map\.json/, { + fetchMock.route(/map\.json/, { body: tileBody, status: 200, headers: { 'Content-Type': 'application/json' } @@ -497,7 +497,7 @@ describe('OsmService', () => { }`; it('loads a node', done => { - fetchMock.mock(/node\/1\.json/, { + fetchMock.route(/node\/1\.json/, { body: nodeBody, status: 200, headers: { 'Content-Type': 'application/json' } @@ -513,7 +513,7 @@ describe('OsmService', () => { it('loads a way', done => { - fetchMock.mock(/way\/1\/full\.json/, { + fetchMock.route(/way\/1\/full\.json/, { body: wayBody, status: 200, headers: { 'Content-Type': 'application/json' } @@ -529,7 +529,7 @@ describe('OsmService', () => { it('does not ignore repeat requests', done => { - fetchMock.mock(/node\/1\.json/, { + fetchMock.route(/node\/1\.json/, { body: nodeBody, status: 200, headers: { 'Content-Type': 'application/json' } @@ -568,7 +568,7 @@ describe('OsmService', () => { }`; it('loads a node', done => { - fetchMock.mock(/node\/1\/1\.json/, { + fetchMock.route(/node\/1\/1\.json/, { body: nodeBody, status: 200, headers: { 'Content-Type': 'application/json' } @@ -584,7 +584,7 @@ describe('OsmService', () => { it('loads a way', done => { - fetchMock.mock(/way\/1\/1\.json/, { + fetchMock.route(/way\/1\/1\.json/, { body: wayBody, status: 200, headers: { 'Content-Type': 'application/json' } @@ -600,7 +600,7 @@ describe('OsmService', () => { it('does not ignore repeat requests', done => { - fetchMock.mock(/node\/1\/1\.json/, { + fetchMock.route(/node\/1\/1\.json/, { body: nodeBody, status: 200, headers: { 'Content-Type': 'application/json' } @@ -722,14 +722,14 @@ describe('OsmService', () => { `; it('fires loadedNotes when notes are loaded', done => { - fetchMock.mock(/notes\?/, { + fetchMock.route(/notes\?/, { body: notesBody, status: 200, headers: { 'Content-Type': 'text/xml' } }); _osm.on('loadedNotes', () => { - expect(fetchMock.calls()).to.have.lengthOf.at.least(1); + expect(fetchMock.callHistory.calls()).to.have.lengthOf.at.least(1); done(); }); diff --git a/test/browser/services/OsmWikibaseService.test.js b/test/browser/services/OsmWikibaseService.test.js index 1092731b7..ab55741a5 100644 --- a/test/browser/services/OsmWikibaseService.test.js +++ b/test/browser/services/OsmWikibaseService.test.js @@ -2,11 +2,15 @@ describe('OsmWikibaseService', () => { let _wikibase; beforeEach(() => { - fetchMock.reset(); + fetchMock.removeRoutes().clearHistory(); _wikibase = new Rapid.OsmWikibaseService(); return _wikibase.initAsync(); }); + afterEach(() => { + fetchMock.removeRoutes().clearHistory(); + }); + function parseQueryString(url) { return Rapid.sdk.utilStringQs(url.substring(url.indexOf('?'))); @@ -253,7 +257,7 @@ describe('OsmWikibaseService', () => { describe('#getEntity', () => { it('calls the given callback with the results of the getEntity data item query', done => { const callback = sinon.spy(); - fetchMock.mock(/action=wbgetentities/, { + fetchMock.route(/action=wbgetentities/, { body: JSON.stringify({ entities: { Q42: keyData(), @@ -269,7 +273,7 @@ describe('OsmWikibaseService', () => { _wikibase.getEntity({ key: 'amenity', value: 'parking', langCodes: ['fr'] }, callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { action: 'wbgetentities', sites: 'wiki', diff --git a/test/browser/services/StreetsideService.test.js b/test/browser/services/StreetsideService.test.js index cbf09e740..3a11abed0 100644 --- a/test/browser/services/StreetsideService.test.js +++ b/test/browser/services/StreetsideService.test.js @@ -21,11 +21,15 @@ describe('StreetsideService', () => { beforeEach(() => { - fetchMock.reset(); + fetchMock.removeRoutes().clearHistory(); _streetside = new Rapid.StreetsideService(new MockContext()); return _streetside.initAsync(); }); + afterEach(() => { + fetchMock.removeRoutes().clearHistory(); + }); + describe('#initAsync', () => { it('initializes cache', () => { @@ -70,7 +74,7 @@ describe('StreetsideService', () => { } ]; - fetchMock.mock(/StreetSideBubbleMetaData/, { + fetchMock.route(/StreetSideBubbleMetaData/, { body: JSON.stringify(data), status: 200, headers: { 'Content-Type': 'text/plain' } @@ -109,7 +113,7 @@ describe('StreetsideService', () => { } ]; - fetchMock.mock(/StreetSideBubbleMetaData/, { + fetchMock.route(/StreetSideBubbleMetaData/, { body: JSON.stringify(data), status: 200, headers: { 'Content-Type': 'text/plain' } @@ -119,7 +123,7 @@ describe('StreetsideService', () => { window.setTimeout(() => { expect(spy.notCalled).to.be.ok; - expect(fetchMock.calls().length).to.eql(0); // no tile requests of any kind + expect(fetchMock.callHistory.calls().length).to.eql(0); // no tile requests of any kind done(); }, 20); }); diff --git a/test/browser/services/TaginfoService.test.js b/test/browser/services/TaginfoService.test.js index da2607d12..d2decdbf8 100644 --- a/test/browser/services/TaginfoService.test.js +++ b/test/browser/services/TaginfoService.test.js @@ -16,18 +16,22 @@ describe('TaginfoService', () => { beforeEach(() => { -// fetchMock.reset(); -// fetchMock.mock(new RegExp('\/keys\/all.*sortname=values_all'), { + fetchMock.removeRoutes().clearHistory(); +// fetchMock.route(new RegExp('\/keys\/all.*sortname=values_all'), { // body: '{"data":[{"count_all":56136034,"key":"name","count_all_fraction":0.0132}]}', // status: 200, // headers: { 'Content-Type': 'application/json' } // }); // note - init() used to fetch these common values, this has been moved to startAsync(). - fetchMock.reset(); + taginfo = new Rapid.TaginfoService(new MockContext()); return taginfo.initAsync(); }); + afterEach(() => { + fetchMock.removeRoutes().clearHistory(); + }); + function parseQueryString(url) { return Rapid.sdk.utilStringQs(url.substring(url.indexOf('?'))); @@ -36,7 +40,7 @@ describe('TaginfoService', () => { describe('#keys', () => { it('calls the given callback with the results of the keys query', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0}]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -46,7 +50,7 @@ describe('TaginfoService', () => { taginfo.keys({ query: 'amen' }, callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { query: 'amen', page: '1', rp: '10', sortname: 'count_all', sortorder: 'desc', lang: 'en' } ); expect(callback.calledOnceWithExactly(null, [{ title: 'amenity', value: 'amenity' }] )).to.be.ok; @@ -55,7 +59,7 @@ describe('TaginfoService', () => { }); it('includes popular keys', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"count_nodes":500000,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},' + '{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes":100}]}', status: 200, @@ -72,7 +76,7 @@ describe('TaginfoService', () => { }); it('includes popular keys with an entity type filter', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"count_nodes":500000,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},' + '{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes":100}]}', status: 200, @@ -89,7 +93,7 @@ describe('TaginfoService', () => { }); it('includes unpopular keys with a wiki page', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},' + '{"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes_fraction":0.0, "in_wiki": true}]}', status: 200, @@ -109,7 +113,7 @@ describe('TaginfoService', () => { }); it('sorts keys with \':\' below keys without \':\'', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"key":"ref:bag","count_all":9790586,"count_all_fraction":0.0028},' + '{"key":"ref","count_all":7933528,"count_all_fraction":0.0023}]}', status: 200, @@ -130,7 +134,7 @@ describe('TaginfoService', () => { describe('#multikeys', () => { it('calls the given callback with the results of the multikeys query', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"count_all":69593,"key":"recycling:glass","count_all_fraction":0.0}]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -140,7 +144,7 @@ describe('TaginfoService', () => { taginfo.multikeys({ query: 'recycling:' }, callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { query: 'recycling:', page: '1', rp: '25', sortname: 'count_all', sortorder: 'desc', lang: 'en' } ); expect(callback.calledOnceWithExactly( @@ -151,7 +155,7 @@ describe('TaginfoService', () => { }); it('excludes multikeys with extra colons', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"count_all":4426,"key":"service:bicycle:retail","count_all_fraction":0.0},' + '{"count_all":22,"key":"service:bicycle:retail:ebikes","count_all_fraction":0.0}]}', status: 200, @@ -170,7 +174,7 @@ describe('TaginfoService', () => { }); it('excludes multikeys with wrong prefix', done => { - fetchMock.mock(/\/keys\/all/, { + fetchMock.route(/\/keys\/all/, { body: '{"data":[{"count_all":4426,"key":"service:bicycle:retail","count_all_fraction":0.0},' + '{"count_all":22,"key":"disused:service:bicycle","count_all_fraction":0.0}]}', status: 200, @@ -191,7 +195,7 @@ describe('TaginfoService', () => { describe('#values', () => { it('calls the given callback with the results of the values query', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"parking","description":"A place for parking cars", "fraction":0.1}]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -201,7 +205,7 @@ describe('TaginfoService', () => { taginfo.values({ key: 'amenity', query: 'par' }, callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( {key: 'amenity', query: 'par', page: '1', rp: '25', sortname: 'count_all', sortorder: 'desc', lang: 'en'} ); expect(callback.calledOnceWithExactly( @@ -212,7 +216,7 @@ describe('TaginfoService', () => { }); it('includes popular values', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"parking","description":"A place for parking cars", "fraction":1.0},' + '{"value":"party","description":"A place for partying", "fraction":0.0}]}', status: 200, @@ -231,7 +235,7 @@ describe('TaginfoService', () => { }); it('does not get values for extremely popular keys', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"Rue Pasteur","description":"", "fraction":0.0001},' + '{"value":"Via Trieste","description":"", "fraction":0.0001}]}', status: 200, @@ -248,7 +252,7 @@ describe('TaginfoService', () => { }); it('excludes values with capital letters and some punctuation', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"parking","description":"A place for parking cars", "fraction":0.2},' + '{"value":"PArking","description":"A common misspelling", "fraction":0.2},' + '{"value":"parking;partying","description":"A place for parking cars *and* partying", "fraction":0.2},' @@ -270,7 +274,7 @@ describe('TaginfoService', () => { }); it('includes network values with capital letters and some punctuation', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"US:TX:FM","description":"Farm to Market Roads in the U.S. state of Texas.", "fraction":0.34},' + '{"value":"US:KY","description":"Primary and secondary state highways in the U.S. state of Kentucky.", "fraction":0.31},' + '{"value":"US:US","description":"U.S. routes in the United States.", "fraction":0.19},' @@ -296,7 +300,7 @@ describe('TaginfoService', () => { }); it('includes biological genus values with capital letters', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"Quercus","description":"Oak", "fraction":0.5}]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -312,7 +316,7 @@ describe('TaginfoService', () => { }); it('includes biological taxon values with capital letters', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"Quercus robur","description":"Oak", "fraction":0.5}]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -328,7 +332,7 @@ describe('TaginfoService', () => { }); it('includes biological species values with capital letters', done => { - fetchMock.mock(/\/key\/values/, { + fetchMock.route(/\/key\/values/, { body: '{"data":[{"value":"Quercus robur","description":"Oak", "fraction":0.5}]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -346,7 +350,7 @@ describe('TaginfoService', () => { describe('#roles', () => { it('calls the given callback with the results of the roles query', done => { - fetchMock.mock(/\/relation\/roles/, { + fetchMock.route(/\/relation\/roles/, { body: '{"data":[{"role":"stop","count_relation_members_fraction":0.1757},' + '{"role":"south","count_relation_members_fraction":0.0035}]}', status: 200, @@ -357,7 +361,7 @@ describe('TaginfoService', () => { taginfo.roles({ rtype: 'route', query: 's', geometry: 'relation' }, callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql( + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql( { rtype: 'route', query: 's', page: '1', rp: '25', sortname: 'count_relation_members', sortorder: 'desc', lang: 'en' } ); expect(callback.calledOnceWithExactly(null, [ @@ -371,7 +375,7 @@ describe('TaginfoService', () => { describe('#docs', () => { it('calls the given callback with the results of the docs query', done => { - fetchMock.mock(/\/tag\/wiki_page/, { + fetchMock.route(/\/tag\/wiki_page/, { body: '{"data":[{"on_way":false,"lang":"en","on_area":true,"image":"File:Car park2.jpg"}]}', status: 200, headers: { 'Content-Type': 'application/json' } @@ -381,7 +385,7 @@ describe('TaginfoService', () => { taginfo.docs({ key: 'amenity', value: 'parking' }, callback); window.setTimeout(() => { - expect(parseQueryString(fetchMock.lastUrl())).to.eql({ key: 'amenity', value: 'parking' }); + expect(parseQueryString(fetchMock.callHistory.lastCall().url)).to.eql({ key: 'amenity', value: 'parking' }); expect(callback.calledOnceWithExactly( null, [{ on_way: false, lang: 'en', on_area: true, image: 'File:Car park2.jpg' }] )).to.be.ok; diff --git a/test/index.html b/test/index.html index 1085736a0..093ae0ce9 100644 --- a/test/index.html +++ b/test/index.html @@ -22,7 +22,6 @@ - diff --git a/test/spec_helpers.js b/test/spec_helpers.js index 840160cb9..78e094768 100644 --- a/test/spec_helpers.js +++ b/test/spec_helpers.js @@ -19,7 +19,6 @@ mocha.setup({ ] }); -delete window.PointerEvent; // force the brower to use mouse events +delete window.PointerEvent; // force the browser to use mouse events -fetchMock.config.fallbackToNetwork = false; -fetchMock.config.overwriteRoutes = false; +window.fetchMock.mockGlobal();