Revert "Revert "feat(router): add angular 1.x router""
This reverts commit 298f1fb6a6
.
This commit is contained in:
parent
964884e761
commit
06487237e5
|
@ -36,6 +36,7 @@ env:
|
||||||
- MODE=saucelabs DART_CHANNEL=dev
|
- MODE=saucelabs DART_CHANNEL=dev
|
||||||
- MODE=dart_experimental DART_CHANNEL=dev
|
- MODE=dart_experimental DART_CHANNEL=dev
|
||||||
- MODE=js DART_CHANNEL=dev
|
- MODE=js DART_CHANNEL=dev
|
||||||
|
- MODE=router DART_CHANNEL=dev
|
||||||
- MODE=lint DART_CHANNEL=dev
|
- MODE=lint DART_CHANNEL=dev
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
|
35
gulpfile.js
35
gulpfile.js
|
@ -37,6 +37,7 @@ var util = require('./tools/build/util');
|
||||||
var bundler = require('./tools/build/bundle');
|
var bundler = require('./tools/build/bundle');
|
||||||
var replace = require('gulp-replace');
|
var replace = require('gulp-replace');
|
||||||
var insert = require('gulp-insert');
|
var insert = require('gulp-insert');
|
||||||
|
var buildRouter = require('./modules/angular1_router/build');
|
||||||
var uglify = require('gulp-uglify');
|
var uglify = require('gulp-uglify');
|
||||||
var shouldLog = require('./tools/build/logging');
|
var shouldLog = require('./tools/build/logging');
|
||||||
var tslint = require('gulp-tslint');
|
var tslint = require('gulp-tslint');
|
||||||
|
@ -604,6 +605,34 @@ gulp.task('!test.unit.js/karma-run', function(done) {
|
||||||
runKarma('karma-js.conf.js', done);
|
runKarma('karma-js.conf.js', done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('test.unit.router', function (done) {
|
||||||
|
runSequence(
|
||||||
|
'!test.unit.router/karma-server',
|
||||||
|
function() {
|
||||||
|
watch('modules/**', [
|
||||||
|
'buildRouter.dev',
|
||||||
|
'!test.unit.router/karma-run'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('!test.unit.router/karma-server', function() {
|
||||||
|
karma.server.start({configFile: __dirname + '/modules/angular1_router/karma-router.conf.js'});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('!test.unit.router/karma-run', function(done) {
|
||||||
|
karma.runner.run({configFile: __dirname + '/modules/angular1_router/karma-router.conf.js'}, function(exitCode) {
|
||||||
|
// ignore exitCode, we don't want to fail the build in the interactive (non-ci) mode
|
||||||
|
// karma will print all test failures
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('buildRouter.dev', function () {
|
||||||
|
buildRouter();
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('test.unit.dart', function (done) {
|
gulp.task('test.unit.dart', function (done) {
|
||||||
runSequence(
|
runSequence(
|
||||||
|
@ -641,6 +670,12 @@ gulp.task('!test.unit.dart/karma-server', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
gulp.task('test.unit.router/ci', function (done) {
|
||||||
|
var browserConf = getBrowsersFromCLI();
|
||||||
|
karma.server.start({configFile: __dirname + '/modules/angular1_router/karma-router.conf.js',
|
||||||
|
singleRun: true, reporters: ['dots'], browsers: browserConf.browsersToRun}, done);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('test.unit.js/ci', function (done) {
|
gulp.task('test.unit.js/ci', function (done) {
|
||||||
var browserConf = getBrowsersFromCLI();
|
var browserConf = getBrowsersFromCLI();
|
||||||
karma.server.start({configFile: __dirname + '/karma-js.conf.js',
|
karma.server.start({configFile: __dirname + '/karma-js.conf.js',
|
||||||
|
|
|
@ -27,6 +27,7 @@ module.exports = function(config) {
|
||||||
|
|
||||||
exclude: [
|
exclude: [
|
||||||
'dist/dart/**/packages/**',
|
'dist/dart/**/packages/**',
|
||||||
|
'modules/angular1_router/**'
|
||||||
],
|
],
|
||||||
|
|
||||||
karmaDartImports: {
|
karmaDartImports: {
|
||||||
|
|
|
@ -31,6 +31,7 @@ module.exports = function(config) {
|
||||||
|
|
||||||
exclude: [
|
exclude: [
|
||||||
'dist/js/dev/es5/**/e2e_test/**',
|
'dist/js/dev/es5/**/e2e_test/**',
|
||||||
|
'dist/angular1_router.js'
|
||||||
],
|
],
|
||||||
|
|
||||||
customLaunchers: sauceConf.customLaunchers,
|
customLaunchers: sauceConf.customLaunchers,
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
var ts = require('typescript');
|
||||||
|
|
||||||
|
var files = [
|
||||||
|
'lifecycle_annotations_impl.ts',
|
||||||
|
'url_parser.ts',
|
||||||
|
'path_recognizer.ts',
|
||||||
|
'route_config_impl.ts',
|
||||||
|
'async_route_handler.ts',
|
||||||
|
'sync_route_handler.ts',
|
||||||
|
'route_recognizer.ts',
|
||||||
|
'instruction.ts',
|
||||||
|
'route_config_nomalizer.ts',
|
||||||
|
'route_lifecycle_reflector.ts',
|
||||||
|
'route_registry.ts',
|
||||||
|
'router.ts'
|
||||||
|
];
|
||||||
|
|
||||||
|
var PRELUDE = '(function(){\n';
|
||||||
|
var POSTLUDE = '\n}());\n';
|
||||||
|
var FACADES = fs.readFileSync(__dirname + '/lib/facades.es5', 'utf8');
|
||||||
|
var TRACEUR_RUNTIME = fs.readFileSync(__dirname + '/../../node_modules/traceur/bin/traceur-runtime.js', 'utf8');
|
||||||
|
var DIRECTIVES = fs.readFileSync(__dirname + '/src/ng_outlet.js', 'utf8');
|
||||||
|
function main() {
|
||||||
|
var dir = __dirname + '/../angular2/src/router/';
|
||||||
|
|
||||||
|
var out = '';
|
||||||
|
|
||||||
|
var sharedCode = '';
|
||||||
|
files.forEach(function (file) {
|
||||||
|
var moduleName = 'router/' + file.replace(/\.ts$/, '');
|
||||||
|
|
||||||
|
sharedCode += transform(moduleName, fs.readFileSync(dir + file, 'utf8'));
|
||||||
|
});
|
||||||
|
|
||||||
|
out += "angular.module('ngComponentRouter')";
|
||||||
|
out += angularFactory('$router', ['$q', '$location', '$$controllerIntrospector',
|
||||||
|
'$browser', '$rootScope', '$injector'], [
|
||||||
|
FACADES,
|
||||||
|
"var exports = {Injectable: function () {}};",
|
||||||
|
"var require = function () {return exports;};",
|
||||||
|
sharedCode,
|
||||||
|
"var RouteConfig = exports.RouteConfig;",
|
||||||
|
"angular.annotations = {RouteConfig: RouteConfig, CanActivate: exports.CanActivate};",
|
||||||
|
"angular.stringifyInstruction = exports.stringifyInstruction;",
|
||||||
|
"var RouteRegistry = exports.RouteRegistry;",
|
||||||
|
"var RootRouter = exports.RootRouter;",
|
||||||
|
//TODO: move this code into a templated JS file
|
||||||
|
"var registry = new RouteRegistry();",
|
||||||
|
"var location = new Location();",
|
||||||
|
|
||||||
|
"$$controllerIntrospector(function (name, constructor) {",
|
||||||
|
"if (constructor.$canActivate) {",
|
||||||
|
"constructor.annotations = constructor.annotations || [];",
|
||||||
|
"constructor.annotations.push(new angular.annotations.CanActivate(function (instruction) {",
|
||||||
|
"return $injector.invoke(constructor.$canActivate, constructor, {",
|
||||||
|
"$routeParams: instruction.component ? instruction.component.params : instruction.params",
|
||||||
|
"});",
|
||||||
|
"}));",
|
||||||
|
"}",
|
||||||
|
"if (constructor.annotations) {",
|
||||||
|
"constructor.annotations.forEach(function(annotation) {",
|
||||||
|
"if (annotation instanceof RouteConfig) {",
|
||||||
|
"annotation.configs.forEach(function (config) {",
|
||||||
|
"registry.config(constructor, config);",
|
||||||
|
"});",
|
||||||
|
"}",
|
||||||
|
"});",
|
||||||
|
"}",
|
||||||
|
"});",
|
||||||
|
|
||||||
|
"var router = new RootRouter(registry, undefined, location, new Object());",
|
||||||
|
"$rootScope.$watch(function () { return $location.path(); }, function (path) { router.navigate(path); });",
|
||||||
|
|
||||||
|
"return router;"
|
||||||
|
].join('\n'));
|
||||||
|
|
||||||
|
return PRELUDE + TRACEUR_RUNTIME + DIRECTIVES + out + POSTLUDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a directory name and a file's TypeScript content, return an object with the ES5 code,
|
||||||
|
* sourcemap, anf exported variable identifier name for the content.
|
||||||
|
*/
|
||||||
|
var IMPORT_RE = new RegExp("import \\{?([\\w\\n_, ]+)\\}? from '(.+)';?", 'g');
|
||||||
|
function transform(dir, contents) {
|
||||||
|
contents = contents.replace(IMPORT_RE, function (match, imports, includePath) {
|
||||||
|
//TODO: remove special-case
|
||||||
|
if (isFacadeModule(includePath) || includePath === './router_outlet') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
return ts.transpile(contents, {
|
||||||
|
target: ts.ScriptTarget.ES5,
|
||||||
|
module: ts.ModuleKind.CommonJS,
|
||||||
|
sourceRoot: dir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function angularFactory(name, deps, body) {
|
||||||
|
return ".factory('" + name + "', [" +
|
||||||
|
deps.map(function (service) {
|
||||||
|
return "'" + service + "', ";
|
||||||
|
}).join('') +
|
||||||
|
"function (" + deps.join(', ') + ") {\n" + body + "\n}])";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function isFacadeModule(modulePath) {
|
||||||
|
return modulePath.indexOf('facade') > -1 ||
|
||||||
|
modulePath === 'angular2/src/reflection/reflection';
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function () {
|
||||||
|
var dist = __dirname + '/../../dist';
|
||||||
|
if (!fs.existsSync(dist)) {
|
||||||
|
fs.mkdirSync(dist);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(dist + '/angular_1_router.js', main(files));
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div ng-app="myApp" ng-controller="MyCtrl">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script src="../../node_modules/angular/angular.js"></script>
|
||||||
|
<script src="../../dist/angular_1_router.js"></script>
|
||||||
|
<script>
|
||||||
|
angular.module('myApp', ['ngComponentRouter'])
|
||||||
|
.controller('MyCtrl', ['$router', function ($router) {
|
||||||
|
console.log($router);
|
||||||
|
$router.navigate('/')
|
||||||
|
.then(console.log.bind(console, 'resolve'), console.log.bind(console, 'reject'));
|
||||||
|
}]);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,28 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var sauceConf = require('../../sauce.conf');
|
||||||
|
|
||||||
|
// This runs the tests for the router in Angular 1.x
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
var options = {
|
||||||
|
frameworks: ['jasmine'],
|
||||||
|
|
||||||
|
files: [
|
||||||
|
'../../node_modules/angular/angular.js',
|
||||||
|
'../../node_modules/angular-animate/angular-animate.js',
|
||||||
|
'../../node_modules/angular-mocks/angular-mocks.js',
|
||||||
|
|
||||||
|
'../../dist/angular_1_router.js',
|
||||||
|
|
||||||
|
'test/*.es5.js',
|
||||||
|
'test/*_spec.js'
|
||||||
|
],
|
||||||
|
|
||||||
|
customLaunchers: sauceConf.customLaunchers,
|
||||||
|
|
||||||
|
browsers: ['ChromeCanary']
|
||||||
|
};
|
||||||
|
|
||||||
|
config.set(options);
|
||||||
|
};
|
|
@ -0,0 +1,305 @@
|
||||||
|
function CONST() {
|
||||||
|
return (function(target) {
|
||||||
|
return target;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function IMPLEMENTS(_) {
|
||||||
|
return (function(t) {
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function CONST_EXPR(expr) {
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPresent (x) {
|
||||||
|
return !!x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBlank (x) {
|
||||||
|
return !x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isString(obj) {
|
||||||
|
return typeof obj === 'string';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isType (x) {
|
||||||
|
return typeof x === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStringMap(obj) {
|
||||||
|
return typeof obj === 'object' && obj !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isArray(obj) {
|
||||||
|
return Array.isArray(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
var PromiseWrapper = {
|
||||||
|
resolve: function (reason) {
|
||||||
|
return $q.when(reason);
|
||||||
|
},
|
||||||
|
|
||||||
|
reject: function (reason) {
|
||||||
|
return $q.reject(reason);
|
||||||
|
},
|
||||||
|
|
||||||
|
catchError: function (promise, fn) {
|
||||||
|
return promise.then(null, fn);
|
||||||
|
},
|
||||||
|
all: function (promises) {
|
||||||
|
return $q.all(promises);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var RegExpWrapper = {
|
||||||
|
create: function(regExpStr, flags) {
|
||||||
|
flags = flags ? flags.replace(/g/g, '') : '';
|
||||||
|
return new RegExp(regExpStr, flags + 'g');
|
||||||
|
},
|
||||||
|
firstMatch: function(regExp, input) {
|
||||||
|
regExp.lastIndex = 0;
|
||||||
|
return regExp.exec(input);
|
||||||
|
},
|
||||||
|
matcher: function (regExp, input) {
|
||||||
|
regExp.lastIndex = 0;
|
||||||
|
return { re: regExp, input: input };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var reflector = {
|
||||||
|
annotations: function (fn) {
|
||||||
|
//TODO: implement me
|
||||||
|
return fn.annotations || [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var MapWrapper = {
|
||||||
|
create: function() {
|
||||||
|
return new Map();
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(m, k) {
|
||||||
|
return m.get(k);
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function(m, k, v) {
|
||||||
|
return m.set(k, v);
|
||||||
|
},
|
||||||
|
|
||||||
|
contains: function (m, k) {
|
||||||
|
return m.has(k);
|
||||||
|
},
|
||||||
|
|
||||||
|
forEach: function (m, fn) {
|
||||||
|
return m.forEach(fn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var StringMapWrapper = {
|
||||||
|
create: function () {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
set: function (m, k, v) {
|
||||||
|
return m[k] = v;
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function (m, k) {
|
||||||
|
return m.hasOwnProperty(k) ? m[k] : undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
contains: function (m, k) {
|
||||||
|
return m.hasOwnProperty(k);
|
||||||
|
},
|
||||||
|
|
||||||
|
keys: function(map) {
|
||||||
|
return Object.keys(map);
|
||||||
|
},
|
||||||
|
|
||||||
|
isEmpty: function(map) {
|
||||||
|
for (var prop in map) {
|
||||||
|
if (map.hasOwnProperty(prop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: function(map, key) {
|
||||||
|
delete map[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
forEach: function (m, fn) {
|
||||||
|
for (prop in m) {
|
||||||
|
if (m.hasOwnProperty(prop)) {
|
||||||
|
fn(m[prop], prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
equals: function (m1, m2) {
|
||||||
|
var k1 = Object.keys(m1);
|
||||||
|
var k2 = Object.keys(m2);
|
||||||
|
if (k1.length != k2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var key;
|
||||||
|
for (var i = 0; i < k1.length; i++) {
|
||||||
|
key = k1[i];
|
||||||
|
if (m1[key] !== m2[key]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
merge: function(m1, m2) {
|
||||||
|
var m = {};
|
||||||
|
for (var attr in m1) {
|
||||||
|
if (m1.hasOwnProperty(attr)) {
|
||||||
|
m[attr] = m1[attr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var attr in m2) {
|
||||||
|
if (m2.hasOwnProperty(attr)) {
|
||||||
|
m[attr] = m2[attr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var List = Array;
|
||||||
|
var ListWrapper = {
|
||||||
|
create: function () {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
|
||||||
|
push: function (l, v) {
|
||||||
|
return l.push(v);
|
||||||
|
},
|
||||||
|
|
||||||
|
forEach: function (l, fn) {
|
||||||
|
return l.forEach(fn);
|
||||||
|
},
|
||||||
|
|
||||||
|
first: function(array) {
|
||||||
|
if (!array)
|
||||||
|
return null;
|
||||||
|
return array[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
map: function (l, fn) {
|
||||||
|
return l.map(fn);
|
||||||
|
},
|
||||||
|
|
||||||
|
join: function (l, str) {
|
||||||
|
return l.join(str);
|
||||||
|
},
|
||||||
|
|
||||||
|
reduce: function(list, fn, init) {
|
||||||
|
return list.reduce(fn, init);
|
||||||
|
},
|
||||||
|
|
||||||
|
filter: function(array, pred) {
|
||||||
|
return array.filter(pred);
|
||||||
|
},
|
||||||
|
|
||||||
|
concat: function(a, b) {
|
||||||
|
return a.concat(b);
|
||||||
|
},
|
||||||
|
|
||||||
|
slice: function(l) {
|
||||||
|
var from = arguments[1] !== (void 0) ? arguments[1] : 0;
|
||||||
|
var to = arguments[2] !== (void 0) ? arguments[2] : null;
|
||||||
|
return l.slice(from, to === null ? undefined : to);
|
||||||
|
},
|
||||||
|
|
||||||
|
maximum: function(list, predicate) {
|
||||||
|
if (list.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var solution = null;
|
||||||
|
var maxValue = -Infinity;
|
||||||
|
for (var index = 0; index < list.length; index++) {
|
||||||
|
var candidate = list[index];
|
||||||
|
if (isBlank(candidate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var candidateValue = predicate(candidate);
|
||||||
|
if (candidateValue > maxValue) {
|
||||||
|
solution = candidate;
|
||||||
|
maxValue = candidateValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return solution;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var StringWrapper = {
|
||||||
|
equals: function (s1, s2) {
|
||||||
|
return s1 === s2;
|
||||||
|
},
|
||||||
|
|
||||||
|
split: function(s, re) {
|
||||||
|
return s.split(re);
|
||||||
|
},
|
||||||
|
|
||||||
|
substring: function(s, start, end) {
|
||||||
|
return s.substr(start, end);
|
||||||
|
},
|
||||||
|
|
||||||
|
replaceAll: function(s, from, replace) {
|
||||||
|
return s.replace(from, replace);
|
||||||
|
},
|
||||||
|
|
||||||
|
startsWith: function(s, start) {
|
||||||
|
return s.startsWith(start);
|
||||||
|
},
|
||||||
|
|
||||||
|
replaceAllMapped: function(s, from, cb) {
|
||||||
|
return s.replace(from, function(matches) {
|
||||||
|
// Remove offset & string from the result array
|
||||||
|
matches.splice(-2, 2);
|
||||||
|
// The callback receives match, p1, ..., pn
|
||||||
|
return cb.apply(null, matches);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
contains: function(s, substr) {
|
||||||
|
return s.indexOf(substr) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO: implement?
|
||||||
|
// I think it's too heavy to ask 1.x users to bring in Rx for the router...
|
||||||
|
function EventEmitter() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var BaseException = Error;
|
||||||
|
|
||||||
|
var ObservableWrapper = {
|
||||||
|
callNext: function(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: https://github.com/angular/angular.js/blob/master/src/ng/browser.js#L227-L265
|
||||||
|
var $__router_47_location__ = {
|
||||||
|
Location: Location
|
||||||
|
};
|
||||||
|
|
||||||
|
function Location(){}
|
||||||
|
Location.prototype.subscribe = function () {
|
||||||
|
//TODO: implement
|
||||||
|
};
|
||||||
|
Location.prototype.path = function () {
|
||||||
|
return $location.path();
|
||||||
|
};
|
||||||
|
Location.prototype.go = function (url) {
|
||||||
|
return $location.path(url);
|
||||||
|
};
|
|
@ -0,0 +1,432 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A module for adding new a routing system Angular 1.
|
||||||
|
*/
|
||||||
|
angular.module('ngComponentRouter', [])
|
||||||
|
.factory('$componentMapper', $componentMapperFactory)
|
||||||
|
.directive('ngOutlet', ngOutletDirective)
|
||||||
|
.directive('ngOutlet', ngOutletFillContentDirective)
|
||||||
|
.directive('ngLink', ngLinkDirective)
|
||||||
|
.directive('a', anchorLinkDirective); // TODO: make the anchor link feature configurable
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A module for inspecting controller constructors
|
||||||
|
*/
|
||||||
|
angular.module('ng')
|
||||||
|
.provider('$$controllerIntrospector', $$controllerIntrospectorProvider)
|
||||||
|
.config(controllerProviderDecorator);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* decorates with routing info
|
||||||
|
*/
|
||||||
|
function controllerProviderDecorator($controllerProvider, $$controllerIntrospectorProvider) {
|
||||||
|
var register = $controllerProvider.register;
|
||||||
|
$controllerProvider.register = function (name, ctrl) {
|
||||||
|
$$controllerIntrospectorProvider.register(name, ctrl);
|
||||||
|
return register.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: decorate $controller ?
|
||||||
|
/*
|
||||||
|
* private service that holds route mappings for each controller
|
||||||
|
*/
|
||||||
|
function $$controllerIntrospectorProvider() {
|
||||||
|
var controllers = [];
|
||||||
|
var controllersByName = {};
|
||||||
|
var onControllerRegistered = null;
|
||||||
|
return {
|
||||||
|
register: function (name, constructor) {
|
||||||
|
if (angular.isArray(constructor)) {
|
||||||
|
constructor = constructor[constructor.length - 1];
|
||||||
|
}
|
||||||
|
controllersByName[name] = constructor;
|
||||||
|
constructor.$$controllerName = name;
|
||||||
|
if (onControllerRegistered) {
|
||||||
|
onControllerRegistered(name, constructor);
|
||||||
|
} else {
|
||||||
|
controllers.push({name: name, constructor: constructor});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$get: ['$componentMapper', function ($componentMapper) {
|
||||||
|
var fn = function (newOnControllerRegistered) {
|
||||||
|
onControllerRegistered = function (name, constructor) {
|
||||||
|
name = $componentMapper.component(name);
|
||||||
|
return newOnControllerRegistered(name, constructor);
|
||||||
|
};
|
||||||
|
while (controllers.length > 0) {
|
||||||
|
var rule = controllers.pop();
|
||||||
|
onControllerRegistered(rule.name, rule.constructor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn.getTypeByName = function (name) {
|
||||||
|
return controllersByName[name];
|
||||||
|
};
|
||||||
|
|
||||||
|
return fn;
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name ngOutlet
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* An ngOutlet is where resolved content goes.
|
||||||
|
*
|
||||||
|
* ## Use
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <div ng-outlet="name"></div>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The value for the `ngOutlet` attribute is optional.
|
||||||
|
*/
|
||||||
|
function ngOutletDirective($animate, $injector, $q, $router, $componentMapper, $controller,
|
||||||
|
$$controllerIntrospector, $templateRequest) {
|
||||||
|
var rootRouter = $router;
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: 'AE',
|
||||||
|
transclude: 'element',
|
||||||
|
terminal: true,
|
||||||
|
priority: 400,
|
||||||
|
require: ['?^^ngOutlet', 'ngOutlet'],
|
||||||
|
link: outletLink,
|
||||||
|
controller: function () {},
|
||||||
|
controllerAs: '$$ngOutlet'
|
||||||
|
};
|
||||||
|
|
||||||
|
function outletLink(scope, $element, attrs, ctrls, $transclude) {
|
||||||
|
var outletName = attrs.ngOutlet || 'default',
|
||||||
|
parentCtrl = ctrls[0],
|
||||||
|
myCtrl = ctrls[1],
|
||||||
|
router = (parentCtrl && parentCtrl.$$router) || rootRouter;
|
||||||
|
|
||||||
|
var childRouter,
|
||||||
|
currentInstruction,
|
||||||
|
currentScope,
|
||||||
|
currentController,
|
||||||
|
currentElement,
|
||||||
|
previousLeaveAnimation;
|
||||||
|
|
||||||
|
function cleanupLastView() {
|
||||||
|
if (previousLeaveAnimation) {
|
||||||
|
$animate.cancel(previousLeaveAnimation);
|
||||||
|
previousLeaveAnimation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentScope) {
|
||||||
|
currentScope.$destroy();
|
||||||
|
currentScope = null;
|
||||||
|
}
|
||||||
|
if (currentElement) {
|
||||||
|
previousLeaveAnimation = $animate.leave(currentElement);
|
||||||
|
previousLeaveAnimation.then(function () {
|
||||||
|
previousLeaveAnimation = null;
|
||||||
|
});
|
||||||
|
currentElement = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.registerOutlet({
|
||||||
|
commit: function (instruction) {
|
||||||
|
var next;
|
||||||
|
var componentInstruction = instruction.component;
|
||||||
|
if (componentInstruction.reuse) {
|
||||||
|
// todo(shahata): lifecycle - onReuse
|
||||||
|
next = $q.when(true);
|
||||||
|
} else {
|
||||||
|
var self = this;
|
||||||
|
next = this.deactivate(instruction).then(function () {
|
||||||
|
return self.activate(componentInstruction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return next.then(function () {
|
||||||
|
if (childRouter) {
|
||||||
|
return childRouter.commit(instruction.child);
|
||||||
|
} else {
|
||||||
|
return $q.when(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
canReuse: function (nextInstruction) {
|
||||||
|
var result;
|
||||||
|
var componentInstruction = nextInstruction.component;
|
||||||
|
if (!currentInstruction ||
|
||||||
|
currentInstruction.componentType !== componentInstruction.componentType) {
|
||||||
|
result = false;
|
||||||
|
} else {
|
||||||
|
// todo(shahata): lifecycle - canReuse
|
||||||
|
result = componentInstruction === currentInstruction ||
|
||||||
|
angular.equals(componentInstruction.params, currentInstruction.params);
|
||||||
|
}
|
||||||
|
return $q.when(result).then(function (result) {
|
||||||
|
// TODO: this is a hack
|
||||||
|
componentInstruction.reuse = result;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
canDeactivate: function (instruction) {
|
||||||
|
if (currentInstruction && currentController && currentController.canDeactivate) {
|
||||||
|
return $q.when(currentController.canDeactivate(instruction && instruction.component, currentInstruction));
|
||||||
|
}
|
||||||
|
return $q.when(true);
|
||||||
|
},
|
||||||
|
deactivate: function (instruction) {
|
||||||
|
// todo(shahata): childRouter.dectivate, dispose component?
|
||||||
|
var result = $q.when();
|
||||||
|
return result.then(function () {
|
||||||
|
if (currentController && currentController.onDeactivate) {
|
||||||
|
return currentController.onDeactivate(instruction && instruction.component, currentInstruction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
activate: function (instruction) {
|
||||||
|
var previousInstruction = currentInstruction;
|
||||||
|
currentInstruction = instruction;
|
||||||
|
childRouter = router.childRouter(instruction.componentType);
|
||||||
|
|
||||||
|
var controllerConstructor, componentName;
|
||||||
|
controllerConstructor = instruction.componentType;
|
||||||
|
componentName = $componentMapper.component(controllerConstructor.$$controllerName);
|
||||||
|
|
||||||
|
var componentTemplateUrl = $componentMapper.template(componentName);
|
||||||
|
return $templateRequest(componentTemplateUrl).then(function (templateHtml) {
|
||||||
|
myCtrl.$$router = childRouter;
|
||||||
|
myCtrl.$$template = templateHtml;
|
||||||
|
}).then(function () {
|
||||||
|
var newScope = scope.$new();
|
||||||
|
var locals = {
|
||||||
|
$scope: newScope,
|
||||||
|
$router: childRouter,
|
||||||
|
$routeParams: (instruction.params || {})
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo(shahata): controllerConstructor is not minify friendly
|
||||||
|
currentController = $controller(controllerConstructor, locals);
|
||||||
|
|
||||||
|
var clone = $transclude(newScope, function (clone) {
|
||||||
|
$animate.enter(clone, null, currentElement || $element);
|
||||||
|
cleanupLastView();
|
||||||
|
});
|
||||||
|
|
||||||
|
var controllerAs = $componentMapper.controllerAs(componentName) || componentName;
|
||||||
|
newScope[controllerAs] = currentController;
|
||||||
|
currentElement = clone;
|
||||||
|
currentScope = newScope;
|
||||||
|
|
||||||
|
if (currentController.onActivate) {
|
||||||
|
return currentController.onActivate(instruction, previousInstruction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, outletName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ngOutletFillContentDirective($compile) {
|
||||||
|
return {
|
||||||
|
restrict: 'EA',
|
||||||
|
priority: -400,
|
||||||
|
require: 'ngOutlet',
|
||||||
|
link: function (scope, $element, attrs, ctrl) {
|
||||||
|
var template = ctrl.$$template;
|
||||||
|
$element.html(template);
|
||||||
|
var link = $compile($element.contents());
|
||||||
|
link(scope);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name ngLink
|
||||||
|
* @description
|
||||||
|
* Lets you link to different parts of the app, and automatically generates hrefs.
|
||||||
|
*
|
||||||
|
* ## Use
|
||||||
|
* The directive uses a simple syntax: `ng-link="componentName({ param: paramValue })"`
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* angular.module('myApp', ['ngFuturisticRouter'])
|
||||||
|
* .controller('AppController', ['$router', function($router) {
|
||||||
|
* $router.config({ path: '/user/:id' component: 'user' });
|
||||||
|
* this.user = { name: 'Brian', id: 123 };
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <div ng-controller="AppController as app">
|
||||||
|
* <a ng-link="user({id: app.user.id})">{{app.user.name}}</a>
|
||||||
|
* </div>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function ngLinkDirective($router, $location, $parse) {
|
||||||
|
var rootRouter = $router;
|
||||||
|
|
||||||
|
return {
|
||||||
|
require: '?^^ngOutlet',
|
||||||
|
restrict: 'A',
|
||||||
|
link: ngLinkDirectiveLinkFn
|
||||||
|
};
|
||||||
|
|
||||||
|
function ngLinkDirectiveLinkFn(scope, elt, attrs, ctrl) {
|
||||||
|
var router = (ctrl && ctrl.$$router) || rootRouter;
|
||||||
|
if (!router) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var link = attrs.ngLink || '';
|
||||||
|
|
||||||
|
function getLink(params) {
|
||||||
|
return './' + angular.stringifyInstruction(router.generate(params));
|
||||||
|
}
|
||||||
|
|
||||||
|
var routeParamsGetter = $parse(link);
|
||||||
|
// we can avoid adding a watcher if it's a literal
|
||||||
|
if (routeParamsGetter.constant) {
|
||||||
|
var params = routeParamsGetter();
|
||||||
|
elt.attr('href', getLink(params));
|
||||||
|
} else {
|
||||||
|
scope.$watch(function () {
|
||||||
|
return routeParamsGetter(scope);
|
||||||
|
}, function (params) {
|
||||||
|
elt.attr('href', getLink(params));
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function anchorLinkDirective($router) {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
link: function (scope, element) {
|
||||||
|
// If the linked element is not an anchor tag anymore, do nothing
|
||||||
|
if (element[0].nodeName.toLowerCase() !== 'a') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
|
||||||
|
var hrefAttrName = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
|
||||||
|
'xlink:href' : 'href';
|
||||||
|
|
||||||
|
element.on('click', function (event) {
|
||||||
|
if (event.which !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var href = element.attr(hrefAttrName);
|
||||||
|
if (href && $router.recognize(href)) {
|
||||||
|
$router.navigate(href);
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name $componentMapperFactory
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* This lets you configure conventions for what controllers are named and where to load templates from.
|
||||||
|
*
|
||||||
|
* The default behavior is to dasherize and serve from `./components`. A component called `myWidget`
|
||||||
|
* uses a controller named `MyWidgetController` and a template loaded from `./components/my-widget/my-widget.html`.
|
||||||
|
*
|
||||||
|
* A component is:
|
||||||
|
* - a controller
|
||||||
|
* - a template
|
||||||
|
* - an optional router
|
||||||
|
*
|
||||||
|
* This service makes it easy to group all of them into a single concept.
|
||||||
|
*/
|
||||||
|
function $componentMapperFactory() {
|
||||||
|
|
||||||
|
var DEFAULT_SUFFIX = 'Controller';
|
||||||
|
|
||||||
|
var componentToCtrl = function componentToCtrlDefault(name) {
|
||||||
|
return name[0].toUpperCase() + name.substr(1) + DEFAULT_SUFFIX;
|
||||||
|
};
|
||||||
|
|
||||||
|
var componentToTemplate = function componentToTemplateDefault(name) {
|
||||||
|
var dashName = dashCase(name);
|
||||||
|
return './components/' + dashName + '/' + dashName + '.html';
|
||||||
|
};
|
||||||
|
|
||||||
|
var ctrlToComponent = function ctrlToComponentDefault(name) {
|
||||||
|
return name[0].toLowerCase() + name.substr(1, name.length - DEFAULT_SUFFIX.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
var componentToControllerAs = function componentToControllerAsDefault(name) {
|
||||||
|
return name;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
controllerName: function (name) {
|
||||||
|
return componentToCtrl(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
controllerAs: function (name) {
|
||||||
|
return componentToControllerAs(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
template: function (name) {
|
||||||
|
return componentToTemplate(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
component: function (name) {
|
||||||
|
return ctrlToComponent(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name $componentMapper#setCtrlNameMapping
|
||||||
|
* @description takes a function for mapping component names to component controller names
|
||||||
|
*/
|
||||||
|
setCtrlNameMapping: function (newFn) {
|
||||||
|
componentToCtrl = newFn;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name $componentMapper#setCtrlAsMapping
|
||||||
|
* @description takes a function for mapping component names to controllerAs name in the template
|
||||||
|
*/
|
||||||
|
setCtrlAsMapping: function (newFn) {
|
||||||
|
componentToControllerAs = newFn;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name $componentMapper#setComponentFromCtrlMapping
|
||||||
|
* @description takes a function for mapping component controller names to component names
|
||||||
|
*/
|
||||||
|
setComponentFromCtrlMapping: function (newFn) {
|
||||||
|
ctrlToComponent = newFn;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name $componentMapper#setTemplateMapping
|
||||||
|
* @description takes a function for mapping component names to component template URLs
|
||||||
|
*/
|
||||||
|
setTemplateMapping: function (newFn) {
|
||||||
|
componentToTemplate = newFn;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function dashCase(str) {
|
||||||
|
return str.replace(/([A-Z])/g, function ($1) {
|
||||||
|
return '-' + $1.toLowerCase();
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('$componentMapper', function () {
|
||||||
|
var elt,
|
||||||
|
$compile,
|
||||||
|
$rootScope,
|
||||||
|
$router,
|
||||||
|
$templateCache;
|
||||||
|
|
||||||
|
function Ctrl() {
|
||||||
|
this.message = 'howdy';
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module('ng');
|
||||||
|
module('ngComponentRouter');
|
||||||
|
module(function ($controllerProvider) {
|
||||||
|
$controllerProvider.register('myComponentController', Ctrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should convert a component name to a controller name', inject(function ($componentMapper) {
|
||||||
|
expect($componentMapper.controllerName('foo')).toBe('FooController');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should convert a controller name to a component name', inject(function ($componentMapper) {
|
||||||
|
expect($componentMapper.component('FooController')).toBe('foo');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should convert a component name to a template URL', inject(function ($componentMapper) {
|
||||||
|
expect($componentMapper.template('foo')).toBe('./components/foo/foo.html');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should work with a controller constructor fn and a template url', inject(function ($componentMapper) {
|
||||||
|
var routes = {};
|
||||||
|
$componentMapper.setCtrlNameMapping(function (name) {
|
||||||
|
return routes[name].controller;
|
||||||
|
});
|
||||||
|
$componentMapper.setTemplateMapping(function (name) {
|
||||||
|
return routes[name].templateUrl;
|
||||||
|
});
|
||||||
|
$componentMapper.setCtrlAsMapping(function (name) {
|
||||||
|
return 'ctrl';
|
||||||
|
});
|
||||||
|
|
||||||
|
routes.myComponent = {
|
||||||
|
controller: Ctrl,
|
||||||
|
templateUrl: '/foo'
|
||||||
|
};
|
||||||
|
|
||||||
|
inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||||
|
$compile = _$compile_;
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
$router = _$router_;
|
||||||
|
$templateCache = _$templateCache_;
|
||||||
|
});
|
||||||
|
|
||||||
|
$templateCache.put('/foo', [200, '{{ctrl.message}}', {}]);
|
||||||
|
|
||||||
|
compile('<ng-outlet></ng-outlet>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/', component: Ctrl }
|
||||||
|
]);
|
||||||
|
|
||||||
|
$router.navigate('/');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.text()).toBe('howdy');
|
||||||
|
}));
|
||||||
|
|
||||||
|
function compile(template) {
|
||||||
|
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||||
|
$rootScope.$digest();
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('$$controllerIntrospector', function () {
|
||||||
|
|
||||||
|
var $controllerProvider;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
module('ng');
|
||||||
|
module('ngComponentRouter');
|
||||||
|
module(function(_$controllerProvider_) {
|
||||||
|
$controllerProvider = _$controllerProvider_;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the introspector function whenever a controller is registered', inject(function ($$controllerIntrospector) {
|
||||||
|
var spy = jasmine.createSpy();
|
||||||
|
$$controllerIntrospector(spy);
|
||||||
|
function Ctrl(){}
|
||||||
|
$controllerProvider.register('SomeController', Ctrl);
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledWith('some', Ctrl);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should call the introspector function whenever a controller is registered with array annotations', inject(function ($$controllerIntrospector) {
|
||||||
|
var spy = jasmine.createSpy();
|
||||||
|
$$controllerIntrospector(spy);
|
||||||
|
function Ctrl(foo){}
|
||||||
|
$controllerProvider.register('SomeController', ['foo', Ctrl]);
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledWith('some', Ctrl);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should retrieve a constructor', inject(function ($$controllerIntrospector) {
|
||||||
|
function Ctrl(foo){}
|
||||||
|
$controllerProvider.register('SomeController', ['foo', Ctrl]);
|
||||||
|
expect($$controllerIntrospector.getTypeByName('SomeController')).toBe(Ctrl);
|
||||||
|
}));
|
||||||
|
});
|
|
@ -0,0 +1,800 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe('ngOutlet', function () {
|
||||||
|
|
||||||
|
var elt,
|
||||||
|
$compile,
|
||||||
|
$rootScope,
|
||||||
|
$router,
|
||||||
|
$templateCache,
|
||||||
|
$controllerProvider,
|
||||||
|
$componentMapperProvider;
|
||||||
|
|
||||||
|
var OneController, TwoController, UserController;
|
||||||
|
|
||||||
|
function instructionFor(componentType) {
|
||||||
|
return jasmine.objectContaining({componentType: componentType});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
module('ng');
|
||||||
|
module('ngComponentRouter');
|
||||||
|
module(function (_$controllerProvider_, _$componentMapperProvider_) {
|
||||||
|
$controllerProvider = _$controllerProvider_;
|
||||||
|
$componentMapperProvider = _$componentMapperProvider_;
|
||||||
|
});
|
||||||
|
|
||||||
|
inject(function (_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||||
|
$compile = _$compile_;
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
$router = _$router_;
|
||||||
|
$templateCache = _$templateCache_;
|
||||||
|
});
|
||||||
|
|
||||||
|
UserController = registerComponent('user', '<div>hello {{user.name}}</div>', function ($routeParams) {
|
||||||
|
this.name = $routeParams.name;
|
||||||
|
});
|
||||||
|
OneController = registerComponent('one', '<div>{{one.number}}</div>', boringController('number', 'one'));
|
||||||
|
TwoController = registerComponent('two', '<div>{{two.number}}</div>', boringController('number', 'two'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should work in a simple case', function () {
|
||||||
|
compile('<ng-outlet></ng-outlet>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/', component: OneController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
$router.navigate('/');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.text()).toBe('one');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// See https://github.com/angular/router/issues/105
|
||||||
|
xit('should warn when instantiating a component with no controller', function () {
|
||||||
|
put('noController', '<div>{{ 2 + 2 }}</div>');
|
||||||
|
$router.config([
|
||||||
|
{ path: '/', component: 'noController' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
spyOn(console, 'warn');
|
||||||
|
compile('<ng-outlet></ng-outlet>');
|
||||||
|
$router.navigate('/');
|
||||||
|
|
||||||
|
expect(console.warn).toHaveBeenCalledWith('Could not find controller for', 'NoControllerController');
|
||||||
|
expect(elt.text()).toBe('4');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should navigate between components with different parameters', function () {
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user/:name', component: UserController }
|
||||||
|
]);
|
||||||
|
compile('<ng-outlet></ng-outlet>');
|
||||||
|
|
||||||
|
$router.navigate('/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('hello brian');
|
||||||
|
|
||||||
|
$router.navigate('/user/igor');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('hello igor');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should not reactivate a parent when navigating between child components with different parameters', function () {
|
||||||
|
var spy = jasmine.createSpy('onActivate');
|
||||||
|
function ParentController() {}
|
||||||
|
ParentController.$routeConfig = [
|
||||||
|
{ path: '/user/:name', component: UserController }
|
||||||
|
];
|
||||||
|
ParentController.prototype.onActivate = spy;
|
||||||
|
|
||||||
|
registerComponent('parent', 'parent { <ng-outlet></ng-outlet> }', ParentController);
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/parent/...', component: ParentController }
|
||||||
|
]);
|
||||||
|
compile('<ng-outlet></ng-outlet>');
|
||||||
|
|
||||||
|
$router.navigate('/parent/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(elt.text()).toBe('parent { hello brian }');
|
||||||
|
|
||||||
|
spy.calls.reset();
|
||||||
|
|
||||||
|
$router.navigate('/parent/user/igor');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
expect(elt.text()).toBe('parent { hello igor }');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should work with nested outlets', function () {
|
||||||
|
var childComponent = registerComponent('childComponent', '<div>inner { <div ng-outlet></div> }</div>', [
|
||||||
|
{ path: '/b', component: OneController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a/...', component: childComponent }
|
||||||
|
]);
|
||||||
|
compile('<div>outer { <div ng-outlet></div> }</div>');
|
||||||
|
|
||||||
|
$router.navigate('/a/b');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.text()).toBe('outer { inner { one } }');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should work with recursive nested outlets', function () {
|
||||||
|
put('two', '<div>recur { <div ng-outlet></div> }</div>');
|
||||||
|
$router.config([
|
||||||
|
{ path: '/recur', component: TwoController },
|
||||||
|
{ path: '/', component: OneController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
compile('<div>root { <div ng-outlet></div> }</div>');
|
||||||
|
$router.navigate('/');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('root { one }');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should allow linking from the parent to the child', function () {
|
||||||
|
put('one', '<div>{{number}}</div>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: OneController },
|
||||||
|
{ path: '/b', component: TwoController, as: 'two' }
|
||||||
|
]);
|
||||||
|
compile('<a ng-link="[\'/two\']">link</a> | outer { <div ng-outlet></div> }');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.find('a').attr('href')).toBe('./b');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow linking from the child and the parent', function () {
|
||||||
|
put('one', '<div><a ng-link="[\'/two\']">{{number}}</a></div>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: OneController },
|
||||||
|
{ path: '/b', component: TwoController, as: 'two' }
|
||||||
|
]);
|
||||||
|
compile('outer { <div ng-outlet></div> }');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.find('a').attr('href')).toBe('./b');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should allow params in routerLink directive', function () {
|
||||||
|
put('router', '<div>outer { <div ng-outlet></div> }</div>');
|
||||||
|
put('one', '<div><a ng-link="[\'/two\', {param: \'lol\'}]">{{number}}</a></div>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: OneController },
|
||||||
|
{ path: '/b/:param', component: TwoController, as: 'two' }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.find('a').attr('href')).toBe('./b/lol');
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: test dynamic links
|
||||||
|
it('should update the href of links with bound params', function () {
|
||||||
|
put('router', '<div>outer { <div ng-outlet></div> }</div>');
|
||||||
|
put('one', '<div><a ng-link="[\'/two\', {param: one.number}]">{{one.number}}</a></div>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: OneController },
|
||||||
|
{ path: '/b/:param', component: TwoController, as: 'two' }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.find('a').attr('href')).toBe('./b/one');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should run the activate hook of controllers', function () {
|
||||||
|
var spy = jasmine.createSpy('activate');
|
||||||
|
var activate = registerComponent('activate', '', {
|
||||||
|
onActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: activate }
|
||||||
|
]);
|
||||||
|
compile('<div>outer { <div ng-outlet></div> }</div>');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass instruction into the activate hook of a controller', function () {
|
||||||
|
var spy = jasmine.createSpy('activate');
|
||||||
|
var UserController = registerComponent('user', '', {
|
||||||
|
onActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user/:name', component: UserController }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalledWith(instructionFor(UserController), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass previous instruction into the activate hook of a controller', function () {
|
||||||
|
var spy = jasmine.createSpy('activate');
|
||||||
|
var activate = registerComponent('activate', '', {
|
||||||
|
onActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user/:name', component: OneController },
|
||||||
|
{ path: '/post/:id', component: activate }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
$router.navigate('/post/123');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(spy).toHaveBeenCalledWith(instructionFor(activate),
|
||||||
|
instructionFor(OneController));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should inject $scope into the controller constructor', function () {
|
||||||
|
|
||||||
|
var injectedScope;
|
||||||
|
var UserController = registerComponent('user', '', function ($scope) {
|
||||||
|
injectedScope = $scope;
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user', component: UserController }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/user');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(injectedScope).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should run the deactivate hook of controllers', function () {
|
||||||
|
var spy = jasmine.createSpy('deactivate');
|
||||||
|
var deactivate = registerComponent('deactivate', '', {
|
||||||
|
onDeactivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: deactivate },
|
||||||
|
{ path: '/b', component: OneController }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
$router.navigate('/b');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass instructions into the deactivate hook of controllers', function () {
|
||||||
|
var spy = jasmine.createSpy('deactivate');
|
||||||
|
var deactivate = registerComponent('deactivate', '', {
|
||||||
|
onDeactivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user/:name', component: deactivate },
|
||||||
|
{ path: '/post/:id', component: OneController }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
$router.navigate('/post/123');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(spy).toHaveBeenCalledWith(instructionFor(OneController),
|
||||||
|
instructionFor(deactivate));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should run the deactivate hook before the activate hook', function () {
|
||||||
|
var log = [];
|
||||||
|
|
||||||
|
var activate = registerComponent('activate', '', {
|
||||||
|
onActivate: function () {
|
||||||
|
log.push('activate');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var deactivate = registerComponent('deactivate', '', {
|
||||||
|
onDeactivate: function () {
|
||||||
|
log.push('deactivate');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: deactivate },
|
||||||
|
{ path: '/b', component: activate }
|
||||||
|
]);
|
||||||
|
compile('outer { <div ng-outlet></div> }');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
$router.navigate('/b');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(log).toEqual(['deactivate', 'activate']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should not activate a component when canActivate returns false', function () {
|
||||||
|
var spy = jasmine.createSpy('activate');
|
||||||
|
var activate = registerComponent('activate', '', {
|
||||||
|
canActivate: function () {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
onActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: activate }
|
||||||
|
]);
|
||||||
|
compile('outer { <div ng-outlet></div> }');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
expect(elt.text()).toBe('outer { }');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should activate a component when canActivate returns true', function () {
|
||||||
|
var spy = jasmine.createSpy('activate');
|
||||||
|
var activate = registerComponent('activate', 'hi', {
|
||||||
|
canActivate: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: activate }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(elt.text()).toBe('hi');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should activate a component when canActivate returns a resolved promise', inject(function ($q) {
|
||||||
|
var spy = jasmine.createSpy('activate');
|
||||||
|
var activate = registerComponent('activate', 'hi', {
|
||||||
|
canActivate: function () {
|
||||||
|
return $q.when(true);
|
||||||
|
},
|
||||||
|
onActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: activate }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(elt.text()).toBe('hi');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should inject into the canActivate hook of controllers', inject(function ($http) {
|
||||||
|
var spy = jasmine.createSpy('canActivate').and.returnValue(true);
|
||||||
|
var activate = registerComponent('activate', '', {
|
||||||
|
canActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
spy.$inject = ['$routeParams', '$http'];
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user/:name', component: activate }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(spy).toHaveBeenCalledWith({name: 'brian'}, $http);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should not navigate when canDeactivate returns false', function () {
|
||||||
|
var activate = registerComponent('activate', 'hi', {
|
||||||
|
canDeactivate: function () {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: activate },
|
||||||
|
{ path: '/b', component: OneController }
|
||||||
|
]);
|
||||||
|
compile('outer { <div ng-outlet></div> }');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('outer { hi }');
|
||||||
|
|
||||||
|
$router.navigate('/b');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('outer { hi }');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should navigate when canDeactivate returns true', function () {
|
||||||
|
var activate = registerComponent('activate', 'hi', {
|
||||||
|
canDeactivate: function () {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: activate },
|
||||||
|
{ path: '/b', component: OneController }
|
||||||
|
]);
|
||||||
|
compile('outer { <div ng-outlet></div> }');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('outer { hi }');
|
||||||
|
|
||||||
|
$router.navigate('/b');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('outer { one }');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should activate a component when canActivate returns true', function () {
|
||||||
|
var spy = jasmine.createSpy('activate');
|
||||||
|
var activate = registerComponent('activate', 'hi', {
|
||||||
|
canActivate: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onActivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/a', component: activate }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/a');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(elt.text()).toBe('hi');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should pass instructions into the canDeactivate hook of controllers', function () {
|
||||||
|
var spy = jasmine.createSpy('canDeactivate').and.returnValue(true);
|
||||||
|
var deactivate = registerComponent('deactivate', '', {
|
||||||
|
canDeactivate: spy
|
||||||
|
});
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user/:name', component: deactivate },
|
||||||
|
{ path: '/post/:id', component: OneController }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
$router.navigate('/post/123');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(spy).toHaveBeenCalledWith(instructionFor(OneController),
|
||||||
|
instructionFor(deactivate));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should change location path', inject(function ($location) {
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user', component: UserController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/user');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect($location.path()).toBe('/user');
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO: test injecting $scope
|
||||||
|
|
||||||
|
it('should navigate on left-mouse click when a link url matches a route', function () {
|
||||||
|
$router.config([
|
||||||
|
{ path: '/', component: OneController },
|
||||||
|
{ path: '/two', component: TwoController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
compile('<a href="/two">link</a> | <div ng-outlet></div>');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('link | one');
|
||||||
|
elt.find('a')[0].click();
|
||||||
|
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('link | two');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should not navigate on non-left mouse click when a link url matches a route', inject(function ($router) {
|
||||||
|
$router.config([
|
||||||
|
{ path: '/', component: OneController },
|
||||||
|
{ path: '/two', component: TwoController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
compile('<a href="./two">link</a> | <div ng-outlet></div>');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('link | one');
|
||||||
|
elt.find('a').triggerHandler({ type: 'click', which: 3 });
|
||||||
|
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('link | one');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// See https://github.com/angular/router/issues/206
|
||||||
|
it('should not navigate a link without an href', function () {
|
||||||
|
$router.config([
|
||||||
|
{ path: '/', component: OneController },
|
||||||
|
{ path: '/two', component: TwoController }
|
||||||
|
]);
|
||||||
|
expect(function () {
|
||||||
|
compile('<a>link</a>');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('link');
|
||||||
|
elt.find('a')[0].click();
|
||||||
|
$rootScope.$digest();
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should change location to the canonical route', inject(function ($location) {
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/', redirectTo: '/user' },
|
||||||
|
{ path: '/user', component: UserController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
$router.navigate('/');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect($location.path()).toBe('/user');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should change location to the canonical route with nested components', inject(function ($location) {
|
||||||
|
var childRouter = registerComponent('childRouter', '<div>inner { <div ng-outlet></div> }</div>', [
|
||||||
|
{ path: '/old-child', redirectTo: '/new-child' },
|
||||||
|
{ path: '/new-child', component: OneController},
|
||||||
|
{ path: '/old-child-two', redirectTo: '/new-child-two' },
|
||||||
|
{ path: '/new-child-two', component: TwoController}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/old-parent', redirectTo: '/new-parent' },
|
||||||
|
{ path: '/new-parent/...', component: childRouter }
|
||||||
|
]);
|
||||||
|
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/old-parent/old-child');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect($location.path()).toBe('/new-parent/new-child');
|
||||||
|
expect(elt.text()).toBe('inner { one }');
|
||||||
|
|
||||||
|
$router.navigate('/old-parent/old-child-two');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect($location.path()).toBe('/new-parent/new-child-two');
|
||||||
|
expect(elt.text()).toBe('inner { two }');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should navigate when the location path changes', inject(function ($location) {
|
||||||
|
$router.config([
|
||||||
|
{ path: '/one', component: OneController }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$location.path('/one');
|
||||||
|
$rootScope.$digest();
|
||||||
|
|
||||||
|
expect(elt.text()).toBe('one');
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('should expose a "navigating" property on $router', inject(function ($q) {
|
||||||
|
var defer;
|
||||||
|
var pendingActivate = registerComponent('pendingActivate', '', {
|
||||||
|
onActivate: function () {
|
||||||
|
defer = $q.defer();
|
||||||
|
return defer.promise;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$router.config([
|
||||||
|
{ path: '/pendingActivate', component: pendingActivate }
|
||||||
|
]);
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.navigate('/pendingActivate');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect($router.navigating).toBe(true);
|
||||||
|
defer.resolve();
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect($router.navigating).toBe(false);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
function registerComponent(name, template, config) {
|
||||||
|
var Ctrl;
|
||||||
|
if (!template) {
|
||||||
|
template = '';
|
||||||
|
}
|
||||||
|
if (!config) {
|
||||||
|
Ctrl = function () {};
|
||||||
|
} else if (angular.isArray(config)) {
|
||||||
|
Ctrl = function () {};
|
||||||
|
Ctrl.annotations = [new angular.annotations.RouteConfig(config)];
|
||||||
|
} else if (typeof config === 'function') {
|
||||||
|
Ctrl = config;
|
||||||
|
} else {
|
||||||
|
Ctrl = function () {};
|
||||||
|
if (config.canActivate) {
|
||||||
|
Ctrl.$canActivate = config.canActivate;
|
||||||
|
delete config.canActivate;
|
||||||
|
}
|
||||||
|
Ctrl.prototype = config;
|
||||||
|
}
|
||||||
|
$controllerProvider.register(componentControllerName(name), Ctrl);
|
||||||
|
put(name, template);
|
||||||
|
return Ctrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function boringController(model, value) {
|
||||||
|
return function () {
|
||||||
|
this[model] = value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function put(name, template) {
|
||||||
|
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compile(template) {
|
||||||
|
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||||
|
$rootScope.$digest();
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('ngOutlet animations', function () {
|
||||||
|
|
||||||
|
var elt,
|
||||||
|
$animate,
|
||||||
|
$compile,
|
||||||
|
$rootScope,
|
||||||
|
$router,
|
||||||
|
$templateCache,
|
||||||
|
$controllerProvider;
|
||||||
|
|
||||||
|
function UserController($routeParams) {
|
||||||
|
this.name = $routeParams.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
module('ngAnimate');
|
||||||
|
module('ngAnimateMock');
|
||||||
|
module('ngComponentRouter');
|
||||||
|
module(function (_$controllerProvider_) {
|
||||||
|
$controllerProvider = _$controllerProvider_;
|
||||||
|
});
|
||||||
|
|
||||||
|
inject(function (_$animate_, _$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||||
|
$animate = _$animate_;
|
||||||
|
$compile = _$compile_;
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
$router = _$router_;
|
||||||
|
$templateCache = _$templateCache_;
|
||||||
|
});
|
||||||
|
|
||||||
|
put('user', '<div>hello {{user.name}}</div>');
|
||||||
|
$controllerProvider.register('UserController', UserController);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
expect($animate.queue).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work in a simple case', function () {
|
||||||
|
var item;
|
||||||
|
|
||||||
|
compile('<div ng-outlet></div>');
|
||||||
|
|
||||||
|
$router.config([
|
||||||
|
{ path: '/user/:name', component: UserController }
|
||||||
|
]);
|
||||||
|
|
||||||
|
$router.navigate('/user/brian');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('hello brian');
|
||||||
|
|
||||||
|
// "user" component enters
|
||||||
|
item = $animate.queue.shift();
|
||||||
|
expect(item.event).toBe('enter');
|
||||||
|
|
||||||
|
// navigate to pete
|
||||||
|
$router.navigate('/user/pete');
|
||||||
|
$rootScope.$digest();
|
||||||
|
expect(elt.text()).toBe('hello pete');
|
||||||
|
|
||||||
|
// "user pete" component enters
|
||||||
|
item = $animate.queue.shift();
|
||||||
|
expect(item.event).toBe('enter');
|
||||||
|
expect(item.element.text()).toBe('hello pete');
|
||||||
|
|
||||||
|
// "user brian" component leaves
|
||||||
|
item = $animate.queue.shift();
|
||||||
|
expect(item.event).toBe('leave');
|
||||||
|
expect(item.element.text()).toBe('hello brian');
|
||||||
|
});
|
||||||
|
|
||||||
|
function put(name, template) {
|
||||||
|
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compile(template) {
|
||||||
|
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||||
|
$rootScope.$digest();
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Helpers to keep tests DRY
|
||||||
|
*/
|
||||||
|
|
||||||
|
function componentTemplatePath(name) {
|
||||||
|
return './components/' + dashCase(name) + '/' + dashCase(name) + '.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
function componentControllerName(name) {
|
||||||
|
return name[0].toUpperCase() + name.substr(1) + 'Controller';
|
||||||
|
}
|
||||||
|
|
||||||
|
function dashCase(str) {
|
||||||
|
return str.replace(/([A-Z])/g, function ($1) {
|
||||||
|
return '-' + $1.toLowerCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function boringController (model, value) {
|
||||||
|
return function () {
|
||||||
|
this[model] = value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function provideHelpers(fn, preInject) {
|
||||||
|
return function () {
|
||||||
|
var elt,
|
||||||
|
$compile,
|
||||||
|
$rootScope,
|
||||||
|
$router,
|
||||||
|
$templateCache,
|
||||||
|
$controllerProvider;
|
||||||
|
|
||||||
|
module('ng');
|
||||||
|
module('ngNewRouter');
|
||||||
|
module(function(_$controllerProvider_) {
|
||||||
|
$controllerProvider = _$controllerProvider_;
|
||||||
|
});
|
||||||
|
|
||||||
|
inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||||
|
$compile = _$compile_;
|
||||||
|
$rootScope = _$rootScope_;
|
||||||
|
$router = _$router_;
|
||||||
|
$templateCache = _$templateCache_;
|
||||||
|
});
|
||||||
|
|
||||||
|
function registerComponent(name, template, config) {
|
||||||
|
if (!template) {
|
||||||
|
template = '';
|
||||||
|
}
|
||||||
|
var ctrl;
|
||||||
|
if (!config) {
|
||||||
|
ctrl = function () {};
|
||||||
|
} else if (angular.isArray(config)) {
|
||||||
|
ctrl = function () {};
|
||||||
|
ctrl.$routeConfig = config;
|
||||||
|
} else if (typeof config === 'function') {
|
||||||
|
ctrl = config;
|
||||||
|
} else {
|
||||||
|
ctrl = function () {};
|
||||||
|
ctrl.prototype = config;
|
||||||
|
}
|
||||||
|
$controllerProvider.register(componentControllerName(name), ctrl);
|
||||||
|
put(name, template);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function put (name, template) {
|
||||||
|
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compile(template) {
|
||||||
|
var elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||||
|
$rootScope.$digest();
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn({
|
||||||
|
registerComponent: registerComponent,
|
||||||
|
$router: $router,
|
||||||
|
put: put,
|
||||||
|
compile: compile
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular": "1.3.5",
|
"angular": "1.3.5",
|
||||||
|
"angular-animate": "1.3.5",
|
||||||
|
"angular-mocks": "1.3.5",
|
||||||
"base64-js": "^0.0.8",
|
"base64-js": "^0.0.8",
|
||||||
"bower": "^1.3.12",
|
"bower": "^1.3.12",
|
||||||
"broccoli": "^0.15.3",
|
"broccoli": "^0.15.3",
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo =============================================================================
|
||||||
|
# go to project dir
|
||||||
|
SCRIPT_DIR=$(dirname $0)
|
||||||
|
# this is needed because we're running JS tests in Dartium too
|
||||||
|
source $SCRIPT_DIR/env_dart.sh
|
||||||
|
cd $SCRIPT_DIR/../..
|
||||||
|
|
||||||
|
./node_modules/.bin/gulp buildRouter.dev
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo =============================================================================
|
||||||
|
# go to project dir
|
||||||
|
SCRIPT_DIR=$(dirname $0)
|
||||||
|
source $SCRIPT_DIR/env_dart.sh
|
||||||
|
cd $SCRIPT_DIR/../..
|
||||||
|
|
||||||
|
./node_modules/.bin/gulp test.unit.router/ci --browsers=${KARMA_BROWSERS:-ChromeCanary}
|
|
@ -76,6 +76,7 @@ module.exports = function makeBrowserTree(options, destinationPath) {
|
||||||
exclude: [
|
exclude: [
|
||||||
'**/*.cjs',
|
'**/*.cjs',
|
||||||
'benchmarks/e2e_test/**',
|
'benchmarks/e2e_test/**',
|
||||||
|
'angular1_router/**',
|
||||||
// Exclude ES6 polyfill typings when tsc target=ES6
|
// Exclude ES6 polyfill typings when tsc target=ES6
|
||||||
'angular2/traceur-runtime.d.ts',
|
'angular2/traceur-runtime.d.ts',
|
||||||
'angular2/typings/es6-promise/**'
|
'angular2/typings/es6-promise/**'
|
||||||
|
|
|
@ -56,7 +56,7 @@ function stripModulePrefix(relativePath: string): string {
|
||||||
|
|
||||||
function getSourceTree() {
|
function getSourceTree() {
|
||||||
// Transpile everything in 'modules' except for rtts_assertions.
|
// Transpile everything in 'modules' except for rtts_assertions.
|
||||||
var tsInputTree = modulesFunnel(['**/*.js', '**/*.ts', '**/*.dart']);
|
var tsInputTree = modulesFunnel(['**/*.js', '**/*.ts', '**/*.dart'], ['angular1_router/**/*']);
|
||||||
var transpiled = ts2dart(tsInputTree, {
|
var transpiled = ts2dart(tsInputTree, {
|
||||||
generateLibraryName: true,
|
generateLibraryName: true,
|
||||||
generateSourceMap: false,
|
generateSourceMap: false,
|
||||||
|
@ -147,7 +147,7 @@ function getDocsTree() {
|
||||||
var licenses = new MultiCopy('', {
|
var licenses = new MultiCopy('', {
|
||||||
srcPath: 'LICENSE',
|
srcPath: 'LICENSE',
|
||||||
targetPatterns: ['modules/*'],
|
targetPatterns: ['modules/*'],
|
||||||
exclude: ['*/rtts_assert', '*/http', '*/upgrade'], // Not in dart.
|
exclude: ['*/rtts_assert', '*/http', '*/upgrade', '*/angular1_router'] // Not in dart.
|
||||||
});
|
});
|
||||||
licenses = stew.rename(licenses, stripModulePrefix);
|
licenses = stew.rename(licenses, stripModulePrefix);
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ function getDocsTree() {
|
||||||
relativePath => relativePath.replace(/\.dart\.md$/, '.md'));
|
relativePath => relativePath.replace(/\.dart\.md$/, '.md'));
|
||||||
// Copy all assets, ignore .js. and .dart. (handled above).
|
// Copy all assets, ignore .js. and .dart. (handled above).
|
||||||
var docs = modulesFunnel(['**/*.md', '**/*.png', '**/*.html', '**/*.css', '**/*.scss'],
|
var docs = modulesFunnel(['**/*.md', '**/*.png', '**/*.html', '**/*.css', '**/*.scss'],
|
||||||
['**/*.js.md', '**/*.dart.md']);
|
['**/*.js.md', '**/*.dart.md', 'angular1_router/**/*']);
|
||||||
|
|
||||||
var assets = modulesFunnel(['examples/**/*.json']);
|
var assets = modulesFunnel(['examples/**/*.json']);
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ module.exports = function makeNodeTree(destinationPath) {
|
||||||
'angular2/test/core/zone/**',
|
'angular2/test/core/zone/**',
|
||||||
'angular2/test/test_lib/fake_async_spec.ts',
|
'angular2/test/test_lib/fake_async_spec.ts',
|
||||||
'angular2/test/render/xhr_impl_spec.ts',
|
'angular2/test/render/xhr_impl_spec.ts',
|
||||||
'angular2/test/forms/**'
|
'angular2/test/forms/**',
|
||||||
|
'angular1_router/**'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue