Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

toBeFeature #24

Closed
M-Scott-Lassiter opened this issue May 24, 2022 · 2 comments
Closed

toBeFeature #24

M-Scott-Lassiter opened this issue May 24, 2022 · 2 comments

Comments

@M-Scott-Lassiter
Copy link
Owner

M-Scott-Lassiter commented May 24, 2022

Description

This is a building block matcher. Features contain Geometry objects and additional properties. We need a way to reliably test that they have been formatted appropriately.

This matcher tests that an object is a valid feature with any geometry type. Any non-conforming values should fail.

  • It must have a "type" member equal to 'Feature'
  • It must have a "geometry" member with either an object of one of the valid seven geometry types, or the value null
  • It must have a "properties" member, but this may be null or an empty object ({}).
  • If it has an optional "id" member, it must be either a number (except NaN) or string.

A Feature object represents a spatially bounded thing. Every Feature object is a GeoJSON object no matter where it occurs in a GeoJSON text.

  • A Feature object has a "type" member with the value "Feature".
  • A Feature object has a member with the name "geometry". The value of the geometry member SHALL be either a Geometry object as defined above or, in the case that the Feature is unlocated, a JSON null value.
  • If a Feature has a commonly used identifier, that identifier SHOULD be included as a member of the Feature object with the name "id", and the value of this member is either a JSON string or number.
  • A Feature object has a member with the name "properties". The value of the properties member is an object (any JSON object or a JSON null value).

~ https://datatracker.ietf.org/doc/html/rfc7946#section-3.2

Feature objects are prohibited from having a "coordinates", "geometries", or "features" property.

Implementations MUST NOT change the semantics of GeoJSON members and types.

The GeoJSON "coordinates" and "geometries" members define Geometry objects. FeatureCollection and Feature objects, respectively, MUST NOT contain a "coordinates" or "geometries" member.

The GeoJSON "geometry" and "properties" members define a Feature object. FeatureCollection and Geometry objects, respectively, MUST NOT contain a "geometry" or "properties" member.

The GeoJSON "features" member defines a FeatureCollection object. Feature and Geometry objects, respectively, MUST NOT contain a "features" member.

~ https://datatracker.ietf.org/doc/html/rfc7946#section-7.1

Other foreign members ARE allowed (see https://datatracker.ietf.org/doc/html/rfc7946#section-6.1). None of these foreign members should get checked for validity of any type; they may contain anything that is valid JSON.

See GeoJSON Spec: What does it mean to extend GeoJSON without using a foreign member? for further clarification.

Valid GeoJSON Feature Examples

{
    "type": "Feature",
    "bbox": [-10.0, -10.0, 10.0, 10.0],
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    "properties": {
        "prop0": "value0",
        "prop1": {
            "this": "that"
        }
    }
}

An empty geometry and properties

{
    "type": "Feature",
    "geometry": null,
    "properties": null
}
{
    "type": "Feature",
    "geometry": null,
    "properties": {}
}

In each case, the matcher should only get passed the object in its entirety, not the individual components, and not as part of another collection object.

Example Matcher Usage

This matcher will have an optional argument (geometryType) that can check the geometry is a specific type:

expect(testFeature).toBeFeature(geometryType)

Omitting the type will check for any valid geometry.

const testFeature = {
    "type": "Feature",
    "bbox": [-10.0, -10.0, 10.0, 10.0],
    "geometry": {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    "properties": {
        "prop0": "value0",
        "prop1": {
            "this": "that"
        }
    }
}
const multiPoint = {
    type: "MultiPoint",
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

expect(testFeature).toBeFeature()
expect(testFeature).toBeFeature('Polygon')

expect(multiPoint).not.toBeFeature()
expect(testFeature).not.toBeFeature('LineString')
expect(testFeature.geometry).not.toBeFeature('Polygon')

Passing Tests

This matcher should use core functions to test coordinate validity.

Values in Range

  • A Feature containing each of the seven geometry types
    • With no optional argument (i.e. .toBeFeature())
    • Optional argument specifying the type (i.e. .toBeFeature('Point'))

Geometry May Be a Null Value

const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: {
        prop1: 'Some Prop'
    }
}

May Have Optional Bounding Box

  • Logical
    • 2D: [-10.0, -10.0, 10.0, 10.0]
    • 3D: [-10.0, -10.0, 0, 10.0, 10.0, 200]
const testFeature = {
    type: 'Feature',
    bbox: <input>,
    geometry: {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    properties: null
}
  • Illogical
const testFeature = {
    type: 'Feature',
    bbox: [-30.0, -30.0, -20.0, -20.0],
    geometry: {
        "type": "Polygon",
        "coordinates": [
            [
                [-10.0, -10.0],
                [10.0, -10.0],
                [10.0, 10.0],
                [-10.0, -10.0]
            ]
        ]
    },
    properties: null
}
  • Redundant
const testFeature = {
    type: 'Feature',
    bbox: [0, 0, 0, 0],
    geometry: null,
    properties: null
}

Properties May Be a Null Value or Empty Object

const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: null
}
const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: {}
}

Optional ID Must Be String or Number

  • Strings: 'ABCD', 'Test 1', '1', '', '[[[180, 10.2, -125], [-180, 90, 35000]]], [{}]'
  • Numbers: 0, 200, -200, Infinity, -Infinity,
const testFeature = {
    type: 'Feature',
    id: <input>,
    geometry: null,
    properties: null
}

Foreign Properties Allowed

  • Single
const testFeature = {
    type: 'Feature',
    geometry: null,
    properties: null,
    foreign: true
}
  • Multiple
const testFeature = {
    type: 'Feature',
    id: '#1',
    geometry: null,
    properties: null,
    foreign1: true,
    foreign2: 33
}
  • Foreign property that would normally be a valid GeoJSON object
const multiPoint = {
    type: 'MultiPoint',
    coordinates: [
        [101.0, 0.0],
        [102.0, 1.0]
    ]
}

const testFeature = {
    type: 'Feature',
    geometry: multiPoint,
    properties: null,
    geometryDuplicate: multiPoint,
    Geometry: [] // Note captitalized 'G'
}

Failing Tests

Feature Input Not an Object

Any of the following inputs to the matcher should fail:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: 200, -200, Infinity, -Infinity, NaN
  • Arrays: [25, 35, 45000], [ ]
  • Strings: '', 'Random Feature'
  • Stringified JSON:
    JSON.stringify({
      type: 'Feature',
      geometry: null,
      properties: null
    })

Invalid Geometry

  • A Feature containing an invalid version of each of the seven geometry types
    • With no optional argument (i.e. .toBeFeature())
    • Optional argument specifying the type (i.e. .toBeFeature('Point'))

Type Value Incorrect

Input:

  • Each of the values from "Feature Input Not an Object", plus
  • 'Point', 'MultiPoint', 'LineString', 'MultiLineString' 'Polygon', 'MultiPolygon', and 'GeometryCollection', or
  • 'FEATURE', 'feature'
const testFeature = {
    type: <input>,
    geometry: null,
    properties: null
}

Invalid Bounding Box

Input:

  • null
  • undefined
  • []
  • Missing Element: [-10.0, -10.0, 10.0]
  • Out of Range Element: [-10.0, -10.0, 190.0, 10.0]
  • South Greater than North: [-10.0, 10.0, 10.0, -10]
  • Bad altitude: [-10.0, -10.0, 0, 10, 10.0, '200']
const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    bbox: <input>
}

Non-alphanumeric ID

Input:

  • undefined
  • null
  • Booleans: true, false
  • Numbers: NaN
  • Arrays: [25, 35, 45000], [ ]
  • Objects: {prop: 1}, { }
const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    id: <input>
}

Contains Prohibited Properties

Coordinates

const testFeature = {
    type: 'Feature',
    coordinates: [[0, 0], [1, 1, 0]],
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null
}

Geometries

const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    geometries: [
        {
            type: 'Point',
            coordinates: [102.0, 0.5]
        }, {
            type: 'Point',
            coordinates: [122.0, -10.25]
        }
    ]
}

Features

const testFeature = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    },
    properties: null,
    features: [{
        type: 'Feature',
        geometry: {
            type: 'Point',
            coordinates: [0, 0]
            },
        properties: {
           prop0: 'value0'
        }
    }],
    id: 33
}

Missing Required Properties

  • No Type
const testFeature = {
    properties: null,
    geometry: {
        type: 'Point',
        coordinates: [102.0, 0.5]
    }
}
  • No Geometry
const testFeature = {
    type: 'Feature',
    properties: null
}
  • No Properties
const testFeature = {
    type: 'Feature',
    geometry: null
}

Geometry contained in an array

  • Each of the values from "Feature Input Not an Object" except null, plus
  • Single Element
const testFeature = {
    type: 'Feature',
    properties: null,
    geometry: [{
        type: 'Point',
        coordinates: [102.0, 0.5]
    }]
}
  • Multiple Elements
const testFeature = {
    type: 'Feature',
    properties: null,
    geometry: [{
        type: 'Point',
        coordinates: [102.0, 0.5]
    }, {
        type: 'Point',
        coordinates: [122.0, -10.25]
    }]
}

If Not Null, Properties Must be an Object

  • Each of the values from "Feature Input Not an Object" except null
@M-Scott-Lassiter M-Scott-Lassiter added the new matcher proposal Proposal for a new GeoJSON matcher label May 24, 2022
@M-Scott-Lassiter M-Scott-Lassiter self-assigned this May 24, 2022
@M-Scott-Lassiter M-Scott-Lassiter added this to To do in Features via automation May 24, 2022
@M-Scott-Lassiter M-Scott-Lassiter changed the title [New Matcher]: toBeAnyFeature toBeAnyFeature May 24, 2022
@M-Scott-Lassiter M-Scott-Lassiter changed the title toBeAnyFeature toBeFeature May 31, 2022
M-Scott-Lassiter added a commit that referenced this issue Jun 2, 2022
Verifies an object is a valid GeoJSON Feature. In addition to the core function and matcher, this
adds a new setup script for 'features', reorganizes the package.json test scripts, and refactors the
good and bad geometry examples from toBeGeometryCollection.test.js into a separate data file for
reuse. Type definitions updated and reorganized. Note: this matcher test has extensive snapshot
testing to begin addressing #32.

Resolves: #24
github-actions bot pushed a commit that referenced this issue Jun 2, 2022
## [1.0.0-beta.15](v1.0.0-beta.14...v1.0.0-beta.15) (2022-06-02)

### 🎁 Feature Changes

* **toBeFeature:** add new matcher ([551aa7f](551aa7f)), closes [#32](#32) [#24](#24)

### 🎯 Test Changes

* add 'Feature' and 'FeatureCollection' to test list of disallowed geometry type values ([f139a09](f139a09))
* **toBeGeometryCollection:** add an unrecognizable geometry to the invalid tests ([5c041c6](5c041c6))
* **toBeGeometryCollection:** add robust snapshot tests, verify coordinates treated as foreign member ([472d12d](472d12d)), closes [#32](#32) [#33](#33)
* **isValid2DBoundingBox:** add robust snapshot tests ([4363710](4363710)), closes [#32](#32)
* **isValid2DCoordinate:** add robust snapshot tests ([ae92f67](ae92f67)), closes [#32](#32)
* **isValid3DBoundingBox:** add robust snapshot tests ([a37ec48](a37ec48)), closes [#32](#32)
* **isValid3DCoordinate:** add robust snapshot tests ([56fbf92](56fbf92)), closes [#32](#32)
* **isValidBoundingBox:** add robust snapshot tests ([063b94e](063b94e)), closes [#32](#32)
* **isValidCoordinate:** add robust snapshot tests ([7b4a804](7b4a804)), closes [#32](#32)
* **toBeAnyGeometry:** add robust snapshot tests ([8a6e611](8a6e611)), closes [#32](#32)
* **toBeLineStringGeometry:** add robust snapshot tests ([cfaed46](cfaed46)), closes [#32](#32)
* **toBeMultiLineStringGeometry:** add robust snapshot tests ([57dc767](57dc767)), closes [#32](#32)
* **toBeMultiPointGeometry:** add robust snapshot tests ([36013e1](36013e1)), closes [#32](#32)
* **toBeMultiPolygonGeometry:** add robust snapshot tests ([df1c23a](df1c23a)), closes [#32](#32)
* **toBePointGeometry:** add robust snapshot tests ([fd5c516](fd5c516)), closes [#32](#32)
* **toBePolygonGeometry:** add robust snapshot tests ([1c9df69](1c9df69)), closes [#32](#32)
@M-Scott-Lassiter
Copy link
Owner Author

🎉 This issue has been resolved in version 1.0.0-beta.15 🎉

The release is available on:

Your semantic-release bot 📦🚀

@M-Scott-Lassiter M-Scott-Lassiter added released on @beta matchers/features and removed new matcher proposal Proposal for a new GeoJSON matcher labels Jun 2, 2022
Features automation moved this from To do to Done Jun 2, 2022
github-actions bot pushed a commit that referenced this issue Jun 2, 2022
## 1.0.0 (2022-06-02)

### 🧭 API Documentation Changes

* **toBeMultiLineStringGeometry:** add the min point count error to the JSDoc API ([01f6c4b](01f6c4b))
* change JSDoc param types to avoid using GeoJSON unknown types ([06ac03a](06ac03a))
* **all:** cleanup JSDoc formatting and standardize API examples ([300a96d](300a96d))
* update JSDoc descriptions of coordinate core functions ([f5658f3](f5658f3))
* update JSDoc organization ([7ab7eca](7ab7eca))
* **isValidCoordinate:** update returns description ([70bd43e](70bd43e))
* **isValidCoordinate:** update the error and parameter descriptions ([7e3e8ed](7e3e8ed))

### 🐞 Bug Fixes

* add bounding box validity checking to geometry core functions and matchers ([ac6a9a1](ac6a9a1)), closes [/datatracker.ietf.org/doc/html/rfc7946#section-5](https://github.com/M-Scott-Lassiter//datatracker.ietf.org/doc/html/rfc7946/issues/section-5) [#9](#9) [#10](#10) [#11](#11) [#12](#12) [#13](#13) [#14](#14) [#16](#16) [#29](#29)
* **toBeLineStringGeometry:** prohibit single coordinate in "coordinates" member ([ee5de52](ee5de52)), closes [/datatracker.ietf.org/doc/html/rfc7946#section-3](https://github.com/M-Scott-Lassiter//datatracker.ietf.org/doc/html/rfc7946/issues/section-3) [#11](#11)

### 🎯 Test Changes

* add 'Feature' and 'FeatureCollection' to test list of disallowed geometry type values ([f139a09](f139a09))
* **toBeLineStringGeometry:** add a stress test with many points ([22df5c7](22df5c7))
* **toBeGeometryCollection:** add an unrecognizable geometry to the invalid tests ([5c041c6](5c041c6))
* **toBeGeometryCollection:** add robust snapshot tests, verify coordinates treated as foreign member ([472d12d](472d12d)), closes [#32](#32) [#33](#33)
* **isValid2DBoundingBox:** add robust snapshot tests ([4363710](4363710)), closes [#32](#32)
* **isValid2DCoordinate:** add robust snapshot tests ([ae92f67](ae92f67)), closes [#32](#32)
* **isValid3DBoundingBox:** add robust snapshot tests ([a37ec48](a37ec48)), closes [#32](#32)
* **isValid3DCoordinate:** add robust snapshot tests ([56fbf92](56fbf92)), closes [#32](#32)
* **isValidBoundingBox:** add robust snapshot tests ([063b94e](063b94e)), closes [#32](#32)
* **isValidCoordinate:** add robust snapshot tests ([7b4a804](7b4a804)), closes [#32](#32)
* **toBeAnyGeometry:** add robust snapshot tests ([8a6e611](8a6e611)), closes [#32](#32)
* **toBeLineStringGeometry:** add robust snapshot tests ([cfaed46](cfaed46)), closes [#32](#32)
* **toBeMultiLineStringGeometry:** add robust snapshot tests ([57dc767](57dc767)), closes [#32](#32)
* **toBeMultiPointGeometry:** add robust snapshot tests ([36013e1](36013e1)), closes [#32](#32)
* **toBeMultiPolygonGeometry:** add robust snapshot tests ([df1c23a](df1c23a)), closes [#32](#32)
* **toBePointGeometry:** add robust snapshot tests ([fd5c516](fd5c516)), closes [#32](#32)
* **toBePolygonGeometry:** add robust snapshot tests ([1c9df69](1c9df69)), closes [#32](#32)
* **toBeMultiLineStringGeometry:** fix coordinate out of range test that ([d6fe2ac](d6fe2ac))
* **toBeMultiPointGeometry:** fix typo in test and core function that was omitting coverage ([bc10f4e](bc10f4e))
* **isValid2DBoundingBox:** fix typo in test descriptions ([df94c27](df94c27))
* setup the project testing framework ([6a95c37](6a95c37))

### 🎁 Feature Changes

* **isValid2DBoundingBox:** add new matcher function ([7fe56f3](7fe56f3)), closes [#6](#6)
* **isValid2DCoordinate:** add new matcher function ([527bbc4](527bbc4)), closes [#1](#1)
* **isValid3DCoordinate:** add new matcher function ([0329231](0329231)), closes [#2](#2)
* **isValidCoordinate:** add new matcher function ([d7e5b70](d7e5b70)), closes [#4](#4)
* **isValid3DBoundingBox:** add new matcher ([6ee8cc6](6ee8cc6)), closes [#7](#7)
* **isValidBoundingBox:** add new matcher ([9a8b7ed](9a8b7ed)), closes [#8](#8)
* **toBeAnyGeometry:** add new matcher ([ed7c3eb](ed7c3eb)), closes [#15](#15)
* **toBeFeature:** add new matcher ([551aa7f](551aa7f)), closes [#32](#32) [#24](#24)
* **toBeFeatureCollection:** add new matcher ([21fe044](21fe044)), closes [#25](#25)
* **toBeGeometryCollection:** add new matcher ([63cc919](63cc919)), closes [#16](#16)
* **toBeLineStringGeometry:** add new matcher ([54416a5](54416a5)), closes [#11](#11)
* **toBeMultiLineStringGeometry:** add new matcher ([3d3a15e](3d3a15e)), closes [#12](#12)
* **toBeMultiPointGeometry:** add new matcher ([9a12752](9a12752)), closes [#10](#10)
* **toBeMultiPolygonGeometry:** add new matcher ([41fef3a](41fef3a)), closes [#14](#14)
* **toBePointGeometry:** add new matcher ([9973afa](9973afa)), closes [#9](#9)
* **toBePolygonGeometry:** add new matcher ([3b9d18d](3b9d18d)), closes [#13](#13)
* split package exports into matcher and core functionality ([a7340d9](a7340d9)), closes [#5](#5)

### 🏗️ Build Changes

* add conventional-changelog-conventionalcommits as dev dependency ([7361d79](7361d79)), closes [#3](#3)
* **package:** add entry points for all, boundingboxes, and coordinates ([8c1d312](8c1d312))
* move release configuration into a separate shareable file ([fa6e50d](fa6e50d))
* **package:** rename matcher loader entry script ([5221d6d](5221d6d))
* setup initial project environment ([b468a41](b468a41))
* **package:** update the commitizen config for customized scopes ([ac05626](ac05626))
* **devDependencies:** upgrade Jest to v28.1, specify peerDependency at >v24.0.0 ([22d1614](22d1614)), closes [#30](#30)
* **package:** upgrade min required Node version to 16 ([58a9824](58a9824))
* **package:** upgrade minimum required node version from 10 to 14 to match LTS schedule ([1e8a8bb](1e8a8bb))
@M-Scott-Lassiter
Copy link
Owner Author

🎉 This issue has been resolved in version 1.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

This was referenced Jun 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Features
  
Done
Development

No branches or pull requests

1 participant