Still need more browser tests, add server tests, fix kinks
This commit is contained in:
parent
2143d6c955
commit
a9e4f6d4dc
11 changed files with 208 additions and 66 deletions
|
@ -54,7 +54,7 @@ main() {
|
|||
return someQuery(id).reviews.firstWhere(
|
||||
(r) => r.id == reviewId);
|
||||
});
|
||||
}, before: [put, middleware, here]);
|
||||
}, middleware: [put, middleware, here]);
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// A powerful, isomorphic routing library for Dart.
|
||||
library angel_route;
|
||||
|
||||
export 'src/route.dart';
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import 'dart:async' show Stream, StreamController;
|
||||
import 'dart:convert' show JSON;
|
||||
import 'dart:html' show AnchorElement, window;
|
||||
import 'angel_route.dart';
|
||||
|
||||
final RegExp _hash = new RegExp(r'^#/');
|
||||
|
||||
/// A variation of the [Router] support both hash routing and push state.
|
||||
abstract class BrowserRouter extends Router {
|
||||
/// Fires whenever the active route changes. Fires `null` if none is selected (404).
|
||||
Stream<Route> get onRoute;
|
||||
|
||||
/// Set `hash` to true to use hash routing instead of push state.
|
||||
/// `listen` as `true` will call `listen` after initialization.
|
||||
factory BrowserRouter({bool hash: false, bool listen: true, Route root}) {
|
||||
return hash
|
||||
? new _HashRouter(listen: listen, root: root)
|
||||
|
@ -16,8 +19,13 @@ abstract class BrowserRouter extends Router {
|
|||
|
||||
BrowserRouter._([Route root]) : super(root);
|
||||
|
||||
/// Calls `goTo` on the [Route] matching `path`.
|
||||
void go(String path, [Map params]);
|
||||
|
||||
/// Navigates to the given route.
|
||||
void goTo(Route route, [Map params]);
|
||||
|
||||
/// Begins listen for location changes.
|
||||
void listen();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ final RegExp _rgxStraySlashes = new RegExp(r'(^((\\/)|(/))+)|(((\\/)|(/))+$)');
|
|||
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||
|
||||
String _matcherify(String path, {bool expand: true}) {
|
||||
var p = path.replaceAll(new RegExp(r'\/\*$'), "*").replaceAll('/', r'\/');
|
||||
var p = path.replaceAll(new RegExp(r'/\*$'), "*").replaceAll('/', r'\/');
|
||||
|
||||
if (expand) {
|
||||
var match = _param.firstMatch(p);
|
||||
|
@ -44,6 +44,7 @@ String _pathify(String path) {
|
|||
return p;
|
||||
}
|
||||
|
||||
/// Represents a virtual location within an application.
|
||||
class Route {
|
||||
final List<Route> _children = [];
|
||||
final List _handlers = [];
|
||||
|
@ -55,16 +56,36 @@ class Route {
|
|||
String _path;
|
||||
String _pathified;
|
||||
RegExp _resolver;
|
||||
String _stub;
|
||||
RegExp _stub;
|
||||
|
||||
/// Set to `true` to print verbose debug output when interacting with this route.
|
||||
bool debug;
|
||||
|
||||
/// Contains any child routes attached to this one.
|
||||
List<Route> get children => new List.unmodifiable(_children);
|
||||
|
||||
/// A `List` of arbitrary objects chosen to respond to this request.
|
||||
List get handlers => new List.unmodifiable(_handlers);
|
||||
|
||||
/// A `RegExp` that matches requests to this route.
|
||||
RegExp get matcher => _matcher;
|
||||
|
||||
/// The HTTP method this route is designated for.
|
||||
String get method => _method;
|
||||
|
||||
/// The name of this route, if any.
|
||||
String get name => _name;
|
||||
|
||||
/// The hierarchical parent of this route.
|
||||
Route get parent => _parent;
|
||||
|
||||
/// The virtual path on which this route is mounted.
|
||||
String get path => _path;
|
||||
|
||||
/// Arbitrary state attached to this route.
|
||||
final Extensible state = new Extensible();
|
||||
|
||||
/// The [Route] at the top of the hierarchy this route is found in.
|
||||
Route get absoluteParent {
|
||||
Route result = this;
|
||||
|
||||
|
@ -94,8 +115,13 @@ class Route {
|
|||
return result;
|
||||
}
|
||||
|
||||
void _printDebug(msg) {
|
||||
if (debug) print(msg);
|
||||
}
|
||||
|
||||
Route(Pattern path,
|
||||
{Iterable<Route> children: const [],
|
||||
this.debug: false,
|
||||
Iterable handlers: const [],
|
||||
method: "GET",
|
||||
String name: null}) {
|
||||
|
@ -134,7 +160,11 @@ class Route {
|
|||
Iterable handlers: const [],
|
||||
method: "GET",
|
||||
String name: null}) {
|
||||
final segments = path.toString().split('/').where((str) => str.isNotEmpty);
|
||||
final segments = path
|
||||
.toString()
|
||||
.split('/')
|
||||
.where((str) => str.isNotEmpty)
|
||||
.toList(growable: false);
|
||||
Route result;
|
||||
|
||||
if (segments.isEmpty) {
|
||||
|
@ -142,11 +172,22 @@ class Route {
|
|||
children: children, handlers: handlers, method: method, name: name);
|
||||
}
|
||||
|
||||
for (final segment in segments) {
|
||||
if (result == null) {
|
||||
result = new Route(segment);
|
||||
} else
|
||||
result = result.child(segment);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
final segment = segments[i];
|
||||
|
||||
if (i == segments.length - 1) {
|
||||
if (result == null) {
|
||||
result = new Route(segment);
|
||||
} else {
|
||||
result = result.child(segment);
|
||||
}
|
||||
} else {
|
||||
if (result == null) {
|
||||
result = new Route(segment, method: "*");
|
||||
} else {
|
||||
result = result.child(segment, method: "*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result._children.addAll(children);
|
||||
|
@ -157,6 +198,7 @@ class Route {
|
|||
return result;
|
||||
}
|
||||
|
||||
/// Combines the paths and matchers of two [Route] instances, and creates a new instance.
|
||||
factory Route.join(Route parent, Route child) {
|
||||
final String path1 = parent.path
|
||||
.replaceAll(_rgxStart, '')
|
||||
|
@ -183,15 +225,20 @@ class Route {
|
|||
|
||||
parent._children.add(route
|
||||
.._matcher = new RegExp('$pattern1$separator$pattern2')
|
||||
.._parent = parent);
|
||||
.._parent = parent
|
||||
.._stub = child.matcher);
|
||||
parent._printDebug(
|
||||
'Joined $path1 and $path2, produced stub ${route._stub.pattern}');
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
/// Calls [addChild] on all given routes.
|
||||
List<Route> addAll(Iterable<Route> routes, {bool join: true}) {
|
||||
return routes.map((route) => addChild(route, join: join)).toList();
|
||||
}
|
||||
|
||||
/// Adds the given route as a hierarchical child of this one.
|
||||
Route addChild(Route route, {bool join: true}) {
|
||||
Route created = join ? new Route.join(this, route) : route.._parent = this;
|
||||
return created;
|
||||
|
@ -200,6 +247,7 @@ class Route {
|
|||
/// Assigns a name to this route.
|
||||
Route as(String name) => this.._name = name;
|
||||
|
||||
/// Creates a hierarchical child of this route with the given path.
|
||||
Route child(Pattern path,
|
||||
{Iterable<Route> children: const [],
|
||||
Iterable handlers: const [],
|
||||
|
@ -223,6 +271,7 @@ class Route {
|
|||
return result.replaceAll("*", "");
|
||||
}
|
||||
|
||||
/// Attempts to match a path against this route.
|
||||
Match match(String path) =>
|
||||
matcher.firstMatch(path.replaceAll(_straySlashes, ''));
|
||||
|
||||
|
@ -256,17 +305,24 @@ class Route {
|
|||
yield routeMatch.group(i);
|
||||
}
|
||||
|
||||
/// Finds the first route available within this hierarchy that can respond to the given path.
|
||||
///
|
||||
/// Can be used to navigate a route hierarchy like a file system.
|
||||
Route resolve(String path, {bool filter(Route route), String fullPath}) {
|
||||
final _filter = filter ?? (_) => true;
|
||||
final _fullPath = fullPath ?? path;
|
||||
|
||||
if ((path.isEmpty || path == '.') && _filter(this)) {
|
||||
return this;
|
||||
// Try to find index
|
||||
_printDebug('INDEX???');
|
||||
return children.firstWhere((r) => r.path.isEmpty, orElse: () => this);
|
||||
} else if (path == '/') {
|
||||
return absoluteParent.resolve('');
|
||||
} else if (path.replaceAll(_straySlashes, '').isEmpty) {
|
||||
for (Route route in children) {
|
||||
final stub = route.path.replaceAll(this.path, '');
|
||||
|
||||
if (stub == '/' || stub.isEmpty && _filter(route)) return route;
|
||||
if ((stub == '/' || stub.isEmpty) && _filter(route)) return route;
|
||||
}
|
||||
|
||||
if (_filter(this))
|
||||
|
@ -289,6 +345,11 @@ class Route {
|
|||
return this;
|
||||
} else {
|
||||
final segments = path.split('/').where((str) => str.isNotEmpty).toList();
|
||||
_printDebug('Segments: $segments on "/${this.path}"');
|
||||
|
||||
if (segments.isEmpty) {
|
||||
return children.firstWhere((r) => r.path.isEmpty, orElse: () => this);
|
||||
}
|
||||
|
||||
if (segments[0] == '..') {
|
||||
if (parent != null)
|
||||
|
@ -303,6 +364,8 @@ class Route {
|
|||
|
||||
for (Route route in children) {
|
||||
final subPath = '${this.path}/${segments[0]}';
|
||||
_printDebug(
|
||||
'seg0: ${segments[0]}, stub: ${route._stub.pattern}, path: $path, route.path: ${route.path}, route.matcher: ${route.matcher.pattern}, this.matcher: ${matcher.pattern}');
|
||||
|
||||
if (route.match(subPath) != null ||
|
||||
route._resolver.firstMatch(subPath) != null) {
|
||||
|
@ -315,19 +378,22 @@ class Route {
|
|||
'/' +
|
||||
_fullPath.replaceAll(_straySlashes, ''));
|
||||
}
|
||||
} else if (route._stub != null && route._stub.hasMatch(segments[0])) {
|
||||
_printDebug('MAYBE STUB?');
|
||||
return route;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to match "subdirectory"
|
||||
for (Route route in children) {
|
||||
print(
|
||||
_printDebug(
|
||||
'Trying to match subdir for $path; child ${route.path} on ${this.path}');
|
||||
final match = route._parentResolver.firstMatch(path);
|
||||
|
||||
if (match != null) {
|
||||
final subPath =
|
||||
path.replaceFirst(match[0], '').replaceAll(_straySlashes, '');
|
||||
print("Subdir path: $subPath");
|
||||
_printDebug("Subdir path: $subPath");
|
||||
|
||||
for (Route child in route.children) {
|
||||
final testPath = child.path
|
||||
|
@ -341,15 +407,18 @@ class Route {
|
|||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
_printDebug('No subpath match: $subPath');
|
||||
} else
|
||||
print('Nope: $_parentResolver');
|
||||
_printDebug('Nope: $_parentResolver');
|
||||
}
|
||||
|
||||
/*
|
||||
// Try to fill params
|
||||
for (Route route in children) {
|
||||
final params = parseParameters(_fullPath);
|
||||
final _filledPath = makeUri(params);
|
||||
print(
|
||||
_printDebug(
|
||||
'Trying to match filled $_filledPath for ${route.path} on ${this.path}');
|
||||
if ((route.match(_filledPath) != null ||
|
||||
route._resolver.firstMatch(_filledPath) != null) &&
|
||||
|
@ -364,13 +433,13 @@ class Route {
|
|||
_filter(route))
|
||||
return route;
|
||||
else {
|
||||
print('Failed for ${route.matcher} when given $_filledPath');
|
||||
_printDebug('Failed for ${route.matcher} when given $_filledPath');
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// Try to match the whole route, if nothing else works
|
||||
for (Route route in children) {
|
||||
print(
|
||||
_printDebug(
|
||||
'Trying to match full $_fullPath for ${route.path} on ${this.path}');
|
||||
if ((route.match(_fullPath) != null ||
|
||||
route._resolver.firstMatch(_fullPath) != null) &&
|
||||
|
@ -385,7 +454,7 @@ class Route {
|
|||
_filter(route))
|
||||
return route;
|
||||
else {
|
||||
print('Failed for ${route.matcher} when given $_fullPath');
|
||||
_printDebug('Failed for ${route.matcher} when given $_fullPath');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,29 @@
|
|||
import 'extensible.dart';
|
||||
import 'route.dart';
|
||||
import 'routing_exception.dart';
|
||||
|
||||
final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)');
|
||||
|
||||
/// An abstraction over complex [Route] trees. Use this instead of the raw API. :)
|
||||
class Router extends Extensible {
|
||||
/// Set to `true` to print verbose debug output when interacting with this route.
|
||||
bool debug = false;
|
||||
|
||||
/// Additional filters to be run on designated requests.
|
||||
Map<String, dynamic> requestMiddleware = {};
|
||||
|
||||
/// The single [Route] that serves as the root of the hierarchy.
|
||||
final Route root;
|
||||
|
||||
/// Provide a `root` to make this Router revolve around a pre-defined route.
|
||||
/// Not recommended.
|
||||
Router([Route root]) : this.root = root ?? new Route('/', name: '<root>');
|
||||
|
||||
void _printDebug(msg) {
|
||||
if (debug)
|
||||
_printDebug(msg);
|
||||
}
|
||||
|
||||
/// Adds a route that responds to the given path
|
||||
/// for requests with the given method (case-insensitive).
|
||||
/// Provide '*' as the method to respond to all methods.
|
||||
|
@ -23,7 +35,62 @@ class Router extends Extensible {
|
|||
..addAll(middleware ?? [])
|
||||
..add(handler);
|
||||
|
||||
return root.child(path, handlers: handlers, method: method);
|
||||
if (path is RegExp) {
|
||||
return root.child(path, handlers: handlers, method: method);
|
||||
} else if (path.toString().replaceAll(_straySlashes, '').isEmpty) {
|
||||
return root.child(path.toString(), handlers: handlers, method: method);
|
||||
} else {
|
||||
var segments = path
|
||||
.toString()
|
||||
.split('/')
|
||||
.where((str) => str.isNotEmpty)
|
||||
.toList(growable: false);
|
||||
Route result;
|
||||
|
||||
if (segments.isEmpty) {
|
||||
return new Route('/', handlers: handlers, method: method);
|
||||
} else {
|
||||
_printDebug('Want ${segments[0]}');
|
||||
result = resolve(segments[0]);
|
||||
|
||||
if (result != null) {
|
||||
if (segments.length > 1) {
|
||||
_printDebug('Resolved: ${result} for "${segments[0]}"');
|
||||
segments = segments.skip(1).toList(growable: false);
|
||||
|
||||
Route existing;
|
||||
|
||||
do {
|
||||
existing = result.resolve(segments[0]);
|
||||
|
||||
if (existing != null) {
|
||||
result = existing;
|
||||
}
|
||||
} while (existing != null);
|
||||
} else throw new RoutingException("Cannot overwrite existing route '${segments[0]}'.");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
final segment = segments[i];
|
||||
|
||||
if (i == segments.length - 1) {
|
||||
if (result == null) {
|
||||
result = root.child(segment, handlers: handlers, method: method);
|
||||
} else {
|
||||
result = result.child(segment, handlers: handlers, method: method);
|
||||
}
|
||||
} else {
|
||||
if (result == null) {
|
||||
result = root.child(segment, method: "*");
|
||||
} else {
|
||||
result = result.child(segment, method: "*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a visual representation of the route hierarchy and
|
||||
|
@ -45,10 +112,8 @@ class Router extends Extensible {
|
|||
else
|
||||
buf.write("'${p.replaceAll(_straySlashes, '')}'");
|
||||
|
||||
buf.write(' => ');
|
||||
|
||||
if (route.handlers.isNotEmpty)
|
||||
buf.writeln('${route.handlers.length} handler(s)');
|
||||
buf.writeln(' => ${route.handlers.length} handler(s)');
|
||||
else
|
||||
buf.writeln();
|
||||
|
||||
|
@ -60,7 +125,7 @@ class Router extends Extensible {
|
|||
if (header != null && header.isNotEmpty) buf.writeln(header);
|
||||
|
||||
dumpRoute(root);
|
||||
(callback ?? print)(buf);
|
||||
(callback ?? print)(buf.toString());
|
||||
}
|
||||
|
||||
/// Creates a route, and allows you to add child routes to it
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
/// Represents an error in route configuration or navigation.
|
||||
abstract class RoutingException extends Exception {
|
||||
factory RoutingException(String message) => new _RoutingExceptionImpl(message);
|
||||
|
||||
/// Occurs when trying to resolve the parent of a [Route] without a parent.
|
||||
factory RoutingException.orphan() => new _RoutingExceptionImpl("Tried to resolve path '..' on a route that has no parent.");
|
||||
|
||||
/// Occurs when the user attempts to navigate to a non-existent route.
|
||||
factory RoutingException.noSuchRoute(String path) => new _RoutingExceptionImpl("Tried to navigate to non-existent route: '$path'.");
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name: angel_route
|
||||
description: A powerful, isomorphic routing library for Dart.
|
||||
version: 1.0.0-dev
|
||||
version: 1.0.0-dev+1
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_route
|
||||
dev_dependencies:
|
||||
|
|
|
@ -2,55 +2,55 @@ import 'package:angel_route/angel_route.dart';
|
|||
import 'package:test/test.dart';
|
||||
|
||||
main() {
|
||||
final fooById = new Route.build('/foo/:id([0-9]+)', handlers: ['bar']);
|
||||
final foo = fooById.parent;
|
||||
final foo = new Router().root;
|
||||
final fooById = foo.child(':id((?!bar)[0-9]+)', handlers: ['bar']);
|
||||
final bar = fooById.child('bar');
|
||||
final baz = bar.child('//////baz//////', handlers: ['hello', 'world']);
|
||||
final bazById = baz.child(':bazId');
|
||||
final bazById = baz.child(':bazId([A-Za-z]+)');
|
||||
new Router(foo).dumpTree();
|
||||
|
||||
test('matching', () {
|
||||
expect(fooById.children.length, equals(1));
|
||||
expect(fooById.handlers.length, equals(1));
|
||||
expect(fooById.handlerSequence.length, equals(1));
|
||||
expect(fooById.path, equals('foo/:id'));
|
||||
expect(fooById.match('/foo/2'), isNotNull);
|
||||
expect(fooById.match('/foo/aaa'), isNull);
|
||||
expect(fooById.path, equals(':id'));
|
||||
expect(fooById.match('/2'), isNotNull);
|
||||
expect(fooById.match('/aaa'), isNull);
|
||||
expect(fooById.match('/bar'), isNull);
|
||||
expect(fooById.match('/foolish'), isNull);
|
||||
expect(fooById.match('lish'), isNull);
|
||||
expect(fooById.parent, equals(foo));
|
||||
expect(fooById.absoluteParent, equals(foo));
|
||||
|
||||
expect(bar.path, equals('foo/:id/bar'));
|
||||
expect(bar.path, equals(':id/bar'));
|
||||
expect(bar.children.length, equals(1));
|
||||
expect(bar.handlers, isEmpty);
|
||||
expect(bar.handlerSequence.length, equals(1));
|
||||
expect(bar.match('/foo/2/bar'), isNotNull);
|
||||
expect(bar.match('/2/bar'), isNotNull);
|
||||
expect(bar.match('/bar'), isNull);
|
||||
expect(bar.match('/foo/a/bar'), isNull);
|
||||
expect(bar.match('/a/bar'), isNull);
|
||||
expect(bar.parent, equals(fooById));
|
||||
expect(baz.absoluteParent, equals(foo));
|
||||
|
||||
expect(baz.children.length, equals(1));
|
||||
expect(baz.handlers.length, equals(2));
|
||||
expect(baz.handlerSequence.length, equals(3));
|
||||
expect(baz.path, equals('foo/:id/bar/baz'));
|
||||
expect(baz.match('/foo/2A/bar/baz'), isNull);
|
||||
expect(baz.match('/foo/2/bar/baz'), isNotNull);
|
||||
expect(baz.match('/foo/1337/bar/baz'), isNotNull);
|
||||
expect(baz.match('/foo/bat/baz'), isNull);
|
||||
expect(baz.match('/foo/bar/baz/1'), isNull);
|
||||
expect(baz.path, equals(':id/bar/baz'));
|
||||
expect(baz.match('/2A/bar/baz'), isNull);
|
||||
expect(baz.match('/2/bar/baz'), isNotNull);
|
||||
expect(baz.match('/1337/bar/baz'), isNotNull);
|
||||
expect(baz.match('/bat/baz'), isNull);
|
||||
expect(baz.match('/bar/baz/1'), isNull);
|
||||
expect(baz.parent, equals(bar));
|
||||
expect(baz.absoluteParent, equals(foo));
|
||||
});
|
||||
|
||||
test('hierarchy', () {
|
||||
expect(fooById.resolve('/foo/2'), equals(fooById));
|
||||
expect(fooById.resolve('/2'), equals(fooById));
|
||||
|
||||
expect(fooById.resolve('/foo/2/bar'), equals(bar));
|
||||
expect(fooById.resolve('/foo/bar'), isNull);
|
||||
expect(fooById.resolve('/foo/a/bar'), isNull);
|
||||
expect(fooById.resolve('foo/1337/bar/baz'), equals(baz));
|
||||
expect(fooById.resolve('/2/bar'), equals(bar));
|
||||
expect(fooById.resolve('/bar'), isNull);
|
||||
expect(fooById.resolve('/a/bar'), isNull);
|
||||
expect(fooById.resolve('1337/bar/baz'), equals(baz));
|
||||
|
||||
expect(bar.resolve('..'), equals(fooById));
|
||||
|
||||
|
@ -67,11 +67,10 @@ main() {
|
|||
expect(baz.resolve('/2/bar'), equals(bar));
|
||||
expect(baz.resolve('/1337/bar/baz'), equals(baz));
|
||||
|
||||
expect(bar.resolve('/2/baz/e'), equals(bazById));
|
||||
expect(bar.resolve('/2/bar/baz/e'), equals(bazById));
|
||||
expect(bar.resolve('baz/e'), equals(bazById));
|
||||
expect(bar.resolve('baz/e'), isNull);
|
||||
expect(fooById.resolve('/foo/2/baz/e'), equals(bazById));
|
||||
expect(fooById.resolve('/foo/2/baz/2'), isNull);
|
||||
expect(fooById.resolve('/foo/2a/baz/e'), isNull);
|
||||
expect(fooById.resolve('/2/bar/baz/e'), equals(bazById));
|
||||
expect(fooById.resolve('/2/bar/baz/2'), isNull);
|
||||
expect(fooById.resolve('/2a/bar/baz/e'), isNull);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ final ABC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|||
main() {
|
||||
final router = new Router();
|
||||
final indexRoute = router.get('/', () => ':)');
|
||||
final userById = router.delete('/user/:id/detail', (id) => num.parse(id));
|
||||
final fizz = router.post('/user/fizz', null);
|
||||
final deleteUserById = router.delete('/user/:id/detail', (id) => num.parse(id));
|
||||
|
||||
Route lower;
|
||||
final letters = router.group('/letter///', (router) {
|
||||
|
@ -19,9 +20,7 @@ main() {
|
|||
.child('/upper', handlers: [(String id) => id.toUpperCase()[0]]);
|
||||
});
|
||||
|
||||
final fizz = router.post('/user/fizz', null);
|
||||
|
||||
router.dumpTree();
|
||||
router.dumpTree(header: "ROUTER TESTS");
|
||||
|
||||
test('extensible', () {
|
||||
router['two'] = 2;
|
||||
|
@ -34,14 +33,14 @@ main() {
|
|||
expect(lower.absoluteParent, equals(router.root));
|
||||
expect(lower.parent.path, equals('letter/:id'));
|
||||
expect(lower.resolve('../upper').path, equals('letter/:id/upper'));
|
||||
expect(lower.resolve('/user/34/detail'), equals(userById));
|
||||
expect(userById.resolve('../fizz'), equals(fizz));
|
||||
expect(lower.resolve('/user/34/detail'), equals(deleteUserById));
|
||||
expect(deleteUserById.resolve('../../fizz'), equals(fizz));
|
||||
});
|
||||
|
||||
test('resolve', () {
|
||||
expect(router.resolve('/'), equals(indexRoute));
|
||||
expect(router.resolve('user/1337/detail'), equals(userById));
|
||||
expect(router.resolve('/user/1337/detail'), equals(userById));
|
||||
expect(router.resolve('user/1337/detail'), equals(deleteUserById));
|
||||
expect(router.resolve('/user/1337/detail'), equals(deleteUserById));
|
||||
expect(router.resolve('letter/a/lower'), equals(lower));
|
||||
expect(router.resolve('letter/2/lower'), isNull);
|
||||
});
|
||||
|
|
5
uri.dart
5
uri.dart
|
@ -1,5 +0,0 @@
|
|||
main() {
|
||||
final uri = Uri.parse('/foo');
|
||||
print(uri);
|
||||
print(uri.resolve('/bar'));
|
||||
}
|
|
@ -23,6 +23,7 @@ basic(BrowserRouter router) {
|
|||
router.get('a', 'a handler');
|
||||
|
||||
router.group('b', (router) {
|
||||
print(router.root);
|
||||
router.get('a', 'b/a handler');
|
||||
router.get('b', 'b/b handler', middleware: ['b/b middleware']);
|
||||
}, middleware: ['b middleware']);
|
||||
|
|
Loading…
Reference in a new issue