Skip to content

Commit

Permalink
feat(toBeGeometryCollection): add new matcher
Browse files Browse the repository at this point in the history
Verifies an object is a valid GeoJSON GeometryCollection. Also updates the `anyGeometry` core
function and matcher to allow GeometryCollection objects.

Resolves: #16
  • Loading branch information
M-Scott-Lassiter committed May 30, 2022
1 parent 79ef8c9 commit 63cc919
Show file tree
Hide file tree
Showing 17 changed files with 900 additions and 30 deletions.
1 change: 1 addition & 0 deletions .cz-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const coordinateMatchers = [
{ name: 'isValidBoundingBox' },
{ name: 'isValidCoordinate' },
{ name: 'toBeAnyGeometry' },
{ name: 'toBeGeometryCollection' },
{ name: 'toBeLineStringGeometry' },
{ name: 'toBeMultiLineStringGeometry' },
{ name: 'toBeMultiPointGeometry' },
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
- [Coordiantes](#coordiantes)
- [Bounding Boxes](#bounding-boxes)
- [Geometries](#geometries)
- [Geometry Collections](#geometry-collections)
- [Features](#features)
- [Feature Collections](#feature-collections)
- [Functional](#functional)
Expand Down Expand Up @@ -150,7 +149,7 @@ _1.0.0_
- [x] toBeMultiLineStringGeometry
- [x] toBePolygonGeometry
- [x] toBeMultiPolygonGeometry
- [ ] toBeGeometryCollection
- [x] toBeGeometryCollection
- [x] toBeAnyGeometry

---
Expand Down
1 change: 1 addition & 0 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ exports.coordinates = {

exports.geometries = {
anyGeometry: require('./core/geometries/anyGeometry'),
geometryCollection: require('./core/geometries/geometryCollection'),
lineStringGeometry: require('./core/geometries/lineStringGeometry'),
multiLineStringGeometry: require('./core/geometries/multiLineStringGeometry'),
multiPointGeometry: require('./core/geometries/multiPointGeometry'),
Expand Down
35 changes: 26 additions & 9 deletions src/core/geometries/anyGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ const { lineStringGeometry } = require('./lineStringGeometry')
const { multiLineStringGeometry } = require('./multiLineStringGeometry')
const { polygonGeometry } = require('./polygonGeometry')
const { multiPolygonGeometry } = require('./multiPolygonGeometry')
// const { geometryCollection } = require('./geometryCollection')

/**
* Verifies an object meets validity requirements for one of the six basic GeoJSON geometry types:
* Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon
* Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/15
* @param {object} geometryObject A WGS-84 array of [longitude, latitude] or [longitude, latitude, alititude]
* @returns {boolean} True if a valid GeoJSON geometry object. If invalid, it will throw an error.
* @throws {Error} Input must be either a valid Point, MultiPoint, LineString, MultiLineString, Polygon, or MultiPolygon
* @throws {Error} Input must be either a valid Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, or GeometryCollection
* @example
* point = {
* type: 'Point',
Expand Down Expand Up @@ -58,13 +59,22 @@ const { multiPolygonGeometry } = require('./multiPolygonGeometry')
* const badExample = anyGeometry(feature)) // throws error
*/
function anyGeometry(geometryObject) {
// if (
// typeof geometryObject !== 'object' ||
// Array.isArray(geometryObject) ||
// geometryObject === null
// ) {
// throw new Error('Argument must be a GeoJSON Geometry object.')
// }
// The three prohibited properties below account for nested GeometryCollection objects. All other geometry
// core functions include these within them, including geometryCollection.js.
// Note: This might be a good set of checks to abstract away in a utility function later...
if ('geometry' in geometryObject) {
throw new Error(`GeoJSON Geometry objects are forbidden from having a property 'geometry'.`)
}

if ('properties' in geometryObject) {
throw new Error(
`GeoJSON Geometry objects are forbidden from having a property 'properties'.`
)
}

if ('features' in geometryObject) {
throw new Error(`GeoJSON Geometry objects are forbidden from having a property 'features'.`)
}

if (geometryObject?.type === 'Point') {
pointGeometry(geometryObject)
Expand Down Expand Up @@ -96,6 +106,13 @@ function anyGeometry(geometryObject) {
return true
}

if (geometryObject?.type === 'GeometryCollection') {
geometryObject.geometries.forEach((geometry) => {
anyGeometry(geometry)
})
return true
}

throw new Error(
'Object must be either a valid Point, MultiPoint, LineString, MultiLineString, Polygon, or MultiPolygon'
)
Expand Down
105 changes: 105 additions & 0 deletions src/core/geometries/geometryCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const { anyGeometry } = require('./anyGeometry')

/**
* Verifies an object is a valid GeoJSON GeometryCollection. This object requires a
* 'type' property that must equal "GeometryCollection", and a 'geometries' property that contains
* an array of GeoJSON Geometry objects (Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon)
*
* The geometries may be an empty array, but may not be an array of empty arrays or objects.
*
* Foreign members are allowed with the exceptions thrown below.
*
* @memberof Core.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/16
* @param {object} geometryObject a GeoJSON Geometry object
* @returns {boolean} True if a valid GeoJSON GeometryCollection Geometry. If invalid, it will throw an error.
* @throws {Error} Argument not an object
* @throws {Error} Must have a type property with value 'GeometryCollection'
* @throws {Error} Forbidden from having a property 'geometry', 'properties', or 'features'
* @example
* const collection = {
* type: 'GeometryCollection',
* geometries: [{
* "type": 'Point',
* "coordinates": [100.0, 0.0]
* }, {
* type: 'LineString',
* coordinates: [
* [101.0, 0.0],
* [102.0, 1.0]
* ]
* }, {
* type: 'Polygon',
* coordinates: [
* [
* [102.0, 2.0],
* [103.0, 2.0],
* [103.0, 3.0],
* [102.0, 3.0],
* [102.0, 2.0]
* ]
* ]
* }, {
* type: 'Point',
* coordinates: [150.0, 73.0]
* }]
* }
*
* const goodExample = geometryCollection(collection) // true
*
* const badExample1 = geometryCollection(collection.geometries) // throws error
* const badExample2 = geometryCollection(collection.geometries[1]) // throws error
*/
function geometryCollection(geometryObject) {
if (
typeof geometryObject !== 'object' ||
Array.isArray(geometryObject) ||
geometryObject === null
) {
throw new Error('Argument must be a GeoJSON GeometryCollection object.')
}

if (!('geometries' in geometryObject)) {
throw new Error(
`GeoJSON GeometryCollection must contain a 'geometries' with an array of GeoJSON geometries.`
)
}

if (geometryObject.type !== 'GeometryCollection') {
throw new Error(`Must have a type property with value 'GeometryCollection'.`)
}

if ('geometry' in geometryObject) {
throw new Error(
`GeoJSON GeometryCollection objects are forbidden from having a property 'geometry'.`
)
}

if ('properties' in geometryObject) {
throw new Error(
`GeoJSON GeometryCollection objects are forbidden from having a property 'properties'.`
)
}

if ('features' in geometryObject) {
throw new Error(
`GeoJSON GeometryCollection objects are forbidden from having a property 'features'.`
)
}

if (!Array.isArray(geometryObject.geometries)) {
throw new Error('Geometries property must be an array of valid GeoJSON geometry objects.')
}

geometryObject.geometries.forEach((geometry) => {
if (geometry?.type === 'GeometryCollection') {
geometryCollection(geometry)
} else {
anyGeometry(geometry)
}
})

return true
}

exports.geometryCollection = geometryCollection
2 changes: 1 addition & 1 deletion src/core/geometries/lineStringGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function lineStringGeometry(geometryObject) {
)
}

// // Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates) && geometryObject.coordinates.length !== 1) {
throw new Error('Coordinates property must be an array of valid GeoJSON coordinates')
Expand Down
4 changes: 2 additions & 2 deletions src/core/geometries/multiLineStringGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ function multiLineStringGeometry(geometryObject) {
)
}

// // // Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// // If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates) && geometryObject.coordinates.length !== 1) {
throw new Error('Coordinates property must be an array of valid GeoJSON coordinates')
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/geometries/multiPolygonGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ function multiPolygonGeometry(geometryObject) {
)
}

// // Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// // If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
// Geometry objects are allowed to have empty arrays as coordinates, however validCoordinate may not.
// If coordinates is an empty array, we're done. Otherwise, check for coordinate validity.
if (!Array.isArray(geometryObject.coordinates)) {
throw new Error(
'Coordinates property must be an array of valid GeoJSON polygon coordinate arrays.'
Expand Down
2 changes: 2 additions & 0 deletions src/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ exports.coordinates = {
// Geometries
exports.geometries = {
toBeAnyGeometry: require('./matchers/geometries/toBeAnyGeometry').toBeAnyGeometry,
toBeGeometryCollection: require('./matchers/geometries/toBeGeometryCollection')
.toBeGeometryCollection,
toBeLineStringGeometry: require('./matchers/geometries/toBeLineStringGeometry')
.toBeLineStringGeometry,
toBeMultiLineStringGeometry: require('./matchers/geometries/toBeMultiLineStringGeometry')
Expand Down
2 changes: 1 addition & 1 deletion src/matchers/geometries/toBeAnyGeometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { anyGeometry } = require('../../core/geometries/anyGeometry')
// eslint-disable-next-line jsdoc/require-returns
/**
* Verifies an object meets validity requirements for one of the six basic GeoJSON geometry types:
* Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon
* Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection
*
* @memberof Matchers.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/15
Expand Down
93 changes: 93 additions & 0 deletions src/matchers/geometries/toBeGeometryCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const { geometryCollection } = require('../../core/geometries/geometryCollection')

// eslint-disable-next-line jsdoc/require-returns
/**
* Verifies an object is a valid GeoJSON GeometryCollection. This object requires a
* 'type' property that must equal "GeometryCollection", and a 'geometries' property that contains
* an array of GeoJSON Geometry objects (Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon)
*
* The geometries may be an empty array, but may not be an array of empty arrays or objects.
*
* @memberof Matchers.Geometries
* @see https://github.com/M-Scott-Lassiter/jest-geojson/issues/16
* @param {object} geometryObject any GeoJSON Geometry object
* @example
* const collection = {
* type: 'GeometryCollection',
* geometries: [{
* "type": 'Point',
* "coordinates": [100.0, 0.0]
* }, {
* type: 'LineString',
* coordinates: [
* [101.0, 0.0],
* [102.0, 1.0]
* ]
* }, {
* type: 'Polygon',
* coordinates: [
* [
* [102.0, 2.0],
* [103.0, 2.0],
* [103.0, 3.0],
* [102.0, 3.0],
* [102.0, 2.0]
* ]
* ]
* }, {
* type: 'Point',
* coordinates: [150.0, 73.0]
* }]
* }
*
* test('Object is valid GeoJSON Geometry Object', () => {
* expect(collection).toBeGeometryCollection()
* })
* @example
* const lineString = {
* type: 'LineString',
* coordinates: [
* [101.0, 0.0],
* [102.0, 1.0]
* ]
* }
* test('Object is NOT valid GeoJSON Geometry Object', () => {
* expect(collection.geometries).not.toBeGeometryCollection()
* expect(lineString).not.toBeGeometryCollection()
* })
*/
function toBeGeometryCollection(geometryObject) {
const { printReceived, matcherHint } = this.utils
const passMessage =
// eslint-disable-next-line prefer-template
matcherHint('.not.toBeGeometryCollection', 'GeometryObject', '') +
'\n\n' +
`Expected input to not be a valid GeoJSON GeometryCollection object.\n\n` +
`Received: ${printReceived(geometryObject)}`

/**
* Combines a custom error message with built in Jest tools to provide a more descriptive error
* meessage to the end user.
*
* @param {string} errorMessage Error message text to return to the user
* @returns {string} Concatenated Jest test result string
*/
function failMessage(errorMessage) {
return (
// eslint-disable-next-line prefer-template, no-unused-expressions
matcherHint('.toBeGeometryCollection', 'GeometryObject', '') +
'\n\n' +
`${errorMessage}\n\n` +
`Received: ${printReceived(geometryObject)}`
)
}

try {
geometryCollection(geometryObject)
} catch (err) {
return { pass: false, message: () => failMessage(err.message) }
}
return { pass: true, message: () => passMessage }
}

exports.toBeGeometryCollection = toBeGeometryCollection
4 changes: 4 additions & 0 deletions tests/core.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ describe('Geometry Functions Exported', () => {
expect('anyGeometry' in core.geometries).toBeTruthy()
})

test('geometryCollection', () => {
expect('geometryCollection' in core.geometries).toBeTruthy()
})

test('lineStringGeometry', () => {
expect('lineStringGeometry' in core.geometries).toBeTruthy()
})
Expand Down
16 changes: 8 additions & 8 deletions tests/geometries/__snapshots__/toBeAnyGeometry.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Error Snapshot Testing. Throws error: expect([0, 0, 0]).toBeAnyGeometry 1`] = `
"expect(GeometryObject).toBeAnyGeometry()
Object must be either a valid Point, MultiPoint, LineString, MultiLineString, Polygon, or MultiPolygon
Received: false"
`;

exports[`Error Snapshot Testing. Throws error: expect({type: 'Point', coordinates: [0, 0]}).not.toBeAnyGeometry 1`] = `
"expect(GeometryObject).not.toBeAnyGeometry()
Expected input to not be a valid GeoJSON geometry object.
Received: {\\"coordinates\\": [0, 0], \\"type\\": \\"Point\\"}"
`;

exports[`Error Snapshot Testing. Throws error: expect(false).toBeAnyGeometry 1`] = `
"expect(GeometryObject).toBeAnyGeometry()
Cannot use 'in' operator to search for 'geometry' in false
Received: false"
`;
Loading

0 comments on commit 63cc919

Please sign in to comment.