Skip to content
This repository
Browse code

Merge branch 'replacestate-external'

  • Loading branch information...
commit 49cf3af91a7f1fd2a05063bfbfffc15dbb74492b 2 parents 44eaf77 + df9e233
John Bender authored August 25, 2011
1  Makefile
@@ -45,6 +45,7 @@ JSFILES = 	  js/jquery.ui.widget.js \
45 45
 			  js/jquery.mobile.page.js \
46 46
 			  js/jquery.mobile.core.js \
47 47
 			  js/jquery.mobile.navigation.js \
  48
+			  js/jquery.mobile.navigation.pushstate.js \
48 49
 			  js/jquery.mobile.transition.js \
49 50
 			  js/jquery.mobile.degradeInputs.js \
50 51
 			  js/jquery.mobile.dialog.js \
1  build.xml
@@ -29,6 +29,7 @@
29 29
 		  js/jquery.mobile.page.js,
30 30
 		  js/jquery.mobile.core.js,
31 31
 		  js/jquery.mobile.navigation.js,
  32
+		  js/jquery.mobile.navigation.pushstate.js,
32 33
 		  js/jquery.mobile.transition.js,
33 34
 		  js/jquery.mobile.degradeInputs.js,
34 35
 		  js/jquery.mobile.dialog.js,
1  js/index.php
@@ -11,6 +11,7 @@
11 11
 	'jquery.mobile.page.js',
12 12
 	'jquery.mobile.core.js',
13 13
 	'jquery.mobile.navigation.js',
  14
+	'jquery.mobile.navigation.pushstate.js',
14 15
 	'jquery.mobile.transition.js',
15 16
 	'jquery.mobile.degradeInputs.js',
16 17
 	'jquery.mobile.dialog.js',
5  js/jquery.mobile.core.js
@@ -47,10 +47,12 @@
47 47
 
48 48
 		// Error response message - appears when an Ajax page request fails
49 49
 		pageLoadErrorMessage: "Error Loading Page",
50  
-		
  50
+
51 51
 		//automatically initialize the DOM when it's ready
52 52
 		autoInitializePage: true,
53 53
 
  54
+		pushStateEnabled: true,
  55
+
54 56
 		// Support conditions that must be met in order to proceed
55 57
 		// default enhanced qualifications are media query support OR IE 7+
56 58
 		gradeA: function(){
@@ -164,3 +166,4 @@
164 166
 		return $.find( expr, null, null, [ node ] ).length > 0;
165 167
 	};
166 168
 })( jQuery, this );
  169
+
103  js/jquery.mobile.navigation.pushstate.js
... ...
@@ -0,0 +1,103 @@
  1
+/*
  2
+* jQuery Mobile Framework : history.pushState support, layered on top of hashchange
  3
+* Copyright (c) jQuery Project
  4
+* Dual licensed under the MIT or GPL Version 2 licenses.
  5
+* http://jquery.org/license
  6
+*/
  7
+( function( $, window ) {
  8
+	// For now, let's Monkeypatch this onto the end of $.mobile._registerInternalEvents
  9
+	// Scope self to pushStateHandler so we can reference it sanely within the
  10
+	// methods handed off as event handlers
  11
+	var	pushStateHandler = {},
  12
+		self = pushStateHandler,
  13
+		$win = $( window ),
  14
+		url = $.mobile.path.parseUrl( location.href );
  15
+
  16
+	$.extend( pushStateHandler, {
  17
+		// TODO move to a path helper, this is rather common functionality
  18
+		initialFilePath: (function() {
  19
+			return url.pathname + url.search;
  20
+		})(),
  21
+
  22
+		initialHref: url.hrefNoHash,
  23
+
  24
+		// Flag for tracking if a Hashchange naturally occurs after each popstate + replace
  25
+		hashchangeFired: false,
  26
+
  27
+		state: function() {
  28
+			return {
  29
+				hash: location.hash || "#" + self.initialFilePath,
  30
+				title: document.title,
  31
+
  32
+				// persist across refresh
  33
+				initialHref: self.initialHref
  34
+			};
  35
+		},
  36
+
  37
+		// on hash change we want to clean up the url
  38
+		// NOTE this takes place *after* the vanilla navigation hash change
  39
+		// handling has taken place and set the state of the DOM
  40
+		onHashChange: function( e ) {
  41
+			var href, state;
  42
+
  43
+			self.hashchangeFired = true;
  44
+
  45
+			// only replaceState when the hash doesn't represent an embeded page
  46
+			if( $.mobile.path.isPath(location.hash) ) {
  47
+
  48
+				// propulate the hash when its not available
  49
+				state = self.state();
  50
+
  51
+				// make the hash abolute with the current href
  52
+				href = $.mobile.path.makeUrlAbsolute( state.hash.replace("#", ""), location.href );
  53
+
  54
+				// replace the current url with the new href and store the state
  55
+				history.replaceState( state, document.title, href );
  56
+			}
  57
+		},
  58
+
  59
+		// on popstate (ie back or forward) we need to replace the hash that was there previously
  60
+		// cleaned up by the additional hash handling
  61
+		onPopState: function( e ) {
  62
+			var poppedState = e.originalEvent.state;
  63
+
  64
+			// if there's no state its not a popstate we care about, ie chrome's initial popstate
  65
+			// or forward popstate
  66
+			if( poppedState ) {
  67
+
  68
+				// replace the current url with the equivelant hash so that the hashchange binding in vanilla nav
  69
+				// can do its thing one triggered below
  70
+			 	history.replaceState( poppedState, poppedState.title, poppedState.initialHref + poppedState.hash );
  71
+
  72
+				// Urls that reference subpages will fire their own hashchange, so we don't want to trigger 2 in that case.
  73
+				self.hashchangeFired = false;
  74
+
  75
+				setTimeout(function() {
  76
+					if( !self.hashchangeFired ) {
  77
+						$win.trigger( "hashchange" );
  78
+					}
  79
+
  80
+					self.hashchangeFired = false;
  81
+				}, 0);
  82
+			}
  83
+		},
  84
+
  85
+		init: function() {
  86
+			$win.bind( "hashchange", self.onHashChange );
  87
+
  88
+			// Handle popstate events the occur through history changes
  89
+			$win.bind( "popstate", self.onPopState );
  90
+
  91
+			// if there's no hash, we need to replacestate for returning to home
  92
+			if ( location.hash === "" ) {
  93
+				history.replaceState( self.state(), document.title, location.href );
  94
+			}
  95
+		}
  96
+	});
  97
+
  98
+	$( function() {
  99
+		if( $.mobile.pushStateEnabled && $.support.pushState ){
  100
+			pushStateHandler.init();
  101
+		}
  102
+	});
  103
+})( jQuery, this );
2  js/jquery.mobile.support.js
@@ -67,7 +67,7 @@ $.extend( $.support, {
67 67
 	orientation: "orientation" in window,
68 68
 	touch: "ontouchend" in document,
69 69
 	cssTransitions: "WebKitTransitionEvent" in window,
70  
-	pushState: !!history.pushState,
  70
+	pushState: "pushState" in history && "replaceState" in history,
71 71
 	mediaquery: $.mobile.media( "only all" ),
72 72
 	cssPseudoElement: !!propExists( "content" ),
73 73
 	boxShadow: !!propExists( "boxShadow" ) && !bb,
27  tests/jquery.testHelper.js
@@ -16,6 +16,18 @@
16 16
 			}
17 17
 		},
18 18
 
  19
+		// TODO prevent test suite loads when the browser doesn't support push state
  20
+		// and push-state false is defined.
  21
+		setPushStateFor: function( libs ) {
  22
+			if( $.support.pushState && location.search.indexOf( "push-state" ) >= 0 ) {
  23
+				$.support.pushState = false;
  24
+			}
  25
+
  26
+			$.each(libs, function(i, l) {
  27
+				$( "<script>", { src: l }).appendTo("head");
  28
+			});
  29
+		},
  30
+
19 31
 		reloads: {},
20 32
 
21 33
 		reloadLib: function(libName){
@@ -26,8 +38,8 @@
26 38
 				};
27 39
 			}
28 40
 
29  
-			var	lib = this.reloads[libName].lib.clone(),
30  
-			    src = lib.attr('src');
  41
+			var lib = this.reloads[libName].lib.clone(),
  42
+				src = lib.attr('src');
31 43
 
32 44
 			//NOTE append "cache breaker" to force reload
33 45
 			lib.attr('src', src + "?" + this.reloads[libName].count++);
@@ -116,6 +128,17 @@
116 128
 
117 129
 				return returnVal;
118 130
 			};
  131
+		},
  132
+
  133
+		assertUrlLocation: function( args ) {
  134
+			var parts = $.mobile.path.parseUrl( location.href ),
  135
+				pathnameOnward = location.href.replace( parts.domain, "" );
  136
+
  137
+			if( $.support.pushState ) {
  138
+				same( pathnameOnward, args.hashOrPush || args.push, args.report );
  139
+			} else {
  140
+				same( parts.hash, "#" + (args.hashOrPush || args.hash), args.report );
  141
+			}
119 142
 		}
120 143
 	};
121 144
 })(jQuery);
39  tests/unit/listview/listview_core.js
@@ -4,11 +4,13 @@
4 4
 
5 5
 // TODO split out into seperate test files
6 6
 (function($){
  7
+  var home = $.mobile.path.parseUrl( location.href ).pathname;
7 8
 
8 9
 	$.mobile.defaultTransition = "none";
9  
-	module('Basic Linked list', {
  10
+
  11
+	module( "Basic Linked list", {
10 12
 		setup: function(){
11  
-			$.testHelper.openPage("#basic-linked-test");
  13
+			$.testHelper.openPage( "#basic-linked-test" );
12 14
 		}
13 15
 	});
14 16
 
@@ -213,7 +215,7 @@
213 215
 	asyncTest( "changes to the read only page when hash is changed", function() {
214 216
 		$.testHelper.pageSequence([
215 217
 			function(){
216  
-				$.testHelper.openPage("#read-only-list-test")
  218
+				$.testHelper.openPage("#read-only-list-test");
217 219
 			},
218 220
 
219 221
 			function(){
@@ -588,6 +590,8 @@
588 590
 		ok( $("#enhancetest").trigger("create").find(".ui-listview").length, "enhancements applied" );
589 591
 	});
590 592
 
  593
+	module( "Cached Linked List" );
  594
+
591 595
 	var findNestedPages = function(selector){
592 596
 		return $( selector + " #topmost" ).listview( 'childPages' );
593 597
 	};
@@ -596,7 +600,7 @@
596 600
 		$.testHelper.pageSequence([
597 601
 			function(){
598 602
 				//reset for relative url refs
599  
-				$.testHelper.openPage( "#" + location.pathname );
  603
+				$.testHelper.openPage( "#" + home );
600 604
 			},
601 605
 
602 606
 			function(){
@@ -605,7 +609,11 @@
605 609
 
606 610
 			function(){
607 611
 				ok( findNestedPages( "#uncached-nested-list" ).length > 0, "verify that there are nested pages" );
608  
-				$.testHelper.openPage( "#" + location.pathname + "cache-tests/clear.html" );
  612
+				$.testHelper.openPage( "#" + home );
  613
+			},
  614
+
  615
+			function() {
  616
+				$.testHelper.openPage( "#cache-tests/clear.html" );
609 617
 			},
610 618
 
611 619
 			function(){
@@ -620,7 +628,7 @@
620 628
 		$.testHelper.pageSequence([
621 629
 			function(){
622 630
 				//reset for relative url refs
623  
-				$.testHelper.openPage( "#" + location.pathname );
  631
+				$.testHelper.openPage( "#" + home );
624 632
 			},
625 633
 
626 634
 			function(){
@@ -629,7 +637,11 @@
629 637
 
630 638
 			function(){
631 639
 				ok( findNestedPages( "#cached-nested-list" ).length > 0, "verify that there are nested pages" );
632  
-				$.testHelper.openPage( "#" + location.pathname + "cache-tests/clear.html" );
  640
+				$.testHelper.openPage( "#" + home );
  641
+			},
  642
+
  643
+			function() {
  644
+				$.testHelper.openPage( "#cache-tests/clear.html" );
633 645
 			},
634 646
 
635 647
 			function(){
@@ -643,7 +655,7 @@
643 655
 		$.testHelper.pageSequence([
644 656
 			function(){
645 657
 				//reset for relative url refs
646  
-				$.testHelper.openPage( "#" + location.pathname );
  658
+				$.testHelper.openPage( "#" + home );
647 659
 			},
648 660
 
649 661
 			function(){
@@ -652,7 +664,11 @@
652 664
 
653 665
 			function(){
654 666
 				same( $("#cached-nested-list").length, 1 );
655  
-				$.testHelper.openPage("#" + location.pathname + "cache-tests/clear.html");
  667
+				$.testHelper.openPage( "#" + home );
  668
+			},
  669
+
  670
+			function() {
  671
+				$.testHelper.openPage( "#cache-tests/clear.html" );
656 672
 			},
657 673
 
658 674
 			function(){
@@ -667,6 +683,11 @@
667 683
 		expect( listPage.find("li").length );
668 684
 
669 685
 		$.testHelper.pageSequence( [
  686
+			function(){
  687
+				//reset for relative url refs
  688
+				$.testHelper.openPage( "#" + home );
  689
+			},
  690
+
670 691
 			function() {
671 692
 				$.testHelper.openPage( "#search-filter-test" );
672 693
 			},
7  tests/unit/navigation/base-tests.html
@@ -26,8 +26,11 @@
26 26
 	<link rel="stylesheet" href="../../../../../themes/default/"/>
27 27
 	<link rel="stylesheet" href="../../../../../external/qunit.css"/>
28 28
 	<script src="../../../../../external/qunit.js"></script>
29  
-
30  
-	<script src="../../navigation_base.js"></script>
  29
+	<script type="text/javascript">
  30
+		$.testHelper.setPushStateFor([
  31
+			"../../navigation_base.js"
  32
+		]);
  33
+	</script>
31 34
 </head>
32 35
 <body>
33 36
 
102  tests/unit/navigation/index.html
@@ -15,10 +15,14 @@
15 15
 	<link rel="stylesheet" href="../../../external/qunit.css"/>
16 16
 	<script src="../../../external/qunit.js"></script>
17 17
 
18  
-	<script src="navigation_transitions.js"></script>
19  
-  <script src="navigation_helpers.js"></script>
20  
-  <script src="navigation_core.js"></script>
21  
-	<script src="navigation_paths.js"></script>
  18
+	<script type="text/javascript">
  19
+		$.testHelper.setPushStateFor([
  20
+			"navigation_transitions.js",
  21
+			"navigation_helper.js",
  22
+			"navigation_core.js",
  23
+			"navigation_paths.js"
  24
+		]);
  25
+	</script>
22 26
 </head>
23 27
 <body>
24 28
 
@@ -28,69 +32,69 @@ <h2 id="qunit-userAgent"></h2>
28 32
 <ol id="qunit-tests">
29 33
 </ol>
30 34
 
31  
-<div id="harmless-default-page"  data-nstest-role="page">
  35
+<div id="harmless-default-page"	 data-nstest-role="page">
32 36
 </div>
33 37
 
34  
-<div id="foo"  data-nstest-role="page" class="foo-class">
  38
+<div id="foo" data-nstest-role="page" class="foo-class">
35 39
 	<a href="#bar" data-nstest-transition="flip"></a>
36 40
 </div>
37 41
 
38  
-<div id="prefetch"  data-nstest-role="page">
  42
+<div id="prefetch" data-nstest-role="page">
39 43
 	<a href="prefetched.html" data-nstest-prefetch>Prefetch test</a>
40 44
 </div>
41 45
 
42  
-<div id="foozball"  data-nstest-role="page">
  46
+<div id="foozball" data-nstest-role="page">
43 47
 </div>
44 48
 
45  
-<div id="bar"  data-nstest-role="page">
  49
+<div id="bar"	 data-nstest-role="page">
46 50
 	<a href="#baz"></a>
47 51
 </div>
48 52
 
49  
-<div id="baz"  data-nstest-role="page">
  53
+<div id="baz"	 data-nstest-role="page">
50 54
 	<a href="#foo"></a>
51 55
 </div>
52 56
 
53  
-<div id="fade-trans"  data-nstest-role="page">
  57
+<div id="fade-trans" data-nstest-role="page">
54 58
 	<a href="#flip-trans" data-nstest-transition="fade"></a>
55 59
 </div>
56 60
 
57  
-<div id="flip-trans"  data-nstest-role="page">
  61
+<div id="flip-trans" data-nstest-role="page">
58 62
 	<a href="#fade-trans" data-nstest-transition="flip"></a>
59 63
 </div>
60 64
 
61  
-<div id="no-trans"  data-nstest-role="page">
  65
+<div id="no-trans" data-nstest-role="page">
62 66
 	<a href="#pop-trans"></a>
63 67
 </div>
64 68
 
65  
-<div id="pop-trans"  data-nstest-role="page">
  69
+<div id="pop-trans"	 data-nstest-role="page">
66 70
 	<a href="#no-trans" data-nstest-transition="pop"></a>
67 71
 </div>
68 72
 
69  
-<div id="default-trans"  data-nstest-role="page">
  73
+<div id="default-trans"	 data-nstest-role="page">
70 74
 	<a href="#no-trans"></a>
71 75
 </div>
72 76
 
73  
-<div id="data-url"  data-nstest-role="page">
  77
+<div id="data-url" data-nstest-role="page">
74 78
 	<a href="data-url-tests/data-url.html"></a>
75 79
 </div>
76 80
 
77  
-<div id="non-data-url"  data-nstest-role="page">
  81
+<div id="non-data-url" data-nstest-role="page">
78 82
 	<a href="data-url-tests/non-data-url.html"></a>
79 83
 </div>
80 84
 
81  
-<div id="nested-data-url"  data-nstest-role="page">
  85
+<div id="nested-data-url"	 data-nstest-role="page">
82 86
 	<a href="data-url-tests/nested.html"></a>
83 87
 </div>
84 88
 
85  
-<div id="single-quotes-data-url"  data-nstest-role="page">
  89
+<div id="single-quotes-data-url" data-nstest-role="page">
86 90
 	<a href="data-url-tests/single-quotes.html"></a>
87 91
 </div>
88 92
 
89  
-<div id="reverse-attr-data-url"  data-nstest-role="page">
  93
+<div id="reverse-attr-data-url"	 data-nstest-role="page">
90 94
 	<a href="data-url-tests/reverse-attr.html"></a>
91 95
 </div>
92 96
 
93  
-<div id="ajax-disabled-form"  data-nstest-role="page">
  97
+<div id="ajax-disabled-form" data-nstest-role="page">
94 98
 	<form method="POST" id="non-ajax-form" action="/ajax-disabled-form" data-nstest-ajax="false">
95 99
 	</form>
96 100
 
@@ -101,7 +105,7 @@ <h2 id="qunit-userAgent"></h2>
101 105
 	</form>
102 106
 </div>
103 107
 
104  
-<div id="default-trans-dialog"  data-nstest-role="page">
  108
+<div id="default-trans-dialog" data-nstest-role="page">
105 109
 	<a href="#no-trans-dialog" data-nstest-rel="dialog"></a>
106 110
 </div>
107 111
 
@@ -109,34 +113,34 @@ <h2 id="qunit-userAgent"></h2>
109 113
 </div>
110 114
 
111 115
 <div id="dup-history-first" data-nstest-role="page">
112  
-  <a href="#dup-history-second" data-nstest-transition="slideup" data-nstest-role="button" >
  116
+	<a href="#dup-history-second" data-nstest-transition="slideup" data-nstest-role="button" >
113 117
 		Page 2
114 118
 	</a>
115 119
 </div>
116 120
 
117 121
 <div id="dup-history-second" data-nstest-role="page">
118  
-  <a href="#dup-history-first" data-nstest-transition="slideup" data-nstest-role="button">
  122
+	<a href="#dup-history-first" data-nstest-transition="slideup" data-nstest-role="button">
119 123
 		Page 1
120 124
 	</a>
121  
-  <a href="#dup-history-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
  125
+	<a href="#dup-history-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
122 126
 </div>
123 127
 
124 128
 <div id="dup-history-dialog" data-nstest-role="dialog">
125  
-   <div data-nstest-role="header" data-nstest-position="inline">
126  
-     <h1>Dialog</h1>
127  
-   </div>
  129
+	 <div data-nstest-role="header" data-nstest-position="inline">
  130
+		 <h1>Dialog</h1>
  131
+	 </div>
128 132
 </div>
129 133
 
130 134
 <div id="skip-dialog-first" data-nstest-role="page">
131  
-  <div data-nstest-role="content">
132  
-    <a href="#skip-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
133  
-  </div>
  135
+	<div data-nstest-role="content">
  136
+		<a href="#skip-dialog" data-nstest-role="button" data-nstest-transition="pop" data-nstest-rel="dialog">Dialog</a>
  137
+	</div>
134 138
 </div>
135 139
 
136 140
 <div id="skip-dialog" data-nstest-role="dialog">
137  
-  <div data-nstest-role="content">
138  
-    <a href="#skip-dialog-second">Page 2</a>
139  
-  </div>
  141
+	<div data-nstest-role="content">
  142
+		<a href="#skip-dialog-second">Page 2</a>
  143
+	</div>
140 144
 </div>
141 145
 
142 146
 <div id="skip-dialog-second" data-nstest-role="page">
@@ -145,33 +149,33 @@ <h2 id="qunit-userAgent"></h2>
145 149
 
146 150
 
147 151
 <div id="nested-dialog-page" data-nstest-role="page">
148  
-  <div data-nstest-role="content">
149  
-    <a href="#nested-dialog-first">Dialog</a>
150  
-  </div>
  152
+	<div data-nstest-role="content">
  153
+		<a href="#nested-dialog-first">Dialog</a>
  154
+	</div>
151 155
 </div>
152 156
 
153 157
 <div id="nested-dialog-first" data-nstest-role="dialog">
154  
-  <div data-nstest-role="content">
155  
-    <a href="#nested-dialog-second">Dialog 2</a>
156  
-  </div>
  158
+	<div data-nstest-role="content">
  159
+		<a href="#nested-dialog-second">Dialog 2</a>
  160
+	</div>
157 161
 </div>
158 162
 
159 163
 <div id="nested-dialog-second" data-nstest-role="dialog">
160 164
 </div>
161 165
 
162 166
 <div id="relative-after-embeded-page-first" data-nstest-role="page">
163  
-  <div data-nstest-role="content">
164  
-    <a href="#relative-after-embeded-page-second">second page</a>
165  
-  </div>
  167
+	<div data-nstest-role="content">
  168
+		<a href="#relative-after-embeded-page-second">second page</a>
  169
+	</div>
166 170
 </div>
167 171
 
168 172
 <div id="relative-after-embeded-page-second" data-nstest-role="page">
169  
-  <div data-nstest-role="content">
170  
-    <a href="data-url-tests/data-url.html">file path page</a>
171  
-  </div>
  173
+	<div data-nstest-role="content">
  174
+		<a href="data-url-tests/data-url.html">file path page</a>
  175
+	</div>
172 176
 </div>
173 177
 
174  
-<div id="ajax-title-page" data-nstest-title="Title Attr 1"  data-nstest-role="page">
  178
+<div id="ajax-title-page" data-nstest-title="Title Attr 1" data-nstest-role="page">
175 179
 	<a href="title1.html" id="titletest1" data-nstest-transition="none">test</a>
176 180
 	<a href="title2.html" id="titletest2" data-nstest-transition="none">test</a>
177 181
 	<a href="title3.html" id="titletest3" data-nstest-transition="none">test</a>
@@ -243,8 +247,8 @@ <h2 id="qunit-userAgent"></h2>
243 247
 			<input type="hidden" name="foo" value="1">
244 248
 			<input type="hidden" name="bar" value="2">
245 249
 		</form>
246  
-        <a href="form-tests/form-no-action.html">External page containing form with no action.</a>
247  
-    </div>
  250
+				<a href="form-tests/form-no-action.html">External page containing form with no action.</a>
  251
+		</div>
248 252
 </div>
249 253
 
250 254
 <div id="active-state-page1" data-nstest-role="page">
93  tests/unit/navigation/navigation_base.js
@@ -21,21 +21,28 @@
21 21
 		$.testHelper.pageSequence([
22 22
 			function(){
23 23
 				// Navigate from default internal page to another internal page.
24  
-				$.testHelper.openPage("#internal-page-2");
  24
+				$.testHelper.openPage( "#internal-page-2" );
25 25
 			},
26 26
 
27 27
 			function(){
28 28
 				// Verify that we are on the 2nd internal page.
29  
-				same(location.hash, "#internal-page-2", "navigate to internal page");
  29
+				$.testHelper.assertUrlLocation({
  30
+					push: location.pathname + "#internal-page-2",
  31
+					hash: "internal-page-2",
  32
+					report: "navigate to internal page"
  33
+				});
30 34
 
31 35
 				// Navigate to a page that is in the base directory. Note that the application
32  
-				// document and this new page are *NOT* in the same directory.
33  
-				$("#internal-page-2 .bp1").click();
  36
+ 				// document and this new page are *NOT* in the same directory.
  37
+ 				$("#internal-page-2 .bp1").click();
34 38
 			},
35 39
 
36 40
 			function(){
37 41
 				// Verify that we are on the expected page.
38  
-				same(location.hash, "#" + baseDir + "base-page-1.html", "navigate from internal page to page in base directory");
  42
+				$.testHelper.assertUrlLocation({
  43
+					hashOrPush: baseDir + "base-page-1.html",
  44
+					report: "navigate from internal page to page in base directory"
  45
+				});
39 46
 
40 47
 				// Navigate to another page in the same directory as the current page.
41 48
 				$("#base-page-1 .bp2").click();
@@ -43,7 +50,10 @@
43 50
 
44 51
 			function(){
45 52
 				// Verify that we are on the expected page.
46  
-				same(location.hash, "#" + baseDir + "base-page-2.html", "navigate from base directory page to another base directory page");
  53
+				$.testHelper.assertUrlLocation({
  54
+					hashOrPush: baseDir + "base-page-2.html",
  55
+					report: "navigate from base directory page to another base directory page"
  56
+				});
47 57
 
48 58
 				// Navigate to another page in a directory that is the sibling of the base.
49 59
 				$("#base-page-2 .cp1").click();
@@ -51,7 +61,10 @@
51 61
 
52 62
 			function(){
53 63
 				// Verify that we are on the expected page.
54  
-				same(location.hash, "#" + contentDir + "content-page-1.html", "navigate from base directory page to a page in a different directory hierarchy");
  64
+				$.testHelper.assertUrlLocation({
  65
+					hashOrPush: contentDir + "content-page-1.html",
  66
+					report: "navigate from base directory page to a page in a different directory hierarchy"
  67
+				});
55 68
 
56 69
 				// Navigate to another page in a directory that is the sibling of the base.
57 70
 				$("#content-page-1 .cp2").click();
@@ -59,7 +72,10 @@
59 72
 
60 73
 			function(){
61 74
 				// Verify that we are on the expected page.
62  
-				same(location.hash, "#" + contentDir + "content-page-2.html", "navigate to another page within the same non-base directory hierarchy");
  75
+				$.testHelper.assertUrlLocation({
  76
+					hashOrPush: contentDir + "content-page-2.html",
  77
+					report: "navigate to another page within the same non-base directory hierarchy"
  78
+				});
63 79
 
64 80
 				// Navigate to an internal page.
65 81
 				$("#content-page-2 .ip1").click();
@@ -67,7 +83,11 @@
67 83
 
68 84
 			function(){
69 85
 				// Verify that we are on the expected page.
70  
-				same(location.hash, "#internal-page-1", "navigate from a page in a non-base directory to an internal page");
  86
+				$.testHelper.assertUrlLocation({
  87
+					push: location.pathname + "#internal-page-1",
  88
+					hash: "internal-page-1",
  89
+					report: "navigate from a page in a non-base directory to an internal page"
  90
+				});
71 91
 
72 92
 				// Try calling changePage() directly with a relative path.
73 93
 				$.mobile.changePage("base-page-1.html");
@@ -75,7 +95,10 @@
75 95
 
76 96
 			function(){
77 97
 				// Verify that we are on the expected page.
78  
-				same(location.hash, "#" + baseDir + "base-page-1.html", "call changePage() with a filename (no path)");
  98
+				$.testHelper.assertUrlLocation({
  99
+					hashOrPush: baseDir + "base-page-1.html",
  100
+					report: "call changePage() with a filename (no path)"
  101
+				});
79 102
 
80 103
 				// Try calling changePage() directly with a relative path.
81 104
 				$.mobile.changePage("../content/content-page-1.html");
@@ -83,7 +106,10 @@
83 106
 
84 107
 			function(){
85 108
 				// Verify that we are on the expected page.
86  
-				same(location.hash, "#" + contentDir + "content-page-1.html", "call changePage() with a relative path containing up-level references");
  109
+				$.testHelper.assertUrlLocation({
  110
+					hashOrPush: contentDir + "content-page-1.html",
  111
+					report: "call changePage() with a relative path containing up-level references"
  112
+				});
87 113
 
88 114
 				// Try calling changePage() with an id
89 115
 				$.mobile.changePage("content-page-2.html");
@@ -91,7 +117,10 @@
91 117
 
92 118
 			function(){
93 119
 				// Verify that we are on the expected page.
94  
-				same(location.hash, "#" + contentDir + "content-page-2.html", "call changePage() with a relative path should resolve relative to current page");
  120
+				$.testHelper.assertUrlLocation({
  121
+					hashOrPush: contentDir + "content-page-2.html",
  122
+					report: "call changePage() with a relative path should resolve relative to current page"
  123
+				});
95 124
 
96 125
 				// test that an internal page works
97 126
 				$("a.ip2").click();
@@ -99,33 +128,47 @@
99 128
 
100 129
 			function(){
101 130
 				// Verify that we are on the expected page.
102  
-				same(location.hash, "#internal-page-2", "call changePage() with a page id");
  131
+				$.testHelper.assertUrlLocation({
  132
+					hash:  "internal-page-2",
  133
+					push: location.pathname + "#internal-page-2",
  134
+					report: "call changePage() with a page id"
  135
+				});
103 136
 
104 137
 				// Try calling changePage() with an id
105 138
 				$.mobile.changePage("internal-page-1");
106 139
 			},
107 140
 
108 141
 			function(){
  142
+				// Verify that we are on the expected page.
  143
+				$.testHelper.assertUrlLocation({
  144
+					hash:  "internal-page-2",
  145
+					push: location.pathname + "#internal-page-2",
  146
+					report: "calling changePage() with a page id that is not prefixed with '#' should not change page"
  147
+				});
  148
+
109 149
 				// Previous load should have failed and left us on internal-page-2.
110  
-				same(location.hash, "#internal-page-2", "calling changePage() with a page id that is not prefixed with '#' should not change page");
111 150
 				start();
112  
-			}]);
  151
+			}
  152
+		]);
113 153
 	});
114 154
 
115 155
 	asyncTest( "internal form with no action submits to document URL", function(){
116  
-
117 156
 		$.testHelper.pageSequence([
118 157
 			// open our test page
119 158
 			function(){
120  
-				$.testHelper.openPage("#internal-no-action-form-page");
  159
+				$.testHelper.openPage( "#internal-no-action-form-page" );
121 160
 			},
122 161
 
123 162
 			function(){
124  
-				$("#internal-no-action-form-page form").eq(0).submit();
  163
+				$( "#internal-no-action-form-page form" ).eq( 0 ).submit();
125 164
 			},
126 165
 
127 166
 			function(){
128  
-				same(location.hash, "#" + location.pathname + "?foo=1&bar=2", "hash should match document url and not base url");
  167
+				$.testHelper.assertUrlLocation({
  168
+					hashOrPush: location.pathname + "?foo=1&bar=2",
  169
+					report: "hash should match document url and not base url"
  170
+				});
  171
+
129 172
 				start();
130 173
 			}
131 174
 		]);
@@ -140,16 +183,22 @@
140 183
 
141 184
 			function(){
142 185
 				// Make sure we actually navigated to the external page.
143  
-				same(location.hash, "#" + contentDir + "content-page-1.html", "should be on content-page-1.html");
  186
+				$.testHelper.assertUrlLocation({
  187
+					hashOrPush: contentDir + "content-page-1.html",
  188
+					report: "should be on content-page-1.html"
  189
+				});
144 190
 
145 191
 				// Now submit the form in the external page.
146 192
 				$("#content-page-1 form").eq(0).submit();
147 193
 			},
148 194
 
149 195
 			function(){
150  
-				same(location.hash, "#" + contentDir + "content-page-1.html?foo=1&bar=2", "hash should match page url and not document url");
  196
+				$.testHelper.assertUrlLocation({
  197
+					hashOrPush: contentDir + "content-page-1.html?foo=1&bar=2",
  198
+					report: "hash should match page url and not document url"
  199
+				});
  200
+
151 201
 				start();
152 202
 			}]);
153 203
 	});
154  
-
155 204
 })(jQuery);
182  tests/unit/navigation/navigation_core.js
@@ -2,24 +2,40 @@
2 2
  * mobile navigation unit tests
3 3
  */
4 4
 (function($){
  5
+	// TODO move siteDirectory over to the nav path helper
5 6
 	var changePageFn = $.mobile.changePage,
6  
-			originalTitle = document.title,
7  
-			siteDirectory = location.pathname.replace(/[^/]+$/, ""),
8  
-			navigateTestRoot = function(){
9  
-				$.testHelper.openPage( "#" + location.pathname );
10  
-			};
  7
+		originalTitle = document.title,
  8
+		siteDirectory = location.pathname.replace( /[^/]+$/, "" ),
  9
+		home = $.mobile.path.parseUrl(location.pathname).directory,
  10
+		navigateTestRoot = function(){
  11
+			$.testHelper.openPage( "#" + location.pathname );
  12
+		};
11 13
 
12 14
 	module('jquery.mobile.navigation.js', {
13 15
 		setup: function(){
14 16
 			$.mobile.changePage = changePageFn;
15 17
 			document.title = originalTitle;
16 18
 
17  
-			if ( location.hash ) {
  19
+			var pageReset = function( hash ) {
  20
+				hash = hash || "";
  21
+
18 22
 				stop();
19  
-				$(document).one("changepage", function() {
  23
+
  24
+				$(document).one( "changepage", function() {
20 25
 					start();
21  
-				} );
22  
-				location.hash = "";
  26
+				});
  27
+
  28
+				location.hash = "#" + hash;
  29
+			};
  30
+
  31
+			// force the page reset for hash based tests
  32
+			if ( location.hash && !$.support.pushState ) {
  33
+				pageReset();
  34
+			}
  35
+
  36
+			// force the page reset for all pushstate tests
  37
+			if ( $.support.pushState ) {
  38
+				pageReset( home );
23 39
 			}
24 40
 
25 41
 			$.mobile.urlHistory.stack = [];
@@ -52,7 +68,7 @@
52 68
 	asyncTest( "external page is cached in the DOM after pagehide", function(){
53 69
 		$.testHelper.pageSequence([
54 70
 			navigateTestRoot,
55  
-			
  71
+
56 72
 			function(){
57 73
 				$.mobile.changePage( "cached-external.html" );
58 74
 			},
@@ -72,30 +88,29 @@
72 88
 	});
73 89
 
74 90
 	asyncTest( "external page is cached in the DOM after pagehide when option is set globally", function(){
75  
-			$.testHelper.pageSequence([
76  
-				navigateTestRoot,
77  
-			
78  
-				function(){
79  
-					$.mobile.page.prototype.options.domCache = true;
80  
-					$.mobile.changePage( "external.html" );
81  
-				},
82  
-
83  
-				// page is pulled and displayed in the dom
84  
-				function(){
85  
-					same( $( "#external-test" ).length, 1 );
86  
-					window.history.back();
87  
-				},
88  
-
89  
-				// external test page is cached in the dom after transitioning away
90  
-				function(){
91  
-					same( $( "#external-test" ).length, 1 );
92  
-					$.mobile.page.prototype.options.domCache = false;
93  
-					$( "#external-test" ).remove();
94  
-					start();
95  
-				}
96  
-			]);
97  
-		});
98  
-		
  91
+		$.testHelper.pageSequence([
  92
+			navigateTestRoot,
  93
+
  94
+			function(){
  95
+				$.mobile.page.prototype.options.domCache = true;
  96
+				$.mobile.changePage( "external.html" );
  97
+			},
  98
+
  99
+			// page is pulled and displayed in the dom
  100
+			function(){
  101
+				same( $( "#external-test" ).length, 1 );
  102
+				window.history.back();
  103
+			},
  104
+
  105
+			// external test page is cached in the dom after transitioning away
  106
+			function(){
  107
+				same( $( "#external-test" ).length, 1 );
  108
+				$.mobile.page.prototype.options.domCache = false;
  109
+				$( "#external-test" ).remove();
  110
+				start();
  111
+			}]);
  112
+	});
  113
+
99 114
 	asyncTest( "forms with data attribute ajax set to false will not call changePage", function(){
100 115
 		var called = false;
101 116
 		var newChangePage = function(){
@@ -215,12 +230,17 @@
215 230
 		testListening( $.mobile.hashListeningEnabled );
216 231
 	});
217 232
 
218  
-	var testDataUrlHash = function(linkSelector, hashRegex){
  233
+	var testDataUrlHash = function( linkSelector, matches ) {
219 234
 		$.testHelper.pageSequence([
220 235
 			function(){ window.location.hash = ""; },
221 236
 			function(){ $(linkSelector).click(); },
222 237
 			function(){
223  
-				ok(hashRegex.test(location.hash), "should match the regex");
  238
+				$.testHelper.assertUrlLocation(
  239
+					$.extend(matches, {
  240
+						report: "url or hash should match"
  241
+					})
  242
+				);
  243
+
224 244
 				start();
225 245
 			}
226 246
 		]);
@@ -229,19 +249,22 @@
229 249
 	};
230 250
 
231 251
 	test( "when loading a page where data-url is not defined on a sub element hash defaults to the url", function(){
232  
-		testDataUrlHash("#non-data-url a", new RegExp("^#" + siteDirectory + "data-url-tests/non-data-url.html$"));
  252
+		testDataUrlHash( "#non-data-url a", {hashOrPush: siteDirectory + "data-url-tests/non-data-url.html"} );
233 253
 	});
234 254
 
235 255
 	test( "data url works for nested paths", function(){
236  
-		testDataUrlHash("#nested-data-url a", /^#foo\/bar.html$/);
  256
+		var url = "foo/bar.html";
  257
+		testDataUrlHash( "#nested-data-url a", {hash: url, push: home + url} );
237 258
 	});
238 259
 
239 260
 	test( "data url works for single quoted paths and roles", function(){
240  
-		testDataUrlHash("#single-quotes-data-url a", /^#foo\/bar\/single.html$/);
  261
+		var url = "foo/bar/single.html";
  262
+		testDataUrlHash( "#single-quotes-data-url a", {hash: url, push: home + url} );
241 263
 	});
242 264
 
243 265
 	test( "data url works when role and url are reversed on the page element", function(){
244  
-		testDataUrlHash("#reverse-attr-data-url a", /^#foo\/bar\/reverse.html$/);
  266
+		var url = "foo/bar/reverse.html";
  267
+		testDataUrlHash( "#reverse-attr-data-url a", {hash: url, push: home + url} );
245 268
 	});
246 269
 
247 270
 	asyncTest( "last entry choosen amongst multiple identical url history stack entries on hash change", function(){
@@ -283,7 +306,12 @@
283 306
 
284 307
 			// make sure we're at the first page and not the dialog
285 308
 			function(){
286  
-				same(location.hash, "#skip-dialog-first", "should be the first page in the sequence");
  309
+				$.testHelper.assertUrlLocation({
  310
+					hash: "skip-dialog-first",
  311
+					push: home + "#skip-dialog-first",
  312
+					report: "should be the first page in the sequence"
  313
+				});
  314
+
287 315
 				start();
288 316
 			}]);
289 317
 	});
@@ -307,12 +335,16 @@
307 335
 
308 336
 			// make sure we're on the second page and not the dialog
309 337
 			function(){
310  
-				same(location.hash, "#skip-dialog-second", "should be the second page after the dialog");
  338
+				$.testHelper.assertUrlLocation({
  339
+					hash: "skip-dialog-second",
  340
+					push: home + "#skip-dialog-second",
  341
+					report: "should be the second page after the dialog"
  342
+				});
  343
+
311 344
 				start();
312 345
 			}]);
313 346
 	});
314 347
 
315  
-
316 348
 	asyncTest( "going back from a dialog triggered from a dialog should result in the first dialog ", function(){
317 349
 		$.testHelper.pageSequence([
318 350
 			// setup
@@ -449,7 +481,11 @@
449 481
 			},
450 482
 
451 483
 			function(){
452  
-				same(location.hash, "#" + siteDirectory + "data-url-tests/non-data-url.html?foo=bar");
  484
+				$.testHelper.assertUrlLocation({
  485
+					hashOrPush: home + "data-url-tests/non-data-url.html?foo=bar",
  486
+					report: "the hash or url has query params"
  487
+				});
  488
+
453 489
 				ok($(".ui-page-active").jqmData("url").indexOf("?foo=bar") > -1, "the query params are in the data url");
454 490
 				start();
455 491
 			}
@@ -467,12 +503,20 @@
467 503
 			},
468 504
 
469 505
 			function(){
470  
-				same(location.hash, "#" + siteDirectory + "data-url-tests/non-data-url.html?foo=bar");
  506
+				$.testHelper.assertUrlLocation({
  507
+					hashOrPush: home + "data-url-tests/non-data-url.html?foo=bar",
  508
+					report: "the hash or url has query params"
  509
+				});
  510
+
471 511
 				$("#query-param-anchor").click();
472 512
 			},
473 513
 
474 514
 			function(){
475  
-				same(location.hash, "#" + siteDirectory + "data-url-tests/non-data-url.html?foo=bar");
  515
+				$.testHelper.assertUrlLocation({
  516
+					hashOrPush: home + "data-url-tests/non-data-url.html?foo=bar",
  517
+					report: "the hash or url still has query params"
  518
+				});
  519
+
476 520
 				start();
477 521
 			}
478 522
 		]);
@@ -481,7 +525,7 @@
481 525
 	// Special handling inside navigation because query params must be applied to the hash
482 526
 	// or absolute reference and dialogs apply extra information int the hash that must be removed
483 527
 	asyncTest( "query param link from a dialog to itself should be a not add another dialog", function(){
484  
-		var firstDialogHash;
  528
+		var firstDialogLoc;
485 529
 
486 530
 		$.testHelper.pageSequence([
487 531
 			// open our test page
@@ -502,19 +546,18 @@
502 546
 			// attempt to navigate to the same link
503 547
 			function(){
504 548
 				// store the current hash for comparison (with one dialog hash key)
505  
-				firstDialogHash = location.hash;
  549
+				firstDialogLoc = location.hash || location.href;
506 550
 				$("#dialog-param-link-page a").click();
507 551
 			},
508 552
 
509 553
 			function(){
510  
-				same(location.hash, firstDialogHash, "additional dialog hash key not added");
  554
+				same(location.hash || location.href, firstDialogLoc, "additional dialog hash key not added");
511 555
 				start();
512 556
 			}
513 557
 		]);
514 558
 	});
515 559
 
516  
-	asyncTest( "query data passed as string to changePage is appended to URL", function(){
517  
-
  560
+ 	asyncTest( "query data passed as string to changePage is appended to URL", function(){
518 561
 		$.testHelper.pageSequence([
519 562
 			// open our test page
520 563
 			function(){
@@ -524,14 +567,17 @@
524 567
 			},
525 568
 
526 569
 			function(){
527  
-				same(location.hash, "#" + siteDirectory + "form-tests/changepage-data.html?foo=1&bar=2");
  570
+				$.testHelper.assertUrlLocation({
  571
+					hashOrPush: home + "form-tests/changepage-data.html?foo=1&bar=2",
  572
+					report: "the hash or url still has query params"
  573
+				});
  574
+
528 575
 				start();
529 576
 			}
530 577
 		]);
531 578
 	});
532 579
 
533 580
 	asyncTest( "query data passed as object to changePage is appended to URL", function(){
534  
-
535 581
 		$.testHelper.pageSequence([
536 582
 			// open our test page
537 583
 			function(){
@@ -544,14 +590,17 @@
544 590
 			},
545 591
 
546 592
 			function(){
547  
-				same(location.hash, "#" + siteDirectory + "form-tests/changepage-data.html?foo=3&bar=4");
  593
+				$.testHelper.assertUrlLocation({
  594
+					hashOrPush: home + "form-tests/changepage-data.html?foo=3&bar=4",
  595
+					report: "the hash or url still has query params"
  596
+				});
  597
+
548 598
 				start();
549 599
 			}
550 600
 		]);
551 601
 	});
552 602
 
553 603
 	asyncTest( "refresh of a dialog url should not duplicate page", function(){
554  
-
555 604
 		$.testHelper.pageSequence([
556 605
 			// open our test page
557 606
 			function(){
@@ -560,15 +609,19 @@
560 609
 			},
561 610
 
562 611
 			function(){
563  
-				same(location.hash, "#foo&ui-state=dialog", "hash should match what was loaded");
564  
-				same($(".foo-class").length, 1, "should only have one instance of foo-class in the document");
  612
+				$.testHelper.assertUrlLocation({
  613
+					hash: "foo&ui-state=dialog",
  614
+					push: home + "#foo&ui-state=dialog",
  615
+					report: "hash should match what was loaded"
  616
+				});
  617
+
  618
+				same( $(".foo-class").length, 1, "should only have one instance of foo-class in the document" );
565 619
 				start();
566 620
 			}
567 621
 		]);
568 622
 	});
569 623
 
570 624
 	asyncTest( "internal form with no action submits to document URL", function(){
571  
-
572 625
 		$.testHelper.pageSequence([
573 626
 			// open our test page
574 627
 			function(){
@@ -580,14 +633,17 @@
580 633
 			},
581 634
 
582 635
 			function(){