expect(fn () => new Router()) ->toBeInstanceOf(Router::class); it('initializes with basic strategy') ->expect((new Router())->getStrategy()) ->toBeInstanceOf(BasicStrategy::class); it('can set strategy', function () { $router = new Router(); $s = new BasicStrategy(); expect($router->getStrategy())->not->toBe($s); $router->setStrategy($s); expect($router->getStrategy())->toBe($s); }); it('has root') ->expect(fn () => (new Router())->root()) ->toBeInstanceOf(PathSegment::class) ->getType()->toBe(PathSegmentType::Root); it('finds root segment with :dataset path', function (string $path) { $router = new Router(); expect($router->findSegment($path))->segment->toBe($router->root()); })->with(['', '/']); it('finds segment one level', function () { $router = new Router(); $c1 = $router->root()->child($p1 = 'test1'); $c2 = $router->root()->child($p2 = 'test2'); expect($router->findSegment("/{$p1}"))->segment->toBe($c1); expect($router->findSegment("/{$p2}"))->segment->toBe($c2); }); it('finds segment one level parameter', function () { $router = new Router(); $c1 = $router->root()->child('test2', PathSegmentType::Parameter); expect($router->findSegment('/anything')) ->segment->toBe($c1) ->parameters->{'test2'}->toBe('anything'); }); it('finds segment one falls back to parameter', function () { $router = new Router(); $c1 = $router->root()->child($p1 = 'test1'); $c2 = $router->root()->child('test2', PathSegmentType::Parameter); expect($router->findSegment("/{$p1}"))->segment->toBe($c1); expect($router->findSegment('/anything')) ->segment->toBe($c2) ->parameters->{'test2'}->toBe('anything'); }); it('finds segment case insentive', function () { $router = new Router(); $r = $router->root(); $c1 = $r->child('LevEl1'); $c2 = $c1->child('LEVEL2'); $c3 = $c2->child('level3'); expect($router->findSegment('/level1')?->segment)->toBe($c1); expect($router->findSegment('/level1/level2')?->segment)->toBe($c2); expect($router->findSegment('/LevEL1/level2/LEVEL3')?->segment)->toBe($c3); }); it('finds segments in complex routes', function () { $router = new Router(); $r = $router->root(); $s1 = $r->child('level1-1')->child('level2-1'); $s2 = $r->child('level1-1')->child('level2-2'); $s3 = $r->child('level1-2')->child('level2-3'); $s4 = $r->child('level1-1')->child('level2-2')->child('level3-1'); $s5 = $r->child('level1-1'); $s6 = $r->child('level1-3')->child('level2-4', PathSegmentType::Parameter); $s7 = $s6->child('level3-2'); $s8 = $s6->child('level3-3'); expect($router->findSegment('/level1-1')?->segment)->toBe($s5); expect($router->findSegment('/level1-2/level2-3')?->segment)->toBe($s3); expect($router->findSegment('/level1-1/level2-2/level3-1')?->segment)->toBe($s4); expect($router->findSegment('/level1-1/level2-1')?->segment)->toBe($s1); expect($router->findSegment('/level1-1/level2-2')?->segment)->toBe($s2); expect($router->findSegment('/level1-3/param1')) ->segment->toBe($s6) ->parameters->{'level2-4'}->toBe('param1'); expect($router->findSegment('/level1-3/param2/level3-2')) ->segment->toBe($s7) ->parameters->{'level2-4'}->toBe('param2'); expect($router->findSegment('/level1-3/param3/level3-3')) ->segment->toBe($s8) ->parameters->{'level2-4'}->toBe('param3'); }); it('return null when segment not found', function () { $router = new Router(); $r = $router->root(); $r->child('level1-1')->child('level2-1'); $r->child('level1-1')->child('level2-2'); $r->child('level1-2')->child('level2-3'); expect($router->findSegment('/testing/testing'))->toBeNull(); }); it('returns null when finding route') ->expect(fn () => (new Router())->findRoute(HttpMethod::Get, '/')) ->toBeNull(); it('finds a route', function () { $router = new Router(); $seg = $router->root()->child('testing'); $route = new Route(HttpMethod::Get, fn () => null); $seg->addRoute($route); $result = $router->findRoute(HttpMethod::Get, '/testing'); expect($result) ->toBeInstanceOf(RouteMatch::class) ->segmentMatch->segment->toBe($seg) ->route->toBe($route); }); it('finds any method route', function () { $router = new Router(); $router->root()->child('testing')->addRoute(new Route( HttpMethod::Any, fn () => null )); $result = $router->findRoute(HttpMethod::Get, '/testing'); expect($result)->toBeInstanceOf(RouteMatch::class); }); it('finds route method case insensitive', function () { $router = new Router(); $router->root()->child('testing')->addRoute(new Route( HttpMethod::Get, fn () => null )); $result = $router->findRoute('gET', '/testing'); expect($result)->toBeInstanceOf(RouteMatch::class); }); it('calls strategy notFound with dispatch', function () { $router = new Router(); $factory = new Psr17Factory(); $request = $factory->createServerRequest(HttpMethod::Get->value, '/testing/testing'); $response = $factory->createResponse(); $strat = Mockery::mock(Strategy::class); $strat->shouldReceive('notFound') ->with($request) ->andReturn($response); $router->setStrategy($strat); $result = $router->dispatch($request); expect($result)->toBe($response); }); it('calls strategy runRoute with dispatch on match', function () { $router = new Router(); $router->root()->child('testing')->addRoute(new Route( HttpMethod::Get, fn () => null, )); $factory = new Psr17Factory(); $request = $factory->createServerRequest(HttpMethod::Get->value, '/testing'); $response = $factory->createResponse(); $strat = Mockery::mock(Strategy::class); $strat->shouldReceive('runRoute') ->with(Mockery::type(RouteCall::class)) ->andReturn($response); $strat->shouldReceive('runMiddleware') ->with($request, [], Mockery::type('callable')) ->andReturn($response); $router->setStrategy($strat); $result = $router->dispatch($request); expect($result)->toBe($response); }); it('make a route', function () { $router = new Router(); $router->make(HttpMethod::Get, '/test1/test2/test3', fn () => null); expect($router->findRoute(HttpMethod::Get, '/test1/test2/test3')) ->toBeInstanceOf(RouteMatch::class); }); it('make a route with parameter', function () { $router = new Router(); $router->make(HttpMethod::Get, '/test/{test1}/hello', fn () => null); expect($router->findSegment('/test/hello')) ->not->toBeNull() ->parameters->toBe(['test1' => 'hello']); }); it('makes a :dataset route') ->with('http-methods') ->expect(function (HttpMethod $m) { $router = new Router(); $router->{$m->value}($p = '/test1/test2', fn () => null); return $router->findRoute($m, $p); }) ->toBeInstanceOf(RouteMatch::class); it('makes route with or without / prefix', function (string $path) { $router = new Router(); $r = $router->get($path, fn () => null)->getRoute(); expect($router->findRoute(HttpMethod::Get, '/test')?->route)->toBe($r); })->with(['/test', 'test']); it('returns RouteDefinition when making route') ->expect(fn () => (new Router()) ->make(HttpMethod::Get, '/test', fn () => null) ) ->toBeInstanceOf(RouteDefinition::class); it('returns RouteDefinition when making :dataset route') ->with('http-methods') ->expect(fn (HttpMethod $m) => (new Router())->{$m->value}('/test', fn () => null)) ->toBeInstanceOf(RouteDefinition::class); it('has named routes', function () { $router = new Router(); $route = new Route(HttpMethod::Get, fn () => null); $router->addNamedRoute('hello-hello', $route); expect($router->getNamedRoute('hello-hello'))->toBe($route); }); it('overrides existing named routes', function () { $router = new Router(); $route1 = new Route(HttpMethod::Get, fn () => null); $route2 = new Route(HttpMethod::Post, fn () => null); $router->addNamedRoute('hello-hello', $route1); $router->addNamedRoute('hello-hello', $route2); expect($router->getNamedRoute('hello-hello')) ->not->toBe($route1) ->toBe($route2); }); it('generates route from name', function () { $router = new Router(); $router->get('/test', fn () => null)->name('test'); expect($router->route('test'))->toBe('/test'); }); it('generates route with parameters from name', function () { $router = new Router(); $router->get('/test/{test1}/hello', fn () => null)->name('test'); expect($router->route('test', [ 'test1' => 'world', ]))->toBe('/test/world/hello'); }); it('throws when generating route with missing parameter from name', function () { $router = new Router(); $router->get('/test/{test1}/hello', fn () => null)->name('test'); $router->route('test', []); })->throws(MissingParameterException::class); it('throws when generating route with non string parameter from name', function () { $router = new Router(); $router->get('/test/{test1}/hello', fn () => null)->name('test'); $router->route('test', ['test1' => 123]); })->throws(InvalidArgumentException::class); it('builds routes in groups', function () { $router = new Router(); $router ->prefix('/group1/') ->name('group1.') ->group(function ($group) use (&$r1, &$r2) { $r1 = $group->get('/test1', fn () => null)->name('test1')->getRoute(); $group ->prefix('/group2') ->name('group2.') ->group(function ($group) use (&$r2) { $r2 = $group->get('test2', fn () => null)->name('test2')->getRoute(); }); }); $router ->name('no-prefix.') ->group(function ($group) use (&$r3) { $r3 = $group->post('/test3', fn () => null)->name('test3')->getRoute(); }); $r4 = $router->put('/test4', fn () => null)->name('test4')->getRoute(); expect($router->findRoute(HttpMethod::Get, '/group1/test1')?->route)->toBe($r1); expect($router->findRoute(HttpMethod::Get, '/group1/group2/test2')?->route)->toBe($r2); expect($router->findRoute(HttpMethod::Post, '/test3')?->route)->toBe($r3); expect($router->findRoute(HttpMethod::Put, '/test4')?->route)->toBe($r4); expect($router->getNamedRoute('group1.test1'))->toBe($r1); expect($router->getNamedRoute('group1.group2.test2'))->toBe($r2); expect($router->getNamedRoute('no-prefix.test3'))->toBe($r3); expect($router->getNamedRoute('test4'))->toBe($r4); }); it('can create a group with middleware') ->expect((new Router())->middleware(new TestMiddleware())) ->toBeInstanceOf(Group::class); it('runs middleware on dispatch')->todo();