From 2fa76b53fe662a433c66306bf45c249ee9e90775 Mon Sep 17 00:00:00 2001 From: Alexandre Tranchant Date: Sat, 3 Oct 2026 13:06:53 +0200 Subject: [PATCH 1/2] tests: Add legacy tests from wkt parser for spatial decoder --- tests/Legacy/CircularStringTest.php | 66 +++++++++ tests/Legacy/ExceptionTest.php | 96 +++++++++++++ tests/Legacy/GeometryCollectionTest.php | 66 +++++++++ tests/Legacy/LexerTest.php | 176 ++++++++++++++++++++++++ tests/Legacy/LineStringTest.php | 66 +++++++++ tests/Legacy/MultiLineStringTest.php | 66 +++++++++ tests/Legacy/MultiPointTest.php | 66 +++++++++ tests/Legacy/MultiPolygonTest.php | 66 +++++++++ tests/Legacy/ParserTest.php | 121 ++++++++++++++++ tests/Legacy/PointTest.php | 70 ++++++++++ tests/Legacy/PolygonTest.php | 68 +++++++++ tests/Legacy/ReUsedParserTest.php | 77 +++++++++++ tests/Legacy/TriangleTest.php | 130 +++++++++++++++++ tests/Legacy/Utils/SpecificTestCase.php | 117 ++++++++++++++++ 14 files changed, 1251 insertions(+) create mode 100644 tests/Legacy/CircularStringTest.php create mode 100644 tests/Legacy/ExceptionTest.php create mode 100644 tests/Legacy/GeometryCollectionTest.php create mode 100644 tests/Legacy/LexerTest.php create mode 100644 tests/Legacy/LineStringTest.php create mode 100644 tests/Legacy/MultiLineStringTest.php create mode 100644 tests/Legacy/MultiPointTest.php create mode 100644 tests/Legacy/MultiPolygonTest.php create mode 100644 tests/Legacy/ParserTest.php create mode 100644 tests/Legacy/PointTest.php create mode 100644 tests/Legacy/PolygonTest.php create mode 100644 tests/Legacy/ReUsedParserTest.php create mode 100644 tests/Legacy/TriangleTest.php create mode 100644 tests/Legacy/Utils/SpecificTestCase.php diff --git a/tests/Legacy/CircularStringTest.php b/tests/Legacy/CircularStringTest.php new file mode 100644 index 0000000..58f45e4 --- /dev/null +++ b/tests/Legacy/CircularStringTest.php @@ -0,0 +1,66 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class CircularStringTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator + */ + public static function circularStringProvider(): \Generator + { + yield 'testCircularString' => ['CIRCULARSTRING(0 0, 1 1, 1 0)', null, [[0, 0], [1, 1], [1, 0]], null]; + yield 'testCircularStringWithFloat' => ['CIRCULARSTRING(0.0 0.0, 1.1 1.1, 1.0 0.0)', null, [['0', '0'], ['1.1', '1.1'], ['1', '0']], null]; + yield 'testCircularStringWithSrid' => ['SRID=4326;CIRCULARSTRING(0 0, 1 1, 1 0)', 4326, [[0, 0], [1, 1], [1, 0]], null]; + yield 'testCircularStringWithZ' => ['CIRCULARSTRINGZ(0 0 0, 1 1 1, 1 0 -1)', null, [[0, 0, 0], [1, 1, 1], [1, 0, -1]], 'Z']; + yield 'testCircularStringWithZAndSrid' => ['SRID=4326;CIRCULARSTRINGZ(0 0 0, 1 1 1, 1 0 -1)', 4326, [[0, 0, 0], [1, 1, 1], [1, 0, -1]], 'Z']; + yield 'testCircularStringWithM' => ['CIRCULARSTRINGM(0 0 0, 1 1 1, 1 0 -1)', null, [[0, 0, 0], [1, 1, 1], [1, 0, -1]], 'M']; + yield 'testCircularStringWithMAndSrid' => ['SRID=4326;CIRCULARSTRINGM(0 0 0, 1 1 1, 1 0 -1)', 4326, [[0, 0, 0], [1, 1, 1], [1, 0, -1]], 'M']; + yield 'testCircularStringWithZM' => ['CIRCULARSTRINGZM(0 0 0 0, 1 1 1 1, 1 0 -1 0)', null, [[0, 0, 0, 0], [1, 1, 1, 1], [1, 0, -1, 0]], 'ZM']; + yield 'testCircularStringWithZMAndSrid' => ['SRID=4326;CIRCULARSTRINGZM(0 0 0 0, 1 1 1 1.2, 1 0 -1 0)', 4326, [[0, 0, 0, 0], [1, 1, 1, '1.2'], [1, 0, -1, 0]], 'ZM']; + } + + /** + * @param (int|string)[][] $coordinates + */ + #[DataProvider('circularStringProvider')] + public function testCircularString(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + self::assertCircularStringParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/ExceptionTest.php b/tests/Legacy/ExceptionTest.php new file mode 100644 index 0000000..6873e50 --- /dev/null +++ b/tests/Legacy/ExceptionTest.php @@ -0,0 +1,96 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; +use LongitudeOne\SpatialDecoder\Exception\NonInstantiableGeometryTypeException; +use LongitudeOne\SpatialDecoder\Exception\NotYetImplementedException; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class ExceptionTest extends TestCase +{ + /** + * @return \Generator + */ + public static function notInstantiableTypes(): \Generator + { + yield 'CURVE' => ['CURVE', 'According the ISO 13249-3:2016 standard, the "CURVE" type is not instantiable. Did you mean "MULTICURVE"?']; + yield 'GEOMETRY' => ['GEOMETRY', 'According the ISO 13249-3:2016 standard, the "GEOMETRY" type is not instantiable. Did you mean "GEOMETRYCOLLECTION"?']; + yield 'SOLID' => ['SOLID', 'According the ISO 13249-3:2016 standard, the "SOLID" type is not instantiable. Did you mean "POLYGON"?']; + yield 'SURFACE' => ['SURFACE', 'According the ISO 13249-3:2016 standard, the "SURFACE" type is not instantiable. Did you mean "MULTISURFACE"?']; + } + + /** + * @return \Generator + */ + public static function notYetImplementedTypes(): \Generator + { + yield 'BREPSOLID' => ['BREPSOLID', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "BREPSOLID".']; + yield 'CIRCLE' => ['CIRCLE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "CIRCLE".']; + yield 'CLOTHOID' => ['CLOTHOID', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "CLOTHOID".']; + yield 'COMPOUNDCURVE' => ['COMPOUNDCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "COMPOUNDCURVE".']; + yield 'COMPOUNDSURFACE' => ['COMPOUNDSURFACE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "COMPOUNDSURFACE".']; + yield 'CURVEPOLYGON' => ['CURVEPOLYGON', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "CURVEPOLYGON".']; + yield 'ELLIPTICALCURVE' => ['ELLIPTICALCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "ELLIPTICALCURVE".']; + yield 'GEODESICSTRING' => ['GEODESICSTRING', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "GEODESICSTRING".']; + yield 'MULTICURVE' => ['MULTICURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "MULTICURVE".']; + yield 'MULTISURFACE' => ['MULTISURFACE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "MULTISURFACE".']; + yield 'NURBSCURVE' => ['NURBSCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "NURBSCURVE".']; + yield 'SPIRALCURVE' => ['SPIRALCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "SPIRALCURVE".']; + yield 'POLYHDRLSURFACE' => ['POLYHDRLSURFACE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "POLYHDRLSURFACE".']; + yield 'TIN' => ['TIN', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "TIN".']; + } + + public function testNotExistentException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('According the ISO 13249-3:2016 standard, the "FOO" type does not exist.'); + + (new Decoder(new EwktDecoderStrategy()))->decode('FOO(42 42)'); + } + + #[DataProvider('notInstantiableTypes')] + public function testNotInstantiable(string $notInstantiableType, string $expectedMessage): void + { + $this->expectException(NonInstantiableGeometryTypeException::class); + $this->expectExceptionMessage($expectedMessage); + + $toParse = \sprintf('%s(42 42)', $notInstantiableType); + + (new Decoder(new EwktDecoderStrategy()))->decode($toParse); + } + + public function testNotInstantiableException(): void + { + $exception = new NonInstantiableGeometryTypeException('foo'); + self::assertSame('According the ISO 13249-3:2016 standard, the "foo" type is not instantiable.', $exception->getMessage()); + } + + #[DataProvider('notYetImplementedTypes')] + public function testNotYetImplemented(string $notYetImplemented, string $expectedMessage): void + { + $this->expectException(NotYetImplementedException::class); + $this->expectExceptionMessage($expectedMessage); + + $toParse = \sprintf('%s(42 42)', $notYetImplemented); + + (new Decoder(new EwktDecoderStrategy()))->decode($toParse); + } +} diff --git a/tests/Legacy/GeometryCollectionTest.php b/tests/Legacy/GeometryCollectionTest.php new file mode 100644 index 0000000..fa85e78 --- /dev/null +++ b/tests/Legacy/GeometryCollectionTest.php @@ -0,0 +1,66 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class GeometryCollectionTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator, 3: ?string}, null, void> + */ + public static function geometryCollectionProvider(): \Generator + { + yield 'testGeometryCollection' => ['GEOMETRYCOLLECTION(POINT(34.23 -87), LINESTRING(34.23 -87, 45.3 -92))', null, [['type' => 'POINT', 'value' => ['34.23', -87]], ['type' => 'LINESTRING', 'value' => [['34.23', -87], ['45.3', -92]]]], null]; + yield 'testGeometryCollectionWithSrid' => ['SRID=4326;GEOMETRYCOLLECTION(POINT(34.23 -87), LINESTRING(34.23 -87, 45.3 -92))', 4326, [['type' => 'POINT', 'value' => ['34.23', -87]], ['type' => 'LINESTRING', 'value' => [['34.23', -87], ['45.3', -92]]]], null]; + yield 'testGeometryCollectionWithZ' => ['GEOMETRYCOLLECTION(POINT Z(34.23 -87 10), LINESTRING Z(34.23 -87 10, 45.3 -92 10))', null, [['type' => 'POINT', 'value' => ['34.23', -87, 10]], ['type' => 'LINESTRING', 'value' => [['34.23', -87, 10], ['45.3', -92, 10]]]], 'Z']; + yield 'testGeometryCollectionWithZAndSrid' => ['SRID=4326;GEOMETRYCOLLECTION(POINT Z(34.23 -87 10), LINESTRING Z(34.23 -87 10, 45.3 -92 10))', 4326, [['type' => 'POINT', 'value' => ['34.23', -87, 10]], ['type' => 'LINESTRING', 'value' => [['34.23', -87, 10], ['45.3', -92, 10]]]], 'Z']; + yield 'testGeometryCollectionWithM' => ['GEOMETRYCOLLECTION(POINT M(34.23 -87 10), LINESTRING M(34.23 -87 10, 45.3 -92 10))', null, [['type' => 'POINT', 'value' => ['34.23', -87, 10]], ['type' => 'LINESTRING', 'value' => [['34.23', -87, 10], ['45.3', -92, 10]]]], 'M']; + yield 'testGeometryCollectionWithMAndSrid' => ['SRID=4326;GEOMETRYCOLLECTION(POINT M(34.23 -87 10), LINESTRING M(34.23 -87 10, 45.3 -92 10))', 4326, [['type' => 'POINT', 'value' => ['34.23', -87, 10]], ['type' => 'LINESTRING', 'value' => [['34.23', -87, 10], ['45.3', -92, 10]]]], 'M']; + yield 'testGeometryCollectionWithZM' => ['GEOMETRYCOLLECTION(POINT ZM(34.23 -87 10 20), LINESTRING ZM(34.23 -87 10 20, 45.3 -92 10 20))', null, [['type' => 'POINT', 'value' => ['34.23', -87, 10, 20]], ['type' => 'LINESTRING', 'value' => [['34.23', -87, 10, 20], ['45.3', -92, 10, 20]]]], 'ZM']; + yield 'testGeometryCollectionWithZMAndSrid' => ['SRID=4326;GEOMETRYCOLLECTION(POINT ZM(34.23 -87 10 20), LINESTRING ZM(34.23 -87 10 20, 45.3 -92 10 20))', 4326, [['type' => 'POINT', 'value' => ['34.23', -87, 10, 20]], ['type' => 'LINESTRING', 'value' => [['34.23', -87, 10, 20], ['45.3', -92, 10, 20]]]], 'ZM']; + } + + /** + * @param array $coordinates + */ + #[DataProvider('geometryCollectionProvider')] + public function testGeometryCollection(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: array, srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + + self::assertGeometryCollectionParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/LexerTest.php b/tests/Legacy/LexerTest.php new file mode 100644 index 0000000..72ed7ad --- /dev/null +++ b/tests/Legacy/LexerTest.php @@ -0,0 +1,176 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use Doctrine\Common\Lexer\Token; +use LongitudeOne\SpatialDecoder\Strategy\Common\Lexer; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +/** + * Lexer tests. + */ +class LexerTest extends TestCase +{ + /** + * @return \Generator + */ + public static function tokenData(): \Generator + { + yield 'POINT' => ['POINT', [[Lexer::T_POINT, 'POINT', 0]]]; + yield 'POINTM' => ['POINTM', [[Lexer::T_POINT, 'POINT', 0], [Lexer::T_M, 'M', 5]]]; + yield 'POINT M' => ['POINT M', [[Lexer::T_POINT, 'POINT', 0], [Lexer::T_M, 'M', 6]]]; + yield 'POINTZ' => ['POINTZ', [[Lexer::T_POINT, 'POINT', 0], [Lexer::T_Z, 'Z', 5]]]; + yield 'POINT Z' => ['POINT Z', [[Lexer::T_POINT, 'POINT', 0], [Lexer::T_Z, 'Z', 6]]]; + yield 'POINT ZM' => ['POINT ZM', [[Lexer::T_POINT, 'POINT', 0], [Lexer::T_ZM, 'ZM', 6]]]; + yield 'POINTZM' => ['POINTZM', [[Lexer::T_POINT, 'POINT', 0], [Lexer::T_ZM, 'ZM', 5]]]; + + yield 'LINESTRING' => ['LINESTRING', [[Lexer::T_LINESTRING, 'LINESTRING', 0]]]; + yield 'LINESTRINGM' => ['LINESTRINGM', [[Lexer::T_LINESTRING, 'LINESTRING', 0], [Lexer::T_M, 'M', 10]]]; + yield 'LINESTRING M' => ['LINESTRING M', [[Lexer::T_LINESTRING, 'LINESTRING', 0], [Lexer::T_M, 'M', 11]]]; + yield 'LINESTRINGZ' => ['LINESTRINGZ', [[Lexer::T_LINESTRING, 'LINESTRING', 0], [Lexer::T_Z, 'Z', 10]]]; + yield 'LINESTRING Z' => ['LINESTRING Z', [[Lexer::T_LINESTRING, 'LINESTRING', 0], [Lexer::T_Z, 'Z', 11]]]; + yield 'LINESTRING ZM' => ['LINESTRING ZM', [[Lexer::T_LINESTRING, 'LINESTRING', 0], [Lexer::T_ZM, 'ZM', 11]]]; + yield 'LINESTRINGZM' => ['LINESTRINGZM', [[Lexer::T_LINESTRING, 'LINESTRING', 0], [Lexer::T_ZM, 'ZM', 10]]]; + + yield 'POLYGON' => ['POLYGON', [[Lexer::T_POLYGON, 'POLYGON', 0]]]; + yield 'POLYGONM' => ['POLYGONM', [[Lexer::T_POLYGON, 'POLYGON', 0], [Lexer::T_M, 'M', 7]]]; + yield 'POLYGON M' => ['POLYGON M', [[Lexer::T_POLYGON, 'POLYGON', 0], [Lexer::T_M, 'M', 8]]]; + yield 'POLYGONZ' => ['POLYGONZ', [[Lexer::T_POLYGON, 'POLYGON', 0], [Lexer::T_Z, 'Z', 7]]]; + yield 'POLYGON Z' => ['POLYGON Z', [[Lexer::T_POLYGON, 'POLYGON', 0], [Lexer::T_Z, 'Z', 8]]]; + yield 'POLYGON ZM' => ['POLYGON ZM', [[Lexer::T_POLYGON, 'POLYGON', 0], [Lexer::T_ZM, 'ZM', 8]]]; + yield 'POLYGONZM' => ['POLYGONZM', [[Lexer::T_POLYGON, 'POLYGON', 0], [Lexer::T_ZM, 'ZM', 7]]]; + + yield 'MULTIPOINT' => ['MULTIPOINT', [[Lexer::T_MULTIPOINT, 'MULTIPOINT', 0]]]; + yield 'MULTIPOINTM' => ['MULTIPOINTM', [[Lexer::T_MULTIPOINT, 'MULTIPOINT', 0], [Lexer::T_M, 'M', 10]]]; + yield 'MULTIPOINT M' => ['MULTIPOINT M', [[Lexer::T_MULTIPOINT, 'MULTIPOINT', 0], [Lexer::T_M, 'M', 11]]]; + yield 'MULTIPOINTZ' => ['MULTIPOINTZ', [[Lexer::T_MULTIPOINT, 'MULTIPOINT', 0], [Lexer::T_Z, 'Z', 10]]]; + yield 'MULTIPOINT Z' => ['MULTIPOINT Z', [[Lexer::T_MULTIPOINT, 'MULTIPOINT', 0], [Lexer::T_Z, 'Z', 11]]]; + yield 'MULTIPOINT ZM' => ['MULTIPOINT ZM', [[Lexer::T_MULTIPOINT, 'MULTIPOINT', 0], [Lexer::T_ZM, 'ZM', 11]]]; + yield 'MULTIPOINTZM' => ['MULTIPOINTZM', [[Lexer::T_MULTIPOINT, 'MULTIPOINT', 0], [Lexer::T_ZM, 'ZM', 10]]]; + + yield 'MULTILINESTRING' => ['MULTILINESTRING', [[Lexer::T_MULTILINESTRING, 'MULTILINESTRING', 0]]]; + yield 'MULTILINESTRINGM' => ['MULTILINESTRINGM', [[Lexer::T_MULTILINESTRING, 'MULTILINESTRING', 0], [Lexer::T_M, 'M', 15]]]; + yield 'MULTILINESTRING M' => ['MULTILINESTRING M', [[Lexer::T_MULTILINESTRING, 'MULTILINESTRING', 0], [Lexer::T_M, 'M', 16]]]; + yield 'MULTILINESTRINGZ' => ['MULTILINESTRINGZ', [[Lexer::T_MULTILINESTRING, 'MULTILINESTRING', 0], [Lexer::T_Z, 'Z', 15]]]; + yield 'MULTILINESTRING Z' => ['MULTILINESTRING Z', [[Lexer::T_MULTILINESTRING, 'MULTILINESTRING', 0], [Lexer::T_Z, 'Z', 16]]]; + yield 'MULTILINESTRING ZM' => ['MULTILINESTRING ZM', [[Lexer::T_MULTILINESTRING, 'MULTILINESTRING', 0], [Lexer::T_ZM, 'ZM', 16]]]; + + yield 'MULTIPOLYGON' => ['MULTIPOLYGON', [[Lexer::T_MULTIPOLYGON, 'MULTIPOLYGON', 0]]]; + yield 'MULTIPOLYGONM' => ['MULTIPOLYGONM', [[Lexer::T_MULTIPOLYGON, 'MULTIPOLYGON', 0], [Lexer::T_M, 'M', 12]]]; + yield 'MULTIPOLYGON M' => ['MULTIPOLYGON M', [[Lexer::T_MULTIPOLYGON, 'MULTIPOLYGON', 0], [Lexer::T_M, 'M', 13]]]; + yield 'MULTIPOLYGONZ' => ['MULTIPOLYGONZ', [[Lexer::T_MULTIPOLYGON, 'MULTIPOLYGON', 0], [Lexer::T_Z, 'Z', 12]]]; + yield 'MULTIPOLYGON Z' => ['MULTIPOLYGON Z', [[Lexer::T_MULTIPOLYGON, 'MULTIPOLYGON', 0], [Lexer::T_Z, 'Z', 13]]]; + yield 'MULTIPOLYGON ZM' => ['MULTIPOLYGON ZM', [[Lexer::T_MULTIPOLYGON, 'MULTIPOLYGON', 0], [Lexer::T_ZM, 'ZM', 13]]]; + + yield 'GEOMETRYCOLLECTION' => ['GEOMETRYCOLLECTION', [[Lexer::T_GEOMETRYCOLLECTION, 'GEOMETRYCOLLECTION', 0]]]; + yield 'GEOMETRYCOLLECTIONM' => ['GEOMETRYCOLLECTIONM', [[Lexer::T_GEOMETRYCOLLECTION, 'GEOMETRYCOLLECTION', 0], [Lexer::T_M, 'M', 18]]]; + yield 'GEOMETRYCOLLECTION M' => ['GEOMETRYCOLLECTION M', [[Lexer::T_GEOMETRYCOLLECTION, 'GEOMETRYCOLLECTION', 0], [Lexer::T_M, 'M', 19]]]; + yield 'GEOMETRYCOLLECTIONZ' => ['GEOMETRYCOLLECTIONZ', [[Lexer::T_GEOMETRYCOLLECTION, 'GEOMETRYCOLLECTION', 0], [Lexer::T_Z, 'Z', 18]]]; + yield 'GEOMETRYCOLLECTION Z' => ['GEOMETRYCOLLECTION Z', [[Lexer::T_GEOMETRYCOLLECTION, 'GEOMETRYCOLLECTION', 0], [Lexer::T_Z, 'Z', 19]]]; + yield 'GEOMETRYCOLLECTION ZM' => ['GEOMETRYCOLLECTION ZM', [[Lexer::T_GEOMETRYCOLLECTION, 'GEOMETRYCOLLECTION', 0], [Lexer::T_ZM, 'ZM', 19]]]; + yield 'GEOMETRYCOLLECTIONZM' => ['GEOMETRYCOLLECTIONZM', [[Lexer::T_GEOMETRYCOLLECTION, 'GEOMETRYCOLLECTION', 0], [Lexer::T_ZM, 'ZM', 18]]]; + + yield 'COMPOUNDCURVE' => ['COMPOUNDCURVE', [[Lexer::T_COMPOUNDCURVE, 'COMPOUNDCURVE', 0]]]; + yield 'COMPOUNDCURVEM' => ['COMPOUNDCURVEM', [[Lexer::T_COMPOUNDCURVE, 'COMPOUNDCURVE', 0], [Lexer::T_M, 'M', 13]]]; + yield 'COMPOUNDCURVE M' => ['COMPOUNDCURVE M', [[Lexer::T_COMPOUNDCURVE, 'COMPOUNDCURVE', 0], [Lexer::T_M, 'M', 14]]]; + yield 'COMPOUNDCURVEZ' => ['COMPOUNDCURVEZ', [[Lexer::T_COMPOUNDCURVE, 'COMPOUNDCURVE', 0], [Lexer::T_Z, 'Z', 13]]]; + yield 'COMPOUNDCURVE Z' => ['COMPOUNDCURVE Z', [[Lexer::T_COMPOUNDCURVE, 'COMPOUNDCURVE', 0], [Lexer::T_Z, 'Z', 14]]]; + yield 'COMPOUNDCURVE ZM' => ['COMPOUNDCURVE ZM', [[Lexer::T_COMPOUNDCURVE, 'COMPOUNDCURVE', 0], [Lexer::T_ZM, 'ZM', 14]]]; + yield 'COMPOUNDCURVEZM' => ['COMPOUNDCURVEZM', [[Lexer::T_COMPOUNDCURVE, 'COMPOUNDCURVE', 0], [Lexer::T_ZM, 'ZM', 13]]]; + + yield 'CIRCULARSTRING' => ['CIRCULARSTRING', [[Lexer::T_CIRCULARSTRING, 'CIRCULARSTRING', 0]]]; + yield 'CIRCULARSTRINGM' => ['CIRCULARSTRINGM', [[Lexer::T_CIRCULARSTRING, 'CIRCULARSTRING', 0], [Lexer::T_M, 'M', 14]]]; + yield 'CIRCULARSTRING M' => ['CIRCULARSTRING M', [[Lexer::T_CIRCULARSTRING, 'CIRCULARSTRING', 0], [Lexer::T_M, 'M', 15]]]; + yield 'CIRCULARSTRINGZ' => ['CIRCULARSTRINGZ', [[Lexer::T_CIRCULARSTRING, 'CIRCULARSTRING', 0], [Lexer::T_Z, 'Z', 14]]]; + yield 'CIRCULARSTRING Z' => ['CIRCULARSTRING Z', [[Lexer::T_CIRCULARSTRING, 'CIRCULARSTRING', 0], [Lexer::T_Z, 'Z', 15]]]; + yield 'CIRCULARSTRING ZM' => ['CIRCULARSTRING ZM', [[Lexer::T_CIRCULARSTRING, 'CIRCULARSTRING', 0], [Lexer::T_ZM, 'ZM', 15]]]; + + yield '35' => ['35', [[Lexer::T_INTEGER, 35, 0]]]; + yield '-25' => ['-25', [[Lexer::T_INTEGER, -25, 0]]]; + yield '-120.33' => ['-120.33', [[Lexer::T_FLOAT, -120.33, 0]]]; + yield '0.0' => ['0.0', [[Lexer::T_FLOAT, 0.0, 0]]]; + yield '1e-5' => ['1e-5', [[Lexer::T_FLOAT, 0.00001, 0]]]; + + yield 'SRID' => ['SRID', [[Lexer::T_SRID, 'SRID', 0]]]; + yield 'SRID=4326;LINESTRING(0 0.0, 10.1 -10.025, 20.5 25.9, 53E-003 60)' => [ + 'SRID=4326;LINESTRING(0 0.0, 10.1 -10.025, 20.5 25.9, 53E-003 60)', + [ + [Lexer::T_SRID, 'SRID', 0], + [Lexer::T_EQUALS, '=', 4], + [Lexer::T_INTEGER, 4326, 5], + [Lexer::T_SEMICOLON, ';', 9], + [Lexer::T_LINESTRING, 'LINESTRING', 10], + [Lexer::T_OPEN_PARENTHESIS, '(', 20], + [Lexer::T_INTEGER, 0, 21], + [Lexer::T_FLOAT, 0, 23], + [Lexer::T_COMMA, ',', 26], + [Lexer::T_FLOAT, 10.1, 28], + [Lexer::T_FLOAT, -10.025, 33], + [Lexer::T_COMMA, ',', 40], + [Lexer::T_FLOAT, 20.5, 42], + [Lexer::T_FLOAT, 25.9, 47], + [Lexer::T_COMMA, ',', 51], + [Lexer::T_FLOAT, 0.053, 53], + [Lexer::T_INTEGER, 60, 61], + [Lexer::T_CLOSE_PARENTHESIS, ')', 63], + ], + ]; + } + + /** + * @param string $value WKT string to tokenize + * @param array{0: int, 1: string|float|int, 2: int}[] $expected Expected token sequence + */ + #[DataProvider('tokenData')] + public function testTokenRecognition(string $value, array $expected): void + { + $lexer = new Lexer($value); + + foreach ($expected as $token) { + $lexer->moveNext(); + + $actual = $lexer->lookahead; + + self::assertInstanceOf(Token::class, $actual); + self::assertEquals($token[0], $actual->type, "Token type mismatch for input: $value"); + self::assertEquals($token[1], $actual->value, "Token value mismatch for input: $value"); + self::assertEquals($token[2], $actual->position, "Token position mismatch for input: $value"); + } + } + + /** Tests token recognition with a reused lexer. */ + public function testTokenRecognitionReuseLexer(): void + { + $lexer = new Lexer(); + + foreach (self::tokenData() as $testData) { + $lexer->setInput($testData[0]); + + foreach ($testData[1] as $token) { + $lexer->moveNext(); + + $actual = $lexer->lookahead; + + self::assertInstanceOf(Token::class, $actual); + self::assertEquals($token[0], $actual->type, "Token type mismatch for input: $testData[0]"); + self::assertEquals($token[1], $actual->value, "Token value mismatch for input: $testData[0]"); + self::assertEquals($token[2], $actual->position, "Token position mismatch for input: $testData[0]"); + } + } + } +} diff --git a/tests/Legacy/LineStringTest.php b/tests/Legacy/LineStringTest.php new file mode 100644 index 0000000..27230f1 --- /dev/null +++ b/tests/Legacy/LineStringTest.php @@ -0,0 +1,66 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class LineStringTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator + */ + public static function lineStringProvider(): \Generator + { + yield 'testParsingLineStringValue' => ['LINESTRING(34.23 -87, 45.3 -92)', null, [['34.23', -87], ['45.3', -92]], null]; + yield 'testParsingLineStringZValue' => ['LINESTRING(34.23 -87 10, 45.3 -92 10)', null, [['34.23', -87, 10], ['45.3', -92, 10]], 'Z']; + yield 'testParsingLineStringMValue' => ['LINESTRINGM(34.23 -87 10, 45.3 -92 10)', null, [['34.23', -87, 10], ['45.3', -92, 10]], 'M']; + yield 'testParsingLineStringZMValue' => ['LINESTRINGZM(34.23 -87 10 20, 45.3 -92 10 20)', null, [['34.23', -87, 10, 20], ['45.3', -92, 10, 20]], 'ZM']; + yield 'testParsingLineStringValueWithSrid' => ['SRID=4326;LINESTRING(34.23 -87, 45.3 -92)', 4326, [['34.23', -87], ['45.3', -92]], null]; + yield 'testParsingLineStringZValueWithSrid' => ['SRID=4326;LINESTRING(34.23 -87 10, 45.3 -92 10)', 4326, [['34.23', -87, 10], ['45.3', -92, 10]], 'Z']; + yield 'testParsingLineStringMValueWithSrid' => ['SRID=4326;LINESTRINGM(34.23 -87 10, 45.3 -92 10)', 4326, [['34.23', -87, 10], ['45.3', -92, 10]], 'M']; + yield 'testParsingLineStringZMValueWithSrid' => ['SRID=4326;LINESTRINGZM(34.23 -87 10 20, 45.3 -92 10 20)', 4326, [['34.23', -87, 10, 20], ['45.3', -92, 10, 20]], 'ZM']; + } + + /** + * @param (int|string)[][] $coordinates + */ + #[DataProvider('lineStringProvider')] + public function testLineString(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + + self::assertLineStringParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/MultiLineStringTest.php b/tests/Legacy/MultiLineStringTest.php new file mode 100644 index 0000000..066f7cd --- /dev/null +++ b/tests/Legacy/MultiLineStringTest.php @@ -0,0 +1,66 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class MultiLineStringTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator + */ + public static function multiLineStringProvider(): \Generator + { + yield 'testParsingMultiLineStringValue' => ['MULTILINESTRING((0 0,10 0,10 10,0 10),(5 5,7 5,7 7,5 7))', null, [[[0, 0], [10, 0], [10, 10], [0, 10]], [[5, 5], [7, 5], [7, 7], [5, 7]]], null]; + yield 'testParsingMultiLineStringZValue' => ['MULTILINESTRINGZ((0 0 0,10 0 0,10 10 0,0 10 0),(5 5 1,7 5 1,7 7 1,5 7 1))', null, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1]]], 'Z']; + yield 'testParsingMultiLineStringMValue' => ['MULTILINESTRINGM((0 0 0,10 0 0,10 10 0,0 10 0),(5 5 1,7 5 1,7 7 1,5 7 1))', null, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1]]], 'M']; + yield 'testParsingMultiLineStringZMValue' => ['MULTILINESTRINGZM((0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1),(5 5 1 2,7 5 1 2,7 7 1 2,5 7 1 2))', null, [[[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1]], [[5, 5, 1, 2], [7, 5, 1, 2], [7, 7, 1, 2], [5, 7, 1, 2]]], 'ZM']; + yield 'testParsingMultiLineStringValueWithSrid' => ['SRID=4326;MULTILINESTRING((0 0,10 0,10 10,0 10),(5 5,7 5,7 7,5 7))', 4326, [[[0, 0], [10, 0], [10, 10], [0, 10]], [[5, 5], [7, 5], [7, 7], [5, 7]]], null]; + yield 'testParsingMultiLineStringZValueWithSrid' => ['SRID=4326;MULTILINESTRINGZ((0 0 0,10 0 0,10 10 0,0 10 0),(5 5 1,7 5 1,7 7 1,5 7 1))', 4326, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1]]], 'Z']; + yield 'testParsingMultiLineStringMValueWithSrid' => ['SRID=4326;MULTILINESTRINGM((0 0 0,10 0 0,10 10 0,0 10 0),(5 5 1,7 5 1,7 7 1,5 7 1))', 4326, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1]]], 'M']; + yield 'testParsingMultiLineStringZMValueWithSrid' => ['SRID=4326;MULTILINESTRINGZM((0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1),(5 5 1 2,7 5 1 2,7 7 1 2,5 7 1 2))', 4326, [[[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1]], [[5, 5, 1, 2], [7, 5, 1, 2], [7, 7, 1, 2], [5, 7, 1, 2]]], 'ZM']; + } + + /** + * @param (int|string)[][][] $coordinates + */ + #[DataProvider('multiLineStringProvider')] + public function testMultiLineString(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + + self::assertMultiLineStringParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/MultiPointTest.php b/tests/Legacy/MultiPointTest.php new file mode 100644 index 0000000..ee95a9e --- /dev/null +++ b/tests/Legacy/MultiPointTest.php @@ -0,0 +1,66 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class MultiPointTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator + */ + public static function multiPointProvider(): \Generator + { + yield 'testParsingMultiPointValue' => ['MULTIPOINT(0 0,10 0,10 10,0 10)', null, [[0, 0], [10, 0], [10, 10], [0, 10]], null]; + yield 'testParsingMultiPointZValue' => ['MULTIPOINTZ(0 0 0,10 0 0,10 10 0,0 10 0)', null, [[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], 'Z']; + yield 'testParsingMultiPointMValue' => ['MULTIPOINTM(0 0 0,10 0 0,10 10 0,0 10 0)', null, [[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], 'M']; + yield 'testParsingMultiPointZMValue' => ['MULTIPOINTZM(0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1)', null, [[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1]], 'ZM']; + yield 'testParsingMultiPointValueWithSrid' => ['SRID=4326;MULTIPOINT(0 0,10 0,10 10,0 10)', 4326, [[0, 0], [10, 0], [10, 10], [0, 10]], null]; + yield 'testParsingMultiPointZValueWithSrid' => ['SRID=4326;MULTIPOINTZ(0 0 0,10 0 0,10 10 0,0 10 0)', 4326, [[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], 'Z']; + yield 'testParsingMultiPointMValueWithSrid' => ['SRID=4326;MULTIPOINTM(0 0 0,10 0 0,10 10 0,0 10 0)', 4326, [[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0]], 'M']; + yield 'testParsingMultiPointZMValueWithSrid' => ['SRID=4326;MULTIPOINTZM(0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1)', 4326, [[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1]], 'ZM']; + } + + /** + * @param (int|string)[][] $coordinates + */ + #[DataProvider('multiPointProvider')] + public function testMultiPoint(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + + self::assertMultiPointParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/MultiPolygonTest.php b/tests/Legacy/MultiPolygonTest.php new file mode 100644 index 0000000..3098c95 --- /dev/null +++ b/tests/Legacy/MultiPolygonTest.php @@ -0,0 +1,66 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class MultiPolygonTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator + */ + public static function multiPolygonProvider(): \Generator + { + yield 'testParsingMultiPolygonValue' => ['MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),((1 1, 3 1, 3 3, 1 3, 1 1)))', null, [[[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], [[5, 5], [7, 5], [7, 7], [5, 7], [5, 5]]], [[[1, 1], [3, 1], [3, 3], [1, 3], [1, 1]]]], null]; + yield 'testParsingMultiPolygonZValue' => ['MULTIPOLYGON(((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0),(5 5 1,7 5 1,7 7 1,5 7 1,5 5 1)),((1 1 0, 3 1 0, 3 3 0, 1 3 0, 1 1 0)))', null, [[[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1], [5, 5, 1]]], [[[1, 1, 0], [3, 1, 0], [3, 3, 0], [1, 3, 0], [1, 1, 0]]]], 'Z']; + yield 'testParsingMultiPolygonMValue' => ['MULTIPOLYGONM(((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0),(5 5 1,7 5 1,7 7 1,5 7 1,5 5 1)),((1 1 0, 3 1 0, 3 3 0, 1 3 0, 1 1 0)))', null, [[[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1], [5, 5, 1]]], [[[1, 1, 0], [3, 1, 0], [3, 3, 0], [1, 3, 0], [1, 1, 0]]]], 'M']; + yield 'testParsingMultiPolygonZMValue' => ['MULTIPOLYGONZM(((0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1,0 0 0 1),(5 5 1 2,7 5 1 2,7 7 1 2,5 7 1 2,5 5 1 2)),((1 1 0 3, 3 1 0 3, 3 3 0 3, 1 3 0 3, 1 1 0 3)))', null, [[[[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1], [0, 0, 0, 1]], [[5, 5, 1, 2], [7, 5, 1, 2], [7, 7, 1, 2], [5, 7, 1, 2], [5, 5, 1, 2]]], [[[1, 1, 0, 3], [3, 1, 0, 3], [3, 3, 0, 3], [1, 3, 0, 3], [1, 1, 0, 3]]]], 'ZM']; + yield 'testParsingMultiPolygonValueWithSrid' => ['SRID=4326;MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),((1 1, 3 1, 3 3, 1 3, 1 1)))', 4326, [[[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], [[5, 5], [7, 5], [7, 7], [5, 7], [5, 5]]], [[[1, 1], [3, 1], [3, 3], [1, 3], [1, 1]]]], null]; + yield 'testParsingMultiPolygonZValueWithSrid' => ['SRID=4326;MULTIPOLYGONZ(((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0),(5 5 1,7 5 1,7 7 1,5 7 1,5 5 1)),((1 1 0, 3 1 0, 3 3 0, 1 3 0, 1 1 0)))', 4326, [[[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1], [5, 5, 1]]], [[[1, 1, 0], [3, 1, 0], [3, 3, 0], [1, 3, 0], [1, 1, 0]]]], 'Z']; + yield 'testParsingMultiPolygonMValueWithSrid' => ['SRID=4326;MULTIPOLYGONM(((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0),(5 5 1,7 5 1,7 7 1,5 7 1,5 5 1)),((1 1 0, 3 1 0, 3 3 0, 1 3 0, 1 1 0)))', 4326, [[[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]], [[5, 5, 1], [7, 5, 1], [7, 7, 1], [5, 7, 1], [5, 5, 1]]], [[[1, 1, 0], [3, 1, 0], [3, 3, 0], [1, 3, 0], [1, 1, 0]]]], 'M']; + yield 'testParsingMultiPolygonZMValueWithSrid' => ['SRID=4326;MULTIPOLYGONZM(((0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1,0 0 0 1),(5 5 1 2,7 5 1 2,7 7 1 2,5 7 1 2,5 5 1 2)),((1 1 0 3, 3 1 0 3, 3 3 0 3, 1 3 0 3, 1 1 0 3)))', 4326, [[[[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1], [0, 0, 0, 1]], [[5, 5, 1, 2], [7, 5, 1, 2], [7, 7, 1, 2], [5, 7, 1, 2], [5, 5, 1, 2]]], [[[1, 1, 0, 3], [3, 1, 0, 3], [3, 3, 0, 3], [1, 3, 0, 3], [1, 1, 0, 3]]]], 'ZM']; + } + + /** + * @param (int|string)[][][][] $coordinates + */ + #[DataProvider('multiPolygonProvider')] + public function testMultiPolygon(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[][][][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + + self::assertMultiPolygonParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/ParserTest.php b/tests/Legacy/ParserTest.php new file mode 100644 index 0000000..7491e71 --- /dev/null +++ b/tests/Legacy/ParserTest.php @@ -0,0 +1,121 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; +use LongitudeOne\SpatialDecoder\Exception\NonInstantiableGeometryTypeException; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +/** + * Basic parser tests. + */ +class ParserTest extends SpecificTestCase +{ + /** + * @return \Generator + */ + public static function notExistentValuesProvider(): \Generator + { + yield 'testParsingGarbage' => ['@#_$%', 'According the ISO 13249-3:2016 standard, the "@" type does not exist.']; + yield 'testParsingBadType' => ['PNT(10 10)', 'According the ISO 13249-3:2016 standard, the "PNT" type does not exist.']; + yield 'testParsingGeometryCollectionValueWithBadType' => ['GEOMETRYCOLLECTION(PNT(10 10), POINT(30 30), LINESTRING(15 15, 20 20))', 'According the ISO 13249-3:2016 standard, the "PNT" type does not exist.']; + } + + /** + * @return \Generator + */ + public static function notInstantiableTypesProvider(): \Generator + { + yield 'testNotInstantiableGeometry' => ['GEOMETRY', 'According the ISO 13249-3:2016 standard, the "GEOMETRY" type is not instantiable. Did you mean "GEOMETRYCOLLECTION"?']; + yield 'testNotInstantiableCurve' => ['CURVE', 'According the ISO 13249-3:2016 standard, the "CURVE" type is not instantiable. Did you mean "MULTICURVE"?']; + yield 'testNotInstantiableSolid' => ['SOLID', 'According the ISO 13249-3:2016 standard, the "SOLID" type is not instantiable. Did you mean "POLYGON"?']; + yield 'testNotInstantiableSurface' => ['SURFACE', 'According the ISO 13249-3:2016 standard, the "SURFACE" type is not instantiable. Did you mean "MULTISURFACE"?']; + } + + /** + * @return \Generator + */ + public static function unexpectedValues(): \Generator + { + yield 'testParsingPointValueWithBadSrid' => ['SRID=432.6;POINT(34.23 -87)', '[Syntax Error] line 0, col 5: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got "432.6" in value "SRID=432.6;POINT(34.23 -87)"']; + yield 'testParsingPointValueMissingCoordinate' => ['POINT(34.23)', '[Syntax Error] line 0, col 11: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "POINT(34.23)"']; + yield 'testParsingPointMValueMissingCoordinate' => ['POINTM(34.23 10)', '[Syntax Error] line 0, col 15: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "POINTM(34.23 10)"']; + yield 'testParsingPointMValueExtraCoordinate' => ['POINTM(34.23 10 30 40)', '[Syntax Error] line 0, col 19: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "40" in value "POINTM(34.23 10 30 40)"']; + yield 'testParsingPointZMValueMissingCoordinate' => ['POINTZM(34.23 10 45)', '[Syntax Error] line 0, col 19: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "POINTZM(34.23 10 45)"']; + yield 'testParsingPointZMValueExtraCoordinate' => ['POINTZM(34.23 10 45 4.5 99)', '[Syntax Error] line 0, col 24: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "99" in value "POINTZM(34.23 10 45 4.5 99)"']; + yield 'testParsingPointValueShortString' => ['POINT(34.23', '[Syntax Error] line 0, col -1: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got end of string. in value "POINT(34.23"']; + yield 'testParsingPointValueWrongScientificWithSrid' => ['SRID=4326;POINT(4.23test-005 -8e-003)', '[Syntax Error] line 0, col 20: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got "test" in value "SRID=4326;POINT(4.23test-005 -8e-003)"']; + yield 'testParsingPointValueWithComma' => ['POINT(10, 10)', '[Syntax Error] line 0, col 8: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got "," in value "POINT(10, 10)"']; + yield 'testParsingLineStringValueMissingCoordinate' => ['LINESTRING(34.23 -87, 45.3)', '[Syntax Error] line 0, col 26: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "LINESTRING(34.23 -87, 45.3)"']; + yield 'testParsingLineStringValueMismatchedDimensions' => ['LINESTRING(34.23 -87, 45.3 56 23.4)', '[Syntax Error] line 0, col 30: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "23.4" in value "LINESTRING(34.23 -87, 45.3 56 23.4)"']; + yield 'testParsingPolygonValueMissingParenthesis' => ['POLYGON(0 0,10 0,10 10,0 10,0 0)', '[Syntax Error] line 0, col 8: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_OPEN_PARENTHESIS, got "0" in value "POLYGON(0 0,10 0,10 10,0 10,0 0)"']; + yield 'testParsingPolygonValueMismatchedDimension' => ['POLYGON((0 0,10 0,10 10 10,0 10,0 0))', '[Syntax Error] line 0, col 24: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "10" in value "POLYGON((0 0,10 0,10 10 10,0 10,0 0))"']; + yield 'testParsingPolygonValueMultiRingMissingComma' => ['POLYGON((0 0,10 0,10 10,0 10,0 0)(5 5,7 5,7 7,5 7,5 5))', '[Syntax Error] line 0, col 33: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "(" in value "POLYGON((0 0,10 0,10 10,0 10,0 0)(5 5,7 5,7 7,5 7,5 5))"']; + yield 'testParsingMultiLineStringValueMissingComma' => ['MULTILINESTRING((0 0,10 0,10 10,0 10)(5 5,7 5,7 7,5 7))', '[Syntax Error] line 0, col 37: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "(" in value "MULTILINESTRING((0 0,10 0,10 10,0 10)(5 5,7 5,7 7,5 7))"']; + yield 'testParsingMultiPolygonValueMissingParenthesis' => ['MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),(1 1, 3 1, 3 3, 1 3, 1 1))', '[Syntax Error] line 0, col 64: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_OPEN_PARENTHESIS, got "1" in value "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),(1 1, 3 1, 3 3, 1 3, 1 1))"']; + yield 'testParsingGeometryCollectionValueWithMismatchedDimension' => ['GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30 10), LINESTRING(15 15, 20 20))', '[Syntax Error] line 0, col 45: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "10" in value "GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30 10), LINESTRING(15 15, 20 20))"']; + } + + #[DataProvider('notInstantiableTypesProvider')] + public function testNotInstantiable(string $notInstantiableType, string $expectedMessage): void + { + self::expectException(NonInstantiableGeometryTypeException::class); + self::expectExceptionMessage($expectedMessage); + $parser = new Decoder(new EwktDecoderStrategy()); + $parser->decode($notInstantiableType.'(10 10)'); + } + + public function testNullParser(): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('No value provided'); + $parser->decode(''); + } + + #[DataProvider('unexpectedValues')] + public function testParserWithUnexpectedValues(string $value, string $exceptionMessage): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage($exceptionMessage); + + $parser->decode($value); + } + + #[DataProvider('notExistentValuesProvider')] + public function testParsingGarbage(string $garbage, string $message): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage($message); + $parser->decode($garbage); + } + + public function testUnexpectedType(): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('According the ISO 13249-3:2016 standard, the "foo" type does not exist.'); + $parser->decode('foo(10 10)'); + } +} diff --git a/tests/Legacy/PointTest.php b/tests/Legacy/PointTest.php new file mode 100644 index 0000000..47e7e32 --- /dev/null +++ b/tests/Legacy/PointTest.php @@ -0,0 +1,70 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class PointTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator + */ + public static function pointProvider(): \Generator + { + yield 'testParsingPointValue' => ['POINT(34.23 -87)', null, ['34.23', -87], null]; + yield 'testParsingPointZValue' => ['POINT(34.23 -87 10)', null, ['34.23', -87, 10], 'Z']; + yield 'testParsingPointDeclaredZValue' => ['POINTZ(34.23 -87 10)', null, ['34.23', -87, 10], 'Z']; + yield 'testParsingPointMValue' => ['POINTM(34.23 -87 10)', null, ['34.23', -87, 10], 'M']; + yield 'testParsingPointZMValue' => ['POINT(34.23 -87 10 30)', null, ['34.23', -87, 10, 30], 'ZM']; + yield 'testParsingPointDeclaredZMValue' => ['POINT ZM(34.23 -87 10 30)', null, ['34.23', -87, 10, 30], 'ZM']; + yield 'testParsingPointValueWithSrid' => ['SRID=4326;POINT(34.23 -87)', 4326, ['34.23', -87], null]; + yield 'testParsingPointZValueWithSrid' => ['SRID=4326;POINT(34.23 -87 10)', 4326, ['34.23', -87, 10], 'Z']; + yield 'testParsingPointValueScientificWithSrid' => ['SRID=4326;POINT(4.23e-005 -8E-003)', 4326, ['4.23E-5', '-0.008'], null]; + yield 'testParsingPointValueScientific' => ['POINT(4.23e-005 -8E-003)', null, ['4.23E-5', '-0.008'], null]; + yield 'testParsingPointMValueWithSrid' => ['SRID=4326;POINTM(34.23 -87 10)', 4326, ['34.23', -87, 10], 'M']; + yield 'testParsingPointZMValueWithSrid' => ['SRID=4326;POINT(34.23 -87 10 30)', 4326, ['34.23', -87, 10, 30], 'ZM']; + } + + /** + * @param (int|string)[] $coordinates + */ + #[DataProvider('pointProvider')] + public function testPoint(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + + self::assertPointParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/PolygonTest.php b/tests/Legacy/PolygonTest.php new file mode 100644 index 0000000..02b0745 --- /dev/null +++ b/tests/Legacy/PolygonTest.php @@ -0,0 +1,68 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class PolygonTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * @return \Generator + */ + public static function polygonProvider(): \Generator + { + yield 'testParsingPolygonValue' => ['POLYGON((0 0,10 0,10 10,0 10,0 0))', null, [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]], null]; + yield 'testParsingPolygonZValue' => ['POLYGON((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0))', null, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]]], 'Z']; + yield 'testParsingPolygonMValue' => ['POLYGONM((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0))', null, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]]], 'M']; + yield 'testParsingPolygonZMValue' => ['POLYGONZM((0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1,0 0 0 1))', null, [[[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1], [0, 0, 0, 1]]], 'ZM']; + yield 'testParsingPolygonValueWithSrid' => ['SRID=4326;POLYGON((0 0,10 0,10 10,0 10,0 0))', 4326, [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]], null]; + yield 'testParsingPolygonValueMultiRing' => ['POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))', null, [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], [[5, 5], [7, 5], [7, 7], [5, 7], [5, 5]]], null]; + yield 'testParsingPolygonValueMultiRingWithSrid' => ['SRID=4326;POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))', 4326, [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]], [[5, 5], [7, 5], [7, 7], [5, 7], [5, 5]]], null]; + yield 'testParsingPolygonZValueWithSrid' => ['SRID=4326;POLYGON((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0))', 4326, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]]], 'Z']; + yield 'testParsingPolygonMValueWithSrid' => ['SRID=4326;POLYGONM((0 0 0,10 0 0,10 10 0,0 10 0,0 0 0))', 4326, [[[0, 0, 0], [10, 0, 0], [10, 10, 0], [0, 10, 0], [0, 0, 0]]], 'M']; + yield 'testParsingPolygonZMValueWithSrid' => ['SRID=4326;POLYGONZM((0 0 0 1,10 0 0 1,10 10 0 1,0 10 0 1,0 0 0 1))', 4326, [[[0, 0, 0, 1], [10, 0, 0, 1], [10, 10, 0, 1], [0, 10, 0, 1], [0, 0, 0, 1]]], 'ZM']; + } + + /** + * @param (int|string)[][][] $coordinates + */ + #[DataProvider('polygonProvider')] + public function testPolygon(string $value, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($value); + + self::assertPolygonParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/ReUsedParserTest.php b/tests/Legacy/ReUsedParserTest.php new file mode 100644 index 0000000..85fd9a0 --- /dev/null +++ b/tests/Legacy/ReUsedParserTest.php @@ -0,0 +1,77 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; + +class ReUsedParserTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + public function testReusedParser(): void + { + foreach (PointTest::pointProvider() as $name => $testData) { + /** @var array{type:string, value: (int|string)[], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($testData[0]); + self::assertPointParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); + } + foreach (LineStringTest::lineStringProvider() as $name => $testData) { + /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($testData[0]); + self::assertLineStringParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); + } + foreach (PolygonTest::polygonProvider() as $name => $testData) { + /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($testData[0]); + self::assertPolygonParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); + } + foreach (MultiPointTest::multiPointProvider() as $name => $testData) { + /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($testData[0]); + self::assertMultiPointParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); + } + foreach (MultiLineStringTest::multiLineStringProvider() as $name => $testData) { + /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($testData[0]); + self::assertMultiLineStringParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); + } + foreach (MultiPolygonTest::multiPolygonProvider() as $name => $testData) { + /** @var array{type:string, value: (int|string)[][][][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($testData[0]); + self::assertMultiPolygonParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); + } + foreach (GeometryCollectionTest::geometryCollectionProvider() as $name => $testData) { + /** @var array{type:string, value: array, srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($testData[0]); + self::assertGeometryCollectionParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); + } + } +} diff --git a/tests/Legacy/TriangleTest.php b/tests/Legacy/TriangleTest.php new file mode 100644 index 0000000..ad8a34a --- /dev/null +++ b/tests/Legacy/TriangleTest.php @@ -0,0 +1,130 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +class TriangleTest extends SpecificTestCase +{ + private Decoder $parser; + + protected function setUp(): void + { + parent::setUp(); + $this->parser = new Decoder(new EwktDecoderStrategy()); + } + + protected function tearDown(): void + { + unset($this->parser); + parent::tearDown(); + } + + /** + * This method provide triangles with not enough points or too much points. + * + * @return \Generator + */ + public static function badNumberPointsProvider(): \Generator + { + // With 1 point + yield 'testParsingTriangleWith1Point' => ['TRIANGLE((34.23 -87))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; + yield 'testParsingTriangleZWith1Point' => ['TRIANGLEZ((34.23 -87 33))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; + yield 'testParsingTriangleMWith1Point' => ['TRIANGLEM((34.23 -87 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; + yield 'testParsingTriangleZMWith1Point' => ['TRIANGLEZM((34.23 -87 10 20))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; + + // With 2 points + yield 'testParsingTriangleWith2Points' => ['TRIANGLE((34.23 -87, 45.3 -92))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; + yield 'testParsingTriangleZWith2Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; + yield 'testParsingTriangleMWith2Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; + yield 'testParsingTriangleZMWith2Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; + + // With 3 points + yield 'testParsingTriangleWith3Points' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; + yield 'testParsingTriangleZWith3Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; + yield 'testParsingTriangleMWith3Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; + yield 'testParsingTriangleZMWith3Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; + + // With 5 points + yield 'testParsingTriangleWith5Points' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10, 34.23 -87, 45.3 -92))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; + yield 'testParsingTriangleZWith5Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 34.23 -87 33, 45.3 -92 22))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; + yield 'testParsingTriangleMWith5Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 34.23 -87 10, 45.3 -92 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; + yield 'testParsingTriangleZMWith5Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 34.23 -87 10 20, 45.3 -92 10 20))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; + } + + /** + * This method provide not closed triangles. + * According to the ISO-13249 specification, a triangle is a closed ring with fourth points. + * Last point shall be the same as the first one. + * + * @return \Generator + */ + public static function notClosedTriangleProvider(): \Generator + { + yield 'testParsingTriangleWithNotClosedRing' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10, 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87", the fourth is "0 0"']; + yield 'testParsingTriangleZWithNotClosedRing' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 0 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87 33", the fourth is "0 0 0"']; + yield 'testParsingTriangleMWithNotClosedRing' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 0 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87 10", the fourth is "0 0 0"']; + yield 'testParsingTriangleZMWithNotClosedRing' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 0 0 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87 10 20", the fourth is "0 0 0 0"']; + } + + /** + * @return \Generator + */ + public static function triangleProvider(): \Generator + { + yield 'testParsingTriangleValue' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10, 34.23 -87))', null, [['34.23', -87], ['45.3', -92], [10, 10], ['34.23', -87]], null]; + yield 'testParsingTriangleZValue' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 34.23 -87 33))', null, [['34.23', -87, 33], ['45.3', -92, 22], [10, 10, 11], ['34.23', -87, 33]], 'Z']; + yield 'testParsingTriangleMValue' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 34.23 -87 10))', null, [['34.23', -87, 10], ['45.3', -92, 10], [10, 10, 11], ['34.23', -87, 10]], 'M']; + yield 'testParsingTriangleZMValue' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 34.23 -87 10 20))', null, [['34.23', -87, 10, 20], ['45.3', -92, 10, 20], [10, 11, 12, 13], ['34.23', -87, 10, 20]], 'ZM']; + yield 'testParsingTriangleValueWithSrid' => ['SRID=4326;TRIANGLE((34.23 -87, 45.3 -92, 10 10, 34.23 -87))', 4326, [['34.23', -87], ['45.3', -92], [10, 10], ['34.23', -87]], null]; + yield 'testParsingTriangleZValueWithSrid' => ['SRID=4326;TRIANGLE((34.23 -87 33, 45.3 -92 22, 10 10 11, 34.23 -87 33))', 4326, [['34.23', -87, 33], ['45.3', -92, 22], [10, 10, 11], ['34.23', -87, 33]], 'Z']; + yield 'testParsingTriangleMValueWithSrid' => ['SRID=4326;TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 34.23 -87 10))', 4326, [['34.23', -87, 10], ['45.3', -92, 10], [10, 10, 11], ['34.23', -87, 10]], 'M']; + yield 'testParsingTriangleZMValueWithSrid' => ['SRID=4326;TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 34.23 -87 10 20))', 4326, [['34.23', -87, 10, 20], ['45.3', -92, 10, 20], [10, 11, 12, 13], ['34.23', -87, 10, 20]], 'ZM']; + } + + #[DataProvider('badNumberPointsProvider')] + public function testBadNumberPoints(string $actualValue, string $expectedMessage): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($expectedMessage); + $this->parser->decode($actualValue); + } + + #[DataProvider('notClosedTriangleProvider')] + public function testNotClosedTriangle(string $actualValue, string $expectedMessage): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($expectedMessage); + $this->parser->decode($actualValue); + } + + /** + * @param (int|string)[][] $coordinates + */ + #[DataProvider('triangleProvider')] + public function testTriangle(string $actualValue, ?int $srid, array $coordinates, ?string $dimension): void + { + /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ + $actual = $this->parser->decode($actualValue); + + self::assertTriangleParsed($srid, $coordinates, $dimension, $actual); + } +} diff --git a/tests/Legacy/Utils/SpecificTestCase.php b/tests/Legacy/Utils/SpecificTestCase.php new file mode 100644 index 0000000..18c36d4 --- /dev/null +++ b/tests/Legacy/Utils/SpecificTestCase.php @@ -0,0 +1,117 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\Utils; + +use PHPUnit\Framework\TestCase; + +class SpecificTestCase extends TestCase +{ + /** + * @param (int|string)[][] $expectedCoordinates + * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual + */ + protected static function assertCircularStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'CIRCULARSTRING', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param array $expectedCoordinates + * @param array{type:string, value: array, srid: ?int, dimension: ?string} $actual + */ + protected static function assertGeometryCollectionParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'GEOMETRYCOLLECTION', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[][] $expectedCoordinates + * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual + */ + protected static function assertLineStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'LINESTRING', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[][][] $expectedCoordinates + * @param array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual + */ + protected static function assertMultiLineStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'MULTILINESTRING', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[][] $expectedCoordinates + * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual + */ + protected static function assertMultiPointParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'MULTIPOINT', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[][][][] $expectedCoordinates + * @param array{type:string, value: (int|string)[][][][], srid: ?int, dimension: ?string} $actual + */ + protected static function assertMultiPolygonParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'MULTIPOLYGON', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[] $expectedCoordinates + * @param array{type:string, value: (int|string)[], srid: ?int, dimension: ?string} $actual + */ + protected static function assertPointParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'POINT', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[][][] $expectedCoordinates + * @param array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual + */ + protected static function assertPolygonParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'POLYGON', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[][] $expectedCoordinates + * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual + */ + protected static function assertTriangleParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'TRIANGLE', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * @param (int|string)[]|(int|string)[][]|(int|string)[][][]|(int|string)[][][][] $expectedCoordinates + * @param array{type:string, value: (int|string)[]|(int|string)[][]|(int|string)[][][]|(int|string)[][][][], srid: ?int, dimension: ?string} $actual + */ + private static function assertParsed(?int $expectedSrid, string $type, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void + { + $expected['type'] = $type; + $expected['value'] = $expectedCoordinates; + $expected['srid'] = $expectedSrid; + $expected['dimension'] = $expectedDimension; + + self::assertSame($expected, $actual, $message); + } +} From 95102b97bab96cc6452eb2ef72585374324fe51b Mon Sep 17 00:00:00 2001 From: Alexandre Tranchant Date: Sat, 3 Oct 2026 13:35:15 +0200 Subject: [PATCH 2/2] tests: update legacy test cases to remove redundant type annotations and improve exception message assertions tests: update exception messages for WKT parsing and geometry validation tests: update exception messages for unimplemented spatial types in legacy tests feat: decode CircularString in WKT and EWKT tests: update legacy tests to maintain float values and improve exception messages feat: decode CircularString in WKT and EWKT tests: update legacy exception tests to reflect implemented geometry types refactor: update type hints for coordinates to include float in legacy tests refactor: update exception handling in SpecificTestCase to use custom InvalidCoordinateException refactor: enhance test cases with detailed descriptions and setup/teardown methods docs: explain historical EWKT test adaptations docs: update legacy EWKT test expectations and exception handling descriptions refactor: suppress PHPMD warning for excessive method length in tokenData method --- quality/php-mess-detector/test-ruleset.xml | 1 + .../{ => EwktParser}/CircularStringTest.php | 19 +- tests/Legacy/EwktParser/ExceptionTest.php | 114 ++++++++++ .../GeometryCollectionTest.php | 14 +- tests/Legacy/{ => EwktParser}/LexerTest.php | 8 +- .../{ => EwktParser}/LineStringTest.php | 14 +- .../{ => EwktParser}/MultiLineStringTest.php | 14 +- .../{ => EwktParser}/MultiPointTest.php | 14 +- .../{ => EwktParser}/MultiPolygonTest.php | 14 +- tests/Legacy/EwktParser/ParserTest.php | 141 ++++++++++++ tests/Legacy/{ => EwktParser}/PointTest.php | 14 +- tests/Legacy/{ => EwktParser}/PolygonTest.php | 14 +- tests/Legacy/EwktParser/README.md | 85 ++++++++ .../{ => EwktParser}/ReUsedParserTest.php | 16 +- .../Legacy/{ => EwktParser}/TriangleTest.php | 70 +++--- .../EwktParser/Utils/SpecificTestCase.php | 206 ++++++++++++++++++ tests/Legacy/ExceptionTest.php | 96 -------- tests/Legacy/ParserTest.php | 121 ---------- tests/Legacy/Utils/SpecificTestCase.php | 117 ---------- 19 files changed, 687 insertions(+), 405 deletions(-) rename tests/Legacy/{ => EwktParser}/CircularStringTest.php (73%) create mode 100644 tests/Legacy/EwktParser/ExceptionTest.php rename tests/Legacy/{ => EwktParser}/GeometryCollectionTest.php (81%) rename tests/Legacy/{ => EwktParser}/LexerTest.php (97%) rename tests/Legacy/{ => EwktParser}/LineStringTest.php (80%) rename tests/Legacy/{ => EwktParser}/MultiLineStringTest.php (84%) rename tests/Legacy/{ => EwktParser}/MultiPointTest.php (80%) rename tests/Legacy/{ => EwktParser}/MultiPolygonTest.php (87%) create mode 100644 tests/Legacy/EwktParser/ParserTest.php rename tests/Legacy/{ => EwktParser}/PointTest.php (81%) rename tests/Legacy/{ => EwktParser}/PolygonTest.php (83%) create mode 100644 tests/Legacy/EwktParser/README.md rename tests/Legacy/{ => EwktParser}/ReUsedParserTest.php (74%) rename tests/Legacy/{ => EwktParser}/TriangleTest.php (55%) create mode 100644 tests/Legacy/EwktParser/Utils/SpecificTestCase.php delete mode 100644 tests/Legacy/ExceptionTest.php delete mode 100644 tests/Legacy/ParserTest.php delete mode 100644 tests/Legacy/Utils/SpecificTestCase.php diff --git a/quality/php-mess-detector/test-ruleset.xml b/quality/php-mess-detector/test-ruleset.xml index 4e85629..45cd33f 100644 --- a/quality/php-mess-detector/test-ruleset.xml +++ b/quality/php-mess-detector/test-ruleset.xml @@ -14,6 +14,7 @@ + diff --git a/tests/Legacy/CircularStringTest.php b/tests/Legacy/EwktParser/CircularStringTest.php similarity index 73% rename from tests/Legacy/CircularStringTest.php rename to tests/Legacy/EwktParser/CircularStringTest.php index 58f45e4..b3af33d 100644 --- a/tests/Legacy/CircularStringTest.php +++ b/tests/Legacy/EwktParser/CircularStringTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class CircularStringTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -38,12 +40,13 @@ protected function tearDown(): void } /** - * @return \Generator + * @return \Generator */ public static function circularStringProvider(): \Generator { yield 'testCircularString' => ['CIRCULARSTRING(0 0, 1 1, 1 0)', null, [[0, 0], [1, 1], [1, 0]], null]; - yield 'testCircularStringWithFloat' => ['CIRCULARSTRING(0.0 0.0, 1.1 1.1, 1.0 0.0)', null, [['0', '0'], ['1.1', '1.1'], ['1', '0']], null]; + // This legacy test was updated because the decoder now maintains float values. + yield 'testCircularStringWithFloat' => ['CIRCULARSTRING(0.0 0.0, 1.1 1.1, 1.0 0.0)', null, [[0.0, 0.0], [1.1, 1.1], [1.0, 0.0]], null]; yield 'testCircularStringWithSrid' => ['SRID=4326;CIRCULARSTRING(0 0, 1 1, 1 0)', 4326, [[0, 0], [1, 1], [1, 0]], null]; yield 'testCircularStringWithZ' => ['CIRCULARSTRINGZ(0 0 0, 1 1 1, 1 0 -1)', null, [[0, 0, 0], [1, 1, 1], [1, 0, -1]], 'Z']; yield 'testCircularStringWithZAndSrid' => ['SRID=4326;CIRCULARSTRINGZ(0 0 0, 1 1 1, 1 0 -1)', 4326, [[0, 0, 0], [1, 1, 1], [1, 0, -1]], 'Z']; @@ -54,12 +57,16 @@ public static function circularStringProvider(): \Generator } /** - * @param (int|string)[][] $coordinates + * Verify that CIRCULARSTRING values decode to the expected control points and metadata. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (float|int|string)[][] $coordinates Expected circular string coordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('circularStringProvider')] public function testCircularString(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertCircularStringParsed($srid, $coordinates, $dimension, $actual); } diff --git a/tests/Legacy/EwktParser/ExceptionTest.php b/tests/Legacy/EwktParser/ExceptionTest.php new file mode 100644 index 0000000..968eb2d --- /dev/null +++ b/tests/Legacy/EwktParser/ExceptionTest.php @@ -0,0 +1,114 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; + +use LongitudeOne\Core\Enum\GeometryTypeEnum; +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; +use LongitudeOne\SpatialDecoder\Exception\NonInstantiableGeometryTypeException; +use LongitudeOne\SpatialDecoder\Exception\NotYetImplementedException; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\TestCase; + +class ExceptionTest extends TestCase +{ + /** + * @return \Generator + */ + public static function notInstantiableTypes(): \Generator + { + yield 'CURVE' => ['CURVE', 'Geometry type "Curve" is recognized but is not instantiable.']; + yield 'GEOMETRY' => ['GEOMETRY', 'Geometry type "Geometry" is recognized but is not instantiable.']; + yield 'SOLID' => ['SOLID', 'Geometry type "Solid" is recognized but is not instantiable.']; + yield 'SURFACE' => ['SURFACE', 'Geometry type "Surface" is recognized but is not instantiable.']; + } + + /** + * @return \Generator + */ + public static function notYetImplementedTypes(): \Generator + { + yield 'BREPSOLID' => ['BREPSOLID', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::BREPSOLID->value)]; + yield 'CIRCLE' => ['CIRCLE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::CIRCLE->value)]; + yield 'CLOTHOID' => ['CLOTHOID', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::CLOTHOID->value)]; + // This legacy test is commented because it is now implemented in the decoder. + // yield 'COMPOUNDCURVE' => ['COMPOUNDCURVE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::COMPOUNDCURVE->value)]; + yield 'COMPOUNDSURFACE' => ['COMPOUNDSURFACE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::COMPOUNDSURFACE->value)]; + yield 'CURVEPOLYGON' => ['CURVEPOLYGON', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::CURVEPOLYGON->value)]; + yield 'ELLIPTICALCURVE' => ['ELLIPTICALCURVE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::ELLIPTICALCURVE->value)]; + yield 'GEODESICSTRING' => ['GEODESICSTRING', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::GEODESICSTRING->value)]; + yield 'MULTICURVE' => ['MULTICURVE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::MULTICURVE->value)]; + yield 'MULTISURFACE' => ['MULTISURFACE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::MULTISURFACE->value)]; + yield 'NURBSCURVE' => ['NURBSCURVE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::NURBSCURVE->value)]; + yield 'SPIRALCURVE' => ['SPIRALCURVE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::SPIRALCURVE->value)]; + // This legacy test is commented because it is now implemented in the decoder. + // yield 'POLYHEDRALSURFACE' => ['POLYHEDRALSURFACE', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::POLYHEDRALSURFACE->value)]; + yield 'TIN' => ['TIN', \sprintf('Decoding of spatial type "%s" is not yet implemented.', GeometryTypeEnum::TIN->value)]; + } + + /** Ensure an unknown geometry name raises the decoder's invalid-argument exception. */ + public function testNotExistentException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageIsOrContains('The supplied WKT geometry type is not supported. Invalid WKT input: "FOO(42 42)".'); + + (new Decoder(new EwktDecoderStrategy()))->decode('FOO(42 42)'); + } + + /** + * Ensure recognized but non-instantiable types raise the matching geometry-type exception. + * + * @param string $notInstantiableType Type to reject + * @param string $expectedMessage Expected exception message + */ + #[DataProvider('notInstantiableTypes')] + public function testNotInstantiable(string $notInstantiableType, string $expectedMessage): void + { + $this->expectException(NonInstantiableGeometryTypeException::class); + $this->expectExceptionMessageIsOrContains($expectedMessage); + + $toParse = \sprintf('%s(42 42)', $notInstantiableType); + + (new Decoder(new EwktDecoderStrategy()))->decode($toParse); + } + + /** Ensure the non-instantiable exception exposes the correct message and geometry type. */ + public function testNotInstantiableException(): void + { + $exception = new NonInstantiableGeometryTypeException(GeometryTypeEnum::GEOMETRY); + self::assertSame('Geometry type "Geometry" is recognized but is not instantiable.', $exception->getMessage()); + self::assertSame(GeometryTypeEnum::GEOMETRY, $exception->getSpatialType()); + } + + /** + * Ensure recognized but unimplemented geometry types raise the matching exception and message. + * + * @param string $notYetImplemented Type not yet supported + * @param string $expectedMessage Expected exception message + */ + #[DataProvider('notYetImplementedTypes')] + public function testNotYetImplemented(string $notYetImplemented, string $expectedMessage): void + { + $this->expectException(NotYetImplementedException::class); + $this->expectExceptionMessageIsOrContains($expectedMessage); + + $toParse = \sprintf('%s(42 42)', $notYetImplemented); + + (new Decoder(new EwktDecoderStrategy()))->decode($toParse); + } +} diff --git a/tests/Legacy/GeometryCollectionTest.php b/tests/Legacy/EwktParser/GeometryCollectionTest.php similarity index 81% rename from tests/Legacy/GeometryCollectionTest.php rename to tests/Legacy/EwktParser/GeometryCollectionTest.php index fa85e78..ee9d636 100644 --- a/tests/Legacy/GeometryCollectionTest.php +++ b/tests/Legacy/EwktParser/GeometryCollectionTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class GeometryCollectionTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -53,12 +55,16 @@ public static function geometryCollectionProvider(): \Generator } /** - * @param array $coordinates + * Verify that each geometry in a collection decodes with its expected type and coordinates. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param array $coordinates Expected child geometries + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('geometryCollectionProvider')] public function testGeometryCollection(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: array, srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertGeometryCollectionParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/LexerTest.php b/tests/Legacy/EwktParser/LexerTest.php similarity index 97% rename from tests/Legacy/LexerTest.php rename to tests/Legacy/EwktParser/LexerTest.php index 72ed7ad..181ec31 100644 --- a/tests/Legacy/LexerTest.php +++ b/tests/Legacy/EwktParser/LexerTest.php @@ -14,7 +14,7 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use Doctrine\Common\Lexer\Token; use LongitudeOne\SpatialDecoder\Strategy\Common\Lexer; @@ -133,6 +133,8 @@ public static function tokenData(): \Generator } /** + * Verify that WKT geometry names, dimensions, numbers, and punctuation produce the expected tokens. + * * @param string $value WKT string to tokenize * @param array{0: int, 1: string|float|int, 2: int}[] $expected Expected token sequence */ @@ -153,7 +155,9 @@ public function testTokenRecognition(string $value, array $expected): void } } - /** Tests token recognition with a reused lexer. */ + /** + * Verify that reusing one lexer across inputs produces the same tokens as fresh lexer instances. + */ public function testTokenRecognitionReuseLexer(): void { $lexer = new Lexer(); diff --git a/tests/Legacy/LineStringTest.php b/tests/Legacy/EwktParser/LineStringTest.php similarity index 80% rename from tests/Legacy/LineStringTest.php rename to tests/Legacy/EwktParser/LineStringTest.php index 27230f1..317c0c5 100644 --- a/tests/Legacy/LineStringTest.php +++ b/tests/Legacy/EwktParser/LineStringTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class LineStringTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -53,12 +55,16 @@ public static function lineStringProvider(): \Generator } /** - * @param (int|string)[][] $coordinates + * Verify that LINESTRING values decode to the expected coordinates, SRID, and dimension. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (int|string)[][] $coordinates Expected line coordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('lineStringProvider')] public function testLineString(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertLineStringParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/MultiLineStringTest.php b/tests/Legacy/EwktParser/MultiLineStringTest.php similarity index 84% rename from tests/Legacy/MultiLineStringTest.php rename to tests/Legacy/EwktParser/MultiLineStringTest.php index 066f7cd..3d1a31a 100644 --- a/tests/Legacy/MultiLineStringTest.php +++ b/tests/Legacy/EwktParser/MultiLineStringTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class MultiLineStringTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -53,12 +55,16 @@ public static function multiLineStringProvider(): \Generator } /** - * @param (int|string)[][][] $coordinates + * Verify that MULTILINESTRING values decode to the expected lines and metadata. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (int|string)[][][] $coordinates Expected multiline coordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('multiLineStringProvider')] public function testMultiLineString(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertMultiLineStringParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/MultiPointTest.php b/tests/Legacy/EwktParser/MultiPointTest.php similarity index 80% rename from tests/Legacy/MultiPointTest.php rename to tests/Legacy/EwktParser/MultiPointTest.php index ee95a9e..1bb5c26 100644 --- a/tests/Legacy/MultiPointTest.php +++ b/tests/Legacy/EwktParser/MultiPointTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class MultiPointTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -53,12 +55,16 @@ public static function multiPointProvider(): \Generator } /** - * @param (int|string)[][] $coordinates + * Verify that MULTIPOINT values decode to the expected points, SRID, and dimension. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (int|string)[][] $coordinates Expected point coordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('multiPointProvider')] public function testMultiPoint(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertMultiPointParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/MultiPolygonTest.php b/tests/Legacy/EwktParser/MultiPolygonTest.php similarity index 87% rename from tests/Legacy/MultiPolygonTest.php rename to tests/Legacy/EwktParser/MultiPolygonTest.php index 3098c95..f3c64e9 100644 --- a/tests/Legacy/MultiPolygonTest.php +++ b/tests/Legacy/EwktParser/MultiPolygonTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class MultiPolygonTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -53,12 +55,16 @@ public static function multiPolygonProvider(): \Generator } /** - * @param (int|string)[][][][] $coordinates + * Verify that MULTIPOLYGON values decode to the expected polygons and metadata. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (int|string)[][][][] $coordinates Expected multipolygon coordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('multiPolygonProvider')] public function testMultiPolygon(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[][][][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertMultiPolygonParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/EwktParser/ParserTest.php b/tests/Legacy/EwktParser/ParserTest.php new file mode 100644 index 0000000..57313b1 --- /dev/null +++ b/tests/Legacy/EwktParser/ParserTest.php @@ -0,0 +1,141 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; + +use LongitudeOne\SpatialDecoder\Decoder; +use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; +use LongitudeOne\SpatialDecoder\Exception\NonInstantiableGeometryTypeException; +use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; +use PHPUnit\Framework\Attributes\DataProvider; + +/** + * Basic parser tests. + */ +class ParserTest extends SpecificTestCase +{ + /** + * @return \Generator + */ + public static function notExistentValuesProvider(): \Generator + { + yield 'testParsingGarbage' => ['@#_$%', 'The supplied WKT geometry type is not supported. Invalid WKT input: "@#_$%".']; + yield 'testParsingBadType' => ['PNT(10 10)', 'The supplied WKT geometry type is not supported. Invalid WKT input: "PNT(10 10)".']; + yield 'testParsingGeometryCollectionValueWithBadType' => ['GEOMETRYCOLLECTION(PNT(10 10), POINT(30 30), LINESTRING(15 15, 20 20))', 'The supplied WKT geometry type is not supported. Invalid WKT input: "GEOMETRYCOLLECTION(PNT(10 10), POINT(30 30), LINESTRING(15 15, 20 20))".']; + } + + /** + * @return \Generator + */ + public static function notInstantiableTypesProvider(): \Generator + { + yield 'testNotInstantiableGeometry' => ['GEOMETRY', 'Geometry type "Geometry" is recognized but is not instantiable.']; + yield 'testNotInstantiableCurve' => ['CURVE', 'Geometry type "Curve" is recognized but is not instantiable.']; + yield 'testNotInstantiableSolid' => ['SOLID', 'Geometry type "Solid" is recognized but is not instantiable.']; + yield 'testNotInstantiableSurface' => ['SURFACE', 'Geometry type "Surface" is recognized but is not instantiable.']; + } + + /** + * @return \Generator + */ + public static function unexpectedValues(): \Generator + { + yield 'testParsingPointValueWithBadSrid' => ['SRID=432.6;POINT(34.23 -87)', 'Invalid EWKT SRID prefix; expected SRID=; before the root geometry.']; + yield 'testParsingPointValueMissingCoordinate' => ['POINT(34.23)', 'An EWKT coordinate must contain two, three or four ordinates. Invalid WKT input: "POINT(34.23)".']; + yield 'testParsingPointMValueMissingCoordinate' => ['POINTM(34.23 10)', 'The WKT point ordinates do not match its coordinate dimension. Invalid WKT input: "POINTM(34.23 10)".']; + yield 'testParsingPointMValueExtraCoordinate' => ['POINTM(34.23 10 30 40)', 'The WKT point ordinates do not match its coordinate dimension. Invalid WKT input: "POINTM(34.23 10 30 40)".']; + yield 'testParsingPointZMValueMissingCoordinate' => ['POINTZM(34.23 10 45)', 'The WKT point ordinates do not match its coordinate dimension. Invalid WKT input: "POINTZM(34.23 10 45)".']; + yield 'testParsingPointZMValueExtraCoordinate' => ['POINTZM(34.23 10 45 4.5 99)', 'The WKT point ordinates do not match its coordinate dimension. Invalid WKT input: "POINTZM(34.23 10 45 4.5 99)".']; + yield 'testParsingPointValueShortString' => ['POINT(34.23', 'The WKT geometry syntax is malformed. Invalid WKT input: "POINT(34.23".']; + yield 'testParsingPointValueWrongScientificWithSrid' => ['SRID=4326;POINT(4.23test-005 -8e-003)', 'The WKT geometry syntax is malformed. Invalid WKT input: "SRID=4326;POINT(4.23test-005 -8e-003)".']; + yield 'testParsingPointValueWithComma' => ['POINT(10, 10)', 'The WKT geometry syntax is malformed. Invalid WKT input: "POINT(10, 10)".']; + yield 'testParsingLineStringValueMissingCoordinate' => ['LINESTRING(34.23 -87, 45.3)', 'The WKT coordinates do not match their coordinate dimension. Invalid WKT input: "LINESTRING(34.23 -87, 45.3)".']; + yield 'testParsingLineStringValueMismatchedDimensions' => ['LINESTRING(34.23 -87, 45.3 56 23.4)', 'The WKT coordinates do not match their coordinate dimension. Invalid WKT input: "LINESTRING(34.23 -87, 45.3 56 23.4)".']; + yield 'testParsingPolygonValueMissingParenthesis' => ['POLYGON(0 0,10 0,10 10,0 10,0 0)', 'The WKT geometry syntax is malformed. Invalid WKT input: "POLYGON(0 0,10 0,10 10,0 10,0 0)".']; + yield 'testParsingPolygonValueMismatchedDimension' => ['POLYGON((0 0,10 0,10 10 10,0 10,0 0))', 'The WKT coordinates do not match their coordinate dimension. Invalid WKT input: "POLYGON((0 0,10 0,10 10 10,0 10,0 0))".']; + yield 'testParsingPolygonValueMultiRingMissingComma' => ['POLYGON((0 0,10 0,10 10,0 10,0 0)(5 5,7 5,7 7,5 7,5 5))', 'The WKT geometry syntax is malformed. Invalid WKT input: "POLYGON((0 0,10 0,10 10,0 10,0 0)(5 5,7 5,7 7,5 7,5 5))".']; + yield 'testParsingMultiLineStringValueMissingComma' => ['MULTILINESTRING((0 0,10 0,10 10,0 10)(5 5,7 5,7 7,5 7))', 'The WKT geometry syntax is malformed. Invalid WKT input: "MULTILINESTRING((0 0,10 0,10 10,0 10)(5 5,7 5,7 7,5 7))".']; + yield 'testParsingMultiPolygonValueMissingParenthesis' => ['MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),(1 1, 3 1, 3 3, 1 3, 1 1))', 'The WKT geometry syntax is malformed. Invalid WKT input: "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),(1 1, 3 1, 3 3, 1 3, 1 1))".']; + yield 'testParsingGeometryCollectionValueWithMismatchedDimension' => ['GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30 10), LINESTRING(15 15, 20 20))', 'The WKT point ordinates do not match its coordinate dimension. Invalid WKT input: "GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30 10), LINESTRING(15 15, 20 20))".']; + } + + /** + * Ensure recognized but abstract geometry types are rejected with their expected exception messages. + * + * @param string $notInstantiableType Type to reject + * @param string $expectedMessage Expected exception message + */ + #[DataProvider('notInstantiableTypesProvider')] + public function testNotInstantiable(string $notInstantiableType, string $expectedMessage): void + { + self::expectException(NonInstantiableGeometryTypeException::class); + self::expectExceptionMessageIsOrContains($expectedMessage); + $parser = new Decoder(new EwktDecoderStrategy()); + $parser->decode($notInstantiableType.'(10 10)'); + } + + /** Ensure empty input is rejected as an unsupported geometry value. */ + public function testNullParser(): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessageIsOrContains('The supplied WKT geometry type is not supported. Invalid WKT input: "".'); + $parser->decode(''); + } + + /** + * Ensure malformed EWKT syntax and invalid coordinate values produce the expected error messages. + * + * @param string $value EWKT value to reject + * @param string $exceptionMessage Expected exception message + */ + #[DataProvider('unexpectedValues')] + public function testParserWithUnexpectedValues(string $value, string $exceptionMessage): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessageIsOrContains($exceptionMessage); + + $parser->decode($value); + } + + /** + * Ensure unknown geometry names are rejected as unsupported WKT types. + * + * @param string $garbage Input containing an unsupported geometry type + * @param string $message Expected exception message + */ + #[DataProvider('notExistentValuesProvider')] + public function testParsingGarbage(string $garbage, string $message): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessageIsOrContains($message); + $parser->decode($garbage); + } + + /** Ensure an unknown geometry type is rejected with the decoder's unsupported-type error. */ + public function testUnexpectedType(): void + { + $parser = new Decoder(new EwktDecoderStrategy()); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessageIsOrContains('The supplied WKT geometry type is not supported. Invalid WKT input: "foo(10 10)".'); + $parser->decode('foo(10 10)'); + } +} diff --git a/tests/Legacy/PointTest.php b/tests/Legacy/EwktParser/PointTest.php similarity index 81% rename from tests/Legacy/PointTest.php rename to tests/Legacy/EwktParser/PointTest.php index 47e7e32..aa518ef 100644 --- a/tests/Legacy/PointTest.php +++ b/tests/Legacy/EwktParser/PointTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class PointTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -57,12 +59,16 @@ public static function pointProvider(): \Generator } /** - * @param (int|string)[] $coordinates + * Verify that POINT values decode to the expected ordinates, SRID, and dimension. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (int|string)[] $coordinates Expected ordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('pointProvider')] public function testPoint(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertPointParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/PolygonTest.php b/tests/Legacy/EwktParser/PolygonTest.php similarity index 83% rename from tests/Legacy/PolygonTest.php rename to tests/Legacy/EwktParser/PolygonTest.php index 02b0745..47b7b75 100644 --- a/tests/Legacy/PolygonTest.php +++ b/tests/Legacy/EwktParser/PolygonTest.php @@ -14,23 +14,25 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class PolygonTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -55,12 +57,16 @@ public static function polygonProvider(): \Generator } /** - * @param (int|string)[][][] $coordinates + * Verify that POLYGON values decode with the expected rings, SRID, and dimension. + * + * @param string $value EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (int|string)[][][] $coordinates Expected polygon coordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('polygonProvider')] public function testPolygon(string $value, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($value); self::assertPolygonParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/EwktParser/README.md b/tests/Legacy/EwktParser/README.md new file mode 100644 index 0000000..181b094 --- /dev/null +++ b/tests/Legacy/EwktParser/README.md @@ -0,0 +1,85 @@ +# Legacy EWKT test adaptations + +These tests retain historical scenario and provider names from `wkt-parser` +while exercising the current decoder and, for the lexical scenarios identified +below, its internal lexer. They are compatibility scenarios, not a +promise to preserve the old parser API, result arrays, or diagnostic wording. +The current behavior remains covered by `tests/Unit/`. + +The comparison below uses `wkt-parser` revision +[`7d3c8fd`](https://github.com/longitude-one/wkt-parser/tree/7d3c8fd5fd061c44349acc6bc8de8e995371b168), +particularly its [ParserTest.php](https://github.com/longitude-one/wkt-parser/blob/7d3c8fd5fd061c44349acc6bc8de8e995371b168/tests/LongitudeOne/Geo/WKT/Tests/ParserTest.php), +[ExceptionTest.php](https://github.com/longitude-one/wkt-parser/blob/7d3c8fd5fd061c44349acc6bc8de8e995371b168/tests/LongitudeOne/Geo/WKT/Tests/ExceptionTest.php), +and [Parser.php](https://github.com/longitude-one/wkt-parser/blob/7d3c8fd5fd061c44349acc6bc8de8e995371b168/lib/LongitudeOne/Geo/WKT/Parser.php). + +## Lexical scenarios retained as internal tests + +For story #14, the Product Owner explicitly approved retaining +[LexerTest.php](LexerTest.php) unchanged as an exception to the requirement that +migrated tests use the public `Decoder` API. These scenarios remain internal +regression coverage alongside the public decoding scenarios; they do not define +a public lexer contract. + +The following assertions cannot be transposed to `Decoder::decode()` without +changing what they verify: + +- `testTokenRecognition()` uses `tokenData()` to check token types, values and + source positions exposed through `lookahead` after each `moveNext()`. Its + cases cover geometry keywords, attached and spaced dimension suffixes, + integer and floating-point literals (including scientific notation), `SRID`, + and the complete token sequence of the SRID-prefixed `LINESTRING` fixture, + including punctuation. Standalone keywords and numbers are lexical fragments, + not complete geometry inputs. Even for the complete fixture, a decoded + geometry does not expose token boundaries, token types or source positions. +- `testTokenRecognitionReuseLexer()` runs the same provider through one lexer, + calling `setInput()` between cases and checking the resulting token sequences. + It verifies lexer state reset and reuse. The public decoder does not expose + that lexer instance, its `lookahead`, or its input-reset lifecycle. + +Replacing these assertions with geometry results or decoding exceptions would +lose their lexical purpose. Both tests and their provider are therefore retained +as direct tests of `Strategy\Common\Lexer`, rather than counted as scenarios +ported to the public decoding API. + +## Exception classes and messages + +In the table, historical classes belong to `LongitudeOne\Geo\WKT\Exception`; +current classes belong to `LongitudeOne\SpatialDecoder\Exception`. + +| Historical expectation | Migrated expectation. | Reason and traceability | +| --------------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `NotExistentException` for unknown types | `InvalidArgumentException` | `ParserTest::notExistentValuesProvider()`, `testUnexpectedType()` and `ExceptionTest::testNotExistentException()` now exercise decoder rejection of unsupported geometry types. Messages identify the invalid input instead of repeating the old ISO-labelled wording. | +| `UnexpectedValueException` for malformed input | `InvalidArgumentException` | `ParserTest::unexpectedValues()` retains the invalid inputs and named cases. Expected messages now describe SRID prefix, coordinate dimension, or syntax failures from the current decoder, rather than the old lexer's token names and line/column offsets. | +| `NotInstantiableException` | `NonInstantiableGeometryTypeException` | The abstract-type providers in `ParserTest` and `ExceptionTest` use the decoder's dedicated exception. Messages name the recognized enum type; historical “Did you mean” suggestions and ISO-labelled wording are not retained. | +| `NotYetImplementedException` from the old namespace | `NotYetImplementedException` from the decoder namespace | `ExceptionTest::notYetImplementedTypes()` expects the decoder's message, `Decoding of spatial type "…" is not yet implemented.`, rather than a message naming the old `Parser` class. | + +These are adaptations to the existing implementation and exception API, not +new normative claims or a guarantee of identical diagnostics between libraries. + +## No-argument parsing versus empty input + +Historically, `Parser::parse(?string $input = null)` could use constructor input. +`ParserTest::testNullParser()` called `parse()` on a parser without input and +expected `UnexpectedValueException` with `No value provided`. + +The current [Decoder::decode()](../../../lib/Decoder.php) requires an argument. +The migrated [testNullParser()](ParserTest.php) therefore calls `decode('')` and +expects the decoder's `InvalidArgumentException` for an unsupported geometry +value, including `Invalid WKT input: "".` in the message. This replaces the +historical scenario with empty-string rejection; it does not verify equivalent +no-argument behavior. Calling `decode()` without an argument would instead fail +PHP's argument-count check before parsing. + +## Absent SRID: `null` to `0` + +The old parser initialized its result-array `srid` field to `null` when no SRID +prefix was supplied. Historical fixture values retain that `null` for traceability. +The current EWKT strategy returns spatial objects whose default SRID is `0`, as +documented in the [public usage examples](../../../README.md#ewkt-decoding). + +[SpecificTestCase](Utils/SpecificTestCase.php) explicitly compares +`$expectedSrid ?? 0` with `getSrid()` in both `assertParsed()` and +`assertGeometryCollectionParsed()`; child assertions use the same conversion. +This adapts an absent historical array value to the current object model. +Explicit numeric SRIDs are compared unchanged. It does not infer a coordinate +reference system or alter coordinates. diff --git a/tests/Legacy/ReUsedParserTest.php b/tests/Legacy/EwktParser/ReUsedParserTest.php similarity index 74% rename from tests/Legacy/ReUsedParserTest.php rename to tests/Legacy/EwktParser/ReUsedParserTest.php index 85fd9a0..55a6cfe 100644 --- a/tests/Legacy/ReUsedParserTest.php +++ b/tests/Legacy/EwktParser/ReUsedParserTest.php @@ -14,62 +14,60 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; class ReUsedParserTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); parent::tearDown(); } + /** + * Verify that one decoder instance correctly processes successive inputs for each supported geometry. + */ public function testReusedParser(): void { foreach (PointTest::pointProvider() as $name => $testData) { - /** @var array{type:string, value: (int|string)[], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($testData[0]); self::assertPointParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); } foreach (LineStringTest::lineStringProvider() as $name => $testData) { - /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($testData[0]); self::assertLineStringParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); } foreach (PolygonTest::polygonProvider() as $name => $testData) { - /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($testData[0]); self::assertPolygonParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); } foreach (MultiPointTest::multiPointProvider() as $name => $testData) { - /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($testData[0]); self::assertMultiPointParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); } foreach (MultiLineStringTest::multiLineStringProvider() as $name => $testData) { - /** @var array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($testData[0]); self::assertMultiLineStringParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); } foreach (MultiPolygonTest::multiPolygonProvider() as $name => $testData) { - /** @var array{type:string, value: (int|string)[][][][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($testData[0]); self::assertMultiPolygonParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); } foreach (GeometryCollectionTest::geometryCollectionProvider() as $name => $testData) { - /** @var array{type:string, value: array, srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($testData[0]); self::assertGeometryCollectionParsed($testData[1], $testData[2], $testData[3], $actual, 'Failed dataset "'.$name.'"'); } diff --git a/tests/Legacy/TriangleTest.php b/tests/Legacy/EwktParser/TriangleTest.php similarity index 55% rename from tests/Legacy/TriangleTest.php rename to tests/Legacy/EwktParser/TriangleTest.php index ad8a34a..6999c9c 100644 --- a/tests/Legacy/TriangleTest.php +++ b/tests/Legacy/EwktParser/TriangleTest.php @@ -14,24 +14,26 @@ declare(strict_types=1); -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser; use LongitudeOne\SpatialDecoder\Decoder; use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; +use LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils\SpecificTestCase; use PHPUnit\Framework\Attributes\DataProvider; class TriangleTest extends SpecificTestCase { private Decoder $parser; + /** Prepare the decoder before each test. */ protected function setUp(): void { parent::setUp(); $this->parser = new Decoder(new EwktDecoderStrategy()); } + /** Release the decoder after each test. */ protected function tearDown(): void { unset($this->parser); @@ -46,28 +48,28 @@ protected function tearDown(): void public static function badNumberPointsProvider(): \Generator { // With 1 point - yield 'testParsingTriangleWith1Point' => ['TRIANGLE((34.23 -87))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; - yield 'testParsingTriangleZWith1Point' => ['TRIANGLEZ((34.23 -87 33))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; - yield 'testParsingTriangleMWith1Point' => ['TRIANGLEM((34.23 -87 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; - yield 'testParsingTriangleZMWith1Point' => ['TRIANGLEZM((34.23 -87 10 20))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 1.']; + yield 'testParsingTriangleWith1Point' => ['TRIANGLE((34.23 -87))', 'A non-empty WKT line string must contain at least two coordinates. Invalid WKT input: "TRIANGLE((34.23 -87))".']; + yield 'testParsingTriangleZWith1Point' => ['TRIANGLEZ((34.23 -87 33))', 'A non-empty WKT line string must contain at least two coordinates. Invalid WKT input: "TRIANGLEZ((34.23 -87 33))".']; + yield 'testParsingTriangleMWith1Point' => ['TRIANGLEM((34.23 -87 10))', 'A non-empty WKT line string must contain at least two coordinates. Invalid WKT input: "TRIANGLEM((34.23 -87 10))".']; + yield 'testParsingTriangleZMWith1Point' => ['TRIANGLEZM((34.23 -87 10 20))', 'A non-empty WKT line string must contain at least two coordinates. Invalid WKT input: "TRIANGLEZM((34.23 -87 10 20))".']; // With 2 points - yield 'testParsingTriangleWith2Points' => ['TRIANGLE((34.23 -87, 45.3 -92))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; - yield 'testParsingTriangleZWith2Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; - yield 'testParsingTriangleMWith2Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; - yield 'testParsingTriangleZMWith2Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 2.']; + yield 'testParsingTriangleWith2Points' => ['TRIANGLE((34.23 -87, 45.3 -92))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLE((34.23 -87, 45.3 -92))".']; + yield 'testParsingTriangleZWith2Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZ((34.23 -87 33, 45.3 -92 22))".']; + yield 'testParsingTriangleMWith2Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEM((34.23 -87 10, 45.3 -92 10))".']; + yield 'testParsingTriangleZMWith2Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20))".']; // With 3 points - yield 'testParsingTriangleWith3Points' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; - yield 'testParsingTriangleZWith3Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; - yield 'testParsingTriangleMWith3Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; - yield 'testParsingTriangleZMWith3Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 3.']; + yield 'testParsingTriangleWith3Points' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLE((34.23 -87, 45.3 -92, 10 10))".']; + yield 'testParsingTriangleZWith3Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11))".']; + yield 'testParsingTriangleMWith3Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11))".']; + yield 'testParsingTriangleZMWith3Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13))".']; // With 5 points - yield 'testParsingTriangleWith5Points' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10, 34.23 -87, 45.3 -92))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; - yield 'testParsingTriangleZWith5Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 34.23 -87 33, 45.3 -92 22))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; - yield 'testParsingTriangleMWith5Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 34.23 -87 10, 45.3 -92 10))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; - yield 'testParsingTriangleZMWith5Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 34.23 -87 10 20, 45.3 -92 10 20))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points, you provided 5.']; + yield 'testParsingTriangleWith5Points' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10, 34.23 -87, 45.3 -92))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLE((34.23 -87, 45.3 -92, 10 10, 34.23 -87, 45.3 -92))".']; + yield 'testParsingTriangleZWith5Points' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 34.23 -87 33, 45.3 -92 22))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 34.23 -87 33, 45.3 -92 22))".']; + yield 'testParsingTriangleMWith5Points' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 34.23 -87 10, 45.3 -92 10))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 34.23 -87 10, 45.3 -92 10))".']; + yield 'testParsingTriangleZMWith5Points' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 34.23 -87 10 20, 45.3 -92 10 20))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 34.23 -87 10 20, 45.3 -92 10 20))".']; } /** @@ -79,10 +81,10 @@ public static function badNumberPointsProvider(): \Generator */ public static function notClosedTriangleProvider(): \Generator { - yield 'testParsingTriangleWithNotClosedRing' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10, 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87", the fourth is "0 0"']; - yield 'testParsingTriangleZWithNotClosedRing' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 0 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87 33", the fourth is "0 0 0"']; - yield 'testParsingTriangleMWithNotClosedRing' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 0 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87 10", the fourth is "0 0 0"']; - yield 'testParsingTriangleZMWithNotClosedRing' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 0 0 0 0))', 'According to the ISO-13249 specification, a triangle is a closed ring with fourth points. Your first point is "34.23 -87 10 20", the fourth is "0 0 0 0"']; + yield 'testParsingTriangleWithNotClosedRing' => ['TRIANGLE((34.23 -87, 45.3 -92, 10 10, 0 0))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLE((34.23 -87, 45.3 -92, 10 10, 0 0))".']; + yield 'testParsingTriangleZWithNotClosedRing' => ['TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 0 0 0))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZ((34.23 -87 33, 45.3 -92 22, 10 10 11, 0 0 0))".']; + yield 'testParsingTriangleMWithNotClosedRing' => ['TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 0 0 0))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEM((34.23 -87 10, 45.3 -92 10, 10 10 11, 0 0 0))".']; + yield 'testParsingTriangleZMWithNotClosedRing' => ['TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 0 0 0 0))', 'A WKT polygon ring must contain at least four coordinates and be closed. Invalid WKT input: "TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 0 0 0 0))".']; } /** @@ -100,29 +102,45 @@ public static function triangleProvider(): \Generator yield 'testParsingTriangleZMValueWithSrid' => ['SRID=4326;TRIANGLEZM((34.23 -87 10 20, 45.3 -92 10 20, 10 11 12 13, 34.23 -87 10 20))', 4326, [['34.23', -87, 10, 20], ['45.3', -92, 10, 20], [10, 11, 12, 13], ['34.23', -87, 10, 20]], 'ZM']; } + /** + * Reject triangles with an unsupported number of points. + * + * @param string $actualValue EWKT triangle to reject + * @param string $expectedMessage Expected exception message + */ #[DataProvider('badNumberPointsProvider')] public function testBadNumberPoints(string $actualValue, string $expectedMessage): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage($expectedMessage); + $this->expectExceptionMessageIsOrContains($expectedMessage); $this->parser->decode($actualValue); } + /** + * Reject triangles whose rings are not closed. + * + * @param string $actualValue EWKT triangle to reject + * @param string $expectedMessage Expected exception message + */ #[DataProvider('notClosedTriangleProvider')] public function testNotClosedTriangle(string $actualValue, string $expectedMessage): void { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage($expectedMessage); + $this->expectExceptionMessageIsOrContains($expectedMessage); $this->parser->decode($actualValue); } /** - * @param (int|string)[][] $coordinates + * Verify that TRIANGLE values decode to the expected ring coordinates and metadata. + * + * @param string $actualValue EWKT value to decode + * @param int|null $srid Expected spatial reference identifier + * @param (int|string)[][] $coordinates Expected triangle coordinates + * @param string|null $dimension Expected coordinate dimension */ #[DataProvider('triangleProvider')] public function testTriangle(string $actualValue, ?int $srid, array $coordinates, ?string $dimension): void { - /** @var array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual */ $actual = $this->parser->decode($actualValue); self::assertTriangleParsed($srid, $coordinates, $dimension, $actual); diff --git a/tests/Legacy/EwktParser/Utils/SpecificTestCase.php b/tests/Legacy/EwktParser/Utils/SpecificTestCase.php new file mode 100644 index 0000000..bd21ebb --- /dev/null +++ b/tests/Legacy/EwktParser/Utils/SpecificTestCase.php @@ -0,0 +1,206 @@ + 2026 + * Copyright Longitude One 2026 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + */ + +declare(strict_types=1); + +namespace LongitudeOne\SpatialDecoder\Tests\Legacy\EwktParser\Utils; + +use LongitudeOne\SpatialTypes\Interfaces\CollectionInterface; +use LongitudeOne\SpatialTypes\Interfaces\SpatialInterface; +use PHPUnit\Framework\TestCase; +use UnexpectedValueException as InvalidCoordinateException; + +class SpecificTestCase extends TestCase +{ + /** + * Assert that a decoded CircularString matches its expected metadata and coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (float|int|string)[][] $expectedCoordinates Expected circular string coordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertCircularStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'CIRCULARSTRING', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded GeometryCollection and each of its elements match expectations. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param array $expectedCoordinates Expected child geometries + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertGeometryCollectionParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertSame('GEOMETRYCOLLECTION', strtoupper($actual->getType()->value), $message); + self::assertSame($expectedSrid ?? 0, $actual->getSrid(), $message); + self::assertSame($expectedDimension ?? '', $actual->getDimension()->wktModifier(), $message); + self::assertInstanceOf(CollectionInterface::class, $actual, $message); + + $elements = $actual->getElements(); + self::assertCount(\count($expectedCoordinates), $elements, $message); + foreach ($expectedCoordinates as $index => $expectedElement) { + self::assertParsed($expectedSrid, $expectedElement['type'], $expectedElement['value'], $expectedDimension, $elements[$index], $message); + } + } + + /** + * Assert that a decoded LineString matches its expected metadata and coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (int|string)[][] $expectedCoordinates Expected line coordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertLineStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'LINESTRING', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded MultiLineString matches its expected metadata and coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (int|string)[][][] $expectedCoordinates Expected multiline coordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertMultiLineStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'MULTILINESTRING', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded MultiPoint matches its expected metadata and coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (int|string)[][] $expectedCoordinates Expected point coordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertMultiPointParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'MULTIPOINT', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded MultiPolygon matches its expected metadata and coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (int|string)[][][][] $expectedCoordinates Expected multipolygon coordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertMultiPolygonParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'MULTIPOLYGON', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded Point matches its expected metadata and ordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (int|string)[] $expectedCoordinates Expected point ordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertPointParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'POINT', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded Polygon matches its expected metadata and coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (int|string)[][][] $expectedCoordinates Expected polygon coordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertPolygonParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertParsed($expectedSrid, 'POLYGON', $expectedCoordinates, $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded Triangle matches its expected metadata and coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param (int|string)[][] $expectedCoordinates Expected triangle coordinates + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + protected static function assertTriangleParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + // Spatial triangles expose the same ring nesting as polygons. + self::assertParsed($expectedSrid, 'TRIANGLE', [$expectedCoordinates], $expectedDimension, $actual, $message); + } + + /** + * Assert that a decoded geometry matches its type, metadata, and numeric coordinates. + * + * @param int|null $expectedSrid Expected spatial reference identifier + * @param string $type Expected geometry type + * @param (float|int|string)[]|(float|int|string)[][]|(float|int|string)[][][]|(float|int|string)[][][][] $expectedCoordinates Expected coordinate values + * @param string|null $expectedDimension Expected coordinate dimension + * @param SpatialInterface $actual Decoded spatial object + * @param string $message Assertion failure message + */ + private static function assertParsed(?int $expectedSrid, string $type, array $expectedCoordinates, ?string $expectedDimension, SpatialInterface $actual, string $message = ''): void + { + self::assertSame($type, strtoupper($actual->getType()->value), $message); + self::assertSame($expectedSrid ?? 0, $actual->getSrid(), $message); + self::assertSame($expectedDimension ?? '', $actual->getDimension()->wktModifier(), $message); + + self::assertSame(self::numericCoordinates($expectedCoordinates), $actual->toArray(), $message); + } + + /** + * Convert legacy lexer numeric strings to the numbers exposed by spatial objects. + * + * @param array $coordinates legacy coordinate values + * + * @return array + */ + private static function numericCoordinates(array $coordinates): array + { + foreach ($coordinates as $index => $coordinate) { + if (\is_array($coordinate)) { + $coordinates[$index] = self::numericCoordinates($coordinate); + + continue; + } + + if (!is_numeric($coordinate)) { + throw new InvalidCoordinateException('Expected a numeric coordinate.'); + } + + $coordinates[$index] = $coordinate + 0; + } + + return $coordinates; + } +} diff --git a/tests/Legacy/ExceptionTest.php b/tests/Legacy/ExceptionTest.php deleted file mode 100644 index 6873e50..0000000 --- a/tests/Legacy/ExceptionTest.php +++ /dev/null @@ -1,96 +0,0 @@ - 2026 - * Copyright Longitude One 2026 - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - */ - -declare(strict_types=1); - -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; - -use LongitudeOne\SpatialDecoder\Decoder; -use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; -use LongitudeOne\SpatialDecoder\Exception\NonInstantiableGeometryTypeException; -use LongitudeOne\SpatialDecoder\Exception\NotYetImplementedException; -use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\TestCase; - -class ExceptionTest extends TestCase -{ - /** - * @return \Generator - */ - public static function notInstantiableTypes(): \Generator - { - yield 'CURVE' => ['CURVE', 'According the ISO 13249-3:2016 standard, the "CURVE" type is not instantiable. Did you mean "MULTICURVE"?']; - yield 'GEOMETRY' => ['GEOMETRY', 'According the ISO 13249-3:2016 standard, the "GEOMETRY" type is not instantiable. Did you mean "GEOMETRYCOLLECTION"?']; - yield 'SOLID' => ['SOLID', 'According the ISO 13249-3:2016 standard, the "SOLID" type is not instantiable. Did you mean "POLYGON"?']; - yield 'SURFACE' => ['SURFACE', 'According the ISO 13249-3:2016 standard, the "SURFACE" type is not instantiable. Did you mean "MULTISURFACE"?']; - } - - /** - * @return \Generator - */ - public static function notYetImplementedTypes(): \Generator - { - yield 'BREPSOLID' => ['BREPSOLID', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "BREPSOLID".']; - yield 'CIRCLE' => ['CIRCLE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "CIRCLE".']; - yield 'CLOTHOID' => ['CLOTHOID', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "CLOTHOID".']; - yield 'COMPOUNDCURVE' => ['COMPOUNDCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "COMPOUNDCURVE".']; - yield 'COMPOUNDSURFACE' => ['COMPOUNDSURFACE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "COMPOUNDSURFACE".']; - yield 'CURVEPOLYGON' => ['CURVEPOLYGON', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "CURVEPOLYGON".']; - yield 'ELLIPTICALCURVE' => ['ELLIPTICALCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "ELLIPTICALCURVE".']; - yield 'GEODESICSTRING' => ['GEODESICSTRING', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "GEODESICSTRING".']; - yield 'MULTICURVE' => ['MULTICURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "MULTICURVE".']; - yield 'MULTISURFACE' => ['MULTISURFACE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "MULTISURFACE".']; - yield 'NURBSCURVE' => ['NURBSCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "NURBSCURVE".']; - yield 'SPIRALCURVE' => ['SPIRALCURVE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "SPIRALCURVE".']; - yield 'POLYHDRLSURFACE' => ['POLYHDRLSURFACE', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "POLYHDRLSURFACE".']; - yield 'TIN' => ['TIN', 'The LongitudeOne\Geo\WKT\Parser is not yet able to parse "TIN".']; - } - - public function testNotExistentException(): void - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('According the ISO 13249-3:2016 standard, the "FOO" type does not exist.'); - - (new Decoder(new EwktDecoderStrategy()))->decode('FOO(42 42)'); - } - - #[DataProvider('notInstantiableTypes')] - public function testNotInstantiable(string $notInstantiableType, string $expectedMessage): void - { - $this->expectException(NonInstantiableGeometryTypeException::class); - $this->expectExceptionMessage($expectedMessage); - - $toParse = \sprintf('%s(42 42)', $notInstantiableType); - - (new Decoder(new EwktDecoderStrategy()))->decode($toParse); - } - - public function testNotInstantiableException(): void - { - $exception = new NonInstantiableGeometryTypeException('foo'); - self::assertSame('According the ISO 13249-3:2016 standard, the "foo" type is not instantiable.', $exception->getMessage()); - } - - #[DataProvider('notYetImplementedTypes')] - public function testNotYetImplemented(string $notYetImplemented, string $expectedMessage): void - { - $this->expectException(NotYetImplementedException::class); - $this->expectExceptionMessage($expectedMessage); - - $toParse = \sprintf('%s(42 42)', $notYetImplemented); - - (new Decoder(new EwktDecoderStrategy()))->decode($toParse); - } -} diff --git a/tests/Legacy/ParserTest.php b/tests/Legacy/ParserTest.php deleted file mode 100644 index 7491e71..0000000 --- a/tests/Legacy/ParserTest.php +++ /dev/null @@ -1,121 +0,0 @@ - 2026 - * Copyright Longitude One 2026 - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - */ - -declare(strict_types=1); - -namespace LongitudeOne\SpatialDecoder\Tests\Legacy; - -use LongitudeOne\SpatialDecoder\Decoder; -use LongitudeOne\SpatialDecoder\Exception\InvalidArgumentException; -use LongitudeOne\SpatialDecoder\Exception\NonInstantiableGeometryTypeException; -use LongitudeOne\SpatialDecoder\Strategy\EwktDecoderStrategy; -use LongitudeOne\SpatialDecoder\Tests\Legacy\Utils\SpecificTestCase; -use PHPUnit\Framework\Attributes\DataProvider; - -/** - * Basic parser tests. - */ -class ParserTest extends SpecificTestCase -{ - /** - * @return \Generator - */ - public static function notExistentValuesProvider(): \Generator - { - yield 'testParsingGarbage' => ['@#_$%', 'According the ISO 13249-3:2016 standard, the "@" type does not exist.']; - yield 'testParsingBadType' => ['PNT(10 10)', 'According the ISO 13249-3:2016 standard, the "PNT" type does not exist.']; - yield 'testParsingGeometryCollectionValueWithBadType' => ['GEOMETRYCOLLECTION(PNT(10 10), POINT(30 30), LINESTRING(15 15, 20 20))', 'According the ISO 13249-3:2016 standard, the "PNT" type does not exist.']; - } - - /** - * @return \Generator - */ - public static function notInstantiableTypesProvider(): \Generator - { - yield 'testNotInstantiableGeometry' => ['GEOMETRY', 'According the ISO 13249-3:2016 standard, the "GEOMETRY" type is not instantiable. Did you mean "GEOMETRYCOLLECTION"?']; - yield 'testNotInstantiableCurve' => ['CURVE', 'According the ISO 13249-3:2016 standard, the "CURVE" type is not instantiable. Did you mean "MULTICURVE"?']; - yield 'testNotInstantiableSolid' => ['SOLID', 'According the ISO 13249-3:2016 standard, the "SOLID" type is not instantiable. Did you mean "POLYGON"?']; - yield 'testNotInstantiableSurface' => ['SURFACE', 'According the ISO 13249-3:2016 standard, the "SURFACE" type is not instantiable. Did you mean "MULTISURFACE"?']; - } - - /** - * @return \Generator - */ - public static function unexpectedValues(): \Generator - { - yield 'testParsingPointValueWithBadSrid' => ['SRID=432.6;POINT(34.23 -87)', '[Syntax Error] line 0, col 5: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got "432.6" in value "SRID=432.6;POINT(34.23 -87)"']; - yield 'testParsingPointValueMissingCoordinate' => ['POINT(34.23)', '[Syntax Error] line 0, col 11: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "POINT(34.23)"']; - yield 'testParsingPointMValueMissingCoordinate' => ['POINTM(34.23 10)', '[Syntax Error] line 0, col 15: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "POINTM(34.23 10)"']; - yield 'testParsingPointMValueExtraCoordinate' => ['POINTM(34.23 10 30 40)', '[Syntax Error] line 0, col 19: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "40" in value "POINTM(34.23 10 30 40)"']; - yield 'testParsingPointZMValueMissingCoordinate' => ['POINTZM(34.23 10 45)', '[Syntax Error] line 0, col 19: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "POINTZM(34.23 10 45)"']; - yield 'testParsingPointZMValueExtraCoordinate' => ['POINTZM(34.23 10 45 4.5 99)', '[Syntax Error] line 0, col 24: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "99" in value "POINTZM(34.23 10 45 4.5 99)"']; - yield 'testParsingPointValueShortString' => ['POINT(34.23', '[Syntax Error] line 0, col -1: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got end of string. in value "POINT(34.23"']; - yield 'testParsingPointValueWrongScientificWithSrid' => ['SRID=4326;POINT(4.23test-005 -8e-003)', '[Syntax Error] line 0, col 20: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got "test" in value "SRID=4326;POINT(4.23test-005 -8e-003)"']; - yield 'testParsingPointValueWithComma' => ['POINT(10, 10)', '[Syntax Error] line 0, col 8: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got "," in value "POINT(10, 10)"']; - yield 'testParsingLineStringValueMissingCoordinate' => ['LINESTRING(34.23 -87, 45.3)', '[Syntax Error] line 0, col 26: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_INTEGER, got ")" in value "LINESTRING(34.23 -87, 45.3)"']; - yield 'testParsingLineStringValueMismatchedDimensions' => ['LINESTRING(34.23 -87, 45.3 56 23.4)', '[Syntax Error] line 0, col 30: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "23.4" in value "LINESTRING(34.23 -87, 45.3 56 23.4)"']; - yield 'testParsingPolygonValueMissingParenthesis' => ['POLYGON(0 0,10 0,10 10,0 10,0 0)', '[Syntax Error] line 0, col 8: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_OPEN_PARENTHESIS, got "0" in value "POLYGON(0 0,10 0,10 10,0 10,0 0)"']; - yield 'testParsingPolygonValueMismatchedDimension' => ['POLYGON((0 0,10 0,10 10 10,0 10,0 0))', '[Syntax Error] line 0, col 24: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "10" in value "POLYGON((0 0,10 0,10 10 10,0 10,0 0))"']; - yield 'testParsingPolygonValueMultiRingMissingComma' => ['POLYGON((0 0,10 0,10 10,0 10,0 0)(5 5,7 5,7 7,5 7,5 5))', '[Syntax Error] line 0, col 33: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "(" in value "POLYGON((0 0,10 0,10 10,0 10,0 0)(5 5,7 5,7 7,5 7,5 5))"']; - yield 'testParsingMultiLineStringValueMissingComma' => ['MULTILINESTRING((0 0,10 0,10 10,0 10)(5 5,7 5,7 7,5 7))', '[Syntax Error] line 0, col 37: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "(" in value "MULTILINESTRING((0 0,10 0,10 10,0 10)(5 5,7 5,7 7,5 7))"']; - yield 'testParsingMultiPolygonValueMissingParenthesis' => ['MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),(1 1, 3 1, 3 3, 1 3, 1 1))', '[Syntax Error] line 0, col 64: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_OPEN_PARENTHESIS, got "1" in value "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5)),(1 1, 3 1, 3 3, 1 3, 1 1))"']; - yield 'testParsingGeometryCollectionValueWithMismatchedDimension' => ['GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30 10), LINESTRING(15 15, 20 20))', '[Syntax Error] line 0, col 45: Error: Expected LongitudeOne\Geo\WKT\Lexer::T_CLOSE_PARENTHESIS, got "10" in value "GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30 10), LINESTRING(15 15, 20 20))"']; - } - - #[DataProvider('notInstantiableTypesProvider')] - public function testNotInstantiable(string $notInstantiableType, string $expectedMessage): void - { - self::expectException(NonInstantiableGeometryTypeException::class); - self::expectExceptionMessage($expectedMessage); - $parser = new Decoder(new EwktDecoderStrategy()); - $parser->decode($notInstantiableType.'(10 10)'); - } - - public function testNullParser(): void - { - $parser = new Decoder(new EwktDecoderStrategy()); - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('No value provided'); - $parser->decode(''); - } - - #[DataProvider('unexpectedValues')] - public function testParserWithUnexpectedValues(string $value, string $exceptionMessage): void - { - $parser = new Decoder(new EwktDecoderStrategy()); - - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage($exceptionMessage); - - $parser->decode($value); - } - - #[DataProvider('notExistentValuesProvider')] - public function testParsingGarbage(string $garbage, string $message): void - { - $parser = new Decoder(new EwktDecoderStrategy()); - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage($message); - $parser->decode($garbage); - } - - public function testUnexpectedType(): void - { - $parser = new Decoder(new EwktDecoderStrategy()); - - self::expectException(InvalidArgumentException::class); - self::expectExceptionMessage('According the ISO 13249-3:2016 standard, the "foo" type does not exist.'); - $parser->decode('foo(10 10)'); - } -} diff --git a/tests/Legacy/Utils/SpecificTestCase.php b/tests/Legacy/Utils/SpecificTestCase.php deleted file mode 100644 index 18c36d4..0000000 --- a/tests/Legacy/Utils/SpecificTestCase.php +++ /dev/null @@ -1,117 +0,0 @@ - 2026 - * Copyright Longitude One 2026 - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - * - */ - -declare(strict_types=1); - -namespace LongitudeOne\SpatialDecoder\Tests\Legacy\Utils; - -use PHPUnit\Framework\TestCase; - -class SpecificTestCase extends TestCase -{ - /** - * @param (int|string)[][] $expectedCoordinates - * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual - */ - protected static function assertCircularStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'CIRCULARSTRING', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param array $expectedCoordinates - * @param array{type:string, value: array, srid: ?int, dimension: ?string} $actual - */ - protected static function assertGeometryCollectionParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'GEOMETRYCOLLECTION', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[][] $expectedCoordinates - * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual - */ - protected static function assertLineStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'LINESTRING', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[][][] $expectedCoordinates - * @param array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual - */ - protected static function assertMultiLineStringParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'MULTILINESTRING', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[][] $expectedCoordinates - * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual - */ - protected static function assertMultiPointParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'MULTIPOINT', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[][][][] $expectedCoordinates - * @param array{type:string, value: (int|string)[][][][], srid: ?int, dimension: ?string} $actual - */ - protected static function assertMultiPolygonParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'MULTIPOLYGON', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[] $expectedCoordinates - * @param array{type:string, value: (int|string)[], srid: ?int, dimension: ?string} $actual - */ - protected static function assertPointParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'POINT', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[][][] $expectedCoordinates - * @param array{type:string, value: (int|string)[][][], srid: ?int, dimension: ?string} $actual - */ - protected static function assertPolygonParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'POLYGON', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[][] $expectedCoordinates - * @param array{type:string, value: (int|string)[][], srid: ?int, dimension: ?string} $actual - */ - protected static function assertTriangleParsed(?int $expectedSrid, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - self::assertParsed($expectedSrid, 'TRIANGLE', $expectedCoordinates, $expectedDimension, $actual, $message); - } - - /** - * @param (int|string)[]|(int|string)[][]|(int|string)[][][]|(int|string)[][][][] $expectedCoordinates - * @param array{type:string, value: (int|string)[]|(int|string)[][]|(int|string)[][][]|(int|string)[][][][], srid: ?int, dimension: ?string} $actual - */ - private static function assertParsed(?int $expectedSrid, string $type, array $expectedCoordinates, ?string $expectedDimension, array $actual, string $message = ''): void - { - $expected['type'] = $type; - $expected['value'] = $expectedCoordinates; - $expected['srid'] = $expectedSrid; - $expected['dimension'] = $expectedDimension; - - self::assertSame($expected, $actual, $message); - } -}