2 * mobile popup unit tests
6 var urlObject = $.mobile.path.parseLocation(),
7 home = urlObject.pathname + urlObject.search;
9 module( "jquery.mobile.popup.js", {
11 $.testHelper.navReset( home );
15 $.extend($.testHelper, {
17 // detailedEventCascade: call a function and expect a series of events to be triggered (or not to be triggered), and guard
18 // with a timeout against getting stood up. Record the result (timed out / was triggered) for each event, and the order
19 // in which the event arrived wrt. any other events expected.
23 // src: event source (is jQuery object),
24 // event: event name (is string),
25 // NB: It's a good idea to namespace your events, because the handler will be removed
26 // based on the name you give here if a timeout occurs before the event fires.
37 // idx: order in which the event fired
38 // src: event source (is jQuery object),
39 // event: event name (is string)
40 // timedOut: timed out (is boolean)
47 detailedEventCascade: function( seq, result ) {
48 // grab one step from the sequence
52 derefSrc = function( src ) {
53 return ( $.isFunction( src ) ? src() : src );
57 if ( fn === undefined ) {
61 // Attach handlers to the various objects which are to be checked for correct event generation
66 // set a failsafe timer in case one of the events never happens
67 warnTimer = setTimeout( function() {
68 $.each( events, function( key, event ) {
69 if ( newResult[ key ] === undefined ) {
70 // clean up the unused handler
71 derefSrc( event.src ).unbind( event.event );
72 newResult[ key ] = $.extend( {}, event, { timedOut: true } );
76 // Move on to the next step
77 self.detailedEventCascade( seq, newResult );
80 function recordResult( key, event, result ) {
82 newResult[ key ] = $.extend( {}, event, result );
83 // Increment the number of received responses
85 if ( nEventsDone === nEvents ) {
86 // clear the timeout and move on to the next step when all events have been received
87 clearTimeout( warnTimer );
88 setTimeout( function() {
89 self.detailedEventCascade( seq, newResult );
94 $.each( events, function( key, event ) {
95 // Count the events so that we may know how many responses to expect
99 // Hook up to the event
100 derefSrc( event.src ).one( event.event, function() {
101 recordResult( key, event, { timedOut: false, idx: nEventsDone } );
106 setTimeout( function() {
107 recordResult( key, event, { timedOut: true, idx: -1 } );
113 // Call the function with the result of the events
118 function popupEnhancementTests( $sel, prefix ) {
119 var $container = $sel.parent(), $screen = $sel.parent().prev();
121 ok( $sel.data( "popup" ), prefix + ", popup div is associated with a popup widget" );
122 ok( $sel.hasClass( "ui-popup" ), prefix + ", popup payload has class 'ui-popup'" );
123 ok( $container.hasClass( "ui-popup-container" ), prefix + ", popup div parent has class ui-popup-container" );
124 ok( $container.parent().hasClass( "ui-page" ), prefix + ", popup container parent is the page" );
125 ok( $screen.hasClass( "ui-popup-screen" ), prefix + ", popup div is preceded by its screen" );
126 ok( $container.attr( "id" ) === $sel.attr( "id" ) + "-popup", prefix + ", popup container has the id of the payload + '-popup'" );
127 ok( $screen.attr( "id" ) === $sel.attr( "id" ) + "-screen", prefix + ", popup screen has the id of the payload + '-screen'" );
130 function tolTest( el, popup, val, expected ) {
131 el.popup( "option", "tolerance", val );
132 deepEqual( popup._tolerance, expected, "Popup tolerance: '" + val + "' results in expected tolerances" );
135 test( "Popup tolerances are parsed correctly", function() {
136 var tolTestElement = $( "#tolerance-test" ),
137 tolTestPopup = tolTestElement.data( "popup" ),
138 defaultValues = tolTestPopup._tolerance;
141 $.type( defaultValues.t ) === "number" && !isNaN( defaultValues.t ) &&
142 $.type( defaultValues.r ) === "number" && !isNaN( defaultValues.r ) &&
143 $.type( defaultValues.b ) === "number" && !isNaN( defaultValues.b ) &&
144 $.type( defaultValues.l ) === "number" && !isNaN( defaultValues.l ) ), "Default tolerances are numbers and not NaN" );
146 tolTest( tolTestElement, tolTestPopup, "", defaultValues );
147 tolTest( tolTestElement, tolTestPopup, "0", { t: 0, r: 0, b: 0, l: 0 } );
148 tolTest( tolTestElement, tolTestPopup, "14,12", { t: 14, r: 12, b: 14, l: 12 } );
149 tolTest( tolTestElement, tolTestPopup, "9,4,11,5", { t: 9, r: 4, b: 11, l: 5 } );
150 tolTest( tolTestElement, tolTestPopup, null, defaultValues );
153 test( "Popup is enhanced correctly", function() {
154 popupEnhancementTests( $( "#test-popup" ), "When autoenhanced" );
155 ok( $( "#page-content" ).children().first().html() === "<!-- placeholder for test-popup -->", "When autoenhanced, there is a placeholder in the popup div's original location" );
158 test( "Popup rearranges DOM elements correctly when it is destroyed and again when it is re-created", function() {
159 $( "#test-popup" ).popup( "destroy" );
161 ok( $( "#page-content" ).children().first().attr( "id" ) === "test-popup", "After destroying a popup, its payload is returned to its original location" );
162 ok( $( "#page-content" ).children().first().prev().html() !== "<!-- placeholder for test-popup -->", "No placeholder precedes the restored popup" );
163 ok( $( "#page-content" ).children().first().next().html() !== "<!-- placeholder for test-popup -->", "No placeholder succeedes the restored popup" );
165 $( "#test-popup" ).popup();
167 popupEnhancementTests( $( "#test-popup" ), "When re-created" );
168 ok( $( "#page-content" ).children().first().html() === "<!-- placeholder for test-popup -->", "When re-created, there is a placeholder in the popup div's original location" );
171 test( "On-the-fly popup is enhanced and de-enhanced correctly", function() {
172 var $container = $( "<div></div>" ).appendTo( $( "#page-content" ) ),
173 $payload = $( "<p id='otf-popup'>This is an on-the-fly-popup</p>" ).appendTo( $container );
177 popupEnhancementTests( $payload, "When created on-the-fly" );
178 ok( $container.children().first().html() === "<!-- placeholder for otf-popup -->", "When created on-the-fly, there is a placeholder in the popup div's original location" );
179 $payload.popup( "destroy" );
180 ok( !$payload.attr( "class" ), "After destroying on-the-fly popup, the payload has no 'class' attribute" );
181 ok( $container.children().is( $payload ), "After destroying on-the-fly popup, its payload is returned to its original location" );
184 asyncTest( "Popup opens and closes", function() {
185 var $popup = $( "#test-popup" );
188 $.testHelper.detailedEventCascade([
190 $popup.popup( "open" );
194 opened: { src: $popup, event: "popupafteropen.opensandcloses" },
195 hashchange: { src: $(document), event: "hashchange.opensandcloses" }
199 var theOffset = $( "#test-popup p" ).offset();
200 ok( !$popup.parent().prev().hasClass( "ui-screen-hidden" ), "Open popup screen is not hidden" );
201 ok( $popup.attr( "class" ).match( /( |^)ui-body-[a-z]( |$)/ ), "Open popup has a valid overlay theme" );
202 ok( theOffset.left >= 15 && theOffset.top >= 30, "Open popup top left coord is at least (10, 30)" );
204 $popup.popup( "option", "overlayTheme", "a" );
205 ok( $popup.parent().prev().hasClass( "ui-overlay-a" ), "Setting an overlay theme while the popup is open causes the theme to be applied and the screen to be faded in" );
206 ok( $popup.parent().prev().hasClass( "in" ), "Setting an overlay theme while the popup is open causes the theme to be applied and the screen to be faded in" );
207 ok( $popup.parent().hasClass( "ui-popup-active" ), "Open popup has the 'ui-popup-active' class" );
209 $popup.popup( "close" );
213 closed: { src: $popup, event: "popupafterclose.opensandcloses2" },
214 hashchange: { src: $(document), event: "hashchange.opensandcloses2" }
218 ok( !$popup.parent().hasClass( "in" ), "Closed popup container does not have class 'in'" );
219 ok( $popup.parent().prev().hasClass( "ui-screen-hidden" ), "Closed popup screen is hidden" );
220 ok( !$popup.parent().hasClass( "ui-popup-active" ), "Open popup dos not have the 'ui-popup-active' class" );
228 asyncTest( "Link that launches popup is deactivated", function() {
232 $.testHelper.detailedEventCascade([
234 $( "a#open-test-popup" ).click();
238 opened: { src: $( "#test-popup" ), event: "popupafteropen.linkActiveTestStep1" },
239 hashchange: { src: $(document), event: "navigate.linkActive" }
243 ok( !result.opened.timedOut, "Opening a popup did cause 'opened' event" );
244 ok( !$( "a#open-test-popup" ).closest( ".ui-btn" ).hasClass( "ui-btn-active" ), "Opening a popup removes active class from link that launched it" );
245 $( "#test-popup" ).popup( "close" );
249 closed: { src: $( "#test-popup" ), event: "popupafterclose.linkActiveTestStep2" },
250 hashchange: { src: $(document), event: "navigate.linkActive2" }
254 ok( !result.closed.timedOut, "Opening a popup did cause 'closed' event" );
255 $( "a#open-xyzzy-popup" ).click();
256 ok( !$( "a#open-xyzzy-popup" ).closest( ".ui-btn" ).hasClass( "ui-btn-active" ), "Opening a non-existing popup removes active class from link that attempted to launch it" );
258 $( "test-popup" ).popup( "close" );
262 closed: { src: $( "#test-popup" ), event: "popupafterclose.linkActiveTestStep3" },
263 hashchange: { src: $(document), event: "navigate.linkActive3" }
270 asyncTest( "Popup interacts correctly with hashchange", function() {
271 var baseUrl, activeIndex, $popup = $( "#test-popup" );
273 if( !$popup.data( "popup" ).options.history ) {
275 ok( true, "hash change disabled" );
282 $.testHelper.detailedEventCascade([
284 baseUrl = decodeURIComponent( location.href );
285 activeIndex = $.mobile.urlHistory.activeIndex;
286 $popup.popup( "open" );
290 opened: { src: $( "#test-popup" ), event: "popupafteropen.hashInteractStep1" },
291 hashchange: { src: $( window ), event: "hashchange.hashInteractStep1" }
295 ok( !result.hashchange.timedOut, "Opening a popup from a non-dialogHashKey location causes a hashchange event" );
296 ok( decodeURIComponent( location.href ) === baseUrl + ( ( baseUrl.indexOf( "#" ) > -1 ) ? "" : "#" ) + $.mobile.dialogHashKey, "location.href has been updated correctly" );
297 ok( $.mobile.urlHistory.activeIndex === activeIndex + 1, "$.mobile.urlHistory has been advanced correctly" );
298 $( "#test-popup" ).popup( "close" );
302 closed: { src: $( "#test-popup" ), event: "popupafterclose.hashInteractStep2" },
303 navigate: { src: $.mobile.pageContainer, event: "navigate.hashInteractStep2" }
307 ok( !result.navigate.timedOut, "Closing a popup from a non-dialogHashKey location causes a 'navigate' event" );
308 ok( decodeURIComponent( location.href ) === baseUrl, "location.href has been restored after the popup" );
309 ok( $.mobile.urlHistory.activeIndex === activeIndex, "$.mobile.urlHistory has been restored correctly" );
311 // TODO make sure that the afterclose is fired after the nav finishes
312 setTimeout(start, 300);
317 // This test assumes that the popup opens into a state that does not include dialogHashKey.
318 // This should be the case if the previous test has cleaned up correctly.
319 asyncTest( "Opening another page from the popup leaves no trace of the popup in history", function() {
320 var initialActive = $.extend( {}, {}, $.mobile.urlHistory.getActive()),
321 initialHRef = $.mobile.path.parseUrl( decodeURIComponent( location.href ) ),
322 initialBase = initialHRef.protocol + initialHRef.doubleSlash + initialHRef.authority + initialHRef.directory,
323 $popup = $( "#test-popup" );
325 if( !$popup.data( "popup" ).options.history ) {
327 ok( true, "hash change disabled" );
334 $.testHelper.detailedEventCascade([
336 $( "#test-popup" ).popup( "open" );
340 opened: { src: $( "#test-popup" ), event: "popupafteropen.anotherPageStep1" },
341 hashchange: { src: $( window ), event: "hashchange.anotherPageStep1" }
345 $( "#test-popup a" ).click();
349 closed: { src: $( "#test-popup" ), event: "popupafterclose.anotherPageStep2" },
350 hashchange: { src: $( window ), event: "hashchange.anotherPageStep2" }
354 var hRef = $.mobile.path.parseUrl( decodeURIComponent( location.href ) );
355 ok( !result.closed.timedOut, "Popup closed" );
356 ok( !result.hashchange.timedOut, "hashchange did occur" );
357 ok( decodeURIComponent( location.href ) === initialBase + hRef.filename, "New location is exactly the previous location (up to and including path) and the new filename" );
358 window.history.back();
362 hashchange: { src: $( window ), event: "hashchange.anotherPageStep3" },
363 pagechange: { src: $.mobile.pageContainer, event: "pagechange.anotherPageStep3" }
367 var active = $.mobile.urlHistory.getActive(),
370 $.each( initialActive, function( key, value ) {
371 if ( active[key] !== value ) {
378 $.each( active, function( key, value ) {
379 if ( initialActive[key] !== value ) {
386 ok( decodeURIComponent( location.href ) === initialHRef.href, "Going back once places the browser on the initial page" );
387 ok( identical, "Going back returns $.mobile.urlHistory to its initial value" );
388 ok( $.mobile.urlHistory.activeIndex === $.mobile.urlHistory.stack.length - 3, "Going back leaves exactly two entries ahead in $.mobile.urlHistory" );
390 setTimeout( function() { start(); }, 500 );
395 asyncTest( "Sequence page -> popup -> dialog -> popup works", function() {
396 var originallyActivePage = $.mobile.activePage[ 0 ], $popup = $( "#test-popup" );
398 if( !$popup.data( "popup" ).options.history ) {
400 ok( true, "hash change disabled" );
406 $.testHelper.detailedEventCascade([
408 $( "#popup-sequence-test" ).popup( "open" );
412 opened: { src: $( "#popup-sequence-test" ), event: "popupafteropen.sequenceTestStep1" },
413 hashchange: { src: $( window ), event: "hashchange.sequenceTestStep1" }
417 ok( !result.opened.timedOut, "Popup has emitted 'popupafteropen'" );
418 ok( !result.hashchange.timedOut, "A 'hashchange' event has occurred" );
419 $( "#popup-sequence-test-open-dialog" ).click();
423 closed: { src: $( "#popup-sequence-test" ), event: "popupafterclose.sequenceTestStep2" },
424 pageload: { src: $.mobile.pageContainer, event: "pageload.sequenceTestStep2" },
425 pagechange: { src: $.mobile.pageContainer, event: "pagechange.sequenceTestStep3" }
429 ok( !result.closed.timedOut, "Popup has emitted 'popupafterclose'" );
430 ok( !result.pageload.timedOut, "A 'pageload' event (presumably to load the dialog) has occurred" );
431 ok( $( "#popup-sequence-test-dialog" ).length > 0, "The dialog has been loaded successfully" );
432 ok( !result.pagechange.timedOut, "A 'pagechange' event has occurred" );
433 ok( $.mobile.activePage[ 0 ] === $( "#popup-sequence-test-dialog" )[ 0 ], "The dialog is the active page" );
434 $( "a[href='#popup-sequence-test-popup-inside-dialog']" ).click();
438 opened: { src: function() { return $( "#popup-sequence-test-popup-inside-dialog" ); }, event: "popupafteropen.sequenceTestStep3" },
439 hashchange: { src: $( window ), event: "hashchange.sequenceTestStep3" }
443 ok( !result.opened.timedOut, "Popup inside dialog has emitted 'popupafteropen'" );
444 ok( !result.hashchange.timedOut, "Popup inside dialog has caused a 'hashchange'" );
445 window.history.back();
449 close: { src: function() { return $( "#popup-sequence-test-popup-inside-dialog" ); }, event: "popupafterclose.sequenceTestStep4" },
450 hashchange: { src: $( window ), event: "hashchange.sequenceTestStep4" }
454 ok( !result.close.timedOut, "Popup inside dialog has emitted 'popupafterclose'" );
455 ok( !result.hashchange.timedOut, "The closing of the inside popup has resulted in a 'hashchange'" );
456 ok( $.mobile.activePage[ 0 ] === $( "#popup-sequence-test-dialog" )[ 0 ], "The dialog is once more the active page" );
457 window.history.back();
461 pagechange: { src: $.mobile.pageContainer, event: "pagechange.sequenceTestStep5" },
462 hashchange: { src: $( window ), event: "hashchange.sequenceTestStep5" }
466 ok( !result.pagechange.timedOut, "Going back from the dialog has resulted in a 'pagechange'" );
467 ok( !result.hashchange.timedOut, "Going back from the dialog has resulted in a 'hashchange'" );
468 ok( originallyActivePage === $.mobile.activePage[ 0 ], "After going back from the dialog, the originally active page is active once more" );
469 setTimeout( function() { start(); }, 300 );
474 asyncTest( "Popup focused after open", function() {
475 var $link = $( "#open-test-popup" ), $popup = $( "#test-popup" );
479 $popup.parent().one( "focus", function() {
480 ok( true, "focus fired after the popup opens" );
483 // check that after the popup is closed the focus is correct
484 $popup.one( "popupafteropen", function() {
485 ok( true, "afteropen has fired" );
486 $popup.popup( "close" );
489 $popup.one( "popupafterclose", function() {
490 // TODO make sure that the afterclose is fired after the nav finishes
491 setTimeout(start, 300);
494 $popup.popup( "open" );
497 asyncTest( "Popup doesn't alter the url when the history option is disabled", function() {
498 var $popup = $( "#test-history-popup" ), hash = $.mobile.path.parseLocation().hash;
500 $popup.popup( "open" );
502 equal( hash, $.mobile.path.parseLocation().hash, "the hash remains the same" );
504 ok( $popup.is( ":visible" ), "popup is indeed visible" );
506 $popup.one( "popupafterclose", function() {
507 // TODO make sure that the afterclose is fired after the nav finishes
508 setTimeout(start, 300);
511 $popup.popup( "close" );
514 asyncTest( "Navigating away from the popup page closes the popup without history enabled", function() {
515 var $popup = $( "#test-history-popup" );
519 $.testHelper.detailedEventCascade([
521 $popup.popup( "open" );
525 open: { src: $popup, event: "popupafterclose.historyOffTestStep1" }
529 ok( $popup.is( ":visible" ), "popup is indeed visible" );
530 $.mobile.changePage( "#no-popups" );
534 hashchange: { src: $(window), event: "hashchange.historyOffTestStep2" },
535 close: { src: $popup, event: "popupafterclose.historyOffTestStep2" }
539 ok( !result.close.timedOut, "close happened" );
540 ok( !result.close.timedOut, "hashchange happened" );
542 // TODO make sure that the afterclose is fired after the nav finishes
543 setTimeout(start, 300);
548 // TODO would be nice to avoid checking the internal representation
549 // of "openness" but :visible didn't seem to be working in this case
551 asyncTest( "Close links work on a history disabled popup", function() {
552 var $popup = $( "#test-history-popup" );
556 ok( !$popup.data( "popup" )._isOpen, "popup is initially closed" );
558 $popup.popup( 'open' );
559 ok( $popup.data( "popup" )._isOpen, "popup is opened with open method" );
561 $popup.one( "popupafterclose", function() {
562 ok( !$popup.data( "popup" )._isOpen, "popup is closed on link click" );
566 $popup.find( "a" ).click();
569 asyncTest( "Destroy closes the popup first", function() {
570 var $popup = $( "#test-destroy-popup" );
574 $popup.one( "popupafterclose", function() {
575 ok( true, "closed on destroy" );
579 $popup.popup( "destroy" );