Skip to content

Commit f91fb93

Browse files
committed
Stabilize geocode tests
1 parent 342d3db commit f91fb93

File tree

1 file changed

+113
-5
lines changed

1 file changed

+113
-5
lines changed

src/map/geocode.spec.ts

Lines changed: 113 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,139 @@
1-
import { describe, expect, test } from 'vitest'
1+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
2+
import type { Position } from 'geojson'
23

4+
import type { ReverseGeocodingFeature, ReverseGeocodingResponse } from './api-types'
35
import { getFullAddress, getPlaceName, reverseGeocode } from './geocode'
46

7+
const fetchMock = vi.fn()
8+
9+
type MockContext = Partial<ReverseGeocodingFeature['properties']['context']>
10+
11+
function createFeature(fullAddress: string, context: MockContext = {}): ReverseGeocodingFeature {
12+
return {
13+
type: 'Feature',
14+
geometry: {
15+
type: 'Point',
16+
coordinates: [0, 0],
17+
},
18+
properties: {
19+
feature_type: 'address',
20+
name: fullAddress,
21+
name_preferred: fullAddress,
22+
place_formatted: fullAddress,
23+
full_address: fullAddress,
24+
context,
25+
},
26+
} as ReverseGeocodingFeature
27+
}
28+
29+
function createResponse(feature: ReverseGeocodingFeature): ReverseGeocodingResponse {
30+
return {
31+
type: 'FeatureCollection',
32+
attribution: 'test fixture',
33+
features: [feature],
34+
} as ReverseGeocodingResponse
35+
}
36+
37+
function coordinateKey(position: Position): string {
38+
return `${position[0].toFixed(6)},${position[1].toFixed(6)}`
39+
}
40+
41+
function mockReverseGeocode(featuresByCoordinate: Map<string, ReverseGeocodingFeature>) {
42+
fetchMock.mockImplementation(async (input: string | URL | Request) => {
43+
const rawUrl = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url
44+
const url = new URL(rawUrl)
45+
const key = `${url.searchParams.get('longitude')},${url.searchParams.get('latitude')}`
46+
const feature = featuresByCoordinate.get(key)
47+
if (!feature) throw new Error(`Unexpected reverse geocode lookup for ${key}`)
48+
49+
return new Response(JSON.stringify(createResponse(feature)), {
50+
status: 200,
51+
headers: { 'Content-Type': 'application/json' },
52+
})
53+
})
54+
}
55+
56+
beforeEach(() => {
57+
fetchMock.mockReset()
58+
vi.stubGlobal('fetch', fetchMock)
59+
})
60+
61+
afterEach(() => {
62+
vi.restoreAllMocks()
63+
vi.unstubAllGlobals()
64+
})
65+
566
describe('reverseGeocode', () => {
6-
test('return null if coords are [0, 0]', async () => {
67+
test('return null if coords are [0, 0] without calling fetch', async () => {
768
expect(await reverseGeocode([0, 0])).toBeNull()
69+
expect(fetchMock).not.toHaveBeenCalled()
70+
})
71+
72+
test('return first feature from reverse geocode response', async () => {
73+
const position: Position = [-0.10664, 51.514209]
74+
const feature = createFeature('133 Fleet Street, City of London, London, EC4A 2BB, United Kingdom')
75+
mockReverseGeocode(new Map([[coordinateKey(position), feature]]))
76+
77+
expect(await reverseGeocode(position)).toEqual(feature)
78+
expect(fetchMock).toHaveBeenCalledOnce()
79+
})
80+
81+
test('return null when fetch rejects', async () => {
82+
vi.spyOn(console, 'error').mockImplementation(() => {})
83+
fetchMock.mockRejectedValueOnce(new Error('network down'))
84+
85+
expect(await reverseGeocode([-0.10664, 51.514209])).toBeNull()
86+
})
87+
88+
test('return null on non-ok response', async () => {
89+
fetchMock.mockResolvedValueOnce(new Response('denied', { status: 429, statusText: 'Too Many Requests' }))
90+
91+
expect(await reverseGeocode([-0.10664, 51.514209])).toBeNull()
92+
})
93+
94+
test('return null when response json cannot be parsed', async () => {
95+
fetchMock.mockResolvedValueOnce(new Response('{', { status: 200, headers: { 'Content-Type': 'application/json' } }))
96+
97+
expect(await reverseGeocode([-0.10664, 51.514209])).toBeNull()
898
})
999
})
10100

11101
describe('getFullAddress', () => {
12-
test('return null if coords are [0, 0]', async () => {
102+
test('return null if coords are [0, 0] without calling fetch', async () => {
13103
expect(await getFullAddress([0, 0])).toBeNull()
104+
expect(fetchMock).not.toHaveBeenCalled()
14105
})
15106

16107
test('normal usage', async () => {
17-
// expect(await getFullAddress([-77.036574, 38.8976765])).toBe('1600 Pennsylvania Avenue Northwest, Washington, District of Columbia 20500, United States')
108+
mockReverseGeocode(
109+
new Map([
110+
[coordinateKey([-0.10664, 51.514209]), createFeature('133 Fleet Street, City of London, London, EC4A 2BB, United Kingdom')],
111+
[coordinateKey([-2.076843, 51.894799]), createFeature('4 Montpellier Drive, Cheltenham, GL50 1TX, United Kingdom')],
112+
]),
113+
)
114+
18115
expect(await getFullAddress([-0.10664, 51.514209])).toBe('133 Fleet Street, City of London, London, EC4A 2BB, United Kingdom')
19116
expect(await getFullAddress([-2.076843, 51.894799])).toBe('4 Montpellier Drive, Cheltenham, GL50 1TX, United Kingdom')
20117
})
21118
})
22119

23120
describe('getPlaceName', () => {
24-
test('return null if coords are [0, 0]', async () => {
121+
test('return null if coords are [0, 0] without calling fetch', async () => {
25122
expect(await getPlaceName([0, 0])).toBeNull()
123+
expect(fetchMock).not.toHaveBeenCalled()
26124
})
27125

28126
test('normal usage', async () => {
127+
mockReverseGeocode(
128+
new Map([
129+
[coordinateKey([-117.168638, 32.723695]), createFeature('', { neighborhood: { name: 'Little Italy' } })],
130+
[coordinateKey([-118.192757, 33.763015]), createFeature('', { place: { name: 'Downtown Long Beach' } })],
131+
[coordinateKey([-0.113643, 51.504546]), createFeature('', { neighborhood: { name: 'Waterloo' } })],
132+
[coordinateKey([5.572254, 50.64428]), createFeature('', { locality: { name: 'Liège' } })],
133+
[coordinateKey([-2.236802, 53.480931]), createFeature('', { neighborhood: { name: 'Northern Quarter' } })],
134+
]),
135+
)
136+
29137
expect(await getPlaceName([-117.168638, 32.723695])).toBe('Little Italy')
30138
expect(await getPlaceName([-118.192757, 33.763015])).toBe('Downtown Long Beach')
31139
expect(await getPlaceName([-0.113643, 51.504546])).toBe('Waterloo')

0 commit comments

Comments
 (0)