feat(docs): use html5 history api for all routing in the docs app

- Configure our docs app to use new $location with html5 history api!

- Update simple node web server to serve index.html for all links
  (rewritting).

- Update .htaccess file to serve index.html for all links (rewritting).

- At runtime determine the base href path and attach it to the DOM. We
  needed the absolute URL to get all browsers to work well.

- Because of the above, we also need to dynamically determine all needed
  js/css resources and add them to the DOM. This was needed because FF6
  would eagerly fetch resources with wrong URL since the base element is
  added to the dom at runtime.

- All content html files were moved to the partials directory, because
  with the new html5 urls it was impossible to tell if request for
  http://domain/api/angular.filter.html was an html5 url for the html
  filter doc page, or an xhr/appcache request for the content html file
  for the html filter.

f
This commit is contained in:
Vojta Jina 2011-08-30 11:47:24 +02:00 committed by Igor Minar
parent 2bc39bb0b4
commit 13f92de624
12 changed files with 150 additions and 80 deletions

View file

@ -209,7 +209,8 @@ task :package => [:clean, :compile, :docs] do
text = f.read text = f.read
f.truncate 0 f.truncate 0
f.rewind f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js") f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end end
@ -217,10 +218,28 @@ task :package => [:clean, :compile, :docs] do
text = f.read text = f.read
f.truncate 0 f.truncate 0
f.rewind f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js") f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-debug.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-debug.html", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f| File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f|
text = f.read text = f.read
f.truncate 0 f.truncate 0
@ -232,14 +251,16 @@ task :package => [:clean, :compile, :docs] do
text = f.read text = f.read
f.truncate 0 f.truncate 0
f.rewind f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js") f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest", File::RDWR) do |f| File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest", File::RDWR) do |f|
text = f.read text = f.read
f.truncate 0 f.truncate 0
f.rewind f.rewind
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js") f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end end

View file

@ -28,14 +28,14 @@ controller.
In this example we have a simple app which consist of two screens: In this example we have a simple app which consist of two screens:
* Welcome: url `#` Show the user contact information. * Welcome: url `welcome` Show the user contact information.
* Settings: url `#/settings` Show an edit screen for user contact information. * Settings: url `settings` Show an edit screen for user contact information.
The two partials are defined in the following URLs: The two partials are defined in the following URLs:
* {@link ./examples/settings.html} * <a href="./examples/settings.html" ng:ext-link>./examples/settings.html</a>
* {@link ./examples/welcome.html} * <a href="./examples/welcome.html" ng:ext-link>./examples/welcome.html</a>
<doc:example> <doc:example>
<doc:source> <doc:source>
@ -79,7 +79,7 @@ The two partials are defined in the following URLs:
</script> </script>
<div ng:controller="AppCntl"> <div ng:controller="AppCntl">
<h1>Your App Chrome</h1> <h1>Your App Chrome</h1>
[ <a href="#!/welcome">Welcome</a> | <a href="#!/settings">Settings</a> ] [ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
<hr/> <hr/>
<span style="background-color: blue; color: white; padding: 3px;"> <span style="background-color: blue; color: white; padding: 3px;">
Partial: {{$route.current.template}} Partial: {{$route.current.template}}

View file

@ -327,8 +327,8 @@ describe('ngdoc', function(){
expect(doc.requires).toEqual([ expect(doc.requires).toEqual([
{name:'$service', text:'<p>for \n<code>A</code></p>'}, {name:'$service', text:'<p>for \n<code>A</code></p>'},
{name:'$another', text:'<p>for <code>B</code></p>'}]); {name:'$another', text:'<p>for <code>B</code></p>'}]);
expect(doc.html()).toContain('<a href="#!/api/angular.service.$service">$service</a>'); expect(doc.html()).toContain('<a href="api/angular.service.$service">$service</a>');
expect(doc.html()).toContain('<a href="#!/api/angular.service.$another">$another</a>'); expect(doc.html()).toContain('<a href="api/angular.service.$another">$another</a>');
expect(doc.html()).toContain('<p>for \n<code>A</code></p>'); expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
expect(doc.html()).toContain('<p>for <code>B</code></p>'); expect(doc.html()).toContain('<p>for <code>B</code></p>');
}); });
@ -429,13 +429,13 @@ describe('ngdoc', function(){
doc.parse(); doc.parse();
expect(doc.description). expect(doc.description).
toContain('foo <a href="#!/api/angular.foo"><code>angular.foo</code></a>'); toContain('foo <a href="api/angular.foo"><code>angular.foo</code></a>');
expect(doc.description). expect(doc.description).
toContain('da <a href="#!/api/angular.foo"><code>bar foo bar</code></a>'); toContain('da <a href="api/angular.foo"><code>bar foo bar</code></a>');
expect(doc.description). expect(doc.description).
toContain('dad<a href="#!/api/angular.foo"><code>angular.foo</code></a>'); toContain('dad<a href="api/angular.foo"><code>angular.foo</code></a>');
expect(doc.description). expect(doc.description).
toContain('<a href="#!/api/angular.directive.ng:foo"><code>ng:foo</code></a>'); toContain('<a href="api/angular.directive.ng:foo"><code>ng:foo</code></a>');
expect(doc.description). expect(doc.description).
toContain('<a href="http://angularjs.org">http://angularjs.org</a>'); toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
expect(doc.description). expect(doc.description).
@ -447,7 +447,7 @@ describe('ngdoc', function(){
'{@link\napi/angular.foo\na\nb}'); '{@link\napi/angular.foo\na\nb}');
doc.parse(); doc.parse();
expect(doc.description). expect(doc.description).
toContain('<a href="#!/api/angular.foo"><code>a b</code></a>'); toContain('<a href="api/angular.foo"><code>a b</code></a>');
}); });
}); });

View file

@ -29,7 +29,7 @@ function appCache(path) {
var resultPostfix = ["", var resultPostfix = ["",
"FALLBACK:", "FALLBACK:",
"/offline.html", "/ /build/docs/index.html",
"", "",
"# allow access to google analytics and twitter when we are online", "# allow access to google analytics and twitter when we are online",
"NETWORK:", "NETWORK:",
@ -68,7 +68,7 @@ function appCacheTemplate() {
"img/yellow_bkgnd.jpg", "img/yellow_bkgnd.jpg",
"", "",
"FALLBACK:", "FALLBACK:",
"/ offline.html", "/ /build/docs/offline.html",
"", "",
"# allow access to google analytics and twitter when we are online", "# allow access to google analytics and twitter when we are online",
"NETWORK:", "NETWORK:",

View file

@ -22,7 +22,7 @@ writer.makeDir('build/docs/syntaxhighlighter').then(function() {
ngdoc.merge(docs); ngdoc.merge(docs);
var fileFutures = []; var fileFutures = [];
docs.forEach(function(doc){ docs.forEach(function(doc){
fileFutures.push(writer.output(doc.section + '/' + doc.id + '.html', doc.html())); fileFutures.push(writer.output('partials/' + doc.section + '/' + doc.id + '.html', doc.html()));
}); });
writeTheRest(fileFutures); writeTheRest(fileFutures);
@ -43,28 +43,19 @@ function writeTheRest(writesFuture) {
writesFuture.push(writer.copyDir('img')); writesFuture.push(writer.copyDir('img'));
writesFuture.push(writer.copyDir('examples')); writesFuture.push(writer.copyDir('examples'));
var manifest = 'manifest="appcache.manifest"', var manifest = 'manifest="/build/docs/appcache.manifest"';
jq = '<script src="jquery.min.js"></script>',
ngMin = '<script src="../angular.min.js" ng:autobind></script>',
ng = '<script src="../angular.js" ng:autobind></script>';
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index.html', writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index.html',
writer.replace, {'doc:manifest': manifest, writer.replace, {'doc:manifest': manifest}));
'<!-- angular script place holder -->': ngMin}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html', writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html',
writer.replace, {'doc:manifest': manifest, writer.replace, {'doc:manifest': manifest}));
'<!-- angular script place holder -->': ngMin,
'<!-- jquery place holder -->': jq}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-debug.html', writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-debug.html',
writer.replace, {'doc:manifest': '', writer.replace, {'doc:manifest': ''}));
'<!-- angular script place holder -->': ng}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-debug.html', writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-debug.html',
writer.replace, {'doc:manifest': '', writer.replace, {'doc:manifest': ''}));
'<!-- angular script place holder -->': ng,
'<!-- jquery place holder -->': jq}));
writesFuture.push(writer.copyTpl('offline.html')); writesFuture.push(writer.copyTpl('offline.html'));
writesFuture.push(writer.copyTpl('docs-scenario.html')); writesFuture.push(writer.copyTpl('docs-scenario.html'));

View file

@ -133,7 +133,7 @@ Doc.prototype = {
if (!isFullUrl) self.links.push(absUrl); if (!isFullUrl) self.links.push(absUrl);
return '<a href="' + (isFullUrl ? '' + url : '#!/' + absUrl) + '">' return '<a href="' + absUrl + '">'
+ (isAngular ? '<code>' : '') + (isAngular ? '<code>' : '')
+ (title || url).replace(/\n/g, ' ') + (title || url).replace(/\n/g, ' ')
+ (isAngular ? '</code>' : '') + (isAngular ? '</code>' : '')
@ -243,7 +243,7 @@ Doc.prototype = {
} }
dom.h('Dependencies', self.requires, function(require){ dom.h('Dependencies', self.requires, function(require){
dom.tag('code', function(){ dom.tag('code', function(){
dom.tag('a', {href:"#!/api/angular.service." + require.name}, require.name); dom.tag('a', {href: 'api/angular.service.' + require.name}, require.name);
}); });
dom.html(require.text); dom.html(require.text);
}); });
@ -570,23 +570,23 @@ function scenarios(docs){
var specs = []; var specs = [];
specs.push('describe("angular+jqlite", function() {'); specs.push('describe("angular+jqlite", function() {');
appendSpecs('index.html'); appendSpecs('');
specs.push('});'); specs.push('});');
specs.push(''); specs.push('');
specs.push(''); specs.push('');
specs.push('describe("angular+jquery", function() {'); specs.push('describe("angular+jquery", function() {');
appendSpecs('index-jq.html'); appendSpecs('index-jq.html#!/');
specs.push('});'); specs.push('});');
return specs.join('\n'); return specs.join('\n');
function appendSpecs(htmlFile) { function appendSpecs(urlPrefix) {
docs.forEach(function(doc){ docs.forEach(function(doc){
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){'); specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
specs.push(' beforeEach(function(){'); specs.push(' beforeEach(function(){');
specs.push(' browser().navigateTo("' + htmlFile + '#!/' + doc.section + '/' + doc.id + '");'); specs.push(' browser().navigateTo("' + urlPrefix + doc.section + '/' + doc.id + '");');
specs.push(' });'); specs.push(' });');
specs.push(' '); specs.push(' ');
doc.scenarios.forEach(function(scenario){ doc.scenarios.forEach(function(scenario){

View file

@ -8,4 +8,8 @@
RewriteEngine on RewriteEngine on
RewriteCond %{HTTP_COOKIE} ng-offline="NG_VERSION_FULL" RewriteCond %{HTTP_COOKIE} ng-offline="NG_VERSION_FULL"
RewriteRule appcache.manifest appcache-offline.manifest RewriteRule appcache.manifest appcache-offline.manifest
## HTML5 URL Support ##
RewriteRule ^(guide|api|cookbook|misc|tutorial)(/.*)?$ index.html

View file

@ -4,7 +4,8 @@ function DocsController($location, $browser, $window, $cookies) {
var self = this, var self = this,
OFFLINE_COOKIE_NAME = 'ng-offline', OFFLINE_COOKIE_NAME = 'ng-offline',
DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/; DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/,
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
this.$location = $location; this.$location = $location;
@ -13,7 +14,7 @@ function DocsController($location, $browser, $window, $cookies) {
self.subpage = false; self.subpage = false;
self.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full); self.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
if (!$location.path()) { if (!$location.path() || INDEX_PATH.test($location.path())) {
$location.path('/api').replace(); $location.path('/api').replace();
} }
@ -40,11 +41,11 @@ function DocsController($location, $browser, $window, $cookies) {
}); });
this.getUrl = function(page){ this.getUrl = function(page){
return '#!/' + page.section + '/' + page.id; return page.section + '/' + page.id;
}; };
this.getCurrentPartial = function(){ this.getCurrentPartial = function(){
return this.partialId ? ('./' + this.sectionId + '/' + this.partialId + '.html') : ''; return this.partialId ? ('./partials/' + this.sectionId + '/' + this.partialId + '.html') : '';
}; };
this.getClass = function(page) { this.getClass = function(page) {
@ -127,7 +128,7 @@ function TutorialInstructionsCtrl($cookieStore) {
angular.service('$locationConfig', function() { angular.service('$locationConfig', function() {
return { return {
html5Mode: false, html5Mode: true,
hashPrefix: '!' hashPrefix: '!'
}; };
}); });

View file

@ -5,12 +5,66 @@
doc:manifest> doc:manifest>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
<meta name="fragment" content="!"> <meta name="fragment" content="!">
<link rel="stylesheet" href="docs-combined.css" type="text/css"/> <title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
<link rel="stylesheet" href="syntaxhighlighter/syntaxhighlighter-combined.css" type="text/css"/> <script type="text/javascript">
// dynamically add base tag as well as css and javascript files.
// we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources
// before the base attribute is added, causing 404 and terribly slow loading of the docs app.
(function() {
var indexFile = (location.pathname.match(/\/(index[^\.]*\.html)/) || ['', 'index.html'])[1],
rUrl = /(api|guide|misc|tutorial|cookbook|index[^\.]*\.html).*$/,
baseUrl = location.href.replace(rUrl, indexFile),
jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
debug = /index[^\.]*-debug\.html$/.test(baseUrl),
angularPath = debug ? '../angular.js' : '../angular.min.js',
headEl = document.getElementsByTagName('head')[0],
sync = true;
addTag('base', {href: baseUrl});
addTag('link', {rel: 'stylesheet', href: 'docs-combined.css', type: 'text/css'});
addTag('link', {rel: 'stylesheet', href: 'syntaxhighlighter/syntaxhighlighter-combined.css',
type: 'text/css'});
addTag('script', {src: 'syntaxhighlighter/syntaxhighlighter-combined.js'}, sync);
if (jQuery) addTag('script', {src: 'jquery.min.js'});
addTag('script', {src: angularPath, 'ng:autobind':''}, sync);
addTag('script', {src: 'docs-combined.js'}, sync);
addTag('script', {src: 'docs-keywords.js'}, sync);
function addTag(name, attributes, sync) {
var el = document.createElement(name),
attrName;
for (attrName in attributes) {
el.setAttribute(attrName, attributes[attrName]);
}
sync ? document.write(outerHTML(el)) : headEl.appendChild(el);
}
function outerHTML(node){
// if IE, Chrome take the internal method otherwise build one
return node.outerHTML || (
function(n){
var div = document.createElement('div'), h;
div.appendChild(n);
h = div.innerHTML;
div = null;
return h;
})(node);
}
})();
// force page reload when new update is available
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
window.applicationCache.swapCache();
window.location.reload();
}
}, false);
<script>
// GA asynchronous tracker // GA asynchronous tracker
var _gaq = _gaq || []; var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-8594346-3']); _gaq.push(['_setAccount', 'UA-8594346-3']);
@ -21,14 +75,6 @@
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})(); })();
// force page reload when new update is available
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
window.applicationCache.swapCache();
window.location.reload();
}
}, false);
</script> </script>
</head> </head>
@ -45,11 +91,11 @@
<ul id="navbar"> <ul id="navbar">
<li><a href="http://angularjs.org/">AngularJS</a></li> <li><a href="http://angularjs.org/">AngularJS</a></li>
<li><a href="#!/misc/started" ng:class="selectedSection('misc')">Getting Started</a></li> <li><a href="misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
<li><a href="#!/tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li> <li><a href="tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
<li><a href="#!/api" ng:class="selectedSection('api')">API Reference</a></li> <li><a href="api" ng:class="selectedSection('api')">API Reference</a></li>
<li><a href="#!/cookbook" ng:class="selectedSection('cookbook')">Examples</a></li> <li><a href="cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
<li><a href="#!/guide" ng:class="selectedSection('guide')">Developer Guide</a></li> <li><a href="guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
</ul> </ul>
<div id="sidebar"> <div id="sidebar">
@ -100,11 +146,5 @@
<button id="cacheButton" ng:click="enableOffline()">Let me have them all!</button> <button id="cacheButton" ng:click="enableOffline()">Let me have them all!</button>
</div> </div>
</div> </div>
<script src="syntaxhighlighter/syntaxhighlighter-combined.js"></script>
<!-- jquery place holder -->
<!-- angular script place holder -->
<script src="docs-combined.js"></script>
<script src="docs-keywords.js"></script>
</body> </body>
</html> </html>

View file

@ -91,6 +91,18 @@ StaticServlet.prototype.handleRequest = function(req, res) {
var parts = path.split('/'); var parts = path.split('/');
if (parts[parts.length-1].charAt(0) === '.') if (parts[parts.length-1].charAt(0) === '.')
return self.sendForbidden_(req, res, path); return self.sendForbidden_(req, res, path);
// docs rewriting
var REWRITE = /\/(guide|api|cookbook|misc|tutorial)\/.*$/,
IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/,
match;
if (!IGNORED.test(path) && (match = path.match(REWRITE))) {
path = path.replace(match[0], '/index.html');
sys.puts('Rewrite to ' + path);
}
// end of docs rewriting
fs.stat(path, function(err, stat) { fs.stat(path, function(err, stat) {
if (err) if (err)
return self.sendMissing_(req, res, path); return self.sendMissing_(req, res, path);

View file

@ -166,10 +166,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
<input name="value" /><br /> <input name="value" /><br />
<a id="link-1" href ng:click="value = 1">link 1</a> (link, don't reload)<br /> <a id="link-1" href ng:click="value = 1">link 1</a> (link, don't reload)<br />
<a id="link-2" href="" ng:click="value = 2">link 2</a> (link, don't reload)<br /> <a id="link-2" href="" ng:click="value = 2">link 2</a> (link, don't reload)<br />
<a id="link-3" ng:href="#!/{{'123'}}" ng:click="value = 3">link 3</a> (link, reload!)<br /> <a id="link-3" ng:href="/{{'123'}}" ng:ext-link>link 3</a> (link, reload!)<br />
<a id="link-4" href="" name="xx" ng:click="value = 4">anchor</a> (link, don't reload)<br /> <a id="link-4" href="" name="xx" ng:click="value = 4">anchor</a> (link, don't reload)<br />
<a id="link-5" name="xxx" ng:click="value = 5">anchor</a> (no link)<br /> <a id="link-5" name="xxx" ng:click="value = 5">anchor</a> (no link)<br />
<a id="link-6" ng:href="#!/{{value}}">link</a> (link, change hash) <a id="link-6" ng:href="/{{value}}" ng:ext-link>link</a> (link, change hash)
</doc:source> </doc:source>
<doc:scenario> <doc:scenario>
it('should execute ng:click but not reload when href without value', function() { it('should execute ng:click but not reload when href without value', function() {
@ -185,10 +185,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
}); });
it('should execute ng:click and change url when ng:href specified', function() { it('should execute ng:click and change url when ng:href specified', function() {
expect(element('#link-3').attr('href')).toBe("/123");
element('#link-3').click(); element('#link-3').click();
expect(input('value').val()).toEqual('3'); expect(browser().location().path()).toEqual('/123');
expect(element('#link-3').attr('href')).toBe("#!/123");
expect(browser().location().hash()).toEqual('!/123');
}); });
it('should execute ng:click but not reload when href empty string and name specified', function() { it('should execute ng:click but not reload when href empty string and name specified', function() {
@ -205,9 +205,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
it('should only change url when only ng:href', function() { it('should only change url when only ng:href', function() {
input('value').enter('6'); input('value').enter('6');
expect(element('#link-6').attr('href')).toBe("/6");
element('#link-6').click(); element('#link-6').click();
expect(browser().location().hash()).toEqual('!/6'); expect(browser().location().path()).toEqual('/6');
expect(element('#link-6').attr('href')).toBe("#!/6");
}); });
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>

View file

@ -1390,10 +1390,10 @@ angularWidget("@ng:non-bindable", noop);
function MyCtrl($route) { function MyCtrl($route) {
$route.when('/overview', $route.when('/overview',
{ controller: OverviewCtrl, { controller: OverviewCtrl,
template: 'guide/dev_guide.overview.html'}); template: 'partials/guide/dev_guide.overview.html'});
$route.when('/bootstrap', $route.when('/bootstrap',
{ controller: BootstrapCtrl, { controller: BootstrapCtrl,
template: 'guide/dev_guide.bootstrap.auto_bootstrap.html'}); template: 'partials/guide/dev_guide.bootstrap.auto_bootstrap.html'});
}; };
MyCtrl.$inject = ['$route']; MyCtrl.$inject = ['$route'];
@ -1401,9 +1401,9 @@ angularWidget("@ng:non-bindable", noop);
function OverviewCtrl(){} function OverviewCtrl(){}
</script> </script>
<div ng:controller="MyCtrl"> <div ng:controller="MyCtrl">
<a href="#!/overview">overview</a> | <a href="overview">overview</a> |
<a href="#!/bootstrap">bootstrap</a> | <a href="bootstrap">bootstrap</a> |
<a href="#!/undefined">undefined</a> <a href="undefined">undefined</a>
<br/> <br/>