updates on the changes on warning and lat and long fixed to make it good
This commit is contained in:
202
tests/lib/coords.test.js
Normal file
202
tests/lib/coords.test.js
Normal file
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
coord,
|
||||
coordError,
|
||||
coordsOf,
|
||||
hasCoords,
|
||||
latitude,
|
||||
longitude,
|
||||
} from '@/lib/coords';
|
||||
|
||||
/**
|
||||
* Coordinate validation.
|
||||
*
|
||||
* The first block is the whole reason this module exists: the guard it replaces
|
||||
* was `Number.isFinite(Number(value))`, and every case below passed it, because
|
||||
* null, '' and false all coerce to 0 and 0 is finite. A missing pin became
|
||||
* latitude 0 / longitude 0 and was priced as an 11,000 km run.
|
||||
*/
|
||||
|
||||
const COIMBATORE = { latitude: 11.0168, longitude: 76.9558 };
|
||||
|
||||
describe('coord', () => {
|
||||
describe('the empty values that used to pass as zero', () => {
|
||||
it.each([
|
||||
['null', null],
|
||||
['undefined', undefined],
|
||||
['empty string', ''],
|
||||
['false', false],
|
||||
['true', true],
|
||||
['an empty array', []],
|
||||
])('rejects %s', (_label, value) => {
|
||||
// Number(value) is 0 for every one of these, and 0 is finite — which is
|
||||
// exactly how they got through before.
|
||||
expect(coord(value, 'lat')).toBeNull();
|
||||
expect(coord(value, 'lng')).toBeNull();
|
||||
});
|
||||
|
||||
it('confirms the coercion that caused the bug', () => {
|
||||
expect(Number(null)).toBe(0);
|
||||
expect(Number.isFinite(Number(null))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('zero', () => {
|
||||
it('rejects a literal zero on both axes', () => {
|
||||
// (0, 0) is a real point in the Gulf of Guinea. In this product it is
|
||||
// only ever the residue of a failed geocode.
|
||||
expect(coord(0, 'lat')).toBeNull();
|
||||
expect(coord(0, 'lng')).toBeNull();
|
||||
expect(coord('0', 'lat')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('unparseable values', () => {
|
||||
it.each(['abc', 'N/A', '--', 'NaN', {}])('rejects %p', (value) => {
|
||||
expect(coord(value, 'lat')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('range', () => {
|
||||
it('accepts the poles and the antimeridian exactly', () => {
|
||||
expect(coord(90, 'lat')).toBe(90);
|
||||
expect(coord(-90, 'lat')).toBe(-90);
|
||||
expect(coord(180, 'lng')).toBe(180);
|
||||
expect(coord(-180, 'lng')).toBe(-180);
|
||||
});
|
||||
|
||||
it('rejects a latitude past the poles', () => {
|
||||
expect(coord(90.1, 'lat')).toBeNull();
|
||||
expect(coord(-91, 'lat')).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects a longitude past the antimeridian', () => {
|
||||
expect(coord(180.5, 'lng')).toBeNull();
|
||||
expect(coord(-181, 'lng')).toBeNull();
|
||||
});
|
||||
|
||||
it('catches a transposition only when the longitude exceeds 90', () => {
|
||||
// The honest limit of a range check. Kolkata is (22.5726, 88.3639) and
|
||||
// Dibrugarh is (27.4728, 94.9120) — transpose the second and the
|
||||
// "latitude" becomes 94.9, past the pole, so it is caught.
|
||||
expect(coord(94.912, 'lat')).toBeNull();
|
||||
expect(hasCoords(94.912, 27.4728)).toBe(false);
|
||||
});
|
||||
|
||||
it('CANNOT catch a transposed Coimbatore pin — documented limitation', () => {
|
||||
// 76.9558 is a perfectly legal latitude (northern Siberia), so a
|
||||
// transposed Coimbatore pin passes every check in this module. Range
|
||||
// validation only catches a swap when one axis leaves the other's
|
||||
// domain, i.e. |longitude| > 90. Anything west of 90°E transposes
|
||||
// silently.
|
||||
//
|
||||
// Pinned as a test so nobody reads `coord` as swap-proof. Catching this
|
||||
// needs a plausibility check against the operating region (see
|
||||
// geocodingService.isWithinIndia), not a bounds check.
|
||||
expect(coord(COIMBATORE.longitude, 'lat')).toBe(76.9558);
|
||||
expect(hasCoords(COIMBATORE.longitude, COIMBATORE.latitude)).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects an unknown axis rather than guessing a bound', () => {
|
||||
expect(coord(11.0168, 'elevation')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('real coordinates', () => {
|
||||
it('passes a Coimbatore pin through unchanged', () => {
|
||||
expect(coord(COIMBATORE.latitude, 'lat')).toBe(11.0168);
|
||||
expect(coord(COIMBATORE.longitude, 'lng')).toBe(76.9558);
|
||||
});
|
||||
|
||||
it('parses a numeric string, which is how a sheet cell arrives', () => {
|
||||
expect(coord('11.0168', 'lat')).toBe(11.0168);
|
||||
expect(coord(' 76.9558 ', 'lng')).toBe(76.9558);
|
||||
});
|
||||
|
||||
it('accepts southern and western hemispheres', () => {
|
||||
expect(coord(-33.8688, 'lat')).toBe(-33.8688);
|
||||
expect(coord(-74.006, 'lng')).toBe(-74.006);
|
||||
});
|
||||
});
|
||||
|
||||
describe('axis helpers', () => {
|
||||
it('applies the latitude bound', () => {
|
||||
expect(latitude(91)).toBeNull();
|
||||
expect(latitude(11.0168)).toBe(11.0168);
|
||||
});
|
||||
|
||||
it('applies the longitude bound', () => {
|
||||
expect(longitude(76.9558)).toBe(76.9558);
|
||||
expect(longitude(181)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasCoords', () => {
|
||||
it('needs both halves', () => {
|
||||
expect(hasCoords(11.0168, 76.9558)).toBe(true);
|
||||
expect(hasCoords(11.0168, null)).toBe(false);
|
||||
expect(hasCoords(null, 76.9558)).toBe(false);
|
||||
expect(hasCoords(11.0168, 0)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects the pair the geocoding shim used to produce', () => {
|
||||
// geocodingService returned 0 from geometry.location.lat() when a place
|
||||
// had no coordinates. This is that pair.
|
||||
expect(hasCoords(0, 0)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('coordsOf', () => {
|
||||
it('reads the API/form shape', () => {
|
||||
expect(coordsOf(COIMBATORE)).toEqual({ latitude: 11.0168, longitude: 76.9558 });
|
||||
});
|
||||
|
||||
it('reads the Leaflet/map-picker shape', () => {
|
||||
expect(coordsOf({ lat: 11.0168, lng: 76.9558 })).toEqual({
|
||||
latitude: 11.0168,
|
||||
longitude: 76.9558,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null when either half is unusable', () => {
|
||||
expect(coordsOf({ latitude: 11.0168, longitude: null })).toBeNull();
|
||||
expect(coordsOf({ latitude: 0, longitude: 0 })).toBeNull();
|
||||
expect(coordsOf({ latitude: '', longitude: '' })).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for a non-object', () => {
|
||||
expect(coordsOf(null)).toBeNull();
|
||||
expect(coordsOf(undefined)).toBeNull();
|
||||
expect(coordsOf('11.0168,76.9558')).toBeNull();
|
||||
});
|
||||
|
||||
it('prefers the explicit spelling when both are present', () => {
|
||||
expect(coordsOf({ latitude: 11.0168, lat: 99, longitude: 76.9558, lng: 99 })).toEqual({
|
||||
latitude: 11.0168,
|
||||
longitude: 76.9558,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('coordError', () => {
|
||||
it('names a missing pin rather than a number problem', () => {
|
||||
expect(coordError('', 'lat')).toBe('Navigate cannot work without a pin');
|
||||
expect(coordError(null, 'lng')).toBe('Navigate cannot work without a pin');
|
||||
expect(coordError(0, 'lat')).toBe('Navigate cannot work without a pin');
|
||||
});
|
||||
|
||||
it('names a number problem when it is one', () => {
|
||||
expect(coordError('abc', 'lat')).toBe('Enter a number');
|
||||
expect(coordError(true, 'lat')).toBe('Enter a number');
|
||||
});
|
||||
|
||||
it('names the axis and its bounds when out of range', () => {
|
||||
expect(coordError(91, 'lat')).toBe('Latitude must be between -90 and 90');
|
||||
expect(coordError(181, 'lng')).toBe('Longitude must be between -180 and 180');
|
||||
});
|
||||
|
||||
it('is null for a good coordinate', () => {
|
||||
expect(coordError(11.0168, 'lat')).toBeNull();
|
||||
expect(coordError(76.9558, 'lng')).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user