• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OneBusAway / wayfinder / 22210581532

20 Feb 2026 03:53AM UTC coverage: 79.59% (+1.1%) from 78.449%
22210581532

push

github

web-flow
Merge pull request #346 from OneBusAway/update

Prep 2026.2 Release

1685 of 1861 branches covered (90.54%)

Branch coverage included in aggregate %.

1106 of 1442 new or added lines in 33 files covered. (76.7%)

3 existing lines in 3 files now uncovered.

10739 of 13749 relevant lines covered (78.11%)

4.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.93
/src/lib/geocoder.js
1
export async function googleGeocode({ apiKey, query, bounds = null }) {
1✔
2
        let url = `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(query)}&key=${apiKey}`;
4✔
3

4
        if (bounds) {
4✔
5
                url += `&bounds=${bounds.south},${bounds.west}|${bounds.north},${bounds.east}`;
1✔
6
        }
1✔
7
        const response = await fetch(url);
4✔
8
        const data = await response.json();
4✔
9

10
        if (data.status !== 'OK' || data.results.length === 0) {
4✔
11
                return null;
1✔
12
        }
1✔
13

14
        const result = data.results[0];
3✔
15

16
        if (bounds) {
4✔
17
                const loc = result.geometry.location;
1✔
18
                if (
1✔
19
                        loc.lat < bounds.south ||
1✔
20
                        loc.lat > bounds.north ||
1✔
21
                        loc.lng < bounds.west ||
1!
NEW
22
                        loc.lng > bounds.east
×
23
                ) {
1✔
24
                        return null;
1✔
25
                }
1✔
26
        }
1✔
27

28
        return createGeocodingResult({
2✔
29
                geometry: result.geometry,
2✔
30
                formatted_address: result.formatted_address,
2✔
31
                name: result.formatted_address
2✔
32
        });
2✔
33
}
2✔
34

35
export async function googlePlacesAutocomplete({ apiKey, input, bounds = null }) {
1✔
36
        const requestBody = { input };
3✔
37

38
        if (bounds) {
3!
NEW
39
                requestBody.locationRestriction = {
×
NEW
40
                        rectangle: {
×
NEW
41
                                low: {
×
NEW
42
                                        latitude: bounds.south,
×
NEW
43
                                        longitude: bounds.west
×
44
                                },
×
NEW
45
                                high: {
×
NEW
46
                                        latitude: bounds.north,
×
NEW
47
                                        longitude: bounds.east
×
NEW
48
                                }
×
49
                        }
×
50
                };
×
51
        }
×
52

53
        const response = await fetch(`https://places.googleapis.com/v1/places:autocomplete`, {
3✔
54
                method: 'POST',
3✔
55
                headers: {
3✔
56
                        'Content-Type': 'application/json',
3✔
57
                        'X-Goog-Api-Key': apiKey
3✔
58
                },
3✔
59
                body: JSON.stringify(requestBody)
3✔
60
        });
3✔
61
        const data = await response.json();
3✔
62

63
        if (!data.suggestions) {
3✔
64
                return [];
1✔
65
        }
1✔
66

67
        const suggestions = [];
2✔
68
        for (const suggestion of data.suggestions) {
2✔
69
                const prediction = suggestion.placePrediction;
2✔
70

71
                const suggestionObject = createSuggestion(
2✔
72
                        prediction.placeId,
2✔
73
                        prediction.text.text,
2✔
74
                        prediction.text.text
2✔
75
                );
2✔
76

77
                if (suggestionObject) {
2✔
78
                        suggestions.push(suggestionObject);
2✔
79
                }
2✔
80
        }
2✔
81

82
        return suggestions;
2✔
83
}
2✔
84

85
export async function bingGeocode({ apiKey, query, bounds = null }) {
1✔
86
        let url = `https://dev.virtualearth.net/REST/v1/Locations?query=${encodeURIComponent(query)}&key=${apiKey}`;
4✔
87

88
        if (bounds) {
4✔
89
                const centerLat = (bounds.north + bounds.south) / 2;
1✔
90
                const centerLon = (bounds.east + bounds.west) / 2;
1✔
91
                url += `&userLocation=${centerLat},${centerLon}`;
1✔
92
        }
1✔
93

94
        const response = await fetch(url);
4✔
95
        const data = await response.json();
4✔
96

97
        if (
4✔
98
                data.statusCode !== 200 ||
4✔
99
                !data.resourceSets ||
4✔
100
                data.resourceSets.length === 0 ||
4✔
101
                !data.resourceSets[0].resources ||
4✔
102
                data.resourceSets[0].resources.length === 0
3✔
103
        ) {
4✔
104
                return null;
1✔
105
        }
1✔
106

107
        const result = data.resourceSets[0].resources[0];
3✔
108
        const coordinates = result.point.coordinates;
3✔
109

110
        if (bounds) {
4✔
111
                const [lat, lng] = coordinates;
1✔
112
                if (lat < bounds.south || lat > bounds.north || lng < bounds.west || lng > bounds.east) {
1!
113
                        return null;
1✔
114
                }
1✔
115
        }
1✔
116

117
        return createGeocodingResult({
2✔
118
                geometry: {
2✔
119
                        location: {
2✔
120
                                lat: coordinates[0],
2✔
121
                                lng: coordinates[1]
2✔
122
                        }
2✔
123
                },
2✔
124
                name: result.name,
2✔
125
                formatted_address: result.name
2✔
126
        });
2✔
127
}
2✔
128

129
export async function bingAutoSuggestPlaces({ apiKey, query, bounds = null }) {
1✔
130
        let url = `https://dev.virtualearth.net/REST/v1/Autosuggest?query=${encodeURIComponent(query)}&key=${apiKey}`;
4✔
131

132
        if (bounds) {
4!
133
                const centerLat = (bounds.north + bounds.south) / 2;
×
134
                const centerLng = (bounds.east + bounds.west) / 2;
×
135
                url += `&userLocation=${centerLat},${centerLng}`;
×
136
        }
×
137

138
        const rawBingResult = await fetch(url, {
4✔
139
                method: 'GET',
4✔
140
                headers: { Accept: 'application/json' }
4✔
141
        });
4✔
142

143
        const data = await rawBingResult.json();
4✔
144

145
        const resourceSets = data.resourceSets;
4✔
146

147
        if (!resourceSets || resourceSets.length === 0 || resourceSets[0].estimatedTotal === 0) {
4✔
148
                return [];
1✔
149
        }
1✔
150

151
        const resources = resourceSets[0].resources;
3✔
152
        if (!resources || resources.length === 0) {
4✔
153
                return [];
1✔
154
        }
1✔
155

156
        const suggestions = [];
2✔
157
        for (const resource of resources) {
2✔
158
                if (resource.value && Array.isArray(resource.value)) {
2✔
159
                        for (const item of resource.value) {
1✔
160
                                const displayText = item.name
1✔
161
                                        ? `${item.name} - ${item.address.formattedAddress}`
1✔
162
                                        : item.address.formattedAddress;
1!
163

164
                                const suggestion = createSuggestion(
1✔
165
                                        null,
1✔
166
                                        item.name || item.address.formattedAddress,
1!
167
                                        displayText
1✔
168
                                );
1✔
169

170
                                if (suggestion) {
1✔
171
                                        suggestions.push(suggestion);
1✔
172
                                }
1✔
173
                        }
1✔
174
                } else {
1✔
175
                        const suggestion = createSuggestion(
1✔
176
                                null,
1✔
177
                                resource.name || resource.address.formattedAddress,
1!
178
                                resource.address.formattedAddress
1✔
179
                        );
1✔
180

181
                        if (suggestion) {
1✔
182
                                suggestions.push(suggestion);
1✔
183
                        }
1✔
184
                }
1✔
185
        }
2✔
186

187
        return suggestions;
2✔
188
}
2✔
189

190
export async function fetchAutocompleteResults(provider, query, apiKey, bounds = null) {
1✔
191
        switch (provider) {
3✔
192
                case 'google':
3✔
193
                        return await googlePlacesAutocomplete({ apiKey, input: query, bounds });
1✔
194
                case 'bing':
3✔
195
                        return await bingAutoSuggestPlaces({ apiKey, query, bounds });
1✔
196
                default:
3✔
197
                        throw new Error('Invalid geocoding provider');
1✔
198
        }
3✔
199
}
3✔
200

201
/**
1✔
202
 *
1✔
203
 * @param {string} placeId     optional - some providers return a placeId
1✔
204
 * @param {string} name               required - used for geocoding the selected place
1✔
205
 * @param {string} displayText required - used for displaying the selected place
1✔
206
 * @returns
1✔
207
 */
1✔
208
function createSuggestion(placeId, name, displayText) {
4✔
209
        if (!name || !displayText) return null;
4!
210

211
        return {
4✔
212
                ...(placeId && { placeId }),
4✔
213
                name,
4✔
214
                displayText
4✔
215
        };
4✔
216
}
4✔
217

218
/**
1✔
219
 *
1✔
220
 * @param {location{lat,lng}} geometry
1✔
221
 * @param {string} formatted_address
1✔
222
 * @param {string} name
1✔
223
 * @returns
1✔
224
 */
1✔
225
function createGeocodingResult({ geometry, formatted_address, name }) {
4✔
226
        return {
4✔
227
                name: name || formatted_address,
4!
228
                formatted_address: formatted_address,
4✔
229
                geometry: {
4✔
230
                        location: {
4✔
231
                                lat: geometry.location.lat,
4✔
232
                                lng: geometry.location.lng
4✔
233
                        }
4✔
234
                }
4✔
235
        };
4✔
236
}
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc