toString())->toBe('/api/users/{id}'); expect($path->isDynamic())->toBeTrue(); expect($path->getParameterNames())->toBe(['id']); }); it('can be created from string', function () { $path = RoutePath::fromString('/api/users/{id}'); expect($path->toString())->toBe('/api/users/{id}'); expect($path->isDynamic())->toBeTrue(); expect($path->getParameterNames())->toBe(['id']); }); it('handles static routes', function () { $path = RoutePath::fromElements('api', 'health'); expect($path->toString())->toBe('/api/health'); expect($path->isStatic())->toBeTrue(); expect($path->getParameterNames())->toBe([]); }); it('handles wildcard parameters', function () { $path = RoutePath::fromString('/files/{path*}'); expect($path->toString())->toBe('/files/{path*}'); $placeholders = $path->getPlaceholders(); expect($placeholders)->toHaveCount(1); expect($placeholders[0]->isWildcard())->toBeTrue(); }); it('throws on empty path', function () { expect(fn () => RoutePath::fromElements()) ->toThrow(InvalidArgumentException::class); }); }); describe('fluent builder', function () { it('can build paths fluently', function () { $path = RoutePath::create() ->segment('api') ->segment('users') ->parameter('id') ->build(); expect($path->toString())->toBe('/api/users/{id}'); }); it('supports typed parameters', function () { $path = RoutePath::create() ->segment('api') ->segment('users') ->typedParameter('id', 'uuid') ->build(); $placeholders = $path->getPlaceholders(); expect($placeholders[0]->getType())->toBe('uuid'); }); it('supports quick patterns', function () { $path = RoutePath::create() ->segment('api') ->segment('users') ->uuid(); expect($path->toString())->toBe('/api/users/{id}'); }); }); describe('regex compilation', function () { it('compiles static routes to regex', function () { $path = RoutePath::fromString('/api/health'); expect($path->toRegex())->toBe('~^/api/health$~'); }); it('compiles dynamic routes to regex', function () { $path = RoutePath::fromString('/api/users/{id}'); expect($path->toRegex())->toBe('~^/api/users/([^/]+)$~'); }); it('compiles typed parameters correctly', function () { $path = RoutePath::fromElements( 'api', 'users', Placeholder::typed('id', 'int') ); expect($path->toRegex())->toBe('~^/api/users/(\d+)$~'); }); it('compiles wildcard parameters', function () { $path = RoutePath::fromElements( 'files', Placeholder::wildcard('path') ); expect($path->toRegex())->toBe('~^/files/(.+?)$~'); }); }); describe('manipulation', function () { it('can append elements', function () { $base = RoutePath::fromElements('api', 'users'); $extended = $base->append(Placeholder::fromString('id')); expect($extended->toString())->toBe('/api/users/{id}'); }); it('can prepend elements', function () { $base = RoutePath::fromElements('users', Placeholder::fromString('id')); $extended = $base->prepend('api'); expect($extended->toString())->toBe('/api/users/{id}'); }); }); describe('analysis', function () { it('counts segments correctly', function () { $path = RoutePath::fromString('/api/users/{id}'); expect($path->getSegmentCount())->toBe(3); }); it('identifies placeholders', function () { $path = RoutePath::fromString('/api/users/{id}/posts/{postId}'); $placeholders = $path->getPlaceholders(); expect($placeholders)->toHaveCount(2); expect($path->getParameterNames())->toBe(['id', 'postId']); }); }); describe('validation', function () { it('validates segment content', function () { expect(fn () => RoutePath::fromElements('api', '')) ->toThrow(InvalidArgumentException::class); }); it('rejects invalid characters in segments', function () { expect(fn () => RoutePath::fromElements('api', 'users{id}')) ->toThrow(InvalidArgumentException::class); }); }); });