diff --git a/.jscsrc b/.jscsrc
new file mode 100644
index 0000000..576f754
--- /dev/null
+++ b/.jscsrc
@@ -0,0 +1,5 @@
+{
+ "preset": "jquery",
+ // Since we check quotemarks already in jshint, this can be turned off
+ "validateQuoteMarks": false
+}
diff --git a/karma.conf.js b/karma.conf.js
index 4844c94..4d42e54 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -7,11 +7,26 @@ module.exports = function( config ) {
// Frameworks to use
frameworks: [ "qunit" ],
+ proxies : {
+ '/test/' : '/base/test/',
+ '/js/' : '/base/js/',
+ '/node_modules/syn/dist/' : '/base/node_modules/syn/dist/'
+ },
+
// List of files / patterns to load in the browser
files: [
- "test/bootstrap.js",
- "js/impress.js",
- "test/core_tests.js"
+ // The QUnit tests
+ "test/helpers.js",
+ "test/core_tests.js",
+ "test/navigation_tests.js",
+ // Presentation files, for the iframe
+ //"test/core_tests_presentation.html"
+ //{pattern: "test/core_tests_presentation.html", watched: true, served: true, included: false}
+ {pattern: "test/*.html", watched: true, served: true, included: false},
+ {pattern: "test/plugins/*/*.html", watched: true, served: true, included: false},
+ // JS files for iframe
+ {pattern: "js/impress.js", watched: true, served: true, included: false},
+ {pattern: "node_modules/syn/dist/global/syn.js", watched: false, served: true, included: false}
],
// List of files to exclude
@@ -43,11 +58,22 @@ module.exports = function( config ) {
// - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`)
// - PhantomJS
// - IE (only Windows; has to be installed with `npm install karma-ie-launcher`)
- browsers: [ "Chrome" ],
+ //browsers: [ "Chrome" ],
+ //browsers: [ "Firefox" ],
+ browsers: [ "Chrome", "Firefox" ],
- // If browser does not capture in given timeout [ms], kill it
- captureTimeout: 60000,
+ client: {
+ clearContext: false,
+ qunit: {
+ showUI: true,
+ testTimeout: 120*1000
+ }
+ },
+ // If browser does not capture, or produce output, in given timeout [ms], kill it
+ captureTimeout: 60*1000,
+ browserNoActivityTimeout: 60*1000,
+
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false
diff --git a/package.json b/package.json
index 34c40a7..1b377eb 100644
--- a/package.json
+++ b/package.json
@@ -22,20 +22,23 @@
"url": "https://github.com/bartaz/impress.js/issues"
},
"scripts": {
- "lint": "jshint js/impress.js *.js test/bootstrap.js && jscs js/impress.js *.js test/bootstrap.js --preset=jquery",
+ "lint": "jshint js/impress.js test/*.js && jscs js/impress.js test/*.js",
"test": "karma start --single-run",
"test:dev": "karma start",
"test:sauce": "karma start karma.conf-sauce.js"
},
"devDependencies": {
"chrome": "0.1.0",
+ "firefox": "0.0.1",
"jscs": "2.11.0",
"jshint": "2.9.1",
"karma": "0.13.22",
"karma-chrome-launcher": "1.0.1",
"karma-cli": "1.0.0",
+ "karma-firefox-launcher": "~0.1",
"karma-qunit": "1.0.0",
"karma-sauce-launcher": "1.0.0",
- "qunitjs": "2.0.0-rc1"
+ "qunitjs": "2.0.0-rc1",
+ "syn": "0.10.0"
}
}
diff --git a/qunit_test_runner.html b/qunit_test_runner.html
new file mode 100644
index 0000000..92f24d6
--- /dev/null
+++ b/qunit_test_runner.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ QUnit tests for impress.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/bootstrap.js b/test/bootstrap.js
index 6a315e8..774d62f 100644
--- a/test/bootstrap.js
+++ b/test/bootstrap.js
@@ -1,12 +1,25 @@
/*jshint browser:true */
-var root = document.createElement( "div" );
-root.innerHTML = [
- "",
- "
First slide
",
- "
Second slide
",
- "
Third slide
",
- "
Fourth slide
",
- "
"
-].join( "" );
-document.body.appendChild( root );
+// TODO: This is the bootstrap file for *karma*. Poorly named (since karma is
+// only one option, in this repo) but keeping the same name now to avoid
+// unnecessary deviation with upstream.
+// If you just want to run the tests locally, you can open test/index.html in Firefox.
+
+// That's annoying: karma-qunit doesn't provide the qunit-fixture element
+// https://github.com/karma-runner/karma-qunit/issues/18
+
+// This file contains so much HTML, that we will just respectfully disagree about js
+/* jshint quotmark:single */
+/* global document */
+
+var fix = document.createElement( 'div' );
+fix.id = 'qunit-fixture';
+fix.innerHTML = [
+'\n',
+' '
+].join( '' );
diff --git a/test/core_tests.js b/test/core_tests.js
index 12d2378..5a61e74 100644
--- a/test/core_tests.js
+++ b/test/core_tests.js
@@ -1,166 +1,266 @@
-(function() {
- var registerEventListener = function( target, eventType, callback ) {
- target.addEventListener( eventType, callback );
- return function removeRegisteredEventListener() {
- target.removeEventListener( eventType, callback );
+/*
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ *
+ * Released under the MIT license. See LICENSE file.
+ */
+
+/* global document, console, setTimeout, loadIframe, initPresentation, _impressSupported, QUnit */
+
+QUnit.module( "Core Tests" );
+
+QUnit.test( "Initialize Impress.js", function( assert ) {
+ console.log( "Begin init() test" );
+
+ // Init triggers impress:init and impress:stepenter events, which we want to catch.
+ var doneInit = assert.async();
+ var doneStepEnter = assert.async();
+ var doneSync = assert.async();
+
+ loadIframe( "test/core_tests_presentation.html", assert, function() {
+ var iframe = document.getElementById( "presentation-iframe" );
+ var iframeDoc = iframe.contentDocument;
+ var iframeWin = iframe.contentWindow;
+ var root = iframeDoc.querySelector( "div#impress" );
+
+ // Catch events triggered by init()
+ var assertInit = function() {
+ assert.ok( true, "impress:init event triggered." );
+
+ var canvas = iframeDoc.querySelector( "div#impress > div" );
+
+ // Delay and duration don't become set before the first transition actually happened
+ assert.equal( canvas.style.transitionDelay,
+ "0ms",
+ "canvas.style.transitionDelay initialized correctly" );
+ assert.equal( canvas.style.transitionDuration,
+ "0ms",
+ "canvas.style.transitionDuration initialized correctly" );
+
+ doneInit();
+ console.log( "End init() test (async)" );
};
- };
- QUnit.begin(function() {
- impress().init();
- });
+ var assertInitWrapper = function() {
+ setTimeout( function() { assertInit(); }, 10 );
+ };
+ root.addEventListener( "impress:init", assertInitWrapper );
- QUnit.module( "Initialization" );
+ root.addEventListener( "impress:stepenter", function( event ) {
+ assert.ok( true, "impress:stepenter event triggered." );
+ var step1 = iframeDoc.querySelector( "div#step-1" );
+ assert.equal( event.target, step1,
+ event.target.id + " triggered impress:stepenter event." );
+ doneStepEnter();
+ } );
- QUnit.test( "Global Scope", function( assert ) {
- assert.expect( 1 );
- assert.ok( impress, "impress declared in global scope" );
- });
+ // Synchronous code and assertions
+ assert.ok( iframeWin.impress,
+ "impress declared in global scope" );
+ assert.strictEqual( iframeWin.impress().init(), undefined,
+ "impress().init() called." );
+ assert.strictEqual( iframeWin.impress().init(), undefined,
+ "It's ok to call impress().init() a second time, it's a no-op." );
- QUnit.test( "Multiple API instantiation", function( assert ) {
- assert.expect( 0 );
- impress().init();
- });
+ // The asserts below are true immediately after impress().init() returns.
+ // Therefore we test them here, not in an event handler.
+ var notSupportedClass = iframeDoc.body.classList.contains( "impress-not-supported" );
+ var yesSupportedClass = iframeDoc.body.classList.contains( "impress-supported" );
+ if ( !_impressSupported() ) {
+ assert.ok( notSupportedClass,
+ "body.impress-not-supported class still there." );
+ assert.ok( !yesSupportedClass,
+ "body.impress-supported class was NOT added." );
+ } else {
+ assert.ok( !notSupportedClass,
+ "body.impress-not-supported class was removed." );
+ assert.ok( yesSupportedClass,
+ "body.impress-supported class was added." );
- QUnit.test( "Support Markup", function( assert ) {
- assert.expect( 4 );
+ assert.ok( !iframeDoc.body.classList.contains( "impress-disabled" ),
+ "body.impress-disabled is removed." );
+ assert.ok( iframeDoc.body.classList.contains( "impress-enabled" ),
+ "body.impress-enabled is added." );
- var impressNotSupported = document.body.classList.contains( "impress-not-supported" );
- var impressSupported = document.body.classList.contains( "impress-supported" );
- assert.ok( impressSupported, "Have class .impress-supported" );
- assert.notOk( impressNotSupported, "Don't have class .impress-not-supported" );
+ var canvas = iframeDoc.querySelector( "div#impress > div" );
+ assert.ok( !canvas.classList.contains( "step" ) && canvas.id === "",
+ "Additional 'canvas' div inserted between div#impress root and steps." );
+ assert.equal( canvas.style.transform,
+ "rotateZ(0deg) rotateY(0deg) rotateX(0deg) translate3d(1000px, 0px, 0px)",
+ "canvas.style.transform initialized correctly" );
+ assert.equal( canvas.style.transformOrigin,
+ "left top 0px",
+ "canvas.style.transformOrigin initialized correctly" );
+ assert.equal( canvas.style.transformStyle,
+ "preserve-3d",
+ "canvas.style.transformStyle initialized correctly" );
+ assert.equal( canvas.style.transitionProperty,
+ "all",
+ "canvas.style.transitionProperty initialized correctly" );
+ assert.equal( canvas.style.transitionTimingFunction,
+ "ease-in-out",
+ "canvas.style.transitionTimingFunction initialized correctly" );
- var impressDisabled = document.body.classList.contains( "impress-disabled" );
- var impressEnabled = document.body.classList.contains( "impress-enabled" );
- assert.ok( impressEnabled, "Have class .impress-enabled" );
- assert.notOk( impressDisabled, "Don't have class .impress-disabled" );
- });
+ assert.equal( iframeDoc.documentElement.style.height,
+ "100%",
+ "documentElement.style.height is 100%" );
- QUnit.test( "Attributes", function( assert ) {
- assert.expect( 10 );
+ // Steps initialization
+ var step1 = iframeDoc.querySelector( "div#step-1" );
+ assert.equal( step1.style.position,
+ "absolute",
+ "Step position is 'absolute'." );
- var actual, expected;
- var root = document.querySelector( "#impress" );
- var canvas = document.querySelector( "div#impress > div" );
+ assert.ok( step1.classList.contains( "active" ),
+ "Step 1 has active css class." );
- var canvasIsNotAStep = !canvas.classList.contains("step") && canvas.id === "";
- assert.ok( canvasIsNotAStep, "Canvas do not have step element data" );
+ }
+ doneSync();
+ console.log( "End init() test (sync)" );
+ } ); // LoadIframe()
- actual = canvas.style.webkitTransform || canvas.style.transform;
- expected = "rotateZ(0deg) rotateY(0deg) rotateX(0deg) translate3d(1000px, 0px, 0px)";
- assert.strictEqual( actual, expected, "canvas.style.transform initialized correctly" );
+} );
- // Normalize result for IE 11 and Safari.
- actual = canvas.style.webkitTransformOrigin || canvas.style.transformOrigin;
- expected = "left top 0px";
+// Note: Here we focus on testing the core functionality of moving between
+// steps, the css classes set and unset, and events triggered.
+// TODO: more complex animations and check position, transitions, delays, etc...
+QUnit.test( "Impress Core API", function( assert ) {
+ console.log( "Begin core api test" );
+ var done = assert.async();
+ loadIframe( "test/core_tests_presentation.html", assert, function() {
+ initPresentation( assert, function() {
+ var iframe = document.getElementById( "presentation-iframe" );
+ var iframeDoc = iframe.contentDocument;
+ var iframeWin = iframe.contentWindow;
- if ( actual === "left top" || actual === "0% 0%" ) {
- actual = expected;
- }
- assert.strictEqual( actual, expected, "canvas.style.transformOrigin initialized correctly" );
+ // Impress.js itself uses event listeners to manipulate most CSS classes.
+ // Wait a short while before checking, to avoid race.
+ // (See assertStepEnterWrapper and assertStepLeaveWrapper.)
+ var wait = 5; // Milliseconds
- actual = canvas.style.webkitTransformStyle || canvas.style.transformStyle;
- expected = "preserve-3d";
- assert.strictEqual( actual, expected, "canvas.style.transformStyle initialized correctly" );
+ var step1 = iframeDoc.querySelector( "div#step-1" );
+ var step2 = iframeDoc.querySelector( "div#step-2" );
+ var step3 = iframeDoc.querySelector( "div#step-3" );
+ var step4 = iframeDoc.querySelector( "div#fourth" );
+ var root = iframeDoc.querySelector( "div#impress" );
- actual = canvas.style.transitionDelay;
- expected = "0ms";
- assert.strictEqual( actual, expected, "canvas.style.transitionDelay initialized correctly" );
+ // On impress:stepenter, we do some assertions on the "entered" object.
+ // On impress:stepleave, we do some assertions on the "left" object.
+ // Finally we call next() to initialize the next transition, and it starts all over again.
+ var i = 0;
+ var sequence = [ { left: step1,
+ entered: step2,
+ next: function() { return iframeWin.impress().goto( 2 ); },
+ text: "goto() called and returns ok (2->3)" },
+ { left: step2,
+ entered: step3,
+ next: function() { return iframeWin.impress().goto( "fourth" ); },
+ text: "goto() called and returns ok (3->4)" },
+ { left: step3,
+ entered: step4,
+ next: function() { return iframeWin.impress().next(); },
+ text: "next() wraps around to first step (4->1)" },
+ { left: step4,
+ entered: step1,
+ next: function() { return iframeWin.impress().prev(); },
+ text: "prev() wraps around to last step (1->4)" },
+ { left: step1,
+ entered: step4,
+ next: function() { return iframeWin.impress().prev(); },
+ text: "prev() called and returns ok (4->3)" },
+ { left: step4,
+ entered: step3,
+ next: function() { return iframeWin.impress().goto( 0 ); },
+ text: "End of test suite, return to first step with goto(0)." },
+ { left: step3,
+ entered: step1,
+ next: false } // False = end of sequence
+ ];
- actual = canvas.style.transitionDuration;
- expected = "0ms";
- assert.strictEqual( actual, expected, "canvas.style.transitionDuration initialized correctly" );
+ // When both assertStepEnter and assertStepLeave are done, we can go to next step in sequence.
+ var readyCount = 0;
+ var readyForNext = function() {
+ readyCount++;
+ if ( readyCount % 2 === 0 ) {
+ if ( sequence[ i ].next ) {
+ assert.ok( sequence[ i ].next(), sequence[ i ].text );
+ i++;
+ assertImmediately();
+ } else {
+ done();
+ console.log( "End core api test" );
+ }
+ }
+ };
- actual = canvas.style.transitionProperty;
- expected = "all";
- assert.strictEqual( actual, expected, "canvas.style.transitionProperty initialized correctly" );
+ // Things to check on impress:stepenter event -----------------------------//
+ var assertStepEnter = function( event ) {
+ assert.equal( event.target, sequence[ i ].entered,
+ event.target.id + " triggered impress:stepenter event." );
+ assert.ok( event.target.classList.contains( "present" ),
+ event.target.id + " set present css class." );
+ assert.ok( !event.target.classList.contains( "future" ),
+ event.target.id + " unset future css class." );
+ assert.ok( !event.target.classList.contains( "past" ),
+ event.target.id + " unset past css class." );
+ assert.equal( "#/" + event.target.id, iframeWin.location.hash,
+ "Hash is " + "#/" + event.target.id );
- actual = canvas.style.transitionTimingFunction;
- expected = "ease-in-out";
- assert.strictEqual( actual, expected, "canvas.style.transitionTimingFunction initialized correctly" );
+ // Just by way of comparison, check transitionDuration again, in a non-init transition
+ var canvas = iframeDoc.querySelector( "div#impress > div" );
+ assert.equal( canvas.style.transitionDelay,
+ "0ms",
+ "canvas.style.transitionDelay set correctly" );
+ assert.equal( canvas.style.transitionDuration,
+ "1000ms",
+ "canvas.style.transitionDuration set correctly" );
- actual = root.style.perspective;
- expected = "";
- assert.notStrictEqual( actual, expected, "root.style.perspective should be set explicitly for IE 11" );
+ readyForNext();
+ };
- actual = document.documentElement.style.height;
- expected = "100%";
- assert.strictEqual( actual, expected, "documentElement.style.height is 100%" );
- });
+ var assertStepEnterWrapper = function( event ) {
+ setTimeout( function() { assertStepEnter( event ); }, wait );
+ };
+ root.addEventListener( "impress:stepenter", assertStepEnterWrapper );
- QUnit.test( "Steps", function( assert ) {
- assert.expect( 2 );
+ // Things to check on impress:stepleave event -----------------------------//
+ var assertStepLeave = function( event ) {
+ assert.equal( event.target, sequence[ i ].left,
+ event.target.id + " triggered impress:stepleave event." );
+ assert.ok( !event.target.classList.contains( "present" ),
+ event.target.id + " unset present css class." );
+ assert.ok( !event.target.classList.contains( "future" ),
+ event.target.id + " unset future css class." );
+ assert.ok( event.target.classList.contains( "past" ),
+ event.target.id + " set past css class." );
+ readyForNext();
+ };
- var actual, expected;
- var step1 = document.querySelector( "div#step-1" );
+ var assertStepLeaveWrapper = function( event ) {
+ setTimeout( function() { assertStepLeave( event ); }, wait );
+ };
+ root.addEventListener( "impress:stepleave", assertStepLeaveWrapper );
- actual = step1.style.position;
- expected = "absolute";
- assert.strictEqual( actual, expected, "Step position is 'absolute'" );
+ // Things to check immediately after impress().goto() ---------------------------//
+ var assertImmediately = function() {
+ assert.ok( sequence[ i ].entered.classList.contains( "active" ),
+ sequence[ i ].entered.id + " set active css class." );
+ assert.ok( !sequence[ i ].left.classList.contains( "active" ),
+ sequence[ i ].left.id + " unset active css class." );
+ };
- assert.ok( step1.classList.contains( "active" ), "Step 1 has active css class." );
- });
+ // Done with setup. Start testing! -----------------------------------------------//
+ // Do no-op tests first, then trigger the sequence of transitions we setup above. //
- QUnit.module( "Core API" );
+ assert.strictEqual( iframeWin.impress().goto( iframeDoc.querySelector( "div#impress" ) ),
+ false,
+ "goto() to a non-step element fails, as it should." );
+ assert.strictEqual( iframeWin.impress().goto(),
+ false,
+ "goto() fails, as it should." );
- QUnit.test( ".next()", function( assert ) {
- assert.expect( 2 );
-
- var root = document.querySelector( "div#impress" );
- var step1 = document.querySelector( "div#step-1" );
- var step2 = document.querySelector( "div#step-2" );
- var done = assert.async();
-
- impress().next();
-
- assert.ok( step2.classList.contains( "active" ), step2.id + " add active css class." );
- assert.notOk( step1.classList.contains( "active" ), step1.id + " remove active css class." );
-
- // Reset to original state
- var removeStepEnterEvent = registerEventListener( root, "impress:stepenter", function() {
- removeStepEnterEvent();
- done();
- });
- impress().goto( step1 );
- });
-
- QUnit.test( "impress:stepenter event", function( assert ) {
- assert.expect( 4 );
-
- var actual, expected;
- var root = document.querySelector( "div#impress" );
- var step1 = document.querySelector( "div#step-1" );
- var step2 = document.querySelector( "div#step-2" );
- var done = assert.async();
-
- var removeTestEvent = registerEventListener( root, "impress:stepenter", function( event ) {
- actual = event.target;
- expected = step2;
- assert.strictEqual( actual, expected, "Triggered event for the second step" );
-
- assert.ok( step2.classList.contains( "present" ), event.target.id + " add present css class" );
- assert.notOk( step2.classList.contains( "future" ), event.target.id + " remove future css class" );
- assert.notOk( step2.classList.contains( "past" ), event.target.id + " remove active css class." );
-
- removeTestEvent();
-
- // Reset to original state
- var removeCleanupEvent = registerEventListener( root, "impress:stepenter", function() {
- removeCleanupEvent();
- done();
- });
-
- impress().goto( step1 );
- });
-
- impress().next();
- });
-
- QUnit.done(function( details ) {
- // Impress.js will set the hash part of the url, we want to unset it when finished
- // Otherwise a refresh of browser page would not start tests from the last step step
- window.location.hash = "";
- // Add back vertical scrollbar so we can read results if there were failures.
- document.body.style.overflow = 'auto';
- });
-}());
+ // This starts executing the sequence above
+ assert.ok( iframeWin.impress().next(),
+ "next() called and returns ok (1->2)" );
+ } ); // InitPresentation()
+ } ); // LoadIframe()
+} );
diff --git a/test/core_tests_presentation.html b/test/core_tests_presentation.html
new file mode 100644
index 0000000..34ebb9f
--- /dev/null
+++ b/test/core_tests_presentation.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+ The presentation steps used in an iframe in core_tests.js
+
+
+
+
+
First slide
+
+
+
Fourth slide
+
+
+
+
+
+
+
diff --git a/test/helpers.js b/test/helpers.js
new file mode 100644
index 0000000..746facd
--- /dev/null
+++ b/test/helpers.js
@@ -0,0 +1,110 @@
+// This file contains so much HTML, that we will just respectfully disagree about js
+/* jshint quotmark:single */
+/* global document, console, setTimeout, navigator */
+/* exported loadIframe, initPresentation, _impressSupported */
+
+var loadIframe = function( src, assert, callback ) {
+ console.log( 'Begin loadIframe' );
+
+ // When running in Karma, the #qunit-fixture appears from somewhere and we can't set its
+ // contents in advance.
+ var fix = document.getElementById( 'qunit-fixture' );
+ fix.innerHTML = [
+ '\n',
+ ' '
+ ].join( '' );
+
+ var iframe = document.getElementById( 'presentation-iframe' );
+
+ var onLoad = function() {
+ assert.ok( true,
+ 'Presentation loaded. iframe.src = ' + iframe.src );
+ try {
+ assert.ok( iframe.contentDocument,
+ 'Verifying that tests can access the presentation inside the iframe. ' +
+ 'Note: On Firefox this fails when using paths with "../" parts for the iframe.' );
+ }
+ catch ( err ) {
+ assert.ok( false,
+ 'Error when trying to access presentation in iframe. Note: When using Chrome with ' +
+ 'local files (file:///) this will fail with SecurityError. ' +
+ 'You can however use Chrome over Karma.' );
+ }
+ console.log( 'End loadIframe' );
+ callback();
+ };
+
+ // FIXME: Seems to be some race in loading js files inside the iframe (in CircleCI).
+ // The error that happens is that window.impress isn't set yet, even if onLoad event triggered.
+ // Needs more investigation.
+ var onLoadWrapper = function( event ) {
+ setTimeout( function() { onLoad( event ); }, 1000 );
+ };
+ iframe.addEventListener( 'load', onLoadWrapper );
+
+ assert.ok( true,
+ 'Setting iframe.src = ' + src );
+ iframe.src = src;
+};
+
+var initPresentation = function( assert, callback ) {
+ console.log( 'Begin initPresentation' );
+ var iframe = document.getElementById( 'presentation-iframe' );
+ var iframeDoc = iframe.contentDocument;
+ var iframeWin = iframe.contentWindow;
+
+ // Impress:stepenter is the last event triggered in init(), so we wait for that.
+ var waitForStepEnter = function( event ) {
+ assert.ok( true, 'impress (' + event.target.id + ') is now initialized.' );
+ iframeDoc.removeEventListener( 'impress:stepenter', waitForStepEnterWrapper );
+ console.log( 'End initPresentation' );
+ callback();
+ };
+
+ // Unfortunately, impress.js uses the impress:stepenter event internally to
+ // do some things related to entering a step. This causes a race condition when
+ // we listen for the same event and expect it to be done with everything.
+ // We wait 5 ms to resolve the race condition, then it's safe to start testing.
+ var waitForStepEnterWrapper = function( event ) {
+ setTimeout( function() { waitForStepEnter( event ); }, 5 );
+ };
+ iframeDoc.addEventListener( 'impress:stepenter', waitForStepEnterWrapper );
+
+ assert.strictEqual( iframeWin.impress().init(), undefined, 'Initializing impress.' );
+};
+
+// Helper function to determine whether this browser is supported by
+// impress.js or not. Copied from impress.js itself.
+var _impressSupported = function() {
+ var pfx = ( function() {
+ var style = document.createElement( 'dummy' ).style,
+ prefixes = 'Webkit Moz O ms Khtml'.split( ' ' ),
+ memory = {};
+ return function( prop ) {
+ if ( typeof memory[ prop ] === 'undefined' ) {
+ var ucProp = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
+ props = ( prop + ' ' + prefixes.join( ucProp + ' ' ) + ucProp ).split( ' ' );
+ memory[ prop ] = null;
+ for ( var i in props ) {
+ if ( style[ props[ i ] ] !== undefined ) {
+ memory[ prop ] = props[ i ];
+ break;
+ }
+ }
+ }
+ return memory[ prop ];
+ };
+ } )();
+
+ var ua = navigator.userAgent.toLowerCase();
+ return ( pfx( 'perspective' ) !== null ) &&
+ ( document.body.classList ) &&
+ ( document.body.dataset ) &&
+ ( ua.search( /(iphone)|(ipod)|(android)/ ) === -1 );
+};
+
diff --git a/test/navigation_tests.js b/test/navigation_tests.js
new file mode 100644
index 0000000..fd4a05b
--- /dev/null
+++ b/test/navigation_tests.js
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2016 Henrik Ingo (@henrikingo)
+ *
+ * Released under the MIT license. See LICENSE file.
+ */
+/* global QUnit, loadIframe, initPresentation, document, window */
+
+QUnit.module( "Navigation plugin" );
+
+QUnit.test( "Navigation Plugin", function( assert ) {
+ window.console.log( "Begin navigation plugin" );
+ var done = assert.async();
+
+ loadIframe( "test/core_tests_presentation.html", assert, function() {
+ initPresentation( assert, function() {
+ var iframe = document.getElementById( "presentation-iframe" );
+ var iframeDoc = iframe.contentDocument;
+ var iframeWin = iframe.contentWindow;
+
+ var wait = 5; // Milliseconds
+
+ var step1 = iframeDoc.querySelector( "div#step-1" );
+ var step2 = iframeDoc.querySelector( "div#step-2" );
+ var step3 = iframeDoc.querySelector( "div#step-3" );
+ var step4 = iframeDoc.querySelector( "div#fourth" );
+ var root = iframeDoc.querySelector( "div#impress" );
+
+ var i = 0;
+ var sequence = [ { left: step1,
+ entered: step2,
+ next: function() { return iframeWin.syn.type( "bodyid", " " ); },
+ text: "space (2->3)" },
+ { left: step2,
+ entered: step3,
+ next: function() { return iframeWin.syn.type( "bodyid", "[right]" ); },
+ text: "[right] (3->4)" },
+ { left: step3,
+ entered: step4,
+ next: function() { return iframeWin.syn.type( "bodyid", "\t" ); },
+ text: "tab (4->1)" },
+ { left: step4,
+ entered: step1,
+ next: function() { return iframeWin.syn.type( "bodyid", "[down]" ); },
+ text: "[down] (1->2)" },
+ { left: step1,
+ entered: step2,
+ next: function() { return iframeWin.syn.type( "bodyid", "[page-down]" ); },
+ text: "[page-down] (2->3)" },
+ { left: step2,
+ entered: step3,
+ next: function() { return iframeWin.syn.type( "bodyid", "[page-up]" ); },
+ text: "[page-up] (3->2)" },
+ { left: step3,
+ entered: step2,
+ next: function() { return iframeWin.syn.type( "bodyid", "[left]" ); },
+ text: "[left] (2->1)" },
+ { left: step2,
+ entered: step1,
+ next: function() { return iframeWin.syn.type( "bodyid", "[up]" ); },
+ text: "[up] (1->4)" },
+ { left: step1,
+ entered: step4,
+ next: function() { return iframeWin.syn.click( "step-2", {} ); },
+ text: "click on 2 (4->2)" },
+ { left: step4,
+ entered: step2,
+ next: function() { return iframeWin.syn.click( "linktofourth", {} ); },
+ text: "click on link with href to id=fourth (2->4)" },
+ { left: step2,
+ entered: step4,
+ next: function() { return iframeWin.impress().goto( 0 ); },
+ text: "Return to first step with goto(0)." },
+ { left: step4,
+ entered: step1,
+ next: false }
+ ];
+
+ var readyCount = 0;
+ var readyForNext = function() {
+ readyCount++;
+ if ( readyCount % 2 === 0 ) {
+ if ( sequence[ i ].next ) {
+ assert.ok( sequence[ i ].next(), sequence[ i ].text );
+ i++;
+ } else {
+ window.console.log( "End navigation plugin" );
+ done();
+ }
+ }
+ };
+
+ // Things to check on impress:stepenter event -----------------------------//
+ var assertStepEnter = function( event ) {
+ assert.equal( event.target, sequence[ i ].entered,
+ event.target.id + " triggered impress:stepenter event." );
+ readyForNext();
+ };
+
+ var assertStepEnterWrapper = function( event ) {
+ window.setTimeout( function() { assertStepEnter( event ); }, wait );
+ };
+ root.addEventListener( "impress:stepenter", assertStepEnterWrapper );
+
+ // Things to check on impress:stepleave event -----------------------------//
+ var assertStepLeave = function( event ) {
+ assert.equal( event.target, sequence[ i ].left,
+ event.target.id + " triggered impress:stepleave event." );
+ readyForNext();
+ };
+
+ var assertStepLeaveWrapper = function( event ) {
+ window.setTimeout( function() { assertStepLeave( event ); }, wait );
+ };
+ root.addEventListener( "impress:stepleave", assertStepLeaveWrapper );
+
+ assert.ok( iframeWin.impress().next(), "next() called and returns ok (1->2)" );
+ } ); // InitPresentation()
+ } ); // LoadIframe()
+} );
+
+QUnit.test( "Navigation Plugin - No-op tests", function( assert ) {
+ window.console.log( "Begin navigation no-op" );
+ var done = assert.async();
+
+ loadIframe( "test/core_tests_presentation.html", assert, function() {
+ initPresentation( assert, function() {
+ var iframe = document.getElementById( "presentation-iframe" );
+ var iframeDoc = iframe.contentDocument;
+ var iframeWin = iframe.contentWindow;
+
+ var wait = 5; // Milliseconds
+
+ var root = iframeDoc.querySelector( "div#impress" );
+
+ // This should never happen -----------------------------//
+ var assertStepEnter = function( event ) {
+ assert.ok( false,
+ event.target.id + " triggered impress:stepenter event." );
+ };
+
+ var assertStepEnterWrapper = function( event ) {
+ window.setTimeout( function() { assertStepEnter( event ); }, wait );
+ };
+ root.addEventListener( "impress:stepenter", assertStepEnterWrapper );
+
+ // This should never happen -----------------------------//
+ var assertStepLeave = function( event ) {
+ assert.ok( false,
+ event.target.id + " triggered impress:stepleave event." );
+ };
+
+ var assertStepLeaveWrapper = function( event ) {
+ window.setTimeout( function() { assertStepLeave( event ); }, wait );
+ };
+ root.addEventListener( "impress:stepleave", assertStepLeaveWrapper );
+
+ // These are no-op actions, we're already in step-1 -----------------------//
+ assert.ok( iframeWin.syn.click( "step-1", {} ),
+ "Click on step that is currently active, should do nothing." );
+ assert.ok( iframeWin.syn.click( "linktofirst", {} ),
+ "Click on link pointing to step that is currently active, should do nothing." );
+
+ // After delay, if no event triggers are called. We're done.
+ window.setTimeout( function() {
+ window.console.log( "End navigation no-op" ); done();
+ }, 3000 );
+ } ); // InitPresentation()
+ } ); // LoadIframe()
+} );