diff --git a/.idea/runConfigurations/All_Tests.xml b/.idea/runConfigurations/All_Tests.xml deleted file mode 100644 index a824b209..00000000 --- a/.idea/runConfigurations/All_Tests.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Method_Tests.xml b/.idea/runConfigurations/Method_Tests.xml new file mode 100644 index 00000000..436da978 --- /dev/null +++ b/.idea/runConfigurations/Method_Tests.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Use.xml b/.idea/runConfigurations/Use.xml index e8a7c7d0..4ba81c55 100644 --- a/.idea/runConfigurations/Use.xml +++ b/.idea/runConfigurations/Use.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..de2210c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: dart \ No newline at end of file diff --git a/lib/angel_route.dart b/lib/angel_route.dart index 228185e3..bcbee5dd 100644 --- a/lib/angel_route.dart +++ b/lib/angel_route.dart @@ -1,6 +1,5 @@ /// A powerful, isomorphic routing library for Dart. library angel_route; -export 'src/route.dart'; export 'src/router.dart'; export 'src/routing_exception.dart'; \ No newline at end of file diff --git a/lib/browser.dart b/lib/browser.dart index 9bf55fa9..39220f21 100644 --- a/lib/browser.dart +++ b/lib/browser.dart @@ -52,6 +52,11 @@ class _BrowserRouterImpl extends Router implements BrowserRouter { throw new RoutingException.noSuchRoute(path); } + @override + void listen() { + normalize(); + } + void prepareAnchors() { final anchors = window.document.querySelectorAll('a:not([dynamic])'); @@ -85,9 +90,10 @@ class _HashRouter extends _BrowserRouterImpl { @override void listen() { + super.listen(); window.onHashChange.listen((_) { final path = window.location.hash.replaceAll(_hash, ''); - final resolved = resolve(path); + final resolved = resolveOnRoot(path); if (resolved == null || (path.isEmpty && resolved == root)) { _onRoute.add(_current = null); @@ -115,6 +121,7 @@ class _PushStateRouter extends _BrowserRouterImpl { @override void listen() { + super.listen(); window.onPopState.listen((e) { if (e.state is Map && e.state.containsKey('path')) { final resolved = resolve(e.state['path']); diff --git a/lib/src/route.dart b/lib/src/route.dart index 4103f0b9..ef04ab70 100644 --- a/lib/src/route.dart +++ b/lib/src/route.dart @@ -1,11 +1,4 @@ -import 'extensible.dart'; -import 'routing_exception.dart'; - -final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?'); -final RegExp _rgxEnd = new RegExp(r'\$+$'); -final RegExp _rgxStart = new RegExp(r'^\^+'); -final RegExp _rgxStraySlashes = new RegExp(r'(^((\\/)|(/))+)|(((\\/)|(/))+$)'); -final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); +part of angel_route.src.router; String _matcherify(String path, {bool expand: true}) { var p = path.replaceAll(new RegExp(r'/\*$'), "*").replaceAll('/', r'\/'); @@ -48,6 +41,7 @@ String _pathify(String path) { class Route { final List _children = []; final List _handlers = []; + RegExp _head; RegExp _matcher; String _method; String _name; @@ -94,6 +88,14 @@ class Route { return result; } + /// Returns the [Route] instances that will respond to requests + /// to the index of this instance's path. + /// + /// May return `this`. + Iterable get allIndices { + return children.where((r) => r.path.replaceAll(path, '').isEmpty); + } + /// Backtracks up the hierarchy, and builds /// a sequential list of all handlers from both /// this route, and every found parent route. @@ -128,6 +130,8 @@ class Route { if (debug) print(msg); } + Route._base(); + Route(Pattern path, {Iterable children: const [], this.debug: false, @@ -190,8 +194,11 @@ class Route { name: name); } + var head = ''; + for (int i = 0; i < segments.length; i++) { final segment = segments[i]; + head = (head + '/$segment').replaceAll(_straySlashes, ''); if (i == segments.length - 1) { if (result == null) { @@ -206,6 +213,8 @@ class Route { result = result.child(segment, debug: debug, method: "*"); } } + + result._head = new RegExp(_matcherify(head).replaceAll(_rgxEnd, '')); } } @@ -244,33 +253,16 @@ class Route { parent._children.add(route .._matcher = new RegExp('$pattern1$separator$pattern2') + .._head = new RegExp(_matcherify('$path1/$path2'.replaceAll(_straySlashes, '')).replaceAll(_rgxEnd, '')) .._parent = parent .._stub = child.matcher); parent._printDebug( - "Joined '/$path1' and '/$path2', created stub: ${route._stub.pattern}"); + "Joined '/$path1' and '/$path2', created head: ${route._head.pattern} and stub: ${route._stub.pattern}"); return route..debug = parent.debug || child.debug || debug; } - Route _inherit(Route route) { - /* - final List _children = []; - final List _handlers = []; - RegExp _matcher; - String _method; - String _name; - Route _parent; - RegExp _parentResolver; - String _path; - String _pathified; - RegExp _resolver; - RegExp _stub; - - */ - return route.._parent = this; - } - /// Calls [addChild] on all given routes. List addAll(Iterable routes, {bool join: true}) { return routes.map((route) => addChild(route, join: join)).toList(); diff --git a/lib/src/router.dart b/lib/src/router.dart index 2dec5367..367b03bf 100644 --- a/lib/src/router.dart +++ b/lib/src/router.dart @@ -1,7 +1,15 @@ +library angel_route.src.router; + import 'extensible.dart'; -import 'route.dart'; import 'routing_exception.dart'; +part 'route.dart'; + +final RegExp _param = new RegExp(r':([A-Za-z0-9_]+)(\((.+)\))?'); +final RegExp _rgxEnd = new RegExp(r'\$+$'); +final RegExp _rgxStart = new RegExp(r'^\^+'); +final RegExp _rgxStraySlashes = new RegExp(r'(^((\\/)|(/))+)|(((\\/)|(/))+$)'); +final RegExp _slashDollar = new RegExp(r'/+\$'); final RegExp _straySlashes = new RegExp(r'(^/+)|(/+$)'); /// An abstraction over complex [Route] trees. Use this instead of the raw API. :) @@ -20,7 +28,8 @@ class Router extends Extensible { /// Provide a `root` to make this Router revolve around a pre-defined route. /// Not recommended. Router({this.debug: false, Route root}) { - _root = (_root = root ?? new _RootRoute())..debug = debug; + _root = (_root = root ?? new _RootRoute()) + ..debug = debug; } void _printDebug(msg) { @@ -40,7 +49,10 @@ class Router extends Extensible { if (path is RegExp) { return root.child(path, debug: debug, handlers: handlers, method: method); - } else if (path.toString().replaceAll(_straySlashes, '').isEmpty) { + } else if (path + .toString() + .replaceAll(_straySlashes, '') + .isEmpty) { return root.child(path.toString(), debug: debug, handlers: handlers, method: method); } else { @@ -55,8 +67,8 @@ class Router extends Extensible { return new Route('/', debug: debug, handlers: handlers, method: method) ..debug = debug; } else { - result = resolve(segments[0], - (route) => route.method == method || route.method == '*'); + result = resolveOnRoot(segments[0], + filter: (route) => route.method == method || route.method == '*'); if (result != null) { if (segments.length > 1) { @@ -68,7 +80,7 @@ class Router extends Extensible { do { existing = result.resolve(segments[0], filter: (route) => - route.method == method || route.method == '*'); + route.method == method || route.method == '*'); if (existing != null) { result = existing; @@ -110,15 +122,17 @@ class Router extends Extensible { final buf = new StringBuffer(); void dumpRoute(Route route, {Pattern replace: null}) { - for (var i = 0; i < tabs; i++) buf.write(tab); + for (var i = 0; i < tabs; i++) + buf.write(tab); if (route == root) - buf.write('(root)'); + buf.writeln('(root)'); else { buf.write('- ${route.method} '); - final p = - replace != null ? route.path.replaceAll(replace, '') : route.path; + var p = + replace != null ? route.path.replaceAll(replace, '') : route.path; + p = p.replaceAll(_straySlashes, ''); if (p.isEmpty) buf.write("'/'"); @@ -154,7 +168,7 @@ class Router extends Extensible { String name: null, String namespace: null}) { final route = - root.child(path, handlers: middleware, method: method, name: name); + root.child(path, handlers: middleware, method: method, name: name); final router = new Router(root: route); callback(router); @@ -164,7 +178,7 @@ class Router extends Extensible { Map copiedMiddleware = new Map.from(router.requestMiddleware); for (String middlewareName in copiedMiddleware.keys) { requestMiddleware["$middlewarePrefix$middlewareName"] = - copiedMiddleware[middlewareName]; + copiedMiddleware[middlewareName]; } return route; @@ -179,9 +193,130 @@ class Router extends Extensible { /// /// You can pass an additional filter to determine which /// routes count as matches. - Route resolve(String path, [bool filter(Route route)]) => + Route resolveOnRoot(String path, {bool filter(Route route)}) => root.resolve(path, filter: filter); + /// Finds the first [Route] that matches the given path, + /// with the given method. + Route resolve(String path, {String method: 'GET'}) { + final String _path = path.replaceAll(_straySlashes, ''); + final segments = _path.split('/').where((str) => str.isNotEmpty); + _printDebug('Segments: $segments'); + return _resolve(root, _path, method, segments.first, segments.skip(1)); + } + + _validHead(RegExp rgx) { + return !rgx.hasMatch(''); + } + + _resolve(Route ref, String fullPath, String method, String head, + Iterable tail) { + _printDebug( + '$method on $ref: path: $fullPath, head: $head, tail: ${tail.join( + '/')}'); + + // Does the index route match? + if (ref.matcher.hasMatch(fullPath)) { + final index = ref.indexRoute; + + for (Route child in ref.allIndices) { + _printDebug('Possible index: $child'); + + if (child == child.indexRoute && ['*', method].contains(child.method)) { + _printDebug('Possible index was exact match: $child'); + return child; + } + + final resolved = _resolve(child, fullPath, method, head, tail); + + if (resolved != null) { + _printDebug('Resolved from possible index: $resolved'); + return resolved; + } else + _printDebug('Possible index returned null: $child'); + } + + if (['*', method].contains(index.method)) { + return index; + } + } else { + // Try to match children by full path + for (Route child in ref.children) { + if (child.matcher.hasMatch(fullPath)) { + final resolved = _resolve(child, fullPath, method, head, tail); + + if (resolved != null) { + return resolved; + } + } + } + + // Now, let's check if any route's head matches the + // given head. If so, we try to resolve with that + // route, using a head corresponding to the one we + // matched. + for (Route child in ref.children) { + if (child._head != null && + child._head.hasMatch(fullPath) && + _validHead(child._head)) { + final newHead = child._head + .firstMatch(fullPath) + .group(0) + .replaceAll(_straySlashes, ''); + final newTail = fullPath + .replaceAll(child._head, '') + .replaceAll(_straySlashes, '') + .split('/') + .where((str) => str.isNotEmpty); + final resolved = _resolve(child, fullPath, method, newHead, newTail); + + if (resolved != null) { + _printDebug( + 'Head match: $resolved from head: ${child._head.pattern}'); + return resolved; + } + } else if (child._head != null) { + _printDebug( + 'Head ${child._head + .pattern} on $child failed to match $fullPath'); + } + } + } + + if (tail.isEmpty) + return null; + else { + return _resolve( + ref, fullPath, method, head + '/' + tail.first, tail.skip(1)); + } + } + + /// Returns a new Router in which the route tree has been + /// flattened into a linear list. + Router flatten() { + final router = new Router(); + + _flatten(Route route) { + // if (route.children.isNotEmpty && route.method == '*') return; + + final r = new Route._base(); + + r + .._handlers.addAll(route.handlerSequence) + .._head = route._head + .._matcher = route.matcher + .._method = route.method + .._parent = router.root + .._path = route.path; + + router.root._children.add(r); + route.children.forEach(_flatten); + } + + root._children.forEach(_flatten); + return router..debug = debug; + } + /// Incorporates another [Router]'s routes into this one's. /// /// If `hooked` is set to `true` and a [Service] is provided, @@ -200,10 +335,67 @@ class Router extends Extensible { Map copiedMiddleware = new Map.from(router.requestMiddleware); for (String middlewareName in copiedMiddleware.keys) { requestMiddleware["$middlewarePrefix$middlewareName"] = - copiedMiddleware[middlewareName]; + copiedMiddleware[middlewareName]; } - root.child(path, debug: debug).addChild(router.root); + // final route = root.addChild(router.root, join: false); + final route = root.child(path, debug: debug).addChild(router.root); + route.debug = debug; + + if (path is! RegExp) { + final _path = path.toString().replaceAll(_straySlashes, ''); + + _migrateRoute(Route r) { + r._path = '$_path/${r.path}'.replaceAll(_straySlashes, ''); + var stripped = r.matcher.pattern + .replaceAll(_rgxStart, '') + .replaceAll(_rgxEnd, '') + .replaceAll(_rgxStraySlashes, '') + .replaceAll(_straySlashes, ''); + stripped = '$_path/$stripped'.replaceAll(_straySlashes, ''); + r._matcher = new RegExp('^$stripped\$'); + + if (r._head != null) { + final head = r._head.pattern + .replaceAll(_rgxStart, '') + .replaceAll(_rgxEnd, '') + .replaceAll(_rgxStraySlashes, '') + .replaceAll('\\/', '/') + .replaceAll(_straySlashes, ''); + r._head = new RegExp(_matcherify('$_path/$head').replaceAll(_rgxEnd, '')); + _printDebug('Head of migrated route: ${r._head.pattern}'); + } + + r.children.forEach(_migrateRoute); + } + + route.children.forEach(_migrateRoute); + } + } + + /// Removes empty routes that could complicate route resolution. + void normalize() { + _printDebug('Normalizing route tree...'); + + _normalize(Route route) { + route.children.forEach(_normalize); + + if (route.path + .replaceAll(_straySlashes, '') + .isEmpty && + route.children.isNotEmpty) { + _printDebug('Erasing this route: $route'); + route.parent._handlers.addAll(route.handlers); + + for (Route child in route.children) { + route.parent._children.add(child.._parent = route.parent); + } + + route.parent._children.remove(route); + } + } + + root.children.forEach(_normalize); } /// Adds a route that responds to any request matching the given path. @@ -248,7 +440,7 @@ class Router extends Extensible { } class _RootRoute extends Route { - _RootRoute() : super("/", name: ""); + _RootRoute() : super("/", method: '*', name: ""); @override String toString() => "ROOT"; diff --git a/pubspec.yaml b/pubspec.yaml index affb431c..835e0648 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_route description: A powerful, isomorphic routing library for Dart. -version: 1.0.0-dev+5 +version: 1.0.0-dev+6 author: Tobe O homepage: https://github.com/angel-dart/angel_route dev_dependencies: diff --git a/test/all_tests.dart b/test/all_tests.dart deleted file mode 100644 index b8706fc8..00000000 --- a/test/all_tests.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:test/test.dart'; -import 'route/all_tests.dart' as route; -import 'router/all_tests.dart' as router; -import 'server/all_tests.dart' as server; - -main() { - group('route', route.main); - group('router', router.main); - group('server', server.main); -} \ No newline at end of file diff --git a/test/index.html b/test/index.html deleted file mode 100644 index 24f2d4bc..00000000 --- a/test/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Tests - - - - - - \ No newline at end of file diff --git a/test/method/all_tests.dart b/test/method/all_tests.dart new file mode 100644 index 00000000..f53df068 --- /dev/null +++ b/test/method/all_tests.dart @@ -0,0 +1,123 @@ +import 'package:angel_route/angel_route.dart'; +import 'package:test/test.dart'; + +main() { + var router = new Router(debug: true); + final getFoo = router.get('/foo', 'GET'); + final postFoo = router.post('/foo', 'POST'); + + Route getFooBar, postFooBar, patchFooBarId; + + router.group('/foo/bar', (router) { + getFooBar = router.get('/', 'GET'); + postFooBar = router.post('/', 'POST'); + patchFooBarId = router.patch('/:id([0-9]+)', 'PATCH'); + }); + + final Router books = new Router(); + + final getBooks = books.get('/', 'GET'); + final postBooks = books.post('/', 'POST'); + final getBooksFoo = books.get('/foo', 'GET'); + final postBooksFoo = books.post('/foo', 'POST'); + + Route getBooksChapters, + postBooksChapters, + getBooksChaptersReviews, + postBooksChaptersReviews; + + books.group('/:id/chapters', (router) { + getBooksChapters = router.get('/', 'GET'); + postBooksChapters = router.post('/', 'POST'); + + router.group('/:id([A-Za-z]+)/reviews', (router) { + getBooksChaptersReviews = router.get('/', 'GET'); + postBooksChaptersReviews = router.post('/', 'POST'); + }); + }); + + router.mount('/books', books); + router.normalize(); + + group('top level', () { + test('get', () => expect(router.resolve('/foo'), equals(getFoo))); + + test('post', () { + router.dumpTree(); + expect(router.resolve('/foo', method: 'POST'), equals(postFoo)); + }); + }); + + group('group', () { + test('get', () { + expect(router.resolve('/foo/bar'), equals(getFooBar)); + }); + + test('post', () { + expect(router.resolve('/foo/bar', method: 'POST'), equals(postFooBar)); + }); + + test('patch+id', () { + router.dumpTree(); + expect( + router.resolve('/foo/bar/2', method: 'PATCH'), equals(patchFooBarId)); + }); + + test('404', () { + expect(router.resolve('/foo/bar/A', method: 'PATCH'), isNull); + }); + }); + + group('mount', () { + group('no params', () { + test('get', () { + expect(router.resolve('/books'), equals(getBooks)); + expect(router.resolve('/books/foo'), equals(getBooksFoo)); + }); + + test('post', () { + expect(router.resolve('/books', method: 'POST'), equals(postBooks)); + expect( + router.resolve('/books/foo', method: 'POST'), equals(postBooksFoo)); + }); + }); + + group('with params', () { + test('1 param', () { + expect(router.resolve('/books/abc/chapters'), equals(getBooksChapters)); + expect(router.resolve('/books/abc/chapters', method: 'POST'), + equals(postBooksChapters)); + }); + + group('2 params', () { + setUp(router.dumpTree); + + test('get', () { + expect(router.resolve('/books/abc/chapters/ABC/reviews'), + equals(getBooksChaptersReviews)); + }); + + test('post', () { + expect( + router.resolve('/books/abc/chapters/ABC/reviews', method: 'POST'), + equals(postBooksChaptersReviews)); + }); + + test('404', () { + expect(router.resolve('/books/abc/chapters/1'), isNull); + expect(router.resolve('/books/abc/chapters/12'), isNull); + expect(router.resolve('/books/abc/chapters/13.!'), isNull); + }); + }); + }); + }); + + test('flatten', () { + router.dumpTree(header: 'BEFORE FLATTENING:'); + final flat = router.flatten(); + + for (Route route in flat.root.children) { + print('${route.method} ${route.path} => ${route.matcher.pattern}'); + } + }); +} diff --git a/test/packages b/test/packages deleted file mode 120000 index a16c4050..00000000 --- a/test/packages +++ /dev/null @@ -1 +0,0 @@ -../packages \ No newline at end of file diff --git a/test/route/packages b/test/route/packages deleted file mode 120000 index 4b727bf6..00000000 --- a/test/route/packages +++ /dev/null @@ -1 +0,0 @@ -../../packages \ No newline at end of file diff --git a/test/router/all_tests.dart b/test/router/all_tests.dart index 5c83e24a..70544dc4 100644 --- a/test/router/all_tests.dart +++ b/test/router/all_tests.dart @@ -1,7 +1,6 @@ import 'package:angel_route/angel_route.dart'; import 'package:test/test.dart'; import 'fallback.dart' as fallback; -import 'use.dart' as use; final ABC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; @@ -13,7 +12,8 @@ main() { router.delete('/user/:id/detail', (id) => num.parse(id)); Route lower; - final letters = router.group('/letter///', (router) { + + router.group('/letter///', (router) { lower = router .get('/:id([A-Za-z])', (id) => ABC.indexOf(id[0])) .child('////lower', handlers: [(String id) => id.toLowerCase()[0]]); @@ -30,7 +30,6 @@ main() { }); group('fallback', fallback.main); - test('group & use', use.main); test('hierarchy', () { expect(lower.absoluteParent, equals(router.root)); @@ -41,10 +40,10 @@ main() { }); test('resolve', () { - expect(router.resolve('/'), equals(indexRoute)); - 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); + expect(router.resolveOnRoot('/'), equals(indexRoute)); + expect(router.resolveOnRoot('user/1337/detail'), equals(deleteUserById)); + expect(router.resolveOnRoot('/user/1337/detail'), equals(deleteUserById)); + expect(router.resolveOnRoot('letter/a/lower'), equals(lower)); + expect(router.resolveOnRoot('letter/2/lower'), isNull); }); } diff --git a/test/router/fallback.dart b/test/router/fallback.dart index d6cd5472..eee93c6c 100644 --- a/test/router/fallback.dart +++ b/test/router/fallback.dart @@ -13,8 +13,8 @@ main() { final fallback = router.get('*', () => 'fallback'); test('resolve', () { - expect(router.resolve('/foo'), equals(fallback)); - expect(router.resolve('/user/:id'), equals(userById)); - expect(router.resolve('/user/:id', checkPost), isNull); + expect(router.resolveOnRoot('/foo'), equals(fallback)); + expect(router.resolveOnRoot('/user/:id'), equals(userById)); + expect(router.resolveOnRoot('/user/:id', filter: checkPost), isNull); }); } diff --git a/test/router/packages b/test/router/packages deleted file mode 120000 index 4b727bf6..00000000 --- a/test/router/packages +++ /dev/null @@ -1 +0,0 @@ -../../packages \ No newline at end of file diff --git a/test/server/all_tests.dart b/test/server/all_tests.dart index 8469fa8d..34e16dd6 100644 --- a/test/server/all_tests.dart +++ b/test/server/all_tests.dart @@ -22,7 +22,7 @@ main() { url = 'http://${server.address.address}:${server.port}'; server.listen((request) async { - final resolved = router.resolve(request.uri.toString(), (route) { + final resolved = router.resolveOnRoot(request.uri.toString(), filter: (route) { print( '$route matches ${request.method} ${request.uri}? ${route.method == request.method || route.method == '*'}'); return route.method == request.method || route.method == '*'; diff --git a/test/server/packages b/test/server/packages deleted file mode 120000 index 4b727bf6..00000000 --- a/test/server/packages +++ /dev/null @@ -1 +0,0 @@ -../../packages \ No newline at end of file diff --git a/test/router/use.dart b/test/use.dart similarity index 63% rename from test/router/use.dart rename to test/use.dart index d3dea3fe..383f553b 100644 --- a/test/router/use.dart +++ b/test/use.dart @@ -22,14 +22,14 @@ main() { group('no params', () { test('resolve', () { - expect(child.resolve('a'), equals(a)); - expect(child.resolve('b'), equals(b)); - expect(child.resolve('b/c'), equals(c)); + expect(child.resolveOnRoot('a'), equals(a)); + expect(child.resolveOnRoot('b'), equals(b)); + expect(child.resolveOnRoot('b/c'), equals(c)); - expect(parent.resolve('child/a'), equals(a)); - expect(parent.resolve('a'), isNull); - expect(parent.resolve('child/b'), equals(b)); - expect(parent.resolve('child/b/c'), equals(c)); + expect(parent.resolveOnRoot('child/a'), equals(a)); + expect(parent.resolveOnRoot('a'), isNull); + expect(parent.resolveOnRoot('child/b'), equals(b)); + expect(parent.resolveOnRoot('child/b/c'), equals(c)); }); }); } diff --git a/web/hash/packages b/web/hash/packages deleted file mode 120000 index 4b727bf6..00000000 --- a/web/hash/packages +++ /dev/null @@ -1 +0,0 @@ -../../packages \ No newline at end of file diff --git a/web/packages b/web/packages deleted file mode 120000 index a16c4050..00000000 --- a/web/packages +++ /dev/null @@ -1 +0,0 @@ -../packages \ No newline at end of file diff --git a/web/push_state/packages b/web/push_state/packages deleted file mode 120000 index 4b727bf6..00000000 --- a/web/push_state/packages +++ /dev/null @@ -1 +0,0 @@ -../../packages \ No newline at end of file diff --git a/web/shared/packages b/web/shared/packages deleted file mode 120000 index 4b727bf6..00000000 --- a/web/shared/packages +++ /dev/null @@ -1 +0,0 @@ -../../packages \ No newline at end of file