diff --git a/spec/arcgis.spec.ts b/spec/arcgis.spec.ts index 8230b3a..39bde1e 100644 --- a/spec/arcgis.spec.ts +++ b/spec/arcgis.spec.ts @@ -1,12 +1,13 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; import { ArcGis } from '../src/geocoders/arcgis'; describe('L.Control.Geocoder.ArcGis', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new ArcGis(); const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?token=&SingleLine=Innsbruck&outFields=Addr_Type&forStorage=false&maxLocations=10&f=json', { spatialReference: { wkid: 4326, latestWkid: 4326 }, @@ -28,6 +29,7 @@ describe('L.Control.Geocoder.ArcGis', () => { () => geocoder.geocode('Innsbruck', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature = callback.mock.calls[0][0][0]; expect(feature.name).toBe('Innsbruck, Innsbruck-Stadt, Tirol'); expect(feature.center).toStrictEqual({ lat: 47.26800000000003, lng: 11.391300000000058 }); diff --git a/spec/google.spec.ts b/spec/google.spec.ts index 16226af..7a3bd2f 100644 --- a/spec/google.spec.ts +++ b/spec/google.spec.ts @@ -1,13 +1,14 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; import { Google } from '../src/geocoders/google'; import { GeocodingResult } from '../src/geocoders/api'; describe('L.Control.Geocoder.Google', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new Google({ apiKey: '0123xyz' }); const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://maps.googleapis.com/maps/api/geocode/json?key=0123xyz&address=Innsbruck', { results: [ @@ -71,6 +72,7 @@ describe('L.Control.Geocoder.Google', () => { () => geocoder.geocode('Innsbruck', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature: GeocodingResult = callback.mock.calls[0][0][0]; expect(feature.name).toBe('Innsbruck, Austria'); expect(feature.center).toStrictEqual({ lat: 47.2692124, lng: 11.4041024 }); diff --git a/spec/here.spec.ts b/spec/here.spec.ts index 88e4426..ac9c243 100644 --- a/spec/here.spec.ts +++ b/spec/here.spec.ts @@ -1,14 +1,15 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; import { HERE } from '../src/geocoders/here'; import { HEREv2 } from '../src/geocoders/here'; import { GeocodingResult } from '../src/geocoders/api'; describe('L.Control.Geocoder.HERE', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new HERE({ app_id: 'xxx', app_code: 'yyy' }); const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://geocoder.api.here.com/6.2/geocode.json?searchtext=Innsbruck&gen=9&app_id=xxx&app_code=yyy&jsonattributes=1&maxresults=5', { response: { @@ -77,6 +78,7 @@ describe('L.Control.Geocoder.HERE', () => { () => geocoder.geocode('Innsbruck', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature: GeocodingResult = callback.mock.calls[0][0][0]; expect(feature.name).toBe('Innsbruck, Tirol, Österreich'); expect(feature.center).toStrictEqual({ lat: 47.268, lng: 11.3913 }); @@ -89,11 +91,11 @@ describe('L.Control.Geocoder.HERE', () => { }); describe('L.Control.Geocoder.HEREv2', () => { - it('geocodes Innsbruck', () => { + it('geocodes Innsbruck', async () => { const geocodingParams = { at: '50.62925,3.057256' }; const geocoder = new HEREv2({ apiKey: 'xxx', geocodingQueryParams: geocodingParams }); const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://geocode.search.hereapi.com/v1/discover?q=Innsbruck&apiKey=xxx&limit=10&at=50.62925%2C3.057256', { items: [ @@ -167,6 +169,7 @@ describe('L.Control.Geocoder.HEREv2', () => { () => geocoder.geocode('Innsbruck', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature: GeocodingResult = callback.mock.calls[0][0][0]; expect(feature.name).toBe( 'Salumeria Italiana, 151 Richmond St, Boston, MA 02109, United States' diff --git a/spec/latlng.spec.ts b/spec/latlng.spec.ts index 3e5e3ad..6be6531 100644 --- a/spec/latlng.spec.ts +++ b/spec/latlng.spec.ts @@ -1,8 +1,9 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import * as L from 'leaflet'; import { LatLng } from '../src/geocoders/latlng'; describe('LatLng', () => { + afterEach(() =>vi.clearAllMocks()) // test cases from https://github.com/openstreetmap/openstreetmap-website/blob/master/test/controllers/geocoder_controller_test.rb let expected; beforeEach(() => { diff --git a/spec/mapbox.spec.ts b/spec/mapbox.spec.ts index a0f6c21..2b9242b 100644 --- a/spec/mapbox.spec.ts +++ b/spec/mapbox.spec.ts @@ -1,12 +1,13 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; import { Mapbox } from '../src/geocoders/mapbox'; describe('L.Control.Geocoder.Mapbox', () => { - it('geocodes Milwaukee Ave', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Milwaukee Ave', async () => { const geocoder = new Mapbox({ apiKey: '0123' }); const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://api.mapbox.com/geocoding/v5/mapbox.places/Milwaukee%20Ave.json?access_token=0123', { type: 'FeatureCollection', @@ -66,6 +67,7 @@ describe('L.Control.Geocoder.Mapbox', () => { () => geocoder.geocode('Milwaukee Ave', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature = callback.mock.calls[0][0][0]; expect(feature.name).toBe('825 Milwaukee Ave, Deerfield, Illinois 60015, United States'); expect(feature.center).toStrictEqual({ lat: 42.166602, lng: -87.921434 }); diff --git a/spec/mockFetchRequest.ts b/spec/mockFetchRequest.ts new file mode 100644 index 0000000..3cbe43a --- /dev/null +++ b/spec/mockFetchRequest.ts @@ -0,0 +1,14 @@ +import { expect, vi } from 'vitest'; + +export function mockFetchRequest(url: string, response: T, trigger: () => void) { + const headers = { Accept: 'application/json' }; + global.fetch = vi.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve(response) + }) + ) as any; + trigger(); + expect(fetch).toBeCalledTimes(1); + expect(fetch).toBeCalledWith(url, { headers }); +} diff --git a/spec/mockXMLHttpRequest.ts b/spec/mockXMLHttpRequest.ts deleted file mode 100644 index 1bb25ea..0000000 --- a/spec/mockXMLHttpRequest.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, vi } from 'vitest'; - -export function mockXMLHttpRequest(response: T): XMLHttpRequest { - const xhrMock: Partial = { - open: vi.fn(), - send: vi.fn(), - setRequestHeader: vi.fn(), - readyState: 4, - status: 200, - response - }; - vi.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock as XMLHttpRequest); - return xhrMock as XMLHttpRequest; -} - -export function testXMLHttpRequest(url: string, response: T, trigger: () => void) { - const xhrMock = mockXMLHttpRequest(response); - trigger(); - expect(xhrMock.open).toBeCalledWith('GET', url, true); - expect(xhrMock.setRequestHeader).toBeCalledWith('Accept', 'application/json'); - (xhrMock.onreadystatechange as EventListener)(new Event('')); -} diff --git a/spec/nominatim.spec.ts b/spec/nominatim.spec.ts index 1fecd11..81d7a9a 100644 --- a/spec/nominatim.spec.ts +++ b/spec/nominatim.spec.ts @@ -1,14 +1,15 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; import { Nominatim } from '../src/geocoders/nominatim'; describe('L.Control.Geocoder.Nominatim', () => { + afterEach(() => vi.clearAllMocks()); const geocoder = new Nominatim(); - it('geocodes Innsbruck', () => { + it('geocodes Innsbruck', async () => { const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://nominatim.openstreetmap.org/search?q=innsbruck&limit=5&format=json&addressdetails=1', [ { @@ -23,8 +24,7 @@ describe('L.Control.Geocoder.Nominatim', () => { class: 'boundary', type: 'administrative', importance: 0.763909048330467, - icon: - 'https://nominatim.openstreetmap.org/images/mapicons/poi_boundary_administrative.p.20.png', + icon: 'https://nominatim.openstreetmap.org/images/mapicons/poi_boundary_administrative.p.20.png', address: { city_district: 'Innsbruck', city: 'Innsbruck', @@ -38,6 +38,7 @@ describe('L.Control.Geocoder.Nominatim', () => { () => geocoder.geocode('innsbruck', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature = callback.mock.calls[0][0][0]; expect(feature.name).toBe('Innsbruck, Tyrol, Austria'); expect(feature.html).toBe( @@ -54,10 +55,10 @@ describe('L.Control.Geocoder.Nominatim', () => { expect(callback.mock.calls).toMatchSnapshot(); }); - it('reverse geocodes 47.3/11.3', () => { + it('reverse geocodes 47.3/11.3', async () => { const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://nominatim.openstreetmap.org/reverse?lat=47.3&lon=11.3&zoom=9&addressdetails=1&format=json', { place_id: 197718025, @@ -78,6 +79,7 @@ describe('L.Control.Geocoder.Nominatim', () => { () => geocoder.reverse({ lat: 47.3, lng: 11.3 }, 131000, callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature = callback.mock.calls[0][0][0]; expect(feature.name).toBe('Innsbruck-Land, Tyrol, Austria'); expect(feature.html).toBe('Tyrol Austria'); diff --git a/spec/pelias.spec.ts b/spec/pelias.spec.ts index 32e8f1c..a919844 100644 --- a/spec/pelias.spec.ts +++ b/spec/pelias.spec.ts @@ -1,13 +1,14 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; import { Openrouteservice } from '../src/geocoders/pelias'; describe('L.Control.Geocoder.Openrouteservice', () => { + afterEach(() => vi.clearAllMocks()); const geocoder = new Openrouteservice({ apiKey: '0123' }); - it('geocodes Innsbruck', () => { + it('geocodes Innsbruck', async () => { const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://api.openrouteservice.org/geocode/search?api_key=0123&text=innsbruck', { geocoding: { @@ -49,6 +50,7 @@ describe('L.Control.Geocoder.Openrouteservice', () => { () => geocoder.geocode('innsbruck', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature = callback.mock.calls[0][0][0]; expect(feature.name).toBe('Innsbruck, Austria'); expect(feature.center).toStrictEqual({ lat: 47.272308, lng: 11.407851 }); diff --git a/spec/photon.spec.ts b/spec/photon.spec.ts index 463720a..79f4239 100644 --- a/spec/photon.spec.ts +++ b/spec/photon.spec.ts @@ -1,13 +1,14 @@ -import { describe, expect, it, vi } from 'vitest'; -import { testXMLHttpRequest } from './mockXMLHttpRequest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { mockFetchRequest } from './mockFetchRequest'; import { Photon } from '../src/geocoders/photon'; import { GeocodingResult } from '../src/geocoders/api'; describe('L.Control.Geocoder.Photon', () => { - it('geocodes Innsbruck', () => { + afterEach(() => vi.clearAllMocks()); + it('geocodes Innsbruck', async () => { const geocoder = new Photon(); const callback = vi.fn(); - testXMLHttpRequest( + mockFetchRequest( 'https://photon.komoot.io/api/?q=Innsbruck', { features: [ @@ -54,6 +55,7 @@ describe('L.Control.Geocoder.Photon', () => { () => geocoder.geocode('Innsbruck', callback) ); + await vi.waitUntil(() => callback.mock.calls.length); const feature: GeocodingResult = callback.mock.calls[0][0][0]; expect(feature.name).toBe('Innsbruck, Innsbruck, Tyrol, Austria'); expect(feature.center).toStrictEqual({ lat: 47.2654296, lng: 11.3927685 }); diff --git a/src/util.ts b/src/util.ts index 0bf149c..61605c5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -56,31 +56,10 @@ export function getJSON( params: Record, callback: (message: any) => void ): void { - const xmlHttp = new XMLHttpRequest(); - xmlHttp.onreadystatechange = () => { - if (xmlHttp.readyState !== 4) { - return; - } - let message; - if (xmlHttp.status !== 200 && xmlHttp.status !== 304) { - message = ''; - } else if (typeof xmlHttp.response === 'string') { - // IE doesn't parse JSON responses even with responseType: 'json'. - try { - message = JSON.parse(xmlHttp.response); - } catch (e) { - // Not a JSON response - message = xmlHttp.response; - } - } else { - message = xmlHttp.response; - } - callback(message); - }; - xmlHttp.open('GET', url + getParamString(params), true); - xmlHttp.responseType = 'json'; - xmlHttp.setRequestHeader('Accept', 'application/json'); - xmlHttp.send(null); + const headers = { Accept: 'application/json' }; + fetch(url + getParamString(params), { headers }) + .then(response => response.json()) + .then(j => callback(j)); } /**