From 494bf7fe416bc72dd39cf1cd6804da2648b2dc04 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Thu, 27 Jun 2013 18:08:06 -0400 Subject: [PATCH 01/21] strings: add logging for untranslated source messages This should probably be changed to be closer to the goal of being like gettext but we can at least tell developers when we clobber the string --- src/strings.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strings.js b/src/strings.js index 2b1f0c79..fba93de0 100644 --- a/src/strings.js +++ b/src/strings.js @@ -80,7 +80,8 @@ $.extend( $, { string = container[ props[ i ] ]; if ( typeof( string ) != "string" ) { - string = ""; + $.console.debug( "Untranslated source string:", prop ); + string = ""; // FIXME: this breaks gettext()-style convention, which would return source } return string.replace(/\{\d+\}/g, function(capture) { From 2bf6b13bfeb1f61c09b205c530938e30864c863e Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Thu, 27 Jun 2013 18:10:23 -0400 Subject: [PATCH 02/21] User-visible warning when tile sources fail to load This is currently using window.alert in lieu of something more sophisticated but it works for indicating 404s or invalid file contents --- src/strings.js | 3 ++- src/tilesource.js | 12 ++++++++++++ src/viewer.js | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/strings.js b/src/strings.js index fba93de0..9616850a 100644 --- a/src/strings.js +++ b/src/strings.js @@ -45,7 +45,8 @@ var I18N = { ImageFormat: "Sorry, we don't support {0}-based Deep Zoom Images.", Security: "It looks like a security restriction stopped us from " + "loading this Deep Zoom Image.", - Status: "This space unintentionally left blank ({0} {1})." + Status: "This space unintentionally left blank ({0} {1}).", + "Open-Failed": "Unable to open {0}: {1}" }, Tooltips: { diff --git a/src/tilesource.js b/src/tilesource.js index 3168606e..fb22b21c 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -293,6 +293,11 @@ $.TileSource.prototype = { callback = function( data ){ var $TileSource = $.TileSource.determineType( _this, data, url ); + if ( !$TileSource ) { + _this.raiseEvent( 'open-failed', { message: "Unable to load TileSource", source: url } ); + return; + } + options = $TileSource.prototype.configure.apply( _this, [ data, url ]); readySource = new $TileSource( options ); _this.ready = true; @@ -315,6 +320,11 @@ $.TileSource.prototype = { $.makeAjaxRequest( url, function( xhr ) { var data = processResponse( xhr ); callback( data ); + }, function ( xhr ) { + _this.raiseEvent( 'open-failed', { + message: "HTTP " + xhr.status + " attempting to load TileSource", + source: url + }); }); } @@ -458,6 +468,8 @@ $.TileSource.determineType = function( tileSource, data, url ){ return OpenSeadragon[ property ]; } } + + $.console.error( "No TileSource was able to open %s %s", url, data ); }; diff --git a/src/viewer.js b/src/viewer.js index cf5c9aa1..532f5078 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -171,6 +171,12 @@ $.Viewer = function( options ) { //Inherit some behaviors and properties $.EventHandler.call( this ); + + this.addHandler( 'open-failed', function (source, args) { + var msg = $.getString( "Errors.Open-Failed", args.source, args.message); + window.alert( msg ); + }); + $.ControlDock.call( this, options ); //Deal with tile sources @@ -433,6 +439,9 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, tileSource = new $.TileSource( tileSource, function( readySource ){ openTileSource( _this, readySource ); }); + tileSource.addHandler( 'open-failed', function ( name, args ) { + _this.raiseEvent( 'open-failed', args ); + }); } else if ( $.isPlainObject( tileSource ) || tileSource.nodeType ){ if( $.isFunction( tileSource.getTileUrl ) ){ @@ -443,6 +452,13 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, } else { //inline configuration $TileSource = $.TileSource.determineType( _this, tileSource ); + if ( !$TileSource ) { + _this.raiseEvent( 'open-failed', { + message: "Unable to load TileSource", + source: tileSource + }); + return; + } options = $TileSource.prototype.configure.apply( _this, [ tileSource ]); readySource = new $TileSource( options ); openTileSource( _this, readySource ); From 7dcf662fdde72d6b1956f4bc6f447a573499c668 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Fri, 28 Jun 2013 14:32:57 -0400 Subject: [PATCH 03/21] Viewer: add a basic error message display system This might be something we want to make configurable for users who want to completely control how errors are displayed. This also adds the first use of OpenSeadragon.makeCenteredNode currently so we might want to clean up that code as well. --- src/viewer.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/viewer.js b/src/viewer.js index 532f5078..95cfc908 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -174,7 +174,12 @@ $.Viewer = function( options ) { this.addHandler( 'open-failed', function (source, args) { var msg = $.getString( "Errors.Open-Failed", args.source, args.message); - window.alert( msg ); + + var errorDiv = $.makeNeutralElement( "div" ); + $.addClass( errorDiv, "modal-dialog error" ); + errorDiv.appendChild( document.createTextNode( msg ) ); + + _this.container.appendChild( $.makeCenteredNode(errorDiv) ); }); $.ControlDock.call( this, options ); @@ -423,6 +428,13 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, $TileSource, options; + if (this.container) { + var dialogs = this.container.querySelectorAll(".modal-dialog"); + for (var i = 0; i < dialogs.length; i++) { + dialogs[i].parentNode.remove(dialogs[i]); + } + } + //allow plain xml strings or json strings to be parsed here if( $.type( tileSource ) == 'string' ){ if( tileSource.match(/\s*<.*/) ){ From fb7c91acd2b05617b2eb304ca4310f4329e48442 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Fri, 28 Jun 2013 14:54:44 -0400 Subject: [PATCH 04/21] code style --- src/openseadragon.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 4f4e9752..64490b0a 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1313,7 +1313,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ makeAjaxRequest: function( url, onSuccess, onError ) { var request = $.createAjaxRequest(); - if (!$.isFunction(onSuccess)) { + if ( !$.isFunction( onSuccess ) ) { throw new Error( "makeAjaxRequest requires a success callback" ); } @@ -1327,7 +1327,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ } else { $.console.log("AJAX request returned %s: %s", request.status, url); - if ($.isFunction(onError)) { + if ( $.isFunction( onError ) ) { onError( request ); } } @@ -1342,7 +1342,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ request.onreadystatechange = function(){}; - if ($.isFunction(onError)) { + if ( $.isFunction( onError ) ) { onError( request, e ); } } From 7f42dfc03281a7d29c8ce6dae258816070282472 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Fri, 28 Jun 2013 15:01:56 -0400 Subject: [PATCH 05/21] code style --- src/openseadragon.js | 4 ++-- src/viewer.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 64490b0a..a48e8fe9 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1325,7 +1325,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ if ( request.status == 200 ) { onSuccess( request ); } else { - $.console.log("AJAX request returned %s: %s", request.status, url); + $.console.log( "AJAX request returned %s: %s", request.status, url ); if ( $.isFunction( onError ) ) { onError( request ); @@ -1338,7 +1338,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ request.open( "GET", url, true ); request.send( null ); } catch (e) { - $.console.log("%s while making AJAX request: %s", e.name, e.message); + $.console.log( "%s while making AJAX request: %s", e.name, e.message ); request.onreadystatechange = function(){}; diff --git a/src/viewer.js b/src/viewer.js index 95cfc908..fb50cbb8 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -179,7 +179,7 @@ $.Viewer = function( options ) { $.addClass( errorDiv, "modal-dialog error" ); errorDiv.appendChild( document.createTextNode( msg ) ); - _this.container.appendChild( $.makeCenteredNode(errorDiv) ); + _this.container.appendChild( $.makeCenteredNode( errorDiv ) ); }); $.ControlDock.call( this, options ); @@ -428,10 +428,10 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, $TileSource, options; - if (this.container) { - var dialogs = this.container.querySelectorAll(".modal-dialog"); - for (var i = 0; i < dialogs.length; i++) { - dialogs[i].parentNode.remove(dialogs[i]); + if ( this.container ) { + var dialogs = this.container.querySelectorAll( ".modal-dialog" ); + for ( var i = 0; i < dialogs.length; i++ ) { + dialogs[i].parentNode.remove( dialogs[i] ); } } From 630bccad3d1e57b5665cb4cecb2b387a121e8776 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Fri, 28 Jun 2013 15:44:38 -0400 Subject: [PATCH 06/21] Basic demo: set HTML5 doctype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This keeps IE8 out of quirks mode, which breaks things like getWindowSize… --- test/demo/basic.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/demo/basic.html b/test/demo/basic.html index b9a02aef..48506a42 100644 --- a/test/demo/basic.html +++ b/test/demo/basic.html @@ -1,3 +1,4 @@ + OpenSeadragon Basic Demo @@ -23,7 +24,7 @@ // debugMode: true, id: "contentDiv", prefixUrl: "../../build/openseadragon/images/", - tileSources: "../data/testpattern.dzi", + tileSources: "../data/testpattern.dzisn't", showNavigator:true }); From 1f345e4cc5756d55e3e182021f79f3025f8afc95 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Fri, 28 Jun 2013 15:46:31 -0400 Subject: [PATCH 07/21] Viewer: refactor message code into showMessage/hideMessage methods This avoids the need to deal with classes, and explicitly makes it reusable for other needs --- src/viewer.js | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index fb50cbb8..9943c770 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -174,12 +174,7 @@ $.Viewer = function( options ) { this.addHandler( 'open-failed', function (source, args) { var msg = $.getString( "Errors.Open-Failed", args.source, args.message); - - var errorDiv = $.makeNeutralElement( "div" ); - $.addClass( errorDiv, "modal-dialog error" ); - errorDiv.appendChild( document.createTextNode( msg ) ); - - _this.container.appendChild( $.makeCenteredNode( errorDiv ) ); + _this.showMessage( msg ); }); $.ControlDock.call( this, options ); @@ -428,12 +423,7 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, $TileSource, options; - if ( this.container ) { - var dialogs = this.container.querySelectorAll( ".modal-dialog" ); - for ( var i = 0; i < dialogs.length; i++ ) { - dialogs[i].parentNode.remove( dialogs[i] ); - } - } + _this.hideMessage(); //allow plain xml strings or json strings to be parsed here if( $.type( tileSource ) == 'string' ){ @@ -1082,8 +1072,31 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, this.referenceStrip.setFocus( page ); } return this; - } + }, + /** + * Display a message in the viewport + * @function + * @param {String} text message + */ + showMessage: function ( message ) { + var div = this.messageDiv = $.makeNeutralElement( "div" ); + div.appendChild( document.createTextNode( message ) ); + + this.container.appendChild( $.makeCenteredNode( div ) ); + }, + + /** + * Hide any currently displayed viewport message + * @function + */ + hideMessage: function () { + var div = this.messageDiv; + if (div) { + div.parentNode.remove(div); + delete this.messageDiv; + } + } }); /** From 9de45ba28113de7d6731391065c929bff1906a18 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Fri, 28 Jun 2013 15:52:48 -0400 Subject: [PATCH 08/21] Refactor OpenSeadragon.makeCenteredNode * Use CSS display tables for vertical centering (tested back to IE8) * Use the DOM instead of string concatenation * Remove redundant styles ($.makeNeutralElement sets the same values for margin, padding & border) * Return the outer wrapper element to ease DOM addition & removal --- src/openseadragon.js | 62 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index a48e8fe9..d5688aed 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -882,47 +882,49 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ /** * Wraps the given element in a nest of divs so that the element can - * be easily centered. + * be easily centered using CSS tables * @function * @name OpenSeadragon.makeCenteredNode * @param {Element|String} element - * @returns {Element} + * @returns {Element} outermost wrapper element */ makeCenteredNode: function( element ) { - - var div = $.makeNeutralElement( "div" ), - html = [], - innerDiv, - innerDivs; - + // Convert a possible ID to an actual HTMLElement element = $.getElement( element ); - //TODO: I dont understand the use of # inside the style attributes - // below. Invetigate the results of the constructed html in - // the browser and clean up the mark-up to make this clearer. - html.push('
'); - html.push('
'); - html.push('
'); + /* + CSS tables require you to have a display:table/row/cell hierarchy so we need to create + three nested wrapper divs: + */ - div.innerHTML = html.join( '' ); - div = div.firstChild; + var wrappers = [ + $.makeNeutralElement( 'div' ), + $.makeNeutralElement( 'div' ), + $.makeNeutralElement( 'div' ) + ]; - innerDiv = div; - innerDivs = div.getElementsByTagName( "div" ); - while ( innerDivs.length > 0 ) { - innerDiv = innerDivs[ 0 ]; - innerDivs = innerDiv.getElementsByTagName( "div" ); - } + // It feels like we should be able to pass style dicts to makeNeutralElement: + $.extend(wrappers[0].style, { + display: "table", + height: "100%", + width: "100%" + }); - innerDiv.appendChild( element ); + $.extend(wrappers[1].style, { + display: "table-row" + }); - return div; + $.extend(wrappers[2].style, { + display: "table-cell", + verticalAlign: "middle", + textAlign: "center" + }); + + wrappers[0].appendChild(wrappers[1]); + wrappers[1].appendChild(wrappers[2]); + wrappers[2].appendChild(element); + + return wrappers[0]; }, From d447bd561259ad9d7fb1f487f100a5367a047818 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Mon, 1 Jul 2013 12:55:58 -0400 Subject: [PATCH 09/21] Remove detritus left behind from error handler testing --- test/demo/basic.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/demo/basic.html b/test/demo/basic.html index 48506a42..19572b86 100644 --- a/test/demo/basic.html +++ b/test/demo/basic.html @@ -24,7 +24,7 @@ // debugMode: true, id: "contentDiv", prefixUrl: "../../build/openseadragon/images/", - tileSources: "../data/testpattern.dzisn't", + tileSources: "../data/testpattern.dzi", showNavigator:true }); From b9128961580bca577346d54bec01c6c23c2c4fd7 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Mon, 1 Jul 2013 12:58:22 -0400 Subject: [PATCH 10/21] Viewer.showMessage() hides any previous message --- src/viewer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/viewer.js b/src/viewer.js index 9943c770..47d81302 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1080,6 +1080,8 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, * @param {String} text message */ showMessage: function ( message ) { + this.hideMessage(); + var div = this.messageDiv = $.makeNeutralElement( "div" ); div.appendChild( document.createTextNode( message ) ); From 1ab609442025c30d395ef51235ace9dcc2ee56ba Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Mon, 1 Jul 2013 13:43:48 -0400 Subject: [PATCH 11/21] Viewer: make viewport messages private, repeatable * Marked _showMessage/_hideMessage as private * Fix DOM manipulation bug: call standard removeNode() method rather than proprietary WebKit .remove() * Change messageDiv to be the outer wrapper to simplify removing the entire message container --- src/viewer.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/viewer.js b/src/viewer.js index 47d81302..4451df3c 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -174,7 +174,7 @@ $.Viewer = function( options ) { this.addHandler( 'open-failed', function (source, args) { var msg = $.getString( "Errors.Open-Failed", args.source, args.message); - _this.showMessage( msg ); + _this._showMessage( msg ); }); $.ControlDock.call( this, options ); @@ -423,7 +423,7 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, $TileSource, options; - _this.hideMessage(); + _this._hideMessage(); //allow plain xml strings or json strings to be parsed here if( $.type( tileSource ) == 'string' ){ @@ -1077,25 +1077,29 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, /** * Display a message in the viewport * @function + * @private * @param {String} text message */ - showMessage: function ( message ) { - this.hideMessage(); + _showMessage: function ( message ) { + this._hideMessage(); - var div = this.messageDiv = $.makeNeutralElement( "div" ); + var div = $.makeNeutralElement( "div" ); div.appendChild( document.createTextNode( message ) ); - this.container.appendChild( $.makeCenteredNode( div ) ); + this.messageDiv = $.makeCenteredNode( div ); + + this.container.appendChild( this.messageDiv ); }, /** * Hide any currently displayed viewport message * @function + * @private */ - hideMessage: function () { + _hideMessage: function () { var div = this.messageDiv; if (div) { - div.parentNode.remove(div); + div.parentNode.removeChild(div); delete this.messageDiv; } } From 7048cda69eb2ac0f8153cdc20afcbb065ae3caf9 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Mon, 1 Jul 2013 16:55:39 -0400 Subject: [PATCH 12/21] Tests: getString with placeholder expansion --- test/strings.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/strings.js b/test/strings.js index 61a12d93..c454e808 100644 --- a/test/strings.js +++ b/test/strings.js @@ -1,12 +1,19 @@ (function() { module("strings"); + test("getSubString", function() { equal(OpenSeadragon.getString("Errors.Dzi"), "Hmm, this doesn't appear to be a valid Deep Zoom Image.", "Read sub-string"); }); + test("getStringWithPlaceholders", function() { + equal(OpenSeadragon.getString("Errors.Open-Failed", "foo", "bar"), + "Unable to open foo: bar", + "String placeholder replacement"); + }); + test("getInvalidString", function() { equal(OpenSeadragon.getString("Greeting"), "", "Handled unset string key"); From 0ed4703bd31112f12b178b50f311055390a01c6f Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Mon, 1 Jul 2013 18:26:33 -0400 Subject: [PATCH 13/21] Viewport messages: add a class to the dialog for easy styling --- src/viewer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/viewer.js b/src/viewer.js index 4451df3c..8ebbd2b3 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1088,6 +1088,8 @@ $.extend( $.Viewer.prototype, $.EventHandler.prototype, $.ControlDock.prototype, this.messageDiv = $.makeCenteredNode( div ); + $.addClass(this.messageDiv, "openseadragon-message"); + this.container.appendChild( this.messageDiv ); }, From 59a254bea6a4c256e2b8ea96230bacb35624f57d Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Mon, 1 Jul 2013 18:17:45 -0400 Subject: [PATCH 14/21] Tests: add open failure tests, overhaul test framework * Add tests for open failures * Refactor tests to avoid tests depending on implied status from previous tests: 1. The viewer is now created and destroyed for each test to avoid pollution and simplify error handling: nothing starts until you request it. 2. Some tests like Basic: Homepage depended on the Zoom & Pan tests; now this is handled explicitly as part of the test setup 3. All basic tests are now properly async tests (since they needed the viewer to load, they really were in the past except that they were relying on the viewer state left behind from previous tests) * All tests now run inside the qunit-fixture to prevent masking failures. Util.resetDom() has been refactored to use the qunit-fixture and the teardown logic only used in the navigator tests has been moved into the navigator test teardown method * Fixed undeclared mainViewerElement variable in optional path in the navigator tests * JSHint cleanup --- test/basic.js | 245 +++++++++++++++++++++++++++++----------------- test/formats.js | 8 +- test/navigator.js | 47 +++++---- test/test.html | 6 -- test/test.js | 17 ++-- 5 files changed, 195 insertions(+), 128 deletions(-) diff --git a/test/basic.js b/test/basic.js index bd8b731f..6b1468ca 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,144 +1,207 @@ /* global module, asyncTest, $, ok, equal, notEqual, start, test, Util */ (function() { + var viewer; - module('Basic'); + module('Basic', { + setup: function () { + var example = $('
').appendTo("#qunit-fixture"); - // TODO: Test drag - - var viewer = null; - - // ---------- - asyncTest('Open', function() { - $(document).ready(function() { viewer = OpenSeadragon({ id: 'example', prefixUrl: '/build/openseadragon/images/', - tileSources: '/test/data/testpattern.dzi', springStiffness: 100 // Faster animation = faster tests }); + }, + teardown: function () { + if (!!viewer && viewer.close) { + viewer.close(); + } - ok(viewer, 'Viewer exists'); + viewer = null; + } + }); - var openHandler = function(eventSender, eventData) { - viewer.removeHandler('open', openHandler); - ok(true, 'Open event was sent'); - equal(eventSender, viewer, 'Sender of open event was viewer'); - ok(eventData, 'Handler also received event data'); - ok(viewer.viewport, 'Viewport exists'); - ok(viewer.source, 'source exists'); - ok(viewer._updateRequestId, 'timer is on'); - start(); - }; + // ---------- + asyncTest('Open', function() { + ok(viewer, 'Viewer exists'); - viewer.addHandler('open', openHandler); + var openHandler = function(eventSender, eventData) { + viewer.removeHandler('open', openHandler); + ok(true, 'Open event was sent'); + equal(eventSender, viewer, 'Sender of open event was viewer'); + ok(eventData, 'Handler also received event data'); + ok(viewer.viewport, 'Viewport exists'); + ok(viewer.source, 'source exists'); + ok(viewer._updateRequestId, 'timer is on'); + start(); + }; + + viewer.addHandler('open', openHandler); + viewer.open('/test/data/testpattern.dzi'); + }); + + asyncTest('Open Error Handling', function() { + ok(viewer, 'Viewer exists'); + + viewer.addHandler('open', function(eventSender, eventData) { + ok(false, "The open event should not fire for failed opens"); + start(); }); + + viewer.addHandler('open-failed', function(eventSender, eventData) { + ok(true, "The open-failed event should be fired when the source 404s"); + + equal($(".openseadragon-message").length, 1, "Open failures should display a message"); + + start(); + }); + + viewer.open('/test/data/not-a-real-file'); }); // ---------- asyncTest('Zoom', function() { - var viewport = viewer.viewport; - equal(viewport.getZoom(), 1, 'We start out unzoomed'); + viewer.addHandler("open", function () { + var viewport = viewer.viewport; - var zoomHandler = function() { - viewer.removeHandler('animationfinish', zoomHandler); - equal(viewport.getZoom(), 2, 'Zoomed correctly'); - start(); - }; + equal(viewport.getZoom(), 1, 'We start out unzoomed'); - viewer.addHandler('animationfinish', zoomHandler); - viewport.zoomTo(2); + var zoomHandler = function() { + viewer.removeHandler('animationfinish', zoomHandler); + equal(viewport.getZoom(), 2, 'Zoomed correctly'); + start(); + }; + + viewer.addHandler('animationfinish', zoomHandler); + viewport.zoomTo(2); + }); + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Pan', function() { - var viewport = viewer.viewport; - var center = viewport.getCenter(); - ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); + viewer.addHandler("open", function () { + var viewport = viewer.viewport, + center = viewport.getCenter(); - var panHandler = function() { - viewer.removeHandler('animationfinish', panHandler); - center = viewport.getCenter(); - ok(center.x === 0.1 && center.y === 0.1, 'Panned correctly'); - start(); - }; + ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); - viewer.addHandler('animationfinish', panHandler); - viewport.panTo(new OpenSeadragon.Point(0.1, 0.1)); + var panHandler = function() { + viewer.removeHandler('animationfinish', panHandler); + center = viewport.getCenter(); + ok(center.x === 0.1 && center.y === 0.1, 'Panned correctly'); + start(); + }; + + viewer.addHandler('animationfinish', panHandler); + viewport.panTo(new OpenSeadragon.Point(0.1, 0.1)); + }); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Home', function() { - var viewport = viewer.viewport; - var center = viewport.getCenter(); - ok(center.x !== 0.5 && center.y !== 0.5, 'We start out panned'); - notEqual(viewport.getZoom(), 1, 'We start out zoomed'); + // Test setup: + function opener() { + var viewport = viewer.viewport; + viewport.panTo(new OpenSeadragon.Point(0.1, 0.1)); + viewport.zoomTo(2); + } - var homeHandler = function() { - viewer.removeHandler('animationfinish', homeHandler); - center = viewport.getCenter(); - ok(center.x === 0.5 && center.y === 0.5, 'We end up unpanned'); - equal(viewport.getZoom(), 1, 'We end up unzoomed'); - start(); - }; + function stage1() { + var viewport = viewer.viewport, + center = viewport.getCenter(); - viewer.addHandler('animationfinish', homeHandler); - viewport.goHome(true); + viewer.removeHandler('animationfinish', stage1); + + ok(center.x !== 0.5 && center.y !== 0.5, 'We start out panned'); + notEqual(viewport.getZoom(), 1, 'We start out zoomed'); + + var homeHandler = function() { + viewer.removeHandler('animationfinish', homeHandler); + center = viewport.getCenter(); + ok(center.x === 0.5 && center.y === 0.5, 'We end up unpanned'); + equal(viewport.getZoom(), 1, 'We end up unzoomed'); + start(); + }; + + viewer.addHandler('animationfinish', homeHandler); + viewport.goHome(true); + } + + viewer.addHandler("open", opener); + viewer.addHandler("animationfinish", stage1); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Click', function() { - var viewport = viewer.viewport, + viewer.addHandler("open", function () { + var viewport = viewer.viewport, center = viewport.getCenter(); - ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); - equal(viewport.getZoom(), 1, 'We start out unzoomed'); + ok(center.x === 0.5 && center.y === 0.5, 'We start out unpanned'); + equal(viewport.getZoom(), 1, 'We start out unzoomed'); - var clickHandler = function() { - viewer.removeHandler('animationfinish', clickHandler); - center = viewport.getCenter(); - ok(center.x > 0.37 && center.x < 0.38 && center.y > 0.37 && center.y < 0.38, 'Panned correctly'); - equal(viewport.getZoom(), 2, 'Zoomed correctly'); - start(); - }; + var clickHandler = function() { + viewer.removeHandler('animationfinish', clickHandler); + center = viewport.getCenter(); + ok(center.x > 0.37 && center.x < 0.38 && center.y > 0.37 && center.y < 0.38, 'Panned correctly'); + equal(viewport.getZoom(), 2, 'Zoomed correctly'); + start(); + }; - viewer.addHandler('animationfinish', clickHandler); - Util.simulateViewerClick(viewer, 0.25, 0.25); + viewer.addHandler('animationfinish', clickHandler); + Util.simulateViewerClick(viewer, 0.25, 0.25); + }); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- - test('Fullscreen', function() { - ok(!viewer.isFullPage(), 'Started out not fullpage'); - ok(!$(viewer.element).hasClass('fullpage'), - 'No fullpage class on div'); + asyncTest('Fullscreen', function() { + viewer.addHandler("open", function () { + ok(!viewer.isFullPage(), 'Started out not fullpage'); + ok(!$(viewer.element).hasClass('fullpage'), + 'No fullpage class on div'); - viewer.setFullPage(true); - ok(viewer.isFullPage(), 'Enabled fullpage'); - ok($(viewer.element).hasClass('fullpage'), - 'Fullpage class added to div'); + viewer.setFullPage(true); + ok(viewer.isFullPage(), 'Enabled fullpage'); + ok($(viewer.element).hasClass('fullpage'), + 'Fullpage class added to div'); - viewer.setFullPage(false); - ok(!viewer.isFullPage(), 'Disabled fullpage'); - ok(!$(viewer.element).hasClass('fullpage'), - 'Fullpage class removed from div'); + viewer.setFullPage(false); + ok(!viewer.isFullPage(), 'Disabled fullpage'); + ok(!$(viewer.element).hasClass('fullpage'), + 'Fullpage class removed from div'); + start(); + }); + + viewer.open('/test/data/testpattern.dzi'); }); // ---------- asyncTest('Close', function() { - var closeHandler = function() { - viewer.removeHandler('close', closeHandler); - ok(!viewer.source, 'no source'); - $('#example').empty(); - ok(true, 'Close event was sent'); - ok(!viewer._updateRequestId, 'timer is off'); - setTimeout(function() { - ok(!viewer._updateRequestId, 'timer is still off'); - start(); - }, 100); - }; + viewer.addHandler("open", function () { + var closeHandler = function() { + viewer.removeHandler('close', closeHandler); + ok(!viewer.source, 'no source'); + $('#example').empty(); + ok(true, 'Close event was sent'); + ok(!viewer._updateRequestId, 'timer is off'); + setTimeout(function() { + ok(!viewer._updateRequestId, 'timer is still off'); + start(); + }, 100); + }; - viewer.addHandler('close', closeHandler); - viewer.close(); + viewer.addHandler('close', closeHandler); + viewer.close(); + }); + viewer.open('/test/data/testpattern.dzi'); }); })(); diff --git a/test/formats.js b/test/formats.js index aeaae6c9..0c673bcf 100644 --- a/test/formats.js +++ b/test/formats.js @@ -3,7 +3,13 @@ // This module tests whether our various file formats can be opened. // TODO: Add more file formats (with corresponding test data). - module('Formats'); + module('Formats', { + setup: function () { + var example = document.createElement("div"); + example.id = "example"; + document.getElementById("qunit-fixture").appendChild(example); + } + }); var viewer = null; diff --git a/test/navigator.js b/test/navigator.js index 547e39a3..62899328 100644 --- a/test/navigator.js +++ b/test/navigator.js @@ -1,3 +1,5 @@ +/* global QUnit, module, Util, $, console, test, asyncTest, start, ok, equal */ + QUnit.config.autostart = false; (function () { @@ -14,14 +16,18 @@ QUnit.config.autostart = false; topNavigatorClickAdjustment; module("navigator", { - setup:function () { - Util.resetDom(); + setup: function () { + Util.initializeTestDOM(); resetTestVariables(); $(document).scrollTop(0); $(document).scrollLeft(0); }, - teardown:function () { - Util.resetDom(); + teardown: function () { + // jQuery UI creates its controls outside the normal DOM hierarchy which QUnit cleans up: + if ($('#exampleNavigator').is(':ui-dialog')) { + $('#exampleNavigator').dialog('destroy'); + } + resetTestVariables(); } }); @@ -31,7 +37,7 @@ QUnit.config.autostart = false; }); var resetTestVariables = function () { - if (viewer != null) { + if (viewer) { viewer.close(); } displayRegion = null; @@ -125,11 +131,11 @@ QUnit.config.autostart = false; viewerAndNavigatorDisplayReady = viewer.drawer !== null && !viewer.drawer.needsUpdate() && currentDisplayWidth > 0 && - Util.equalsWithVariance(lastDisplayRegionLeft, currentDisplayRegionLeft, .0001) && - Util.equalsWithVariance(lastDisplayWidth, currentDisplayWidth, .0001) && - Util.equalsWithVariance(viewer.viewport.getBounds(true).x, viewer.viewport.getBounds().x, .0001) && - Util.equalsWithVariance(viewer.viewport.getBounds(true).y, viewer.viewport.getBounds().y, .0001) && - Util.equalsWithVariance(viewer.viewport.getBounds(true).width, viewer.viewport.getBounds().width, .0001); + Util.equalsWithVariance(lastDisplayRegionLeft, currentDisplayRegionLeft, 0.0001) && + Util.equalsWithVariance(lastDisplayWidth, currentDisplayWidth, 0.0001) && + Util.equalsWithVariance(viewer.viewport.getBounds(true).x, viewer.viewport.getBounds().x, 0.0001) && + Util.equalsWithVariance(viewer.viewport.getBounds(true).y, viewer.viewport.getBounds().y, 0.0001) && + Util.equalsWithVariance(viewer.viewport.getBounds(true).width, viewer.viewport.getBounds().width, 0.0001); } catch (err) { //Ignore. Subsequent code will try again shortly @@ -138,7 +144,7 @@ QUnit.config.autostart = false; count++; setTimeout(function () { waitForViewer(handler, count, currentDisplayRegionLeft, currentDisplayWidth); - }, 100) + }, 100); } else { if (count === 40) { @@ -201,21 +207,21 @@ QUnit.config.autostart = false; expecteYCoordinate = 1 / viewer.source.aspectRatio - viewer.viewport.getBounds().height; } if (viewer.viewport.getBounds().width < 1) { - Util.assessNumericValue(expectedXCoordinate, viewer.viewport.getBounds().x, .04, ' Viewer at ' + theContentCorner + ', x coord'); + Util.assessNumericValue(expectedXCoordinate, viewer.viewport.getBounds().x, 0.04, ' Viewer at ' + theContentCorner + ', x coord'); } if (viewer.viewport.getBounds().height < 1 / viewer.source.aspectRatio) { - Util.assessNumericValue(expecteYCoordinate, viewer.viewport.getBounds().y, .04, ' Viewer at ' + theContentCorner + ', y coord'); + Util.assessNumericValue(expecteYCoordinate, viewer.viewport.getBounds().y, 0.04, ' Viewer at ' + theContentCorner + ', y coord'); } - } + }; }; var assessViewerInCenter = function () { - var yPositionVariance = .04; + var yPositionVariance = 0.04; if (viewer.source.aspectRatio < 1) { yPositionVariance = yPositionVariance / viewer.source.aspectRatio; } Util.assessNumericValue(1 / viewer.source.aspectRatio / 2, viewer.viewport.getCenter().y, yPositionVariance, ' Viewer at center, y coord'); - Util.assessNumericValue(.5, viewer.viewport.getCenter().x, .4, ' Viewer at center, x coord'); + Util.assessNumericValue(0.5, viewer.viewport.getCenter().x, 0.4, ' Viewer at center, x coord'); }; var clickOnNavigator = function (theContentCorner) { @@ -239,7 +245,7 @@ QUnit.config.autostart = false; yPos = contentStartFromTop + displayRegionHeight; } simulateNavigatorClick(viewer.navigator, xPos, yPos); - } + }; }; var dragNavigatorBackToCenter = function () { @@ -354,11 +360,12 @@ QUnit.config.autostart = false; clientX:1, clientY:1 }; - mainViewerElement.simulate('blur', event); + + $("#" + seadragonProperties.id).simulate('blur', event); + if (testProperties.expectedAutoFade) { setTimeout(assessAutoFadeTriggered,autoFadeWaitTime); - } - else { + } else { setTimeout(assessAutoFadeDisabled,autoFadeWaitTime); } } diff --git a/test/test.html b/test/test.html index 76d9cc2e..c41abe84 100644 --- a/test/test.html +++ b/test/test.html @@ -10,12 +10,6 @@
-
-
-
-
-
-
diff --git a/test/test.js b/test/test.js index 0e763fef..ddded768 100644 --- a/test/test.js +++ b/test/test.js @@ -1,3 +1,5 @@ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util */ + (function() { // ---------- @@ -30,16 +32,11 @@ .simulate('mouseup', event); }, - resetDom: function () { - if ($('#exampleNavigator').is(':ui-dialog')) { - $('#exampleNavigator').dialog('destroy'); - } - $("#exampleNavigator").remove(); - $(".navigator").remove(); - $("#example").empty(); - $("#tallexample").empty(); - $("#wideexample").empty(); - $("#example").parent().append('
'); + initializeTestDOM: function () { + $("#qunit-fixture") + .append('
') + .append('
') + .append('
'); }, equalsWithVariance: function (value1, value2, variance) { From d782da411ebe72bc89a92d260e48998d19971d76 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Tue, 2 Jul 2013 13:56:55 -0400 Subject: [PATCH 15/21] Tests: code cleanup --- test/basic.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/basic.js b/test/basic.js index 6b1468ca..add579cb 100644 --- a/test/basic.js +++ b/test/basic.js @@ -14,7 +14,7 @@ }); }, teardown: function () { - if (!!viewer && viewer.close) { + if (viewer && viewer.close) { viewer.close(); } @@ -189,7 +189,6 @@ var closeHandler = function() { viewer.removeHandler('close', closeHandler); ok(!viewer.source, 'no source'); - $('#example').empty(); ok(true, 'Close event was sent'); ok(!viewer._updateRequestId, 'timer is off'); setTimeout(function() { From 5a300998a8a2b38ca942d8ffe83ebc2ff2af5204 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Tue, 2 Jul 2013 15:12:16 -0400 Subject: [PATCH 16/21] Tests: console log capture utility --- test/test.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/test.js b/test/test.js index ddded768..51d2a33b 100644 --- a/test/test.js +++ b/test/test.js @@ -71,5 +71,46 @@ }; + /* + Test console log capture + + 1. Only the OpenSeadragon.console logger is touched + 2. All log messages are stored in window.testLog in arrays keyed on the logger name (e.g. log, + warning, error, etc.) as JSON-serialized strings to simplify comparisons + 3. The captured log arrays have a custom contains() method for ease of testing + 4. testLog.reset() will clear all of the message arrays, intended for use in test setup routines + */ + var testConsole = window.testConsole = {}, + testLog = window.testLog = { + log: [], + debug: [], + info: [], + warn: [], + error: [], + reset: function () { + for (var i in testLog) { + if (testLog.hasOwnProperty(i) && 'length' in testLog[i]) { + testLog[i].length = 0; + } + } + } + }; + + for (var i in testLog) { + if (testLog.hasOwnProperty(i) && testLog[i].push) { + testConsole[i] = (function (arr) { + return function () { + var args = Array.prototype.slice.call(arguments, 0); // Coerce to true Array + arr.push(JSON.stringify(args)); // Store as JSON to avoid tedious array-equality tests + }; + })(testLog[i]); + + testLog[i].contains = function (needle) { + return this.indexOf(needle) > -1; + }; + } + } + + OpenSeadragon.console = testConsole; })(); From 7131c7a7da3acd025db5fc41a1c06723423848bf Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Tue, 2 Jul 2013 15:12:31 -0400 Subject: [PATCH 17/21] Tests: check for AJAX error log message --- test/basic.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/basic.js b/test/basic.js index add579cb..61f36673 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,4 +1,4 @@ -/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util */ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */ (function() { var viewer; @@ -7,6 +7,8 @@ setup: function () { var example = $('
').appendTo("#qunit-fixture"); + testLog.reset(); + viewer = OpenSeadragon({ id: 'example', prefixUrl: '/build/openseadragon/images/', @@ -54,6 +56,9 @@ equal($(".openseadragon-message").length, 1, "Open failures should display a message"); + ok(testLog.log.contains('["AJAX request returned %s: %s",404,"/test/data/not-a-real-file"]'), + "AJAX failures should be logged to the console"); + start(); }); From 2a3044e0cf0585bf997fa6ea6a74eeab6ceba602 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Tue, 2 Jul 2013 15:20:17 -0400 Subject: [PATCH 18/21] Tests: verify invalid string log messages --- test/strings.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/strings.js b/test/strings.js index c454e808..1c740a8a 100644 --- a/test/strings.js +++ b/test/strings.js @@ -1,6 +1,12 @@ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */ + (function() { - module("strings"); + module("strings", { + setup: function () { + testLog.reset(); + } + }); test("getSubString", function() { equal(OpenSeadragon.getString("Errors.Dzi"), @@ -15,10 +21,13 @@ }); test("getInvalidString", function() { - equal(OpenSeadragon.getString("Greeting"), "", - "Handled unset string key"); - equal(OpenSeadragon.getString("Errors"), "", - "Handled requesting parent key"); + equal(OpenSeadragon.getString("Greeting"), "", "Handled unset string key"); + ok(testLog.debug.contains('["Untranslated source string:","Greeting"]'), + 'Invalid string keys are logged'); + + equal(OpenSeadragon.getString("Errors"), "", "Handled requesting parent key"); + ok(testLog.debug.contains('["Untranslated source string:","Errors"]'), + 'Invalid string parent keys are logged'); }); test("setString", function() { From 9fb4ae2a9a8ad8d73a692169cd09772cf551b8af Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Tue, 2 Jul 2013 16:09:19 -0400 Subject: [PATCH 19/21] Tests: more targeted log capture setup @Ventero pointed out that functions also have a length property --- test/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index 51d2a33b..e117df73 100644 --- a/test/test.js +++ b/test/test.js @@ -89,7 +89,7 @@ error: [], reset: function () { for (var i in testLog) { - if (testLog.hasOwnProperty(i) && 'length' in testLog[i]) { + if (testLog.hasOwnProperty(i) && 'length' in testLog[i] && 'push' in testLog[i]) { testLog[i].length = 0; } } From 6d7dd71577c86585a8eb2b9fc070475862bf5b70 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Tue, 2 Jul 2013 16:20:56 -0400 Subject: [PATCH 20/21] Tests: IE8 support for log capture No Array.indexOf. We are living like animals! --- test/test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index e117df73..37b7a075 100644 --- a/test/test.js +++ b/test/test.js @@ -106,7 +106,12 @@ })(testLog[i]); testLog[i].contains = function (needle) { - return this.indexOf(needle) > -1; + for (var i = 0; i < this.length; i++) { + if (this[i] == needle) { + return true; + } + } + return false; }; } } From 3434fe600c2a6351b6a0b81150cb127126bfd7fb Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Tue, 2 Jul 2013 16:28:59 -0400 Subject: [PATCH 21/21] Tests: update AJAX tests * IE8 support for makeAjaxRequest test (xhr.response does not exist, responseText is supported by all major browsers) * Update makeAjaxRequest test to confirm that the success callback is called and that the error callback is not * Add a test for makeAjaxRequest with an invalid file and verify that error callback is called but the success callback is not --- test/utils.js | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/test/utils.js b/test/utils.js index 7489f03b..f46f9a84 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,3 +1,5 @@ +/* global module, asyncTest, $, ok, equal, strictEqual, notEqual, start, test, Util, testLog */ + (function() { module("utils"); @@ -56,10 +58,33 @@ asyncTest("makeAjaxRequest", function() { var timeWatcher = Util.timeWatcher(); - OpenSeadragon.makeAjaxRequest('data/testpattern.dzi', function(xhr) { - ok(/deepzoom/.test(xhr.response), 'file loaded'); - timeWatcher.done(); - }); + OpenSeadragon.makeAjaxRequest('data/testpattern.dzi', + function(xhr) { + equal(xhr.status, 200, 'Success callback called for HTTP 200'); + ok(/deepzoom/.test(xhr.responseText), 'Success function called'); + timeWatcher.done(); + }, + function(xhr) { + ok(false, 'Error callback should not be called'); + timeWatcher.done(); + } + ); + }); + + asyncTest("makeAjaxRequest for invalid file", function() { + var timeWatcher = Util.timeWatcher(); + + OpenSeadragon.makeAjaxRequest('not-a-real-dzi-file', + function(xhr) { + ok(false, 'Success function should not be called for errors'); + timeWatcher.done(); + }, + function(xhr) { + equal(xhr.status, 404, 'Error callback called for HTTP 404'); + ok(true, 'Error function should be called for errors'); + timeWatcher.done(); + } + ); }); // ----------