From 40125054cf449bc0a9a724c699e66f3d913d0599 Mon Sep 17 00:00:00 2001 From: "oskar.wiksten@gmail.com" Date: Mon, 11 Apr 2011 18:29:22 +0000 Subject: [PATCH] Initial version of updated consolidated editor. git-svn-id: https://andors-trail.googlecode.com/svn/trunk@66 08aca716-68be-ccc6-4d58-36f5abd142ac --- AndorsTrailEdit/AndorsTrailEditor.js | 453 +++++++ AndorsTrailEdit/DataStore.js | 141 ++ AndorsTrailEdit/EditorTabs.js | 84 ++ AndorsTrailEdit/FieldList.js | 62 + AndorsTrailEdit/ImageSelector.js | 111 ++ AndorsTrailEdit/editor.html | 767 +++++++++++ AndorsTrailEdit/inc/jquery.shorten.min.js | 7 + AndorsTrailEdit/inc/qunit.js | 1442 +++++++++++++++++++++ AndorsTrailEdit/logo.png | Bin 0 -> 4337 bytes AndorsTrailEdit/styles2.css | 35 + AndorsTrailEdit/unittest.html | 464 +++++++ 11 files changed, 3566 insertions(+) create mode 100644 AndorsTrailEdit/AndorsTrailEditor.js create mode 100644 AndorsTrailEdit/DataStore.js create mode 100644 AndorsTrailEdit/EditorTabs.js create mode 100644 AndorsTrailEdit/FieldList.js create mode 100644 AndorsTrailEdit/ImageSelector.js create mode 100644 AndorsTrailEdit/editor.html create mode 100644 AndorsTrailEdit/inc/jquery.shorten.min.js create mode 100644 AndorsTrailEdit/inc/qunit.js create mode 100644 AndorsTrailEdit/logo.png create mode 100644 AndorsTrailEdit/styles2.css create mode 100644 AndorsTrailEdit/unittest.html diff --git a/AndorsTrailEdit/AndorsTrailEditor.js b/AndorsTrailEdit/AndorsTrailEditor.js new file mode 100644 index 000000000..04dee05cf --- /dev/null +++ b/AndorsTrailEdit/AndorsTrailEditor.js @@ -0,0 +1,453 @@ +function IncludeJavascript(jsFile) { + document.write(' + + + + Andor's Trail Content Editor + + + +
+
+ +
+
Import
+
Export
+
+
+ +
+
+

Actor status effects

+
+
+
Add
+
+ +

Quests

+
+
+
Add
+
+ +

Items

+
+
+
Add
+
+ +

Droplists

+
+
+
Add
+
+ +

Dialogue

+
+
    +
  • TODO: Conversation 1
  • +
  • TODO: Conversation 2
  • +
  • TODO: Conversation 3
  • +
+
Add
+
+ +

Monsters & NPCs

+
+
+
Add
+
+ + +
+
+ +
+
+ +
+

Welcome to the content editor for Andor's Trail.

+ Start by importing some content from the game by pressing the top right "Import" button.
+ Or you could start by creating new content by selecting something from the left list.
+
+
+
+
+ +
+ + + + + + + + diff --git a/AndorsTrailEdit/inc/jquery.shorten.min.js b/AndorsTrailEdit/inc/jquery.shorten.min.js new file mode 100644 index 000000000..694c9e246 --- /dev/null +++ b/AndorsTrailEdit/inc/jquery.shorten.min.js @@ -0,0 +1,7 @@ +/* + * Shorten, a jQuery plugin to automatically shorten text to fit in a block or a pre-set width and configure how the text ends. + * Copyright (C) 2009-2011 Marc Diethelm + * License: (GPL 3, http://www.gnu.org/licenses/gpl-3.0.txt) see license.txt + */ + +(function(a){function s(g,c){return c.measureText(g).width}function t(g,c){c.text(g);return c.width()}var q=false,o,j,k;a.fn.shorten=function(){var g={},c=arguments,r=c.callee;if(c.length)if(c[0].constructor==Object)g=c[0];else if(c[0]=="options")return a(this).eq(0).data("shorten-options");else g={width:parseInt(c[0]),tail:c[1]};this.css("visibility","hidden");var h=a.extend({},r.defaults,g);return this.each(function(){var e=a(this),d=e.text(),p=d.length,i,f=a("").html(h.tail).text(),l={shortened:false, textOverflow:false};i=e.css("float")!="none"?h.width||e.width():h.width||e.parent().width();if(i<0)return true;e.data("shorten-options",h);this.style.display="block";this.style.whiteSpace="nowrap";if(o){var b=a(this),n=document.createElement("canvas");ctx=n.getContext("2d");b.html(n);ctx.font=b.css("font-style")+" "+b.css("font-variant")+" "+b.css("font-weight")+" "+Math.ceil(parseFloat(b.css("font-size")))+"px "+b.css("font-family");j=ctx;k=s}else{b=a('
'); $td=a("td",b);a(this).html(b);j=$td;k=t}b=k.call(this,d,j);if(b"+h.tail+"
").text();if(n.length==1&&n.charCodeAt(0)==8230){e.text(d);this.style.overflow="hidden";this.style[r._native]="ellipsis";this.style.visibility="visible";l.shortened=true;l.textOverflow="ellipsis";e.data("shorten-info",l);return true}}f=k.call(this,f,j);i-=f;f=i*1.15; if(b-f>0){f=d.substring(0,Math.ceil(p*(f/b)));if(k.call(this,f,j)>i){d=f;p=d.length}}do{p--;d=d.substring(0,p)}while(k.call(this,d,j)>=i);e.html(a.trim(a("").text(d).html())+h.tail);this.style.visibility="visible";l.shortened=true;e.data("shorten-info",l);return true})};var m=document.documentElement.style;if("textOverflow"in m)q="textOverflow";else if("OTextOverflow"in m)q="OTextOverflow";if(typeof Modernizr!="undefined"&&Modernizr.canvastext)o=Modernizr.canvastext;else{m=document.createElement("canvas"); o=!!(m.getContext&&m.getContext("2d")&&typeof m.getContext("2d").fillText==="function")}a.fn.shorten._is_canvasTextSupported=o;a.fn.shorten._native=q;a.fn.shorten.defaults={tail:"…",tooltip:true}})(jQuery); diff --git a/AndorsTrailEdit/inc/qunit.js b/AndorsTrailEdit/inc/qunit.js new file mode 100644 index 000000000..9c1c9af17 --- /dev/null +++ b/AndorsTrailEdit/inc/qunit.js @@ -0,0 +1,1442 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2011 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * or GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var defined = { + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + try { + return !!sessionStorage.getItem; + } catch(e){ + return false; + } + })() +}; + +var testId = 0; + +var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { + this.name = name; + this.testName = testName; + this.expected = expected; + this.testEnvironmentArg = testEnvironmentArg; + this.async = async; + this.callback = callback; + this.assertions = []; +}; +Test.prototype = { + init: function() { + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + this.name; + var li = document.createElement("li"); + li.appendChild( b ); + li.className = "running"; + li.id = this.id = "test-output" + testId++; + tests.appendChild( li ); + } + }, + setup: function() { + if (this.module != config.previousModule) { + if ( config.previousModule ) { + QUnit.moduleDone( { + name: config.previousModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0 }; + QUnit.moduleStart( { + name: this.module + } ); + } + + config.current = this; + this.testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, this.moduleTestEnvironment); + if (this.testEnvironmentArg) { + extend(this.testEnvironment, this.testEnvironmentArg); + } + + QUnit.testStart( { + name: this.testName + } ); + + // allow utility functions to access the current test environment + // TODO why?? + QUnit.current_testEnvironment = this.testEnvironment; + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + this.testEnvironment.setup.call(this.testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); + } + }, + run: function() { + if ( this.async ) { + QUnit.stop(); + } + + if ( config.notrycatch ) { + this.callback.call(this.testEnvironment); + return; + } + try { + this.callback.call(this.testEnvironment); + } catch(e) { + fail("Test " + this.testName + " died, exception and test follows", e, this.callback); + QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }, + teardown: function() { + try { + checkPollution(); + this.testEnvironment.teardown.call(this.testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); + } + }, + finish: function() { + if ( this.expected && this.expected != this.assertions.length ) { + QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + + for ( var i = 0; i < this.assertions.length; i++ ) { + var assertion = this.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + // store result when possible + if ( QUnit.config.reorder && defined.sessionStorage ) { + if (bad) { + sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad) + } else { + sessionStorage.removeItem("qunit-" + this.testName); + } + } + + if (bad == 0) { + ol.style.display = "none"; + } + + var b = document.createElement("strong"); + b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + + var a = document.createElement("a"); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + + addEvent(b, "click", function() { + var next = b.nextSibling.nextSibling, + display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + } + }); + + var li = id(this.id); + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( a ); + li.appendChild( ol ); + + } else { + for ( var i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); + } + + QUnit.testDone( { + name: this.testName, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length + } ); + }, + + queue: function() { + var test = this; + synchronize(function() { + test.init(); + }); + function run() { + // each of these can by async + synchronize(function() { + test.setup(); + }); + synchronize(function() { + test.run(); + }); + synchronize(function() { + test.teardown(); + }); + synchronize(function() { + test.finish(); + }); + } + // defer when previous test run passed, if storage is available + var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); + if (bad) { + run(); + } else { + synchronize(run); + }; + } + +}; + +var QUnit = { + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + config.currentModuleTestEnviroment = testEnvironment; + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = '' + testName + '', testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = '' + config.currentModule + ": " + name; + } + + if ( !validTest(config.currentModule + ": " + testName) ) { + return; + } + + var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); + test.module = config.currentModule; + test.moduleTestEnvironment = config.currentModuleTestEnviroment; + test.queue(); + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.current.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + a = !!a; + var details = { + result: a, + message: msg + }; + msg = escapeHtml(msg); + QUnit.log(details); + config.current.assertions.push({ + result: a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + QUnit.push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + QUnit.push(expected != actual, actual, expected, message); + }, + + deepEqual: function(actual, expected, message) { + QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + }, + + notDeepEqual: function(actual, expected, message) { + QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + }, + + strictEqual: function(actual, expected, message) { + QUnit.push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + QUnit.push(expected !== actual, actual, expected, message); + }, + + raises: function(block, expected, message) { + var actual, ok = false; + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + if (actual) { + // we don't want to validate thrown error + if (!expected) { + ok = true; + // expected is a regexp + } else if (QUnit.objectType(expected) === "regexp") { + ok = expected.test(actual); + // expected is a constructor + } else if (actual instanceof expected) { + ok = true; + // expected is a validation function which returns true is validation passed + } else if (expected.call({}, actual) === true) { + ok = true; + } + } + + QUnit.ok(ok, message); + }, + + start: function() { + config.semaphore--; + if (config.semaphore > 0) { + // don't start until equal number of stop-calls + return; + } + if (config.semaphore < 0) { + // ignore if start is called more often then stop + config.semaphore = 0; + } + // A slight delay, to avoid any current callbacks + if ( defined.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.semaphore++; + config.blocking = true; + + if ( timeout && defined.setTimeout ) { + clearTimeout(config.timeout); + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + } +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + + // by default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + noglobals: false, + notrycatch: false +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + params = location.search.slice( 1 ).split( "&" ), + length = params.length, + urlParams = {}, + current; + + if ( params[ 0 ] ) { + for ( var i = 0; i < length; i++ ) { + current = params[ i ].split( "=" ); + current[ 0 ] = decodeURIComponent( current[ 0 ] ); + // allow just a key to turn on a flag, e.g., test.html?noglobals + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; + urlParams[ current[ 0 ] ] = current[ 1 ]; + if ( current[ 0 ] in config ) { + config[ current[ 0 ] ] = current[ 1 ]; + } + } + } + + QUnit.urlParams = urlParams; + config.filter = urlParams.filter; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +// define these after exposing globals to keep them in these QUnit namespace only +extend(QUnit, { + config: config, + + // Initialize the configuration options + init: function() { + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + filter: "", + queue: [], + semaphore: 0 + }); + + var tests = id( "qunit-tests" ), + banner = id( "qunit-banner" ), + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = 'Running...
 '; + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + * + * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. + */ + reset: function() { + if ( window.jQuery ) { + jQuery( "#main, #qunit-fixture" ).html( config.fixture ); + } else { + var main = id( 'main' ) || id( 'qunit-fixture' ); + if ( main ) { + main.innerHTML = config.fixture; + } + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) == type; + }, + + objectType: function( obj ) { + if (typeof obj === "undefined") { + return "undefined"; + + // consider: typeof null === object + } + if (obj === null) { + return "null"; + } + + var type = Object.prototype.toString.call( obj ) + .match(/^\[object\s(.*)\]$/)[1] || ''; + + switch (type) { + case 'Number': + if (isNaN(obj)) { + return "nan"; + } else { + return "number"; + } + case 'String': + case 'Boolean': + case 'Array': + case 'Date': + case 'RegExp': + case 'Function': + return type.toLowerCase(); + } + if (typeof obj === "object") { + return "object"; + } + return undefined; + }, + + push: function(result, actual, expected, message) { + var details = { + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeHtml(message) || (result ? "okay" : "failed"); + message = '' + message + ""; + expected = escapeHtml(QUnit.jsDump.parse(expected)); + actual = escapeHtml(QUnit.jsDump.parse(actual)); + var output = message + ''; + if (actual != expected) { + output += ''; + output += ''; + } + if (!result) { + var source = sourceFromStacktrace(); + if (source) { + details.source = source; + output += ''; + } + } + output += "
Expected:
' + expected + '
Result:
' + actual + '
Diff:
' + QUnit.diff(expected, actual) +'
Source:
' + source +'
"; + + QUnit.log(details); + + config.current.assertions.push({ + result: !!result, + message: output + }); + }, + + url: function( params ) { + params = extend( extend( {}, QUnit.urlParams ), params ); + var querystring = "?", + key; + for ( key in params ) { + querystring += encodeURIComponent( key ) + "=" + + encodeURIComponent( params[ key ] ) + "&"; + } + return window.location.pathname + querystring.slice( 0, -1 ); + }, + + // Logging callbacks; all receive a single argument with the listed properties + // run test/logs.html for any related changes + begin: function() {}, + // done: { failed, passed, total, runtime } + done: function() {}, + // log: { result, actual, expected, message } + log: function() {}, + // testStart: { name } + testStart: function() {}, + // testDone: { name, failed, passed, total } + testDone: function() {}, + // moduleStart: { name } + moduleStart: function() {}, + // moduleDone: { name, failed, passed, total } + moduleDone: function() {} +}); + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + QUnit.begin({}); + + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + var banner = id("qunit-header"); + if ( banner ) { + banner.innerHTML = ' ' + banner.innerHTML + ' ' + + '' + + ''; + addEvent( banner, "change", function( event ) { + var params = {}; + params[ event.target.name ] = event.target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + addEvent( filter, "click", function() { + var ol = document.getElementById("qunit-tests"); + if ( filter.checked ) { + ol.className = ol.className + " hidepass"; + } else { + var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace(/ hidepass /, " "); + } + if ( defined.sessionStorage ) { + if (filter.checked) { + sessionStorage.setItem("qunit-filter-passed-tests", "true"); + } else { + sessionStorage.removeItem("qunit-filter-passed-tests"); + } + } + }); + if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { + filter.checked = true; + var ol = document.getElementById("qunit-tests"); + ol.className = ol.className + " hidepass"; + } + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + } + + var main = id('main') || id('qunit-fixture'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if (config.autostart) { + QUnit.start(); + } +}); + +function done() { + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( { + name: config.currentModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + runtime = +new Date - config.started, + passed = config.stats.all - config.stats.bad, + html = [ + 'Tests completed in ', + runtime, + ' milliseconds.
', + '', + passed, + ' tests of ', + config.stats.all, + ' passed, ', + config.stats.bad, + ' failed.' + ].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + QUnit.done( { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + } ); +} + +function validTest( name ) { + var filter = config.filter, + run = false; + + if ( !filter ) { + return true; + } + + not = filter.charAt( 0 ) === "!"; + if ( not ) { + filter = filter.slice( 1 ); + } + + if ( name.indexOf( filter ) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + + return run; +} + +// so far supports only Firefox, Chrome and Opera (buggy) +// could be extended in the future to use something like https://github.com/csnover/TraceKit +function sourceFromStacktrace() { + try { + throw new Error(); + } catch ( e ) { + if (e.stacktrace) { + // Opera + return e.stacktrace.split("\n")[6]; + } else if (e.stack) { + // Firefox, Chrome + return e.stack.split("\n")[4]; + } + } +} + +function escapeHtml(s) { + if (!s) { + return ""; + } + s = s + ""; + return s.replace(/[\&"<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + var start = (new Date()).getTime(); + + while ( config.queue.length && !config.blocking ) { + if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { + config.queue.shift()(); + } else { + window.setTimeout( process, 13 ); + break; + } + } + if (!config.blocking && !config.queue.length) { + done(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + } + + var deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + if ( b[prop] === undefined ) { + delete a[prop]; + } else { + a[prop] = b[prop]; + } + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + var parents = []; // stack to avoiding loops from circular referencing + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = QUnit.objectType(o); + if (prop) { + if (QUnit.objectType(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return QUnit.objectType(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i, j, loop; + var len; + + // b could be an object literal here + if ( ! (QUnit.objectType(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + + //track reference to avoid circular references + parents.push(a); + for (i = 0; i < len; i++) { + loop = false; + for(j=0;j= 0) { + type = "array"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + 'undefined':'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + QUnit.jsDump.up(); + for ( var key in map ) + ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); + QUnit.jsDump.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = QUnit.jsDump.HTML ? '<' : '<', + close = QUnit.jsDump.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in QUnit.jsDump.DOMAttrs ) { + var val = node[QUnit.jsDump.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +// from Sizzle.js +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +}; + +/* + * Javascript Diff Algorithm + * By John Resig (http://ejohn.org/) + * Modified by Chu Alan "sprite" + * + * Released under the MIT license. + * + * More Info: + * http://ejohn.org/projects/javascript-diff-algorithm/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" + */ +QUnit.diff = (function() { + function diff(o, n){ + var ns = new Object(); + var os = new Object(); + + for (var i = 0; i < n.length; i++) { + if (ns[n[i]] == null) + ns[n[i]] = { + rows: new Array(), + o: null + }; + ns[n[i]].rows.push(i); + } + + for (var i = 0; i < o.length; i++) { + if (os[o[i]] == null) + os[o[i]] = { + rows: new Array(), + n: null + }; + os[o[i]].rows.push(i); + } + + for (var i in ns) { + if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { + n[ns[i].rows[0]] = { + text: n[ns[i].rows[0]], + row: os[i].rows[0] + }; + o[os[i].rows[0]] = { + text: o[os[i].rows[0]], + row: ns[i].rows[0] + }; + } + } + + for (var i = 0; i < n.length - 1; i++) { + if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && + n[i + 1] == o[n[i].row + 1]) { + n[i + 1] = { + text: n[i + 1], + row: n[i].row + 1 + }; + o[n[i].row + 1] = { + text: o[n[i].row + 1], + row: i + 1 + }; + } + } + + for (var i = n.length - 1; i > 0; i--) { + if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && + n[i - 1] == o[n[i].row - 1]) { + n[i - 1] = { + text: n[i - 1], + row: n[i].row - 1 + }; + o[n[i].row - 1] = { + text: o[n[i].row - 1], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function(o, n){ + o = o.replace(/\s+$/, ''); + n = n.replace(/\s+$/, ''); + var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + + var str = ""; + + var oSpace = o.match(/\s+/g); + if (oSpace == null) { + oSpace = [" "]; + } + else { + oSpace.push(" "); + } + var nSpace = n.match(/\s+/g); + if (nSpace == null) { + nSpace = [" "]; + } + else { + nSpace.push(" "); + } + + if (out.n.length == 0) { + for (var i = 0; i < out.o.length; i++) { + str += '' + out.o[i] + oSpace[i] + ""; + } + } + else { + if (out.n[0].text == null) { + for (n = 0; n < out.o.length && out.o[n].text == null; n++) { + str += '' + out.o[n] + oSpace[n] + ""; + } + } + + for (var i = 0; i < out.n.length; i++) { + if (out.n[i].text == null) { + str += '' + out.n[i] + nSpace[i] + ""; + } + else { + var pre = ""; + + for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { + pre += '' + out.o[n] + oSpace[n] + ""; + } + str += " " + out.n[i].text + nSpace[i] + pre; + } + } + } + + return str; + }; +})(); + +})(this); diff --git a/AndorsTrailEdit/logo.png b/AndorsTrailEdit/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..56c3ecbe6340fe2b7ad33e569b9ddd89994414e1 GIT binary patch literal 4337 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 + + + Andor's Trail editor unit tests + + + + + + + + + + +

Andor's Trail editor unit tests

+

+
+

+
    +
    test markup, will be hidden
    + +