js-zip-code-locator 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PUBLISH.md +27 -0
- package/README.md +200 -0
- package/index.js +349 -0
- package/package.json +11 -0
- package/test/index.js +308 -0
- package/zip-codes.json +1 -0
package/test/index.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { describe, it } = require('node:test');
|
|
4
|
+
const assert = require('node:assert');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const createZipLocator = require('..');
|
|
7
|
+
|
|
8
|
+
const dataPath = path.join(__dirname, '..', 'zip-codes.json');
|
|
9
|
+
|
|
10
|
+
describe('createZipLocator', () => {
|
|
11
|
+
const locator = createZipLocator({ dataPath });
|
|
12
|
+
const locatorKm = createZipLocator({ dataPath, useMiles: false });
|
|
13
|
+
|
|
14
|
+
describe('getZipData(zipCode)', () => {
|
|
15
|
+
it('returns full data (state, city, lat & lng) for a zip code', () => {
|
|
16
|
+
const data = locator.getZipData('35004');
|
|
17
|
+
assert.ok(data);
|
|
18
|
+
assert.strictEqual(data.zip, '35004');
|
|
19
|
+
assert.strictEqual(data.state, 'AL');
|
|
20
|
+
assert.strictEqual(data.city, 'Moody');
|
|
21
|
+
assert.strictEqual(typeof data.latitude, 'number');
|
|
22
|
+
assert.strictEqual(typeof data.longitude, 'number');
|
|
23
|
+
assert.strictEqual(data.full, 'Alabama');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('returns only one entry per zip code', () => {
|
|
27
|
+
const data = locator.getZipData(35007);
|
|
28
|
+
assert.ok(data);
|
|
29
|
+
assert.strictEqual(data.zip, '35007');
|
|
30
|
+
const data2 = locator.getZipData('35007');
|
|
31
|
+
assert.deepStrictEqual(data, data2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('normalizes zip to 5 characters (zero-padded)', () => {
|
|
35
|
+
const data = locator.getZipData(1001);
|
|
36
|
+
assert.ok(data);
|
|
37
|
+
assert.strictEqual(data.zip, '01001');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns undefined for unknown zip', () => {
|
|
41
|
+
assert.strictEqual(locator.getZipData('99999'), undefined);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('getZipCoordinates(zipCode)', () => {
|
|
46
|
+
it('returns latitude and longitude for a zip code', () => {
|
|
47
|
+
const coords = locator.getZipCoordinates('35004');
|
|
48
|
+
assert.ok(coords);
|
|
49
|
+
assert.strictEqual(typeof coords.latitude, 'number');
|
|
50
|
+
assert.strictEqual(typeof coords.longitude, 'number');
|
|
51
|
+
assert.strictEqual(coords.latitude, 33.606379);
|
|
52
|
+
assert.strictEqual(coords.longitude, -86.50249);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('returns undefined for unknown zip', () => {
|
|
56
|
+
assert.strictEqual(locator.getZipCoordinates('00000'), undefined);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getZipCodes(city, state)', () => {
|
|
61
|
+
it('returns all zip codes and coordinates for city and state (can be multiple)', () => {
|
|
62
|
+
const zips = locator.getZipCodes('Moody', 'AL');
|
|
63
|
+
assert.ok(Array.isArray(zips));
|
|
64
|
+
assert.ok(zips.length >= 1);
|
|
65
|
+
zips.forEach((r) => {
|
|
66
|
+
assert.strictEqual(r.city, 'Moody');
|
|
67
|
+
assert.strictEqual(r.state, 'AL');
|
|
68
|
+
assert.ok(r.zip);
|
|
69
|
+
assert.ok(typeof r.latitude === 'number' && typeof r.longitude === 'number');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('is case-insensitive for city and state', () => {
|
|
74
|
+
const zips1 = locator.getZipCodes('moody', 'al');
|
|
75
|
+
const zips2 = locator.getZipCodes('Moody', 'AL');
|
|
76
|
+
assert.strictEqual(zips1.length, zips2.length);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('returns empty array when no match', () => {
|
|
80
|
+
const zips = locator.getZipCodes('NoSuchCity', 'XX');
|
|
81
|
+
assert.deepStrictEqual(zips, []);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('getClosestByZipCode(zipCode, radius, useMiles)', () => {
|
|
86
|
+
it('returns zips within radius (default miles)', () => {
|
|
87
|
+
const results = locator.getClosestByZipCode('35004', 10);
|
|
88
|
+
assert.ok(Array.isArray(results));
|
|
89
|
+
assert.ok(results.length >= 1);
|
|
90
|
+
results.forEach((r) => {
|
|
91
|
+
assert.ok(r.distance >= 0 && r.distance <= 10);
|
|
92
|
+
assert.ok(r.zip && r.latitude != null && r.longitude != null);
|
|
93
|
+
});
|
|
94
|
+
const sorted = [...results].sort((a, b) => a.distance - b.distance);
|
|
95
|
+
assert.deepStrictEqual(results, sorted);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('accepts useMiles false for kilometers', () => {
|
|
99
|
+
const resultsKm = locator.getClosestByZipCode('35004', 16, false); // ~10 mi in km
|
|
100
|
+
assert.ok(Array.isArray(resultsKm));
|
|
101
|
+
resultsKm.forEach((r) => {
|
|
102
|
+
assert.ok(r.distance <= 16);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns empty array for unknown zip', () => {
|
|
107
|
+
assert.deepStrictEqual(locator.getClosestByZipCode('99999', 5), []);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('getDataByCoordinates(lat, lng, options)', () => {
|
|
112
|
+
it('returns zip records with distance, default sort by distance', () => {
|
|
113
|
+
const results = locator.getDataByCoordinates(33.6, -86.5);
|
|
114
|
+
assert.ok(Array.isArray(results));
|
|
115
|
+
assert.ok(results.length >= 1);
|
|
116
|
+
results.forEach((r) => {
|
|
117
|
+
assert.ok(typeof r.distance === 'number');
|
|
118
|
+
assert.ok(r.zip && r.latitude != null && r.longitude != null);
|
|
119
|
+
});
|
|
120
|
+
const sorted = [...results].sort((a, b) => a.distance - b.distance);
|
|
121
|
+
assert.deepStrictEqual(results, sorted);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('supports sort by zip', () => {
|
|
125
|
+
const results = locator.getDataByCoordinates(33.6, -86.5, { sort: 'zip' });
|
|
126
|
+
const sorted = [...results].sort((a, b) => a.zip.localeCompare(b.zip));
|
|
127
|
+
assert.deepStrictEqual(results, sorted);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('supports sort by city', () => {
|
|
131
|
+
const results = locator.getDataByCoordinates(33.6, -86.5, { sort: 'city' });
|
|
132
|
+
const sorted = [...results].sort(
|
|
133
|
+
(a, b) => a.city.localeCompare(b.city) || a.state.localeCompare(b.state)
|
|
134
|
+
);
|
|
135
|
+
assert.deepStrictEqual(results, sorted);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('uses instance distance unit (km when useMiles false)', () => {
|
|
139
|
+
const results = locatorKm.getDataByCoordinates(33.6, -86.5);
|
|
140
|
+
assert.ok(results.length >= 1);
|
|
141
|
+
assert.ok(typeof results[0].distance === 'number');
|
|
142
|
+
assert.ok(results[0].distance > 0);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('getDistanceBetweenZipCodes(zip1, zip2, useMiles)', () => {
|
|
147
|
+
it('returns distance between two zips (default miles)', () => {
|
|
148
|
+
const d = locator.getDistanceBetweenZipCodes('35004', '35007');
|
|
149
|
+
assert.ok(typeof d === 'number');
|
|
150
|
+
assert.ok(d >= 0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('returns distance in km when useMiles is false', () => {
|
|
154
|
+
const dMiles = locator.getDistanceBetweenZipCodes('35004', '35007', true);
|
|
155
|
+
const dKm = locator.getDistanceBetweenZipCodes('35004', '35007', false);
|
|
156
|
+
assert.ok(dKm > 0);
|
|
157
|
+
assert.ok(Math.abs(dKm / dMiles - 1.60934) < 0.1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('returns undefined if either zip not found', () => {
|
|
161
|
+
assert.strictEqual(locator.getDistanceBetweenZipCodes('99999', '35004'), undefined);
|
|
162
|
+
assert.strictEqual(locator.getDistanceBetweenZipCodes('35004', '00000'), undefined);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('returns 0 for same zip', () => {
|
|
166
|
+
assert.strictEqual(locator.getDistanceBetweenZipCodes('35004', '35004'), 0);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('getZipsInState(state)', () => {
|
|
171
|
+
it('returns all zips in the state', () => {
|
|
172
|
+
const zips = locator.getZipsInState('AL');
|
|
173
|
+
assert.ok(Array.isArray(zips));
|
|
174
|
+
assert.ok(zips.length >= 1);
|
|
175
|
+
zips.forEach((r) => {
|
|
176
|
+
assert.strictEqual(r.state, 'AL');
|
|
177
|
+
assert.ok(r.zip && r.latitude != null && r.longitude != null && r.city);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('accepts lowercase state', () => {
|
|
182
|
+
const zipsAl = locator.getZipsInState('al');
|
|
183
|
+
const zipsAL = locator.getZipsInState('AL');
|
|
184
|
+
assert.strictEqual(zipsAl.length, zipsAL.length);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('returns empty array for unknown state', () => {
|
|
188
|
+
assert.deepStrictEqual(locator.getZipsInState('XX'), []);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('getCitiesInState(state)', () => {
|
|
193
|
+
it('returns unique city names sorted', () => {
|
|
194
|
+
const cities = locator.getCitiesInState('AL');
|
|
195
|
+
assert.ok(Array.isArray(cities));
|
|
196
|
+
assert.ok(cities.length >= 1);
|
|
197
|
+
const unique = [...new Set(cities)];
|
|
198
|
+
assert.strictEqual(cities.length, unique.length);
|
|
199
|
+
const sorted = [...cities].sort((a, b) => a.localeCompare(b));
|
|
200
|
+
assert.deepStrictEqual(cities, sorted);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('includes known city from state', () => {
|
|
204
|
+
const cities = locator.getCitiesInState('AL');
|
|
205
|
+
assert.ok(cities.includes('Moody'));
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('isValidZip(zipCode)', () => {
|
|
210
|
+
it('returns true for existing zip', () => {
|
|
211
|
+
assert.strictEqual(locator.isValidZip('35004'), true);
|
|
212
|
+
assert.strictEqual(locator.isValidZip(35007), true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('returns false for unknown zip', () => {
|
|
216
|
+
assert.strictEqual(locator.isValidZip('99999'), false);
|
|
217
|
+
assert.strictEqual(locator.isValidZip('00000'), false);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('searchZipPrefix(prefix)', () => {
|
|
222
|
+
it('returns zips starting with prefix', () => {
|
|
223
|
+
const results = locator.searchZipPrefix('350');
|
|
224
|
+
assert.ok(Array.isArray(results));
|
|
225
|
+
results.forEach((r) => assert.ok(r.zip.startsWith('350')));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('returns empty array for empty prefix', () => {
|
|
229
|
+
assert.deepStrictEqual(locator.searchZipPrefix(''), []);
|
|
230
|
+
assert.deepStrictEqual(locator.searchZipPrefix(' '), []);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('getZipsInBoundingBox(minLat, maxLat, minLng, maxLng)', () => {
|
|
235
|
+
it('returns zips inside the box', () => {
|
|
236
|
+
const results = locator.getZipsInBoundingBox(33.5, 34, -87, -86);
|
|
237
|
+
assert.ok(Array.isArray(results));
|
|
238
|
+
results.forEach((r) => {
|
|
239
|
+
assert.ok(r.latitude >= 33.5 && r.latitude <= 34);
|
|
240
|
+
assert.ok(r.longitude >= -87 && r.longitude <= -86);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('accepts min/max in either order', () => {
|
|
245
|
+
const r1 = locator.getZipsInBoundingBox(33.5, 34, -87, -86);
|
|
246
|
+
const r2 = locator.getZipsInBoundingBox(34, 33.5, -86, -87);
|
|
247
|
+
assert.strictEqual(r1.length, r2.length);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('getDistancesFromZip(originZip, zipCodes, useMiles?)', () => {
|
|
252
|
+
it('returns distances for each target zip', () => {
|
|
253
|
+
const results = locator.getDistancesFromZip('35004', ['35007', '35005']);
|
|
254
|
+
assert.strictEqual(results.length, 2);
|
|
255
|
+
results.forEach((r) => assert.ok('zip' in r && 'distance' in r));
|
|
256
|
+
assert.ok(typeof results[0].distance === 'number');
|
|
257
|
+
assert.ok(typeof results[1].distance === 'number');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('returns undefined distance for unknown target zip', () => {
|
|
261
|
+
const results = locator.getDistancesFromZip('35004', ['35007', '99999']);
|
|
262
|
+
assert.strictEqual(results[1].zip, '99999');
|
|
263
|
+
assert.strictEqual(results[1].distance, undefined);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('returns empty array when origin zip unknown', () => {
|
|
267
|
+
const results = locator.getDistancesFromZip('99999', ['35004', '35007']);
|
|
268
|
+
assert.strictEqual(results.length, 2);
|
|
269
|
+
assert.strictEqual(results[0].distance, undefined);
|
|
270
|
+
assert.strictEqual(results[1].distance, undefined);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('getCityState(zipCode)', () => {
|
|
275
|
+
it('returns city, state, and formatted string', () => {
|
|
276
|
+
const result = locator.getCityState('35004');
|
|
277
|
+
assert.ok(result);
|
|
278
|
+
assert.strictEqual(result.city, 'Moody');
|
|
279
|
+
assert.strictEqual(result.state, 'AL');
|
|
280
|
+
assert.strictEqual(result.formatted, 'Moody, AL');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('returns undefined for unknown zip', () => {
|
|
284
|
+
assert.strictEqual(locator.getCityState('99999'), undefined);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('getFullStateName(stateAbbr)', () => {
|
|
289
|
+
it('returns full state name for abbreviation', () => {
|
|
290
|
+
assert.strictEqual(locator.getFullStateName('AL'), 'Alabama');
|
|
291
|
+
assert.strictEqual(locator.getFullStateName('al'), 'Alabama');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('returns undefined for unknown state', () => {
|
|
295
|
+
assert.strictEqual(locator.getFullStateName('XX'), undefined);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe('module default export', () => {
|
|
301
|
+
const locator = require('..');
|
|
302
|
+
it('exposes getZipData as default instance', () => {
|
|
303
|
+
assert.strictEqual(typeof locator.getZipData, 'function');
|
|
304
|
+
const data = locator.getZipData('35004');
|
|
305
|
+
assert.ok(data);
|
|
306
|
+
assert.strictEqual(data.zip, '35004');
|
|
307
|
+
});
|
|
308
|
+
});
|