Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'replacestate-external'

  • Loading branch information...
commit 49cf3af91a7f1fd2a05063bfbfffc15dbb74492b 2 parents 44eaf77 + df9e233
John Bender johnbender authored
1  Makefile
View
@@ -45,6 +45,7 @@ JSFILES = js/jquery.ui.widget.js \
js/jquery.mobile.page.js \
js/jquery.mobile.core.js \
js/jquery.mobile.navigation.js \
+ js/jquery.mobile.navigation.pushstate.js \
js/jquery.mobile.transition.js \
js/jquery.mobile.degradeInputs.js \
js/jquery.mobile.dialog.js \
1  build.xml
View
@@ -29,6 +29,7 @@
js/jquery.mobile.page.js,
js/jquery.mobile.core.js,
js/jquery.mobile.navigation.js,
+ js/jquery.mobile.navigation.pushstate.js,
js/jquery.mobile.transition.js,
js/jquery.mobile.degradeInputs.js,
js/jquery.mobile.dialog.js,
1  js/index.php
View
@@ -11,6 +11,7 @@
'jquery.mobile.page.js',
'jquery.mobile.core.js',
'jquery.mobile.navigation.js',
+ 'jquery.mobile.navigation.pushstate.js',
'jquery.mobile.transition.js',
'jquery.mobile.degradeInputs.js',
'jquery.mobile.dialog.js',
5 js/jquery.mobile.core.js
View
@@ -47,10 +47,12 @@
// Error response message - appears when an Ajax page request fails
pageLoadErrorMessage: "Error Loading Page",
-
+
//automatically initialize the DOM when it's ready
autoInitializePage: true,
+ pushStateEnabled: true,
+
// Support conditions that must be met in order to proceed
// default enhanced qualifications are media query support OR IE 7+
gradeA: function(){
@@ -164,3 +166,4 @@
return $.find( expr, null, null, [ node ] ).length > 0;
};
})( jQuery, this );
+
103 js/jquery.mobile.navigation.pushstate.js
View
@@ -0,0 +1,103 @@
+/*
+* jQuery Mobile Framework : history.pushState support, layered on top of hashchange
+* Copyright (c) jQuery Project
+* Dual licensed under the MIT or GPL Version 2 licenses.
+* http://jquery.org/license
+*/
+( function( $, window ) {
+ // For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents
+ // Scope self to pushStateHandler so we can reference it sanely within the
+ // methods handed off as event handlers
+ var pushStateHandler = {},
+ self = pushStateHandler,
+ $win = $( window ),
+ url = $.mobile.path.parseUrl( location.href );
+
+ $.extend( pushStateHandler, {
+ // TODO move to a path helper, this is rather common functionality
+ initialFilePath: (function() {
+ return url.pathname + url.search;
+ })(),
+
+ initialHref: url.hrefNoHash,
+
+ // Flag for tracking if a Hashchange naturally occurs after each popstate + replace
+ hashchangeFired: false,
+
+ state: function() {
+ return {
+ hash: location.hash || "#" + self.initialFilePath,
+ title: document.title,
+
+ // persist across refresh
+ initialHref: self.initialHref
+ };
+ },
+
+ // on hash change we want to clean up the url
+ // NOTE this takes place *after* the vanilla navigation hash change
+ // handling has taken place and set the state of the DOM
+ onHashChange: function( e ) {
+ var href, state;
+
+ self.hashchangeFired = true;
+
+ // only replaceState when the hash doesn't represent an embeded page
+ if( $.mobile.path.isPath(location.hash) ) {
+
+ // propulate the hash when its not available
+ state = self.state();
+
+ // make the hash abolute with the current href
+ href = $.mobile.path.makeUrlAbsolute( state.hash.replace("#", ""), location.href );
+
+ // replace the current url with the new href and store the state
+ history.replaceState( state, document.title, href );
+ }
+ },
+
+ // on popstate (ie back or forward) we need to replace the hash that was there previously
+ // cleaned up by the additional hash handling
+ onPopState: function( e ) {
+ var poppedState = e.originalEvent.state;
+
+ // if there's no state its not a popstate we care about, ie chrome's initial popstate
+ // or forward popstate
+ if( poppedState ) {
+
+ // replace the current url with the equivelant hash so that the hashchange binding in vanilla nav
+ // can do its thing one triggered below
+ history.replaceState( poppedState, poppedState.title, poppedState.initialHref + poppedState.hash );
+
+ // Urls that reference subpages will fire their own hashchange, so we don't want to trigger 2 in that case.
+ self.hashchangeFired = false;
+
+ setTimeout(function() {
+ if( !self.hashchangeFired ) {
+ $win.trigger( "hashchange" );
+ }
+
+ self.hashchangeFired = false;
+ }, 0);
+ }
+ },
+
+ init: function() {
+ $win.bind( "hashchange", self.onHashChange );
+
+ // Handle popstate events the occur through history changes
+ $win.bind( "popstate", self.onPopState );
+
+ // if there's no hash, we need to replacestate for returning to home
+ if ( location.hash === "" ) {
+ history.replaceState( self.state(), document.title, location.href );
+ }
+ }
+ });
+
+ $( function() {
+ if( $.mobile.pushStateEnabled && $.support.pushState ){
+ pushStateHandler.init();
+ }
+ });
+})( jQuery, this );
2  js/jquery.mobile.support.js
View
@@ -67,7 +67,7 @@ $.extend( $.support, {
orientation: "orientation" in window,
touch: "ontouchend" in document,
cssTransitions: "WebKitTransitionEvent" in window,
- pushState: !!history.pushState,
+ pushState: "pushState" in history && "replaceState" in history,
mediaquery: $.mobile.media( "only all" ),
cssPseudoElement: !!propExists( "content" ),
boxShadow: !!propExists( "boxShadow" ) && !bb,
27 tests/jquery.testHelper.js
View
@@ -16,6 +16,18 @@
}
},
+ // TODO prevent test suite loads when the browser doesn't support push state
+ // and push-state false is defined.
+ setPushStateFor: function( libs ) {
+ if( $.support.pushState && location.search.indexOf( "push-state" ) >= 0 ) {
+ $.support.pushState = false;
+ }
+
+ $.each(libs, function(i, l) {
+ $( "<script>", { src: l }).appendTo("head");
+ });
+ },
+
reloads: {},
reloadLib: function(libName){
@@ -26,8 +38,8 @@
};
}
- var lib = this.reloads[libName].lib.clone(),
- src = lib.attr('src');
+ var lib = this.reloads[libName].lib.clone(),
+ src = lib.attr('src');
//NOTE append "cache breaker" to force reload
lib.attr('src', src + "?" + this.reloads[libName].count++);
@@ -116,6 +128,17 @@
return returnVal;
};
+ },
+
+ assertUrlLocation: function( args ) {
+ var parts = $.mobile.path.parseUrl( location.href ),
+ pathnameOnward = location.href.replace( parts.domain, "" );
+
+ if( $.support.pushState ) {
+ same( pathnameOnward, args.hashOrPush || args.push, args.report );
+ } else {
+ same( parts.hash, "#" + (args.hashOrPush || args.hash), args.report );
+ }
}
};
})(jQuery);
39 tests/unit/listview/listview_core.js
View
@@ -4,11 +4,13 @@
// TODO split out into seperate test files
(function($){
+ var home = $.mobile.path.parseUrl( location.href ).pathname;
$.mobile.defaultTransition = "none";
- module('Basic Linked list', {
+
+ module( "Basic Linked list", {
setup: function(){
- $.testHelper.openPage("#basic-linked-test");
+ $.testHelper.openPage( "#basic-linked-test" );
}
});
@@ -213,7 +215,7 @@
asyncTest( "changes to the read only page when hash is changed", function() {
$.testHelper.pageSequence([
function(){
- $.testHelper.openPage("#read-only-list-test")
+ $.testHelper.openPage("#read-only-list-test");
},
function(){
@@ -588,6 +590,8 @@
ok( $("#enhancetest").trigger("create").find(".ui-listview").length, "enhancements applied" );
});
+ module( "Cached Linked List" );
+
var findNestedPages = function(selector){
return $( selector + " #topmost" ).listview( 'childPages' );
};
@@ -596,7 +600,7 @@
$.testHelper.pageSequence([
function(){
//reset for relative url refs
- $.testHelper.openPage( "#" + location.pathname );
+ $.testHelper.openPage( "#" + home );
},
function(){
@@ -605,7 +609,11 @@
function(){
ok( findNestedPages( "#uncached-nested-list" ).length > 0, "verify that there are nested pages" );
- $.testHelper.openPage( "#" + location.pathname + "cache-tests/clear.html" );
+ $.testHelper.openPage( "#" + home );
+ },
+
+ function() {
+ $.testHelper.openPage( "#cache-tests/clear.html" );
},
function(){
@@ -620,7 +628,7 @@
$.testHelper.pageSequence([
function(){
//reset for relative url refs
- $.testHelper.openPage( "#" + location.pathname );
+ $.testHelper.openPage( "#" + home );
},
function(){
@@ -629,7 +637,11 @@
function(){
ok( findNestedPages( "#cached-nested-list" ).length > 0, "verify that there are nested pages" );
- $.testHelper.openPage( "#" + location.pathname + "cache-tests/clear.html" );
+ $.testHelper.openPage( "#" + home );
+ },
+
+ function() {
+ $.testHelper.openPage( "#cache-tests/clear.html" );
},
function(){
@@ -643,7 +655,7 @@
$.testHelper.pageSequence([
function(){
//reset for relative url refs
- $.testHelper.openPage( "#" + location.pathname );
+ $.testHelper.openPage( "#" + home );
},
function(){
@@ -652,7 +664,11 @@
function(){
same( $("#cached-nested-list").length, 1 );
- $.testHelper.openPage("#" + location.pathname + "cache-tests/clear.html");
+ $.testHelper.openPage( "#" + home );
+ },
+
+ function() {
+ $.testHelper.openPage( "#cache-tests/clear.html" );
},
function(){
@@ -667,6 +683,11 @@
expect( listPage.find("li").length );
$.testHelper.pageSequence( [
+ function(){
+ //reset for relative url refs
+ $.testHelper.openPage( "#" + home );
+ },
+
function() {
$.testHelper.openPage( "#search-filter-test" );
},
7 tests/unit/navigation/base-tests.html
View
@@ -26,8 +26,11 @@
<link rel="stylesheet" href="../../../../../themes/default/"/>
<link rel="stylesheet" href="../../../../../external/qunit.css"/>
<script src="../../../../../external/qunit.js"></script>
-
- <script src="../../navigation_base.js"></script>
+ <script type="text/javascript">
+ $.testHelper.setPushStateFor([
+ "../../navigation_base.js"
+ ]);
+ </script>
</head>
<body>
102 tests/unit/navigation/index.html
View
@@ -15,10 +15,14 @@
<link rel="stylesheet" href="../../../external/qunit.css"/>
<script src="../../../external/qunit.js"></script>
- <script src="navigation_transitions.js"></script>
- <script src="navigation_helpers.js"></script>
- <script src="navigation_core.js"></script>
- <script src="navigation_paths.js"></script>
+ <script type="text/javascript">
+ $.testHelper.setPushStateFor([
+ "navigation_transitions.js",
+ "navigation_helper.js",
+ "navigation_core.js",
+ "navigation_paths.js"
+ ]);
+ </script>
</head>
<body>
@@ -28,69 +32,69 @@ <h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests">
</ol>
-<div id="harmless-default-page" data-nstest-role="page">
+<div id="harmless-default-page" data-nstest-role="page">
</div>
-<div id="foo" data-nstest-role="page" class="foo-class">
+<div id="foo" data-nstest-role="page" class="foo-class">
<a href="#bar" data-nstest-transition="flip"></a>
</div>
-<div id="prefetch" data-nstest-role="page">
+<div id="prefetch" data-nstest-role="page">
<a href="prefetched.html" data-nstest-prefetch>Prefetch test</a>
</div>
-<div id="foozball" data-nstest-role="page">
+<div id="foozball" data-nstest-role="page">
</div>
-<div id="bar" data-nstest-role="page">
+<div id="bar" data-nstest-role="page">
<a href="#baz"></a>
</div>
-<div id="baz" data-nstest-role="page">
+<div id="baz" data-nstest-role="page">
<a href="#foo"></a>
</div>
-<div id="fade-trans" data-nstest-role="page">
+<div id="fade-trans" data-nstest-role="page">
<a href="#flip-trans" data-nstest-transition="fade"></a>
</div>
-<div id="flip-trans" data-nstest-role="page">
+<div id="flip-trans" data-nstest-role="page">
<a href="#fade-trans" data-nstest-transition="flip"></a>
</div>
-<div id="no-trans" data-nstest-role="page">
+<div id="no-trans" data-nstest-role="page">
<a href="#pop-trans"></a>
</div>
-<div id="pop-trans" data-nstest-role="page">
+<div id="pop-trans" data-nstest-role="page">
<a href="#no-trans" data-nstest-transition="pop"></a>
</div>
-<div id="default-trans" data-nstest-role="page">
+<div id="default-trans" data-nstest-role="page">
<a href="#no-trans"></a>
</div>
-<div id="data-url" data-nstest-role="page">
+<div id="data-url" data-nstest-role="page">
<a href="data-url-tests/data-url.html"></a>
</div>
-<div id="non-data-url" data-nstest-role="page">
+<div id="non-data-url" data-nstest-role="page">
<a href="data-url-tests/non-data-url.html"></a>
</div>
-<div id="nested-data-url" data-nstest-role="page">
+<div id="nested-data-url" data-nstest-role="page">
<a href="data-url-tests/nested.html"></a>
</div>
-<div id="single-quotes-data-url" data-nstest-role="page">
+<div id="single-quotes-data-url" data-nstest-role="page">
<a href="data-url-tests/single-quotes.html"></a>
</div>
-<div id="reverse-attr-data-url" data-nstest-role="page">
+<div id="reverse-attr-data-url" data-nstest-role="page">
<a href="data-url-tests/reverse-attr.html"></a>
</div>
-<div id="ajax-disabled-form" data-nstest-role="page">
+<div id="ajax-disabled-form" data-nstest-role="page">
<form method="POST" id="non-ajax-form" action="/ajax-disabled-form" data-nstest-ajax="false">
</form>
@@ -101,7 +105,7 @@ <h2 id="qunit-userAgent"></h2>
</form>
</div>
-<div id="default-trans-dialog" data-nstest-role="page">
+<div id="default-trans-dialog" data-nstest-role="page">
<a href="#no-trans-dialog" data-nstest-rel="dialog"></a>
</div>
@@ -109,34 +113,34 @@ <h2 id="qunit-userAgent"></h2>
</div>
<div id="dup-history-first" data-nstest-role="page">
- <a href="#dup-history-second" data-nstest-transition="slideup" data-nstest-role="button" >
+ <a href="#dup-history-second" data-nstest-transition="slideup" data-nstest-role="button" >
Page 2
</a>
</div>
<div id="dup-history-second" data-nstest-role="page">
- <a href="#dup-history-first" data-nstest-transition="slideup" data-nstest-role="button">
+ <a href="#dup-history-first" data-nstest-transition="slideup" data-nstest-role="button">
Page 1
</a>
- <a href="#dup-history-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
+ <a href="#dup-history-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
</div>
<div id="dup-history-dialog" data-nstest-role="dialog">
- <div data-nstest-role="header" data-nstest-position="inline">
- <h1>Dialog</h1>
- </div>
+ <div data-nstest-role="header" data-nstest-position="inline">
+ <h1>Dialog</h1>
+ </div>
</div>
<div id="skip-dialog-first" data-nstest-role="page">
- <div data-nstest-role="content">
- <a href="#skip-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
- </div>
+ <div data-nstest-role="content">
+ <a href="#skip-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
+ </div>
</div>
<div id="skip-dialog" data-nstest-role="dialog">
- <div data-nstest-role="content">
- <a href="#skip-dialog-second">Page 2</a>
- </div>
+ <div data-nstest-role="content">
+ <a href="#skip-dialog-second">Page 2</a>
+ </div>
</div>
<div id="skip-dialog-second" data-nstest-role="page">
@@ -145,33 +149,33 @@ <h2 id="qunit-userAgent"></h2>
<div id="nested-dialog-page" data-nstest-role="page">
- <div data-nstest-role="content">
- <a href="#nested-dialog-first">Dialog</a>
- </div>
+ <div data-nstest-role="content">
+ <a href="#nested-dialog-first">Dialog</a>
+ </div>
</div>
<div id="nested-dialog-first" data-nstest-role="dialog">
- <div data-nstest-role="content">
- <a href="#nested-dialog-second">Dialog 2</a>
- </div>
+ <div data-nstest-role="content">
+ <a href="#nested-dialog-second">Dialog 2</a>
+ </div>
</div>
<div id="nested-dialog-second" data-nstest-role="dialog">
</div>
<div id="relative-after-embeded-page-first" data-nstest-role="page">
- <div data-nstest-role="content">
- <a href="#relative-after-embeded-page-second">second page</a>
- </div>
+ <div data-nstest-role="content">
+ <a href="#relative-after-embeded-page-second">second page</a>
+ </div>
</div>
<div id="relative-after-embeded-page-second" data-nstest-role="page">
- <div data-nstest-role="content">
- <a href="data-url-tests/data-url.html">file path page</a>
- </div>
+ <div data-nstest-role="content">
+ <a href="data-url-tests/data-url.html">file path page</a>
+ </div>
</div>
-<div id="ajax-title-page" data-nstest-title="Title Attr 1" data-nstest-role="page">
+<div id="ajax-title-page" data-nstest-title="Title Attr 1" data-nstest-role="page">
<a href="title1.html" id="titletest1" data-nstest-transition="none">test</a>
<a href="title2.html" id="titletest2" data-nstest-transition="none">test</a>
<a href="title3.html" id="titletest3" data-nstest-transition="none">test</a>
@@ -243,8 +247,8 @@ <h2 id="qunit-userAgent"></h2>
<input type="hidden" name="foo" value="1">
<input type="hidden" name="bar" value="2">
</form>
- <a href="form-tests/form-no-action.html">External page containing form with no action.</a>
- </div>
+ <a href="form-tests/form-no-action.html">External page containing form with no action.</a>
+ </div>
</div>
<div id="active-state-page1" data-nstest-role="page">
93 tests/unit/navigation/navigation_base.js
View
@@ -21,21 +21,28 @@
$.testHelper.pageSequence([
function(){
// Navigate from default internal page to another internal page.
- $.testHelper.openPage("#internal-page-2");
+ $.testHelper.openPage( "#internal-page-2" );
},
function(){
// Verify that we are on the 2nd internal page.
- same(location.hash, "#internal-page-2", "navigate to internal page");
+ $.testHelper.assertUrlLocation({
+ push: location.pathname + "#internal-page-2",
+ hash: "internal-page-2",
+ report: "navigate to internal page"
+ });
// Navigate to a page that is in the base directory. Note that the application
- // document and this new page are *NOT* in the same directory.
- $("#internal-page-2 .bp1").click();
+ // document and this new page are *NOT* in the same directory.
+ $("#internal-page-2 .bp1").click();
},
function(){
// Verify that we are on the expected page.
- same(location.hash, "#" + baseDir + "base-page-1.html", "navigate from internal page to page in base directory");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: baseDir + "base-page-1.html",
+ report: "navigate from internal page to page in base directory"
+ });
// Navigate to another page in the same directory as the current page.
$("#base-page-1 .bp2").click();
@@ -43,7 +50,10 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#" + baseDir + "base-page-2.html", "navigate from base directory page to another base directory page");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: baseDir + "base-page-2.html",
+ report: "navigate from base directory page to another base directory page"
+ });
// Navigate to another page in a directory that is the sibling of the base.
$("#base-page-2 .cp1").click();
@@ -51,7 +61,10 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#" + contentDir + "content-page-1.html", "navigate from base directory page to a page in a different directory hierarchy");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: contentDir + "content-page-1.html",
+ report: "navigate from base directory page to a page in a different directory hierarchy"
+ });
// Navigate to another page in a directory that is the sibling of the base.
$("#content-page-1 .cp2").click();
@@ -59,7 +72,10 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#" + contentDir + "content-page-2.html", "navigate to another page within the same non-base directory hierarchy");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: contentDir + "content-page-2.html",
+ report: "navigate to another page within the same non-base directory hierarchy"
+ });
// Navigate to an internal page.
$("#content-page-2 .ip1").click();
@@ -67,7 +83,11 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#internal-page-1", "navigate from a page in a non-base directory to an internal page");
+ $.testHelper.assertUrlLocation({
+ push: location.pathname + "#internal-page-1",
+ hash: "internal-page-1",
+ report: "navigate from a page in a non-base directory to an internal page"
+ });
// Try calling changePage() directly with a relative path.
$.mobile.changePage("base-page-1.html");
@@ -75,7 +95,10 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#" + baseDir + "base-page-1.html", "call changePage() with a filename (no path)");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: baseDir + "base-page-1.html",
+ report: "call changePage() with a filename (no path)"
+ });
// Try calling changePage() directly with a relative path.
$.mobile.changePage("../content/content-page-1.html");
@@ -83,7 +106,10 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#" + contentDir + "content-page-1.html", "call changePage() with a relative path containing up-level references");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: contentDir + "content-page-1.html",
+ report: "call changePage() with a relative path containing up-level references"
+ });
// Try calling changePage() with an id
$.mobile.changePage("content-page-2.html");
@@ -91,7 +117,10 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#" + contentDir + "content-page-2.html", "call changePage() with a relative path should resolve relative to current page");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: contentDir + "content-page-2.html",
+ report: "call changePage() with a relative path should resolve relative to current page"
+ });
// test that an internal page works
$("a.ip2").click();
@@ -99,33 +128,47 @@
function(){
// Verify that we are on the expected page.
- same(location.hash, "#internal-page-2", "call changePage() with a page id");
+ $.testHelper.assertUrlLocation({
+ hash: "internal-page-2",
+ push: location.pathname + "#internal-page-2",
+ report: "call changePage() with a page id"
+ });
// Try calling changePage() with an id
$.mobile.changePage("internal-page-1");
},
function(){
+ // Verify that we are on the expected page.
+ $.testHelper.assertUrlLocation({
+ hash: "internal-page-2",
+ push: location.pathname + "#internal-page-2",
+ report: "calling changePage() with a page id that is not prefixed with '#' should not change page"
+ });
+
// Previous load should have failed and left us on internal-page-2.
- same(location.hash, "#internal-page-2", "calling changePage() with a page id that is not prefixed with '#' should not change page");
start();
- }]);
+ }
+ ]);
});
asyncTest( "internal form with no action submits to document URL", function(){
-
$.testHelper.pageSequence([
// open our test page
function(){
- $.testHelper.openPage("#internal-no-action-form-page");
+ $.testHelper.openPage( "#internal-no-action-form-page" );
},
function(){
- $("#internal-no-action-form-page form").eq(0).submit();
+ $( "#internal-no-action-form-page form" ).eq( 0 ).submit();
},
function(){
- same(location.hash, "#" + location.pathname + "?foo=1&bar=2", "hash should match document url and not base url");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: location.pathname + "?foo=1&bar=2",
+ report: "hash should match document url and not base url"
+ });
+
start();
}
]);
@@ -140,16 +183,22 @@
function(){
// Make sure we actually navigated to the external page.
- same(location.hash, "#" + contentDir + "content-page-1.html", "should be on content-page-1.html");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: contentDir + "content-page-1.html",
+ report: "should be on content-page-1.html"
+ });
// Now submit the form in the external page.
$("#content-page-1 form").eq(0).submit();
},
function(){
- same(location.hash, "#" + contentDir + "content-page-1.html?foo=1&bar=2", "hash should match page url and not document url");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: contentDir + "content-page-1.html?foo=1&bar=2",
+ report: "hash should match page url and not document url"
+ });
+
start();
}]);
});
-
})(jQuery);
182 tests/unit/navigation/navigation_core.js
View
@@ -2,24 +2,40 @@
* mobile navigation unit tests
*/
(function($){
+ // TODO move siteDirectory over to the nav path helper
var changePageFn = $.mobile.changePage,
- originalTitle = document.title,
- siteDirectory = location.pathname.replace(/[^/]+$/, ""),
- navigateTestRoot = function(){
- $.testHelper.openPage( "#" + location.pathname );
- };
+ originalTitle = document.title,
+ siteDirectory = location.pathname.replace( /[^/]+$/, "" ),
+ home = $.mobile.path.parseUrl(location.pathname).directory,
+ navigateTestRoot = function(){
+ $.testHelper.openPage( "#" + location.pathname );
+ };
module('jquery.mobile.navigation.js', {
setup: function(){
$.mobile.changePage = changePageFn;
document.title = originalTitle;
- if ( location.hash ) {
+ var pageReset = function( hash ) {
+ hash = hash || "";
+
stop();
- $(document).one("changepage", function() {
+
+ $(document).one( "changepage", function() {
start();
- } );
- location.hash = "";
+ });
+
+ location.hash = "#" + hash;
+ };
+
+ // force the page reset for hash based tests
+ if ( location.hash && !$.support.pushState ) {
+ pageReset();
+ }
+
+ // force the page reset for all pushstate tests
+ if ( $.support.pushState ) {
+ pageReset( home );
}
$.mobile.urlHistory.stack = [];
@@ -52,7 +68,7 @@
asyncTest( "external page is cached in the DOM after pagehide", function(){
$.testHelper.pageSequence([
navigateTestRoot,
-
+
function(){
$.mobile.changePage( "cached-external.html" );
},
@@ -72,30 +88,29 @@
});
asyncTest( "external page is cached in the DOM after pagehide when option is set globally", function(){
- $.testHelper.pageSequence([
- navigateTestRoot,
-
- function(){
- $.mobile.page.prototype.options.domCache = true;
- $.mobile.changePage( "external.html" );
- },
-
- // page is pulled and displayed in the dom
- function(){
- same( $( "#external-test" ).length, 1 );
- window.history.back();
- },
-
- // external test page is cached in the dom after transitioning away
- function(){
- same( $( "#external-test" ).length, 1 );
- $.mobile.page.prototype.options.domCache = false;
- $( "#external-test" ).remove();
- start();
- }
- ]);
- });
-
+ $.testHelper.pageSequence([
+ navigateTestRoot,
+
+ function(){
+ $.mobile.page.prototype.options.domCache = true;
+ $.mobile.changePage( "external.html" );
+ },
+
+ // page is pulled and displayed in the dom
+ function(){
+ same( $( "#external-test" ).length, 1 );
+ window.history.back();
+ },
+
+ // external test page is cached in the dom after transitioning away
+ function(){
+ same( $( "#external-test" ).length, 1 );
+ $.mobile.page.prototype.options.domCache = false;
+ $( "#external-test" ).remove();
+ start();
+ }]);
+ });
+
asyncTest( "forms with data attribute ajax set to false will not call changePage", function(){
var called = false;
var newChangePage = function(){
@@ -215,12 +230,17 @@
testListening( $.mobile.hashListeningEnabled );
});
- var testDataUrlHash = function(linkSelector, hashRegex){
+ var testDataUrlHash = function( linkSelector, matches ) {
$.testHelper.pageSequence([
function(){ window.location.hash = ""; },
function(){ $(linkSelector).click(); },
function(){
- ok(hashRegex.test(location.hash), "should match the regex");
+ $.testHelper.assertUrlLocation(
+ $.extend(matches, {
+ report: "url or hash should match"
+ })
+ );
+
start();
}
]);
@@ -229,19 +249,22 @@
};
test( "when loading a page where data-url is not defined on a sub element hash defaults to the url", function(){
- testDataUrlHash("#non-data-url a", new RegExp("^#" + siteDirectory + "data-url-tests/non-data-url.html$"));
+ testDataUrlHash( "#non-data-url a", {hashOrPush: siteDirectory + "data-url-tests/non-data-url.html"} );
});
test( "data url works for nested paths", function(){
- testDataUrlHash("#nested-data-url a", /^#foo\/bar.html$/);
+ var url = "foo/bar.html";
+ testDataUrlHash( "#nested-data-url a", {hash: url, push: home + url} );
});
test( "data url works for single quoted paths and roles", function(){
- testDataUrlHash("#single-quotes-data-url a", /^#foo\/bar\/single.html$/);
+ var url = "foo/bar/single.html";
+ testDataUrlHash( "#single-quotes-data-url a", {hash: url, push: home + url} );
});
test( "data url works when role and url are reversed on the page element", function(){
- testDataUrlHash("#reverse-attr-data-url a", /^#foo\/bar\/reverse.html$/);
+ var url = "foo/bar/reverse.html";
+ testDataUrlHash( "#reverse-attr-data-url a", {hash: url, push: home + url} );
});
asyncTest( "last entry choosen amongst multiple identical url history stack entries on hash change", function(){
@@ -283,7 +306,12 @@
// make sure we're at the first page and not the dialog
function(){
- same(location.hash, "#skip-dialog-first", "should be the first page in the sequence");
+ $.testHelper.assertUrlLocation({
+ hash: "skip-dialog-first",
+ push: home + "#skip-dialog-first",
+ report: "should be the first page in the sequence"
+ });
+
start();
}]);
});
@@ -307,12 +335,16 @@
// make sure we're on the second page and not the dialog
function(){
- same(location.hash, "#skip-dialog-second", "should be the second page after the dialog");
+ $.testHelper.assertUrlLocation({
+ hash: "skip-dialog-second",
+ push: home + "#skip-dialog-second",
+ report: "should be the second page after the dialog"
+ });
+
start();
}]);
});
-
asyncTest( "going back from a dialog triggered from a dialog should result in the first dialog ", function(){
$.testHelper.pageSequence([
// setup
@@ -449,7 +481,11 @@
},
function(){
- same(location.hash, "#" + siteDirectory + "data-url-tests/non-data-url.html?foo=bar");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: home + "data-url-tests/non-data-url.html?foo=bar",
+ report: "the hash or url has query params"
+ });
+
ok($(".ui-page-active").jqmData("url").indexOf("?foo=bar") > -1, "the query params are in the data url");
start();
}
@@ -467,12 +503,20 @@
},
function(){
- same(location.hash, "#" + siteDirectory + "data-url-tests/non-data-url.html?foo=bar");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: home + "data-url-tests/non-data-url.html?foo=bar",
+ report: "the hash or url has query params"
+ });
+
$("#query-param-anchor").click();
},
function(){
- same(location.hash, "#" + siteDirectory + "data-url-tests/non-data-url.html?foo=bar");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: home + "data-url-tests/non-data-url.html?foo=bar",
+ report: "the hash or url still has query params"
+ });
+
start();
}
]);
@@ -481,7 +525,7 @@
// Special handling inside navigation because query params must be applied to the hash
// or absolute reference and dialogs apply extra information int the hash that must be removed
asyncTest( "query param link from a dialog to itself should be a not add another dialog", function(){
- var firstDialogHash;
+ var firstDialogLoc;
$.testHelper.pageSequence([
// open our test page
@@ -502,19 +546,18 @@
// attempt to navigate to the same link
function(){
// store the current hash for comparison (with one dialog hash key)
- firstDialogHash = location.hash;
+ firstDialogLoc = location.hash || location.href;
$("#dialog-param-link-page a").click();
},
function(){
- same(location.hash, firstDialogHash, "additional dialog hash key not added");
+ same(location.hash || location.href, firstDialogLoc, "additional dialog hash key not added");
start();
}
]);
});
- asyncTest( "query data passed as string to changePage is appended to URL", function(){
-
+ asyncTest( "query data passed as string to changePage is appended to URL", function(){
$.testHelper.pageSequence([
// open our test page
function(){
@@ -524,14 +567,17 @@
},
function(){
- same(location.hash, "#" + siteDirectory + "form-tests/changepage-data.html?foo=1&bar=2");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: home + "form-tests/changepage-data.html?foo=1&bar=2",
+ report: "the hash or url still has query params"
+ });
+
start();
}
]);
});
asyncTest( "query data passed as object to changePage is appended to URL", function(){
-
$.testHelper.pageSequence([
// open our test page
function(){
@@ -544,14 +590,17 @@
},
function(){
- same(location.hash, "#" + siteDirectory + "form-tests/changepage-data.html?foo=3&bar=4");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: home + "form-tests/changepage-data.html?foo=3&bar=4",
+ report: "the hash or url still has query params"
+ });
+
start();
}
]);
});
asyncTest( "refresh of a dialog url should not duplicate page", function(){
-
$.testHelper.pageSequence([
// open our test page
function(){
@@ -560,15 +609,19 @@
},
function(){
- same(location.hash, "#foo&ui-state=dialog", "hash should match what was loaded");
- same($(".foo-class").length, 1, "should only have one instance of foo-class in the document");
+ $.testHelper.assertUrlLocation({
+ hash: "foo&ui-state=dialog",
+ push: home + "#foo&ui-state=dialog",
+ report: "hash should match what was loaded"
+ });
+
+ same( $(".foo-class").length, 1, "should only have one instance of foo-class in the document" );
start();
}
]);
});
asyncTest( "internal form with no action submits to document URL", function(){
-
$.testHelper.pageSequence([
// open our test page
function(){
@@ -580,14 +633,17 @@
},
function(){
- same(location.hash, "#" + location.pathname + "?foo=1&bar=2", "hash should match what was loaded");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: home + "?foo=1&bar=2",
+ report: "hash should match what was loaded"
+ });
+
start();
}
]);
});
asyncTest( "external page containing form with no action submits to page URL", function(){
-
$.testHelper.pageSequence([
// open our test page
function(){
@@ -603,7 +659,11 @@
},
function(){
- same(location.hash, "#" + siteDirectory + "form-tests/form-no-action.html?foo=1&bar=2", "hash should match page url and not document url");
+ $.testHelper.assertUrlLocation({
+ hashOrPush: home + "form-tests/form-no-action.html?foo=1&bar=2",
+ report: "hash should match page url and not document url"
+ });
+
start();
}
]);
5 tests/unit/navigation/navigation_paths.js
View
@@ -2,6 +2,9 @@
* mobile navigation path unit tests
*/
(function($){
+ var url = $.mobile.path.parseUrl( location.href ),
+ home = location.href.replace( url.domain, "" );
+
var testPageLoad = function(testPageAnchorSelector, expectedTextValue){
expect( 2 );
@@ -9,7 +12,7 @@
function(){
// reset before each test, all tests expect original page
// for relative urls
- $.testHelper.openPage( "#" + location.pathname);
+ $.testHelper.openPage( "#" + home);
},
// open our test page
10 tests/unit/navigation/push-state-disabled-base-tests.html
View
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <!-- forces the base-tests into a push state disabled run and allows for
+ the test suite runner to pick it up as a seperate test set. See test/unit/ls.php,
+ test/unit/runner.js, base-tests.html, and tests/jquery.testHelper.js for more. -->
+ <meta http-equiv="refresh" content="0; url='base-tests.html?push-state=false'">
+<body>
+</body>
+</html>
10 tests/unit/navigation/push-state-disabled-tests.html
View
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <!-- forces the base-tests into a push state disabled run and allows for
+ the test suite runner to pick it up as a seperate test set. See test/unit/ls.php,
+ test/unit/runner.js, base-tests.html, and tests/jquery.testHelper.js for more. -->
+ <meta http-equiv="refresh" content="0; url='./?push-state=false'">
+<body>
+</body>
+</html>
7 tests/unit/runner.js
View
@@ -17,6 +17,13 @@ $(function() {
// establish a timeout for a given suite in case of async tests hanging
self.testTimer = setTimeout( self.onTimeout, self.testTimeout );
+ // it might be a redirect with query params for push state
+ // tests skip this call and expect another
+ if( !self.frame.QUnit ) {
+ self.$frameElem.one( "load", self.onFrameLoad );
+ return;
+ }
+
// when the QUnit object reports done in the iframe
// run the onFrameDone method
self.frame.QUnit.done = self.onFrameDone;

3 comments on commit 49cf3af

Chris Jacob

I assume this has had JQM's usually extensive testing run over it (you guys rock).... but just to check; have the cross-browser compatibility issues with the HTML5 History API (raised here #16 (comment)) been addressed? If there are "known bugs" with the current solution, could those be added to the documentation? Are there parts of History.js (https://github.com/balupton/history.js) that could be ported over to maintain a consistent/reliable pushState experience cross browser (mobile and desktop)?

P.S I wish I could be more hand's on - but testing/coding this stuff is beyond me. So thx for all your hard work!

John Bender

@chrisjacob

We've spotted a few anomalies (at least 2 browser bugs filed) but all of our testing points to success in the way we implemented our "pushState" suport through replaceState. We're hoping to address bugs here where necessary.

As for History.js, its really hard to justify adding an enormous external dependency like that to the project even though it may suck to reproduce much of the functionality ourselves. Even so, I'll talk with the team about evaluating it since we've talked about a nav refactor at some point anyhow.

Chris Jacob

@johnbender

Thank you for the quick feedback. I hope you can at least cherry pick some of @balupton 's hard work ^_^

Please sign in to comment.
Something went wrong with that request. Please try again.