#!/usr/bin/env bash
-. "$TM_SUPPORT_PATH/lib/webpreview.sh"
-html_header "Javascript Runner" "Open Web Inspector for log"
-
-YOURCODE=`cat`
-
-cat <<HTML
-<script>
-(function(){try{
-
- function e_sh(s){
- return s
- .toString()
- .replace(/\x27/g,"’")
- .replace(/\"/g,'\\\"')
- ;
- };
-
- window.alert = function(s){
- TextMate.system('"$DIALOG" -e -p \'{messageTitle="JavaScript";informativeText="'+e_sh(s)+'";}\'', null);
- };
-
- var __TM_confirm_Status;
- window.confirm = function(s){
- TextMate.system('"$DIALOG" -e -p \'{messageTitle="JavaScript";informativeText="'+e_sh(s)+'";buttonTitles=("OK","Cancel");}\'', null)
- .onreadoutput = function(s){ __TM_confirm_Status = s != 1; };
- return(__TM_confirm_Status);
- };
-
-}catch(e){ console.log(e.message); }; })();
-</script>
-<input type="button" value="Show Code" onclick="var c=document.getElementById('code');c.style.display==''?c.style.display='none':c.style.display='';" />
-<pre id="code" style="display:none">
-$YOURCODE
-</pre>
-<pre id="log">
-<script>
-try{throw {}}catch(e){ window.__line=(e.line||0) + 3 };
-try{
-/*---- YOUR CODE ----*/
-$YOURCODE
-/*---- /YOUR CODE ----*/
-}catch(e){
- console.log(e);
- var __line_offset = ${TM_INPUT_START_LINE:-1};
- alert("$TM_FILEPATH:" + ((e.line||1) - __line + __line_offset) +"\n"+ e.message||'')
-};
-</script></pre>
-HTML
-
-html_footer
+ #!/usr/bin/env ruby
+require ENV['TM_BUNDLE_SUPPORT'] + '/run_js'
input
selection
diff --git a/Support/JSSpec/JSSpec.css b/Support/JSSpec/JSSpec.css
new file mode 100755
index 0000000..2efe90e
--- /dev/null
+++ b/Support/JSSpec/JSSpec.css
@@ -0,0 +1,226 @@
+@CHARSET "UTF-8";
+
+/* --------------------
+ * @Layout
+ */
+
+html {
+ overflow: hidden;
+}
+
+body, #jsspec_container {
+ overflow: hidden;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ background-color: white;
+}
+
+#title {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 40px;
+ overflow: hidden;
+}
+
+#list {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 40px;
+ left: 0px;
+ bottom: 0px;
+ overflow: auto;
+ width: 250px;
+ _height:expression(document.body.clientHeight-40);
+}
+
+#log {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 40px;
+ left: 250px;
+ right: 0px;
+ bottom: 0px;
+ overflow: auto;
+ _height:expression(document.body.clientHeight-40);
+ _width:expression(document.body.clientWidth-250);
+}
+
+
+
+/* --------------------
+ * @Decorations and colors
+ */
+* {
+ padding: 0;
+ margin: 0;
+ font-family: "Lucida Grande", Helvetica, sans-serif;
+}
+
+li {
+ list-style: none;
+}
+
+/* hiding subtitles */
+h2 {
+ display: none;
+}
+
+/* title section */
+div#title {
+ padding: 0em 0.5em;
+}
+
+div#title h1 {
+ font-size: 1.5em;
+ float: left;
+}
+
+div#title ul li {
+ float: left;
+ padding: 0.5em 0em 0.5em 0.75em;
+}
+
+div#title p {
+ float:right;
+ margin-right:1em;
+ font-size: 0.75em;
+}
+
+/* spec container */
+ul.specs {
+ margin: 0.5em;
+}
+ul.specs li {
+ margin-bottom: 0.1em;
+}
+
+/* spec title */
+ul.specs li h3 {
+ font-weight: bold;
+ font-size: 0.75em;
+ padding: 0.2em 1em;
+ cursor: pointer;
+ _cursor: hand;
+}
+
+/* example container */
+ul.examples li {
+ border-style: solid;
+ border-width: 0px 0px 1px 5px;
+ margin: 0.2em 0em 0.2em 1em;
+}
+
+/* example title */
+ul.examples li h4 {
+ font-weight: normal;
+ font-size: 0.75em;
+ margin-left: 1em;
+}
+
+pre.examples-code {
+ margin: 0.5em 2em;
+ padding: 0.5em;
+ background: white;
+ border: solid 1px #CCC;
+ font-size: 10px;
+ font-family: "Panic Sans", "Monaco", monospace !important;
+}
+
+/* example explaination */
+ul.examples li div {
+ padding: 1em 2em;
+ font-size: 0.75em;
+}
+
+/* styles for ongoing, success, failure, error */
+div.success, div.success a {
+ color: #FFFFFF;
+ background-color: #65C400;
+}
+
+ul.specs li.success h3, ul.specs li.success h3 a {
+ color: #FFFFFF;
+ background-color: #65C400;
+}
+
+ul.examples li.success, ul.examples li.success a {
+ color: #3D7700;
+ background-color: #DBFFB4;
+ border-color: #65C400;
+}
+
+div.exception, div.exception a {
+ color: #FFFFFF;
+ background-color: #C20000;
+}
+
+ul.specs li.exception h3, ul.specs li.exception h3 a {
+ color: #FFFFFF;
+ background-color: #C20000;
+}
+
+ul.examples li.exception, ul.examples li.exception a {
+ color: #C20000;
+ background-color: #FFFBD3;
+ border-color: #C20000;
+}
+
+div.ongoing, div.ongoing a {
+ color: #000000;
+ background-color: #FFFF80;
+}
+
+ul.specs li.ongoing h3, ul.specs li.ongoing h3 a {
+ color: #000000;
+ background-color: #FFFF80;
+}
+
+ul.examples li.ongoing, ul.examples li.ongoing a {
+ color: #000000;
+ background-color: #FFFF80;
+ border-color: #DDDD00;
+}
+
+
+
+/* --------------------
+ * values
+ */
+.number_value, .string_value, .regexp_value, .boolean_value, .dom_value {
+ font-family: monospace;
+ color: blue;
+}
+.object_value, .array_value {
+ line-height: 2em;
+ padding: 0.1em 0.2em;
+ margin: 0.1em 0;
+}
+.date_value {
+ font-family: monospace;
+ color: olive;
+}
+.undefined_value, .null_value {
+ font-style: italic;
+ color: blue;
+}
+.dom_attr_name {
+}
+.dom_attr_value {
+ color: red;
+}
+.dom_path {
+ font-size: 0.75em;
+ color: gray;
+}
+strong {
+ font-weight: normal;
+ background-color: #FFC6C6;
+}
diff --git a/Support/JSSpec/JSSpec.js b/Support/JSSpec/JSSpec.js
new file mode 100755
index 0000000..91cd142
--- /dev/null
+++ b/Support/JSSpec/JSSpec.js
@@ -0,0 +1,1548 @@
+/**
+ * JSSpec
+ *
+ * Copyright 2007 Alan Kang
+ * - mailto:jania902@gmail.com
+ * - http://jania.pe.kr
+ *
+ * http://jania.pe.kr/aw/moin.cgi/JSSpec
+ *
+ * Dependencies:
+ * - diff_match_patch.js ( http://code.google.com/p/google-diff-match-patch )
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * Namespace
+ */
+
+var JSSpec = {
+ specs: [],
+
+ EMPTY_FUNCTION: function() {},
+
+ Browser: {
+ // By Rendering Engines
+ Trident: navigator.appName === "Microsoft Internet Explorer",
+ Webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+ Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
+ KHTML: navigator.userAgent.indexOf('KHTML') !== -1,
+ Presto: navigator.appName === "Opera",
+
+ // By Platforms
+ Mac: navigator.userAgent.indexOf("Macintosh") !== -1,
+ Ubuntu: navigator.userAgent.indexOf('Ubuntu') !== -1,
+ Win: navigator.userAgent.indexOf('Windows') !== -1,
+
+ // By Browsers
+ IE: navigator.appName === "Microsoft Internet Explorer",
+ IE6: navigator.userAgent.indexOf('MSIE 6') !== -1,
+ IE7: navigator.userAgent.indexOf('MSIE 7') !== -1,
+ IE8: navigator.userAgent.indexOf('MSIE 8') !== -1,
+
+ FF: navigator.userAgent.indexOf('Firefox') !== -1,
+ FF2: navigator.userAgent.indexOf('Firefox/2') !== -1,
+ FF3: navigator.userAgent.indexOf('Firefox/3') !== -1,
+ Safari: navigator.userAgent.indexOf('Safari') !== -1
+ }
+};
+
+
+
+/**
+ * Executor
+ */
+JSSpec.Executor = function(target, onSuccess, onException) {
+ this.target = target;
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
+
+ if(JSSpec.Browser.Trident) {
+ // Exception handler for Trident. It helps to collect exact line number where exception occured.
+ window.onerror = function(message, fileName, lineNumber) {
+ var self = window._curExecutor;
+ var ex = {message:message, fileName:fileName, lineNumber:lineNumber};
+
+ if(JSSpec._secondPass) {
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
+ delete JSSpec._secondPass;
+ delete JSSpec._assertionFailure;
+
+ ex.type = "failure";
+ self.onException(self, ex);
+ } else if(JSSpec._assertionFailure) {
+ JSSpec._secondPass = true;
+ self.run();
+ } else {
+ self.onException(self, ex);
+ }
+
+ return true;
+ };
+ }
+};
+JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException) {
+ var merged = {
+ message:assertionFailure.message,
+ fileName:normalException.fileName,
+ lineNumber:normalException.lineNumber
+ };
+
+ return merged;
+};
+
+JSSpec.Executor.prototype.run = function() {
+ var self = this;
+ var target = this.target;
+ var onSuccess = this.onSuccess;
+ var onException = this.onException;
+
+ window.setTimeout(
+ function() {
+ var result;
+ if(JSSpec.Browser.Trident) {
+ window._curExecutor = self;
+
+ result = self.target();
+ self.onSuccess(self, result);
+ } else {
+ try {
+ result = self.target();
+ self.onSuccess(self, result);
+ } catch(ex) {
+ if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line};
+
+ if(JSSpec._secondPass) {
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
+ delete JSSpec._secondPass;
+ delete JSSpec._assertionFailure;
+
+ ex.type = "failure";
+ self.onException(self, ex);
+ } else if(JSSpec._assertionFailure) {
+ JSSpec._secondPass = true;
+ self.run();
+ } else {
+ self.onException(self, ex);
+ }
+ }
+ }
+ },
+ 0
+ );
+};
+
+
+
+/**
+ * CompositeExecutor composites one or more executors and execute them sequencially.
+ */
+JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException) {
+ this.queue = [];
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
+ this.continueOnException = !!continueOnException;
+};
+
+JSSpec.CompositeExecutor.prototype.addFunction = function(func) {
+ this.addExecutor(new JSSpec.Executor(func));
+};
+
+JSSpec.CompositeExecutor.prototype.addExecutor = function(executor) {
+ var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1];
+ if(last) {
+ last.next = executor;
+ }
+
+ executor.parent = this;
+ executor.onSuccessBackup = executor.onSuccess;
+ executor.onSuccess = function(result) {
+ this.onSuccessBackup(result);
+ if(this.next) {
+ this.next.run();
+ } else {
+ this.parent.onSuccess();
+ }
+ };
+ executor.onExceptionBackup = executor.onException;
+ executor.onException = function(executor, ex) {
+ this.onExceptionBackup(executor, ex);
+
+ if(this.parent.continueOnException) {
+ if(this.next) {
+ this.next.run();
+ } else {
+ this.parent.onSuccess();
+ }
+ } else {
+ this.parent.onException(executor, ex);
+ }
+ };
+
+ this.queue.push(executor);
+};
+
+JSSpec.CompositeExecutor.prototype.run = function() {
+ if(this.queue.length > 0) {
+ this.queue[0].run();
+ }
+};
+
+/**
+ * Spec is a set of Examples in a specific context
+ */
+JSSpec.Spec = function(context, entries) {
+ this.id = JSSpec.Spec.id++;
+ this.context = context;
+ this.url = location.href;
+
+ this.filterEntriesByEmbeddedExpressions(entries);
+ this.extractOutSpecialEntries(entries);
+ this.examples = this.makeExamplesFromEntries(entries);
+ this.examplesMap = this.makeMapFromExamples(this.examples);
+};
+
+JSSpec.Spec.id = 0;
+JSSpec.Spec.prototype.getExamples = function() {
+ return this.examples;
+};
+
+JSSpec.Spec.prototype.hasException = function() {
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
+};
+
+JSSpec.Spec.prototype.getTotalFailures = function() {
+ var examples = this.examples;
+ var failures = 0;
+ for(var i = 0; i < examples.length; i++) {
+ if(examples[i].isFailure()) failures++;
+ }
+ return failures;
+};
+
+JSSpec.Spec.prototype.getTotalErrors = function() {
+ var examples = this.examples;
+ var errors = 0;
+ for(var i = 0; i < examples.length; i++) {
+ if(examples[i].isError()) errors++;
+ }
+ return errors;
+};
+
+JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries) {
+ var isTrue;
+ for(name in entries) if(entries.hasOwnProperty(name)) {
+ var m = name.match(/\[\[(.+)\]\]/);
+ if(m && m[1]) {
+ eval("isTrue = (" + m[1] + ")");
+ if(!isTrue) delete entries[name];
+ }
+ }
+};
+
+JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) {
+ this.beforeEach = JSSpec.EMPTY_FUNCTION;
+ this.beforeAll = JSSpec.EMPTY_FUNCTION;
+ this.afterEach = JSSpec.EMPTY_FUNCTION;
+ this.afterAll = JSSpec.EMPTY_FUNCTION;
+
+ for(name in entries) if(entries.hasOwnProperty(name)) {
+ if(name == 'before' || name == 'before each' || name == 'before_each') {
+ this.beforeEach = entries[name];
+ } else if(name == 'before all' || name == 'before_all') {
+ this.beforeAll = entries[name];
+ } else if(name == 'after' || name == 'after each' || name == 'after_each') {
+ this.afterEach = entries[name];
+ } else if(name == 'after all' || name == 'after_all') {
+ this.afterAll = entries[name];
+ }
+ }
+
+ delete entries['before'];
+ delete entries['before each'];
+ delete entries['before_each'];
+ delete entries['before all'];
+ delete entries['before_all'];
+ delete entries['after'];
+ delete entries['after each'];
+ delete entries['after_each'];
+ delete entries['after all'];
+ delete entries['after_all'];
+};
+
+JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) {
+ var examples = [];
+ for(name in entries) if(entries.hasOwnProperty(name)) {
+ examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
+ }
+ return examples;
+};
+
+JSSpec.Spec.prototype.makeMapFromExamples = function(examples) {
+ var map = {};
+ for(var i = 0; i < examples.length; i++) {
+ var example = examples[i];
+ map[example.id] = examples[i];
+ }
+ return map;
+};
+
+JSSpec.Spec.prototype.getExampleById = function(id) {
+ return this.examplesMap[id];
+};
+
+JSSpec.Spec.prototype.getExecutor = function() {
+ var self = this;
+ var onException = function(executor, ex) {
+ self.exception = ex;
+ };
+
+ var composite = new JSSpec.CompositeExecutor();
+ composite.addFunction(function() {JSSpec.log.onSpecStart(self);});
+ composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex) {
+ self.exception = ex;
+ JSSpec.log.onSpecEnd(self);
+ }));
+
+ var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true);
+ for(var i = 0; i < this.examples.length; i++) {
+ exampleAndAfter.addExecutor(this.examples[i].getExecutor());
+ }
+ exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException));
+ exampleAndAfter.addFunction(function() {JSSpec.log.onSpecEnd(self);});
+ composite.addExecutor(exampleAndAfter);
+
+ return composite;
+};
+
+/**
+ * Example
+ */
+JSSpec.Example = function(name, target, before, after) {
+ this.id = JSSpec.Example.id++;
+ this.name = name;
+ this.target = target;
+ this.before = before;
+ this.after = after;
+};
+
+JSSpec.Example.id = 0;
+JSSpec.Example.prototype.isFailure = function() {
+ return this.exception && this.exception.type == "failure";
+};
+
+JSSpec.Example.prototype.isError = function() {
+ return this.exception && !this.exception.type;
+};
+
+JSSpec.Example.prototype.getExecutor = function() {
+ var self = this;
+ var onException = function(executor, ex) {
+ self.exception = ex;
+ };
+
+ var composite = new JSSpec.CompositeExecutor();
+ composite.addFunction(function() {JSSpec.log.onExampleStart(self);});
+ composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex) {
+ self.exception = ex;
+ JSSpec.log.onExampleEnd(self);
+ }));
+
+ var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true);
+
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException));
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException));
+ targetAndAfter.addFunction(function() {JSSpec.log.onExampleEnd(self);});
+
+ composite.addExecutor(targetAndAfter);
+
+ return composite;
+};
+
+/**
+ * Runner
+ */
+JSSpec.Runner = function(specs, logger) {
+ JSSpec.log = logger;
+
+ this.totalExamples = 0;
+ this.specs = [];
+ this.specsMap = {};
+ this.addAllSpecs(specs);
+};
+
+JSSpec.Runner.prototype.addAllSpecs = function(specs) {
+ for(var i = 0; i < specs.length; i++) {
+ this.addSpec(specs[i]);
+ }
+};
+
+JSSpec.Runner.prototype.addSpec = function(spec) {
+ this.specs.push(spec);
+ this.specsMap[spec.id] = spec;
+ this.totalExamples += spec.getExamples().length;
+};
+
+JSSpec.Runner.prototype.getSpecById = function(id) {
+ return this.specsMap[id];
+};
+
+JSSpec.Runner.prototype.getSpecByContext = function(context) {
+ for(var i = 0; i < this.specs.length; i++) {
+ if(this.specs[i].context == context) return this.specs[i];
+ }
+ return null;
+};
+
+JSSpec.Runner.prototype.getSpecs = function() {
+ return this.specs;
+};
+
+JSSpec.Runner.prototype.hasException = function() {
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
+};
+
+JSSpec.Runner.prototype.getTotalFailures = function() {
+ var specs = this.specs;
+ var failures = 0;
+ for(var i = 0; i < specs.length; i++) {
+ failures += specs[i].getTotalFailures();
+ }
+ return failures;
+};
+
+JSSpec.Runner.prototype.getTotalErrors = function() {
+ var specs = this.specs;
+ var errors = 0;
+ for(var i = 0; i < specs.length; i++) {
+ errors += specs[i].getTotalErrors();
+ }
+ return errors;
+};
+
+
+JSSpec.Runner.prototype.run = function() {
+ JSSpec.log.onRunnerStart();
+ var executor = new JSSpec.CompositeExecutor(function() {JSSpec.log.onRunnerEnd()},null,true);
+ for(var i = 0; i < this.specs.length; i++) {
+ executor.addExecutor(this.specs[i].getExecutor());
+ }
+ executor.run();
+};
+
+
+JSSpec.Runner.prototype.rerun = function(context) {
+ JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log);
+ JSSpec.runner.run();
+};
+
+/**
+ * Logger
+ */
+JSSpec.Logger = function() {
+ this.finishedExamples = 0;
+ this.startedAt = null;
+};
+
+JSSpec.Logger.prototype.onRunnerStart = function() {
+ this._title = document.title;
+
+ this.startedAt = new Date();
+ var container = document.getElementById('jsspec_container');
+ if(container) {
+ container.innerHTML = "";
+ } else {
+ container = document.createElement("DIV");
+ container.id = "jsspec_container";
+ document.body.appendChild(container);
+ }
+
+ var title = document.createElement("DIV");
+ title.id = "title";
+ title.innerHTML = [
+ 'JSSpec
',
+ '',
+ JSSpec.options.rerun ? '- [X] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '
' : '',
+ ' - ' + JSSpec.runner.totalExamples + ' examples
',
+ ' - 0 failures
',
+ ' - 0 errors
',
+ ' - 0% done
',
+ ' - 0 secs
',
+ '
',
+ 'JSSpec homepage
',
+ ].join("");
+ container.appendChild(title);
+
+ var list = document.createElement("DIV");
+ list.id = "list";
+ list.innerHTML = [
+ 'List
',
+ '',
+ function() {
+ var specs = JSSpec.runner.getSpecs();
+ var sb = [];
+ for(var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ sb.push('-
');
+ }
+ return sb.join("");
+ }(),
+ '
'
+ ].join("");
+ container.appendChild(list);
+
+ var log = document.createElement("DIV");
+ log.id = "log";
+ log.innerHTML = [
+ 'Log
',
+ '',
+ function() {
+ var specs = JSSpec.runner.getSpecs();
+ var sb = [];
+ for(var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ sb.push(' - ');
+ sb.push('
' + JSSpec.util.escapeTags(specs[i].context) + ' [rerun]
');
+ sb.push(' ');
+ sb.push(' ');
+ }
+ return sb.join("");
+ }(),
+ '
'
+ ].join("");
+
+ container.appendChild(log);
+
+ // add event handler for toggling
+ var specs = JSSpec.runner.getSpecs();
+ var sb = [];
+ for(var i = 0; i < specs.length; i++) {
+ var spec = document.getElementById("spec_" + specs[i].id);
+ var title = spec.getElementsByTagName("H3")[0];
+ title.onclick = function(e) {
+ var target = document.getElementById(this.parentNode.id + "_examples");
+ target.style.display = target.style.display == "none" ? "block" : "none";
+ return true;
+ }
+ }
+};
+
+JSSpec.Logger.prototype.onRunnerEnd = function() {
+ if(JSSpec.runner.hasException()) {
+ var times = 4;
+ var title1 = "*" + this._title;
+ var title2 = "*F" + JSSpec.runner.getTotalFailures() + " E" + JSSpec.runner.getTotalErrors() + "* " + this._title;
+ } else {
+ var times = 2;
+ var title1 = this._title;
+ var title2 = "Success";
+ }
+ this.blinkTitle(times,title1,title2);
+};
+
+JSSpec.Logger.prototype.blinkTitle = function(times, title1, title2) {
+ var times = times * 2;
+ var mode = true;
+
+ var f = function() {
+ if(times > 0) {
+ document.title = mode ? title1 : title2;
+ mode = !mode;
+ times--;
+ window.setTimeout(f, 500);
+ } else {
+ document.title = title1;
+ }
+ };
+
+ f();
+};
+
+JSSpec.Logger.prototype.onSpecStart = function(spec) {
+ var spec_list = document.getElementById("spec_" + spec.id + "_list");
+ var spec_log = document.getElementById("spec_" + spec.id);
+
+ spec_list.className = "ongoing";
+ spec_log.className = "ongoing";
+};
+
+JSSpec.Logger.prototype.onSpecEnd = function(spec) {
+ var spec_list = document.getElementById("spec_" + spec.id + "_list");
+ var spec_log = document.getElementById("spec_" + spec.id);
+ var examples = document.getElementById("spec_" + spec.id + "_examples");
+ var className = spec.hasException() ? "exception" : "success";
+
+ spec_list.className = className;
+ spec_log.className = className;
+
+ if(JSSpec.options.autocollapse && !spec.hasException()) examples.style.display = "none";
+
+ if(spec.exception) {
+ spec_log.appendChild(document.createTextNode(" - " + spec.exception.message));
+ }
+};
+
+JSSpec.Logger.prototype.onExampleStart = function(example) {
+ var li = document.getElementById("example_" + example.id);
+ li.className = "ongoing";
+};
+
+JSSpec.Logger.prototype.onExampleEnd = function(example) {
+ var li = document.getElementById("example_" + example.id);
+ li.className = example.exception ? "exception" : "success";
+
+ if(example.exception) {
+ var div = document.createElement("DIV");
+ div.innerHTML = example.exception.message + "
" + " at " + example.exception.fileName + ", line " + example.exception.lineNumber + "
";
+ li.appendChild(div);
+ }
+
+ var title = document.getElementById("title");
+ var runner = JSSpec.runner;
+
+ title.className = runner.hasException() ? "exception" : "success";
+
+ this.finishedExamples++;
+ document.getElementById("total_failures").innerHTML = runner.getTotalFailures();
+ document.getElementById("total_errors").innerHTML = runner.getTotalErrors();
+ var progress = parseInt(this.finishedExamples / runner.totalExamples * 100);
+ document.getElementById("progress").innerHTML = progress;
+ document.getElementById("total_elapsed").innerHTML = (new Date().getTime() - this.startedAt.getTime()) / 1000;
+
+ document.title = progress + "%: " + this._title;
+};
+
+/**
+ * IncludeMatcher
+ */
+JSSpec.IncludeMatcher = function(actual, expected, condition) {
+ this.actual = actual;
+ this.expected = expected;
+ this.condition = condition;
+ this.match = false;
+ this.explaination = this.makeExplain();
+};
+
+JSSpec.IncludeMatcher.createInstance = function(actual, expected, condition) {
+ return new JSSpec.IncludeMatcher(actual, expected, condition);
+};
+
+JSSpec.IncludeMatcher.prototype.matches = function() {
+ return this.match;
+};
+
+JSSpec.IncludeMatcher.prototype.explain = function() {
+ return this.explaination;
+};
+
+JSSpec.IncludeMatcher.prototype.makeExplain = function() {
+ if(typeof this.actual.length == 'undefined') {
+ return this.makeExplainForNotArray();
+ } else {
+ return this.makeExplainForArray();
+ }
+};
+
+JSSpec.IncludeMatcher.prototype.makeExplainForNotArray = function() {
+ if(this.condition) {
+ this.match = !!this.actual[this.expected];
+ } else {
+ this.match = !this.actual[this.expected];
+ }
+
+ var sb = [];
+ sb.push('actual value:
');
+ sb.push('' + JSSpec.util.inspect(this.actual, false, this.expected) + '
');
+ sb.push('should ' + (this.condition ? '' : 'not') + ' include:
');
+ sb.push('' + JSSpec.util.inspect(this.expected) + '
');
+ return sb.join("");
+};
+
+JSSpec.IncludeMatcher.prototype.makeExplainForArray = function() {
+ var matches;
+ if(this.condition) {
+ for(var i = 0; i < this.actual.length; i++) {
+ matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
+ if(matches) {
+ this.match = true;
+ break;
+ }
+ }
+ } else {
+ for(var i = 0; i < this.actual.length; i++) {
+ matches = JSSpec.EqualityMatcher.createInstance(this.expected, this.actual[i]).matches();
+ if(matches) {
+ this.match = false;
+ break;
+ }
+ }
+ }
+
+ if(this.match) return "";
+
+ var sb = [];
+ sb.push('actual value:
');
+ sb.push('' + JSSpec.util.inspect(this.actual, false, this.condition ? null : i) + '
');
+ sb.push('should ' + (this.condition ? '' : 'not') + ' include:
');
+ sb.push('' + JSSpec.util.inspect(this.expected) + '
');
+ return sb.join("");
+};
+
+/**
+ * PropertyLengthMatcher
+ */
+JSSpec.PropertyLengthMatcher = function(num, property, o, condition) {
+ this.num = num;
+ this.o = o;
+ this.property = property;
+ if((property == 'characters' || property == 'items') && typeof o.length != 'undefined') {
+ this.property = 'length';
+ }
+
+ this.condition = condition;
+ this.conditionMet = function(x) {
+ if(condition == 'exactly') return x.length == num;
+ if(condition == 'at least') return x.length >= num;
+ if(condition == 'at most') return x.length <= num;
+
+ throw "Unknown condition '" + condition + "'";
+ };
+ this.match = false;
+ this.explaination = this.makeExplain();
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplain = function() {
+ if(this.o._type == 'String' && this.property == 'length') {
+ this.match = this.conditionMet(this.o);
+ return this.match ? '' : this.makeExplainForString();
+ } else if(typeof this.o.length != 'undefined' && this.property == "length") {
+ this.match = this.conditionMet(this.o);
+ return this.match ? '' : this.makeExplainForArray();
+ } else if(typeof this.o[this.property] != 'undefined' && this.o[this.property] != null) {
+ this.match = this.conditionMet(this.o[this.property]);
+ return this.match ? '' : this.makeExplainForObject();
+ } else if(typeof this.o[this.property] == 'undefined' || this.o[this.property] == null) {
+ this.match = false;
+ return this.makeExplainForNoProperty();
+ }
+
+ this.match = true;
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForString = function() {
+ var sb = [];
+
+ var exp = this.num == 0 ?
+ 'be an empty string' :
+ 'have ' + this.condition + ' ' + this.num + ' characters';
+
+ sb.push('actual value has ' + this.o.length + ' characters:
');
+ sb.push('' + JSSpec.util.inspect(this.o) + '
');
+ sb.push('but it should ' + exp + '.
');
+
+ return sb.join("");
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForArray = function() {
+ var sb = [];
+
+ var exp = this.num == 0 ?
+ 'be an empty array' :
+ 'have ' + this.condition + ' ' + this.num + ' items';
+
+ sb.push('actual value has ' + this.o.length + ' items:
');
+ sb.push('' + JSSpec.util.inspect(this.o) + '
');
+ sb.push('but it should ' + exp + '.
');
+
+ return sb.join("");
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForObject = function() {
+ var sb = [];
+
+ var exp = this.num == 0 ?
+ 'be empty' :
+ 'have ' + this.condition + ' ' + this.num + ' ' + this.property + '.';
+
+ sb.push('actual value has ' + this.o[this.property].length + ' ' + this.property + ':
');
+ sb.push('' + JSSpec.util.inspect(this.o, false, this.property) + '
');
+ sb.push('but it should ' + exp + '.
');
+
+ return sb.join("");
+};
+
+JSSpec.PropertyLengthMatcher.prototype.makeExplainForNoProperty = function() {
+ var sb = [];
+
+ sb.push('actual value:
');
+ sb.push('' + JSSpec.util.inspect(this.o) + '
');
+ sb.push('should have ' + this.condition + ' ' + this.num + ' ' + this.property + ' but there\'s no such property.
');
+
+ return sb.join("");
+};
+
+JSSpec.PropertyLengthMatcher.prototype.matches = function() {
+ return this.match;
+};
+
+JSSpec.PropertyLengthMatcher.prototype.explain = function() {
+ return this.explaination;
+};
+
+JSSpec.PropertyLengthMatcher.createInstance = function(num, property, o, condition) {
+ return new JSSpec.PropertyLengthMatcher(num, property, o, condition);
+};
+
+/**
+ * EqualityMatcher
+ */
+JSSpec.EqualityMatcher = {};
+
+JSSpec.EqualityMatcher.createInstance = function(expected, actual) {
+ if(expected == null || actual == null) {
+ return new JSSpec.NullEqualityMatcher(expected, actual);
+ } else if(expected._type && expected._type == actual._type) {
+ if(expected._type == "String") {
+ return new JSSpec.StringEqualityMatcher(expected, actual);
+ } else if(expected._type == "Date") {
+ return new JSSpec.DateEqualityMatcher(expected, actual);
+ } else if(expected._type == "Number") {
+ return new JSSpec.NumberEqualityMatcher(expected, actual);
+ } else if(expected._type == "Array") {
+ return new JSSpec.ArrayEqualityMatcher(expected, actual);
+ } else if(expected._type == "Boolean") {
+ return new JSSpec.BooleanEqualityMatcher(expected, actual);
+ }
+ }
+
+ return new JSSpec.ObjectEqualityMatcher(expected, actual);
+};
+
+JSSpec.EqualityMatcher.basicExplain = function(expected, actual, expectedDesc, actualDesc) {
+ var sb = [];
+
+ sb.push(actualDesc || 'actual value:
');
+ sb.push('' + JSSpec.util.inspect(actual) + '
');
+ sb.push(expectedDesc || 'should be:
');
+ sb.push('' + JSSpec.util.inspect(expected) + '
');
+
+ return sb.join("");
+};
+
+JSSpec.EqualityMatcher.diffExplain = function(expected, actual) {
+ var sb = [];
+
+ sb.push('diff:
');
+ sb.push('');
+
+ var dmp = new diff_match_patch();
+ var diff = dmp.diff_main(expected, actual);
+ dmp.diff_cleanupEfficiency(diff);
+
+ sb.push(JSSpec.util.inspect(dmp.diff_prettyHtml(diff), true));
+
+ sb.push('
');
+
+ return sb.join("");
+};
+
+/**
+ * BooleanEqualityMatcher
+ */
+JSSpec.BooleanEqualityMatcher = function(expected, actual) {
+ this.expected = expected;
+ this.actual = actual;
+};
+
+JSSpec.BooleanEqualityMatcher.prototype.explain = function() {
+ var sb = [];
+
+ sb.push('actual value:
');
+ sb.push('' + JSSpec.util.inspect(this.actual) + '
');
+ sb.push('should be:
');
+ sb.push('' + JSSpec.util.inspect(this.expected) + '
');
+
+ return sb.join("");
+};
+
+JSSpec.BooleanEqualityMatcher.prototype.matches = function() {
+ return this.expected == this.actual;
+};
+
+/**
+ * NullEqualityMatcher
+ */
+JSSpec.NullEqualityMatcher = function(expected, actual) {
+ this.expected = expected;
+ this.actual = actual;
+};
+
+JSSpec.NullEqualityMatcher.prototype.matches = function() {
+ return this.expected == this.actual && typeof this.expected == typeof this.actual;
+};
+
+JSSpec.NullEqualityMatcher.prototype.explain = function() {
+ return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
+};
+
+JSSpec.DateEqualityMatcher = function(expected, actual) {
+ this.expected = expected;
+ this.actual = actual;
+};
+
+JSSpec.DateEqualityMatcher.prototype.matches = function() {
+ return this.expected.getTime() == this.actual.getTime();
+};
+
+JSSpec.DateEqualityMatcher.prototype.explain = function() {
+ var sb = [];
+
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
+ sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected.toString(), this.actual.toString()));
+
+ return sb.join("");
+};
+
+/**
+ * ObjectEqualityMatcher
+ */
+JSSpec.ObjectEqualityMatcher = function(expected, actual) {
+ this.expected = expected;
+ this.actual = actual;
+ this.match = this.expected == this.actual;
+ this.explaination = this.makeExplain();
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.matches = function() {return this.match};
+
+JSSpec.ObjectEqualityMatcher.prototype.explain = function() {return this.explaination};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplain = function() {
+ if(this.expected == this.actual) {
+ this.match = true;
+ return "";
+ }
+
+ if(JSSpec.util.isDomNode(this.expected)) {
+ return this.makeExplainForDomNode();
+ }
+
+ var key, expectedHasItem, actualHasItem;
+
+ for(key in this.expected) {
+ expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
+ actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
+ if(expectedHasItem && !actualHasItem) return this.makeExplainForMissingItem(key);
+ }
+ for(key in this.actual) {
+ expectedHasItem = this.expected[key] != null && typeof this.expected[key] != 'undefined';
+ actualHasItem = this.actual[key] != null && typeof this.actual[key] != 'undefined';
+ if(actualHasItem && !expectedHasItem) return this.makeExplainForUnknownItem(key);
+ }
+
+ for(key in this.expected) {
+ var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[key], this.actual[key]);
+ if(!matcher.matches()) return this.makeExplainForItemMismatch(key);
+ }
+
+ this.match = true;
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForDomNode = function(key) {
+ var sb = [];
+
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
+
+ return sb.join("");
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForMissingItem = function(key) {
+ var sb = [];
+
+ sb.push('actual value has no item named ' + JSSpec.util.inspect(key) + '
');
+ sb.push('' + JSSpec.util.inspect(this.actual, false, key) + '
');
+ sb.push('but it should have the item whose value is ' + JSSpec.util.inspect(this.expected[key]) + '
');
+ sb.push('' + JSSpec.util.inspect(this.expected, false, key) + '
');
+
+ return sb.join("");
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForUnknownItem = function(key) {
+ var sb = [];
+
+ sb.push('actual value has item named ' + JSSpec.util.inspect(key) + '
');
+ sb.push('' + JSSpec.util.inspect(this.actual, false, key) + '
');
+ sb.push('but there should be no such item
');
+ sb.push('' + JSSpec.util.inspect(this.expected, false, key) + '
');
+
+ return sb.join("");
+};
+
+JSSpec.ObjectEqualityMatcher.prototype.makeExplainForItemMismatch = function(key) {
+ var sb = [];
+
+ sb.push('actual value has an item named ' + JSSpec.util.inspect(key) + ' whose value is ' + JSSpec.util.inspect(this.actual[key]) + '
');
+ sb.push('' + JSSpec.util.inspect(this.actual, false, key) + '
');
+ sb.push('but it\'s value should be ' + JSSpec.util.inspect(this.expected[key]) + '
');
+ sb.push('' + JSSpec.util.inspect(this.expected, false, key) + '
');
+
+ return sb.join("");
+};
+
+
+
+
+/**
+ * ArrayEqualityMatcher
+ */
+JSSpec.ArrayEqualityMatcher = function(expected, actual) {
+ this.expected = expected;
+ this.actual = actual;
+ this.match = this.expected == this.actual;
+ this.explaination = this.makeExplain();
+};
+
+JSSpec.ArrayEqualityMatcher.prototype.matches = function() {return this.match};
+
+JSSpec.ArrayEqualityMatcher.prototype.explain = function() {return this.explaination};
+
+JSSpec.ArrayEqualityMatcher.prototype.makeExplain = function() {
+ if(this.expected.length != this.actual.length) return this.makeExplainForLengthMismatch();
+
+ for(var i = 0; i < this.expected.length; i++) {
+ var matcher = JSSpec.EqualityMatcher.createInstance(this.expected[i], this.actual[i]);
+ if(!matcher.matches()) return this.makeExplainForItemMismatch(i);
+ }
+
+ this.match = true;
+};
+
+JSSpec.ArrayEqualityMatcher.prototype.makeExplainForLengthMismatch = function() {
+ return JSSpec.EqualityMatcher.basicExplain(
+ this.expected,
+ this.actual,
+ 'but it should be ' + this.expected.length + '
',
+ 'actual value has ' + this.actual.length + ' items
'
+ );
+};
+
+JSSpec.ArrayEqualityMatcher.prototype.makeExplainForItemMismatch = function(index) {
+ var postfix = ["th", "st", "nd", "rd", "th"][Math.min((index + 1) % 10,4)];
+
+ var sb = [];
+
+ sb.push('' + (index + 1) + postfix + ' item (index ' + index + ') of actual value is ' + JSSpec.util.inspect(this.actual[index]) + ':
');
+ sb.push('' + JSSpec.util.inspect(this.actual, false, index) + '
');
+ sb.push('but it should be ' + JSSpec.util.inspect(this.expected[index]) + ':
');
+ sb.push('' + JSSpec.util.inspect(this.expected, false, index) + '
');
+
+ return sb.join("");
+};
+
+/**
+ * NumberEqualityMatcher
+ */
+JSSpec.NumberEqualityMatcher = function(expected, actual) {
+ this.expected = expected;
+ this.actual = actual;
+};
+
+JSSpec.NumberEqualityMatcher.prototype.matches = function() {
+ if(this.expected == this.actual) return true;
+};
+
+JSSpec.NumberEqualityMatcher.prototype.explain = function() {
+ return JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual);
+};
+
+/**
+ * StringEqualityMatcher
+ */
+JSSpec.StringEqualityMatcher = function(expected, actual) {
+ this.expected = expected;
+ this.actual = actual;
+};
+
+JSSpec.StringEqualityMatcher.prototype.matches = function() {
+ return this.expected == this.actual;
+};
+
+JSSpec.StringEqualityMatcher.prototype.explain = function() {
+ var sb = [];
+
+ sb.push(JSSpec.EqualityMatcher.basicExplain(this.expected, this.actual));
+ sb.push(JSSpec.EqualityMatcher.diffExplain(this.expected, this.actual));
+ return sb.join("");
+};
+
+/**
+ * PatternMatcher
+ */
+JSSpec.PatternMatcher = function(actual, pattern, condition) {
+ this.actual = actual;
+ this.pattern = pattern;
+ this.condition = condition;
+ this.match = false;
+ this.explaination = this.makeExplain();
+};
+
+JSSpec.PatternMatcher.createInstance = function(actual, pattern, condition) {
+ return new JSSpec.PatternMatcher(actual, pattern, condition);
+};
+
+JSSpec.PatternMatcher.prototype.makeExplain = function() {
+ var sb;
+ if(this.actual == null || this.actual._type != 'String') {
+ sb = [];
+ sb.push('actual value:
');
+ sb.push('' + JSSpec.util.inspect(this.actual) + '
');
+ sb.push('should ' + (this.condition ? '' : 'not') + ' match with pattern:
');
+ sb.push('' + JSSpec.util.inspect(this.pattern) + '
');
+ sb.push('but pattern matching cannot be performed.
');
+ return sb.join("");
+ } else {
+ this.match = this.condition == !!this.actual.match(this.pattern);
+ if(this.match) return "";
+
+ sb = [];
+ sb.push('actual value:
');
+ sb.push('' + JSSpec.util.inspect(this.actual) + '
');
+ sb.push('should ' + (this.condition ? '' : 'not') + ' match with pattern:
');
+ sb.push('' + JSSpec.util.inspect(this.pattern) + '
');
+ return sb.join("");
+ }
+};
+
+JSSpec.PatternMatcher.prototype.matches = function() {
+ return this.match;
+};
+
+JSSpec.PatternMatcher.prototype.explain = function() {
+ return this.explaination;
+};
+
+/**
+ * Domain Specific Languages
+ */
+JSSpec.DSL = {};
+
+JSSpec.DSL.forString = {
+ normalizeHtml: function() {
+ var html = this;
+
+ // Uniformize quotation, turn tag names and attribute names into lower case
+ html = html.replace(/<(\/?)(\w+)([^>]*?)>/img, function(str, closingMark, tagName, attrs) {
+ var sortedAttrs = JSSpec.util.sortHtmlAttrs(JSSpec.util.correctHtmlAttrQuotation(attrs).toLowerCase())
+ return "<" + closingMark + tagName.toLowerCase() + sortedAttrs + ">"
+ });
+
+ // validation self-closing tags
+ html = html.replace(/<(br|hr|img)([^>]*?)>/mg, function(str, tag, attrs) {
+ return "<" + tag + attrs + " />";
+ });
+
+ // append semi-colon at the end of style value
+ html = html.replace(/style="(.*?)"/mg, function(str, styleStr) {
+ styleStr = JSSpec.util.sortStyleEntries(styleStr.strip()); // for Safari
+ if(styleStr.charAt(styleStr.length - 1) != ';') styleStr += ";"
+
+ return 'style="' + styleStr + '"'
+ });
+
+ // sort style entries
+
+ // remove empty style attributes
+ html = html.replace(/ style=";"/mg, "");
+
+ // remove new-lines
+ html = html.replace(/\r/mg, '');
+ html = html.replace(/\n/mg, '');
+
+ return html;
+ }
+};
+
+
+JSSpec.DSL.describe = function(context, entries, base) {
+ if(base) {
+ for(var i = 0; i < JSSpec.specs.length; i++) {
+ if(JSSpec.specs[i].context === base) {
+ base = JSSpec.specs[i];
+ break;
+ }
+ }
+
+ for(var i = 0; i < base.examples.length; i++) {
+ var example = base.examples[i];
+
+ if(!entries[example.name]) entries[example.name] = example.target;
+ }
+ }
+
+ JSSpec.specs.push(new JSSpec.Spec(context, entries));
+};
+
+JSSpec.DSL.value_of = function(target) {
+ if(JSSpec._secondPass) return {};
+
+ var subject = new JSSpec.DSL.Subject(target);
+ return subject;
+};
+
+JSSpec.DSL.Subject = function(target) {
+ this.target = target;
+};
+
+JSSpec.DSL.Subject.prototype._type = 'Subject';
+
+JSSpec.DSL.Subject.prototype.should_fail = function(message) {
+ JSSpec._assertionFailure = {message:message};
+ throw JSSpec._assertionFailure;
+};
+
+JSSpec.DSL.Subject.prototype.should_be = function(expected) {
+ var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
+ if(!matcher.matches()) {
+ JSSpec._assertionFailure = {message:matcher.explain()};
+ throw JSSpec._assertionFailure;
+ }
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be = function(expected) {
+ // TODO JSSpec.EqualityMatcher should support 'condition'
+ var matcher = JSSpec.EqualityMatcher.createInstance(expected, this.target);
+ if(matcher.matches()) {
+ JSSpec._assertionFailure = {message:"'" + this.target + "' should not be '" + expected + "'"};
+ throw JSSpec._assertionFailure;
+ }
+};
+
+JSSpec.DSL.Subject.prototype.should_be_empty = function() {
+ this.should_have(0, this.getType() == 'String' ? 'characters' : 'items');
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be_empty = function() {
+ this.should_have_at_least(1, this.getType() == 'String' ? 'characters' : 'items');
+};
+
+JSSpec.DSL.Subject.prototype.should_be_true = function() {
+ this.should_be(true);
+};
+
+JSSpec.DSL.Subject.prototype.should_be_false = function() {
+ this.should_be(false);
+};
+
+JSSpec.DSL.Subject.prototype.should_be_null = function() {
+ this.should_be(null);
+};
+
+JSSpec.DSL.Subject.prototype.should_be_undefined = function() {
+ this.should_be(undefined);
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be_null = function() {
+ this.should_not_be(null);
+};
+
+JSSpec.DSL.Subject.prototype.should_not_be_undefined = function() {
+ this.should_not_be(undefined);
+};
+
+JSSpec.DSL.Subject.prototype._should_have = function(num, property, condition) {
+ var matcher = JSSpec.PropertyLengthMatcher.createInstance(num, property, this.target, condition);
+ if(!matcher.matches()) {
+ JSSpec._assertionFailure = {message:matcher.explain()};
+ throw JSSpec._assertionFailure;
+ }
+};
+
+JSSpec.DSL.Subject.prototype.should_have = function(num, property) {
+ this._should_have(num, property, "exactly");
+};
+
+JSSpec.DSL.Subject.prototype.should_have_exactly = function(num, property) {
+ this._should_have(num, property, "exactly");
+};
+
+JSSpec.DSL.Subject.prototype.should_have_at_least = function(num, property) {
+ this._should_have(num, property, "at least");
+};
+
+JSSpec.DSL.Subject.prototype.should_have_at_most = function(num, property) {
+ this._should_have(num, property, "at most");
+};
+
+JSSpec.DSL.Subject.prototype.should_include = function(expected) {
+ var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, true);
+ if(!matcher.matches()) {
+ JSSpec._assertionFailure = {message:matcher.explain()};
+ throw JSSpec._assertionFailure;
+ }
+};
+
+JSSpec.DSL.Subject.prototype.should_not_include = function(expected) {
+ var matcher = JSSpec.IncludeMatcher.createInstance(this.target, expected, false);
+ if(!matcher.matches()) {
+ JSSpec._assertionFailure = {message:matcher.explain()};
+ throw JSSpec._assertionFailure;
+ }
+};
+
+JSSpec.DSL.Subject.prototype.should_match = function(pattern) {
+ var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, true);
+ if(!matcher.matches()) {
+ JSSpec._assertionFailure = {message:matcher.explain()};
+ throw JSSpec._assertionFailure;
+ }
+}
+JSSpec.DSL.Subject.prototype.should_not_match = function(pattern) {
+ var matcher = JSSpec.PatternMatcher.createInstance(this.target, pattern, false);
+ if(!matcher.matches()) {
+ JSSpec._assertionFailure = {message:matcher.explain()};
+ throw JSSpec._assertionFailure;
+ }
+};
+
+JSSpec.DSL.Subject.prototype.getType = function() {
+ if(typeof this.target == 'undefined') {
+ return 'undefined';
+ } else if(this.target == null) {
+ return 'null';
+ } else if(this.target._type) {
+ return this.target._type;
+ } else if(JSSpec.util.isDomNode(this.target)) {
+ return 'DomNode';
+ } else {
+ return 'object';
+ }
+};
+
+/**
+ * Utilities
+ */
+JSSpec.util = {
+ escapeTags: function(string) {
+ return string.replace(//img, '>');
+ },
+ escapeMetastring: function(string) {
+ return string.replace(/\r/img, '\\r').replace(/\n/img, '\\n').replace(/\¶\;\
/img, '\\n').replace(/\t/img, '\\t');
+ },
+ parseOptions: function(defaults) {
+ var options = defaults;
+
+ var url = location.href;
+ var queryIndex = url.indexOf('?');
+ if(queryIndex == -1) return options;
+
+ var query = url.substring(queryIndex + 1).split('#')[0];
+ var pairs = query.split('&');
+ for(var i = 0; i < pairs.length; i++) {
+ var tokens = pairs[i].split('=');
+ options[tokens[0]] = tokens[1];
+ }
+
+ return options;
+ },
+ correctHtmlAttrQuotation: function(html) {
+ html = html.replace(/(\w+)=['"]([^'"]+)['"]/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
+ html = html.replace(/(\w+)=([^ '"]+)/mg,function (str, name, value) {return name + '=' + '"' + value + '"';});
+ html = html.replace(/'/mg, '"');
+
+ return html;
+ },
+ sortHtmlAttrs: function(html) {
+ var attrs = [];
+ html.replace(/((\w+)="[^"]+")/mg, function(str, matched) {
+ attrs.push(matched);
+ });
+ return attrs.length == 0 ? "" : " " + attrs.sort().join(" ");
+ },
+ sortStyleEntries: function(styleText) {
+ var entries = styleText.split(/; /);
+ return entries.sort().join("; ");
+ },
+ escapeHtml: function(str) {
+ if(!this._div) {
+ this._div = document.createElement("DIV");
+ this._text = document.createTextNode('');
+ this._div.appendChild(this._text);
+ }
+ this._text.data = str;
+ return this._div.innerHTML;
+ },
+ isDomNode: function(o) {
+ // TODO: make it more stricter
+ return (typeof o.nodeName == 'string') && (typeof o.nodeType == 'number');
+ },
+ inspectDomPath: function(o) {
+ var sb = [];
+ while(o && o.nodeName != '#document' && o.parent) {
+ var siblings = o.parentNode.childNodes;
+ for(var i = 0; i < siblings.length; i++) {
+ if(siblings[i] == o) {
+ sb.push(o.nodeName + (i == 0 ? '' : '[' + i + ']'));
+ break;
+ }
+ }
+ o = o.parentNode;
+ }
+ return sb.join(" > ");
+ },
+ inspectDomNode: function(o) {
+ if(o.nodeType == 1) {
+ var nodeName = o.nodeName.toLowerCase();
+ var sb = [];
+ sb.push('');
+ sb.push("<");
+ sb.push(nodeName);
+
+ var attrs = o.attributes;
+ for(var i = 0; i < attrs.length; i++) {
+ if(
+ attrs[i].nodeValue &&
+ attrs[i].nodeName != 'contentEditable' &&
+ attrs[i].nodeName != 'style' &&
+ typeof attrs[i].nodeValue != 'function'
+ ) sb.push(' ' + attrs[i].nodeName.toLowerCase() + '="' + attrs[i].nodeValue + '"');
+ }
+ if(o.style && o.style.cssText) {
+ sb.push(' style="' + o.style.cssText + '"');
+ }
+ sb.push('>');
+ sb.push(JSSpec.util.escapeHtml(o.innerHTML));
+ sb.push('</' + nodeName + '>');
+ sb.push(' (' + JSSpec.util.inspectDomPath(o) + ')' );
+ sb.push('');
+ return sb.join("");
+ } else if(o.nodeType == 3) {
+ return '#text ' + o.nodeValue + '';
+ } else {
+ return 'UnknownDomNode';
+ }
+ },
+ inspect: function(o, dontEscape, emphasisKey) {
+ var sb, inspected;
+
+ if(typeof o == 'undefined') return 'undefined';
+ if(o == null) return 'null';
+ if(o._type == 'String') return '"' + (dontEscape ? JSSpec.util.escapeMetastring(o) : JSSpec.util.escapeHtml(JSSpec.util.escapeMetastring(o))) + '"';
+
+ if(o._type == 'Date') {
+ return '"' + o.toString() + '"';
+ }
+
+ if(o._type == 'Number') return '' + (dontEscape ? o : JSSpec.util.escapeHtml(o)) + '';
+
+ if(o._type == 'Boolean') return '' + o + '';
+
+ if(o._type == 'RegExp') return '' + JSSpec.util.escapeHtml(o.toString()) + '';
+
+ if(JSSpec.util.isDomNode(o)) return JSSpec.util.inspectDomNode(o);
+
+ if(o._type == 'Array' || typeof o.length != 'undefined') {
+ sb = [];
+ for(var i = 0; i < o.length; i++) {
+ inspected = JSSpec.util.inspect(o[i]);
+ sb.push(i == emphasisKey ? ('' + inspected + '') : inspected);
+ }
+ return '[' + sb.join(', ') + ']';
+ }
+
+ // object
+ sb = [];
+ for(var key in o) {
+ if(key == 'should') continue;
+
+ inspected = JSSpec.util.inspect(key) + ":" + JSSpec.util.inspect(o[key]);
+ sb.push(key == emphasisKey ? ('' + inspected + '') : inspected);
+ }
+ return '{' + sb.join(', ') + '}';
+ }
+};
+
+describe = JSSpec.DSL.describe;
+behavior_of = JSSpec.DSL.describe;
+value_of = JSSpec.DSL.value_of;
+expect = JSSpec.DSL.value_of; // @deprecated
+
+String.prototype._type = "String";
+Number.prototype._type = "Number";
+Date.prototype._type = "Date";
+Array.prototype._type = "Array";
+Boolean.prototype._type = "Boolean";
+RegExp.prototype._type = "RegExp";
+
+var targets = [Array.prototype, Date.prototype, Number.prototype, String.prototype, Boolean.prototype, RegExp.prototype];
+
+String.prototype.normalizeHtml = JSSpec.DSL.forString.normalizeHtml;
+String.prototype.asHtml = String.prototype.normalizeHtml; //@deprecated
+String.prototype.strip = function() {return this.replace(/^\s+/, '').replace(/\s+$/, '');}
+
+
+/**
+ * Main
+ */
+JSSpec.defaultOptions = {
+ autorun: 1,
+ specIdBeginsWith: 0,
+ exampleIdBeginsWith: 0,
+ autocollapse: 1
+};
+JSSpec.options = JSSpec.util.parseOptions(JSSpec.defaultOptions);
+
+JSSpec.Spec.id = JSSpec.options.specIdBeginsWith;
+JSSpec.Example.id = JSSpec.options.exampleIdBeginsWith;
+
+
+
+window.onload = function() {
+ if(JSSpec.specs.length > 0) {
+ if(!JSSpec.options.inSuite) {
+ JSSpec.runner = new JSSpec.Runner(JSSpec.specs, new JSSpec.Logger());
+ if(JSSpec.options.rerun) {
+ JSSpec.runner.rerun(decodeURIComponent(JSSpec.options.rerun));
+ } else {
+ JSSpec.runner.run();
+ }
+ } else {
+ // in suite, send all specs to parent
+ var parentWindow = window.frames.parent.window;
+ for(var i = 0; i < JSSpec.specs.length; i++) {
+ parentWindow.JSSpec.specs.push(JSSpec.specs[i]);
+ }
+ }
+ } else {
+ var links = document.getElementById('list').getElementsByTagName('A');
+ var frameContainer = document.createElement('DIV');
+ frameContainer.style.display = 'none';
+ document.body.appendChild(frameContainer);
+
+ for(var i = 0; i < links.length; i++) {
+ var frame = document.createElement('IFRAME');
+ frame.src = links[i].href + '?inSuite=0&specIdBeginsWith=' + (i * 10000) + '&exampleIdBeginsWith=' + (i * 10000);
+ frameContainer.appendChild(frame);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Support/JSSpec/diff_match_patch.js b/Support/JSSpec/diff_match_patch.js
new file mode 100755
index 0000000..885e051
--- /dev/null
+++ b/Support/JSSpec/diff_match_patch.js
@@ -0,0 +1 @@
+function diff_match_patch(){this.Diff_Timeout=1.0;this.Diff_EditCost=4;this.Diff_DualThreshold=32;this.Match_Balance=0.5;this.Match_Threshold=0.5;this.Match_MinLength=100;this.Match_MaxLength=1000;this.Patch_Margin=4;function getMaxBits(){var maxbits=0;var oldi=1;var newi=2;while(oldi!=newi){maxbits++;oldi=newi;newi=newi<<1}return maxbits}this.Match_MaxBits=getMaxBits()}var DIFF_DELETE=-1;var DIFF_INSERT=1;var DIFF_EQUAL=0;diff_match_patch.prototype.diff_main=function(text1,text2,opt_checklines){if(text1==text2){return[[DIFF_EQUAL,text1]]}if(typeof opt_checklines=='undefined'){opt_checklines=true}var checklines=opt_checklines;var commonlength=this.diff_commonPrefix(text1,text2);var commonprefix=text1.substring(0,commonlength);text1=text1.substring(commonlength);text2=text2.substring(commonlength);commonlength=this.diff_commonSuffix(text1,text2);var commonsuffix=text1.substring(text1.length-commonlength);text1=text1.substring(0,text1.length-commonlength);text2=text2.substring(0,text2.length-commonlength);var diffs=this.diff_compute(text1,text2,checklines);if(commonprefix){diffs.unshift([DIFF_EQUAL,commonprefix])}if(commonsuffix){diffs.push([DIFF_EQUAL,commonsuffix])}this.diff_cleanupMerge(diffs);return diffs};diff_match_patch.prototype.diff_compute=function(text1,text2,checklines){var diffs;if(!text1){return[[DIFF_INSERT,text2]]}if(!text2){return[[DIFF_DELETE,text1]]}var longtext=text1.length>text2.length?text1:text2;var shorttext=text1.length>text2.length?text2:text1;var i=longtext.indexOf(shorttext);if(i!=-1){diffs=[[DIFF_INSERT,longtext.substring(0,i)],[DIFF_EQUAL,shorttext],[DIFF_INSERT,longtext.substring(i+shorttext.length)]];if(text1.length>text2.length){diffs[0][0]=diffs[2][0]=DIFF_DELETE}return diffs}longtext=shorttext=null;var hm=this.diff_halfMatch(text1,text2);if(hm){var text1_a=hm[0];var text1_b=hm[1];var text2_a=hm[2];var text2_b=hm[3];var mid_common=hm[4];var diffs_a=this.diff_main(text1_a,text2_a,checklines);var diffs_b=this.diff_main(text1_b,text2_b,checklines);return diffs_a.concat([[DIFF_EQUAL,mid_common]],diffs_b)}if(checklines&&text1.length+text2.length<250){checklines=false}var linearray;if(checklines){var a=this.diff_linesToChars(text1,text2);text1=a[0];text2=a[1];linearray=a[2]}diffs=this.diff_map(text1,text2);if(!diffs){diffs=[[DIFF_DELETE,text1],[DIFF_INSERT,text2]]}if(checklines){this.diff_charsToLines(diffs,linearray);this.diff_cleanupSemantic(diffs);diffs.push([DIFF_EQUAL,'']);var pointer=0;var count_delete=0;var count_insert=0;var text_delete='';var text_insert='';while(pointer=1&&count_insert>=1){var a=this.diff_main(text_delete,text_insert,false);diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert);pointer=pointer-count_delete-count_insert;for(var j=a.length-1;j>=0;j--){diffs.splice(pointer,0,a[j])}pointer=pointer+a.length}count_insert=0;count_delete=0;text_delete='';text_insert=''}pointer++}diffs.pop()}return diffs};diff_match_patch.prototype.diff_linesToChars=function(text1,text2){var linearray=[];var linehash={};linearray.push('');function diff_linesToCharsMunge(text){var chars='';while(text){var i=text.indexOf('\n');if(i==-1){i=text.length}var line=text.substring(0,i+1);text=text.substring(i+1);if(linehash.hasOwnProperty?linehash.hasOwnProperty(line):(linehash[line]!==undefined)){chars+=String.fromCharCode(linehash[line])}else{linearray.push(line);linehash[line]=linearray.length-1;chars+=String.fromCharCode(linearray.length-1)}}return chars}var chars1=diff_linesToCharsMunge(text1);var chars2=diff_linesToCharsMunge(text2);return[chars1,chars2,linearray]};diff_match_patch.prototype.diff_charsToLines=function(diffs,linearray){for(var x=0;x0&&(new Date()).getTime()>ms_end){return null}v_map1[d]={};for(var k=-d;k<=d;k+=2){if(k==-d||k!=d&&v1[k-1]=0;d--){while(1){if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty((x-1)+','+y):(v_map[d][(x-1)+','+y]!==undefined)){x--;if(last_op===DIFF_DELETE){path[0][1]=text1.charAt(x)+path[0][1]}else{path.unshift([DIFF_DELETE,text1.charAt(x)])}last_op=DIFF_DELETE;break}else if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty(x+','+(y-1)):(v_map[d][x+','+(y-1)]!==undefined)){y--;if(last_op===DIFF_INSERT){path[0][1]=text2.charAt(y)+path[0][1]}else{path.unshift([DIFF_INSERT,text2.charAt(y)])}last_op=DIFF_INSERT;break}else{x--;y--;if(last_op===DIFF_EQUAL){path[0][1]=text1.charAt(x)+path[0][1]}else{path.unshift([DIFF_EQUAL,text1.charAt(x)])}last_op=DIFF_EQUAL}}}return path};diff_match_patch.prototype.diff_path2=function(v_map,text1,text2){var path=[];var x=text1.length;var y=text2.length;var last_op=null;for(var d=v_map.length-2;d>=0;d--){while(1){if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty((x-1)+','+y):(v_map[d][(x-1)+','+y]!==undefined)){x--;if(last_op===DIFF_DELETE){path[path.length-1][1]+=text1.charAt(text1.length-x-1)}else{path.push([DIFF_DELETE,text1.charAt(text1.length-x-1)])}last_op=DIFF_DELETE;break}else if(v_map[d].hasOwnProperty?v_map[d].hasOwnProperty(x+','+(y-1)):(v_map[d][x+','+(y-1)]!==undefined)){y--;if(last_op===DIFF_INSERT){path[path.length-1][1]+=text2.charAt(text2.length-y-1)}else{path.push([DIFF_INSERT,text2.charAt(text2.length-y-1)])}last_op=DIFF_INSERT;break}else{x--;y--;if(last_op===DIFF_EQUAL){path[path.length-1][1]+=text1.charAt(text1.length-x-1)}else{path.push([DIFF_EQUAL,text1.charAt(text1.length-x-1)])}last_op=DIFF_EQUAL}}}return path};diff_match_patch.prototype.diff_commonPrefix=function(text1,text2){if(!text1||!text2||text1.charCodeAt(0)!==text2.charCodeAt(0)){return 0}var pointermin=0;var pointermax=Math.min(text1.length,text2.length);var pointermid=pointermax;var pointerstart=0;while(pointermintext2.length?text1:text2;var shorttext=text1.length>text2.length?text2:text1;if(longtext.length<10||shorttext.length<1){return null}var dmp=this;function diff_halfMatchI(longtext,shorttext,i){var seed=longtext.substring(i,i+Math.floor(longtext.length/4));var j=-1;var best_common='';var best_longtext_a,best_longtext_b,best_shorttext_a,best_shorttext_b;while((j=shorttext.indexOf(seed,j+1))!=-1){var prefixLength=dmp.diff_commonPrefix(longtext.substring(i),shorttext.substring(j));var suffixLength=dmp.diff_commonSuffix(longtext.substring(0,i),shorttext.substring(0,j));if(best_common.length=longtext.length/2){return[best_longtext_a,best_longtext_b,best_shorttext_a,best_shorttext_b,best_common]}else{return null}}var hm1=diff_halfMatchI(longtext,shorttext,Math.ceil(longtext.length/4));var hm2=diff_halfMatchI(longtext,shorttext,Math.ceil(longtext.length/2));var hm;if(!hm1&&!hm2){return null}else if(!hm2){hm=hm1}else if(!hm1){hm=hm2}else{hm=hm1[4].length>hm2[4].length?hm1:hm2}var text1_a,text1_b,text2_a,text2_b;if(text1.length>text2.length){text1_a=hm[0];text1_b=hm[1];text2_a=hm[2];text2_b=hm[3]}else{text2_a=hm[0];text2_b=hm[1];text1_a=hm[2];text1_b=hm[3]}var mid_common=hm[4];return[text1_a,text1_b,text2_a,text2_b,mid_common]};diff_match_patch.prototype.diff_cleanupSemantic=function(diffs){var changes=false;var equalities=[];var lastequality=null;var pointer=0;var length_changes1=0;var length_changes2=0;while(pointer=bestScore){bestScore=score;bestEquality1=equality1;bestEdit=edit;bestEquality2=equality2}}if(diffs[pointer-1][1]!=bestEquality1){diffs[pointer-1][1]=bestEquality1;diffs[pointer][1]=bestEdit;diffs[pointer+1][1]=bestEquality2}}pointer++}};diff_match_patch.prototype.diff_cleanupEfficiency=function(diffs){var changes=false;var equalities=[];var lastequality='';var pointer=0;var pre_ins=false;var pre_del=false;var post_ins=false;var post_del=false;while(pointer0&&diffs[pointer-count_delete-count_insert-1][0]==DIFF_EQUAL){diffs[pointer-count_delete-count_insert-1][1]+=text_insert.substring(0,commonlength)}else{diffs.splice(0,0,[DIFF_EQUAL,text_insert.substring(0,commonlength)]);pointer++}text_insert=text_insert.substring(commonlength);text_delete=text_delete.substring(commonlength)}commonlength=this.diff_commonSuffix(text_insert,text_delete);if(commonlength!==0){diffs[pointer][1]=text_insert.substring(text_insert.length-commonlength)+diffs[pointer][1];text_insert=text_insert.substring(0,text_insert.length-commonlength);text_delete=text_delete.substring(0,text_delete.length-commonlength)}}if(count_delete===0){diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_INSERT,text_insert])}else if(count_insert===0){diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_DELETE,text_delete])}else{diffs.splice(pointer-count_delete-count_insert,count_delete+count_insert,[DIFF_DELETE,text_delete],[DIFF_INSERT,text_insert])}pointer=pointer-count_delete-count_insert+(count_delete?1:0)+(count_insert?1:0)+1}else if(pointer!==0&&diffs[pointer-1][0]==DIFF_EQUAL){diffs[pointer-1][1]+=diffs[pointer][1];diffs.splice(pointer,1)}else{pointer++}count_insert=0;count_delete=0;text_delete='';text_insert=''}}if(diffs[diffs.length-1][1]===''){diffs.pop()}var changes=false;pointer=1;while(pointerloc){break}last_chars1=chars1;last_chars2=chars2}if(diffs.length!=x&&diffs[x][0]===DIFF_DELETE){return last_chars2}return last_chars2+(loc-last_chars1)};diff_match_patch.prototype.diff_prettyHtml=function(diffs){this.diff_addIndex(diffs);var html=[];for(var x=0;x/g,'>');t=t.replace(/\n/g,'¶
');if(m===DIFF_DELETE){html.push('',t,'')}else if(m===DIFF_INSERT){html.push('',t,'')}else{html.push('',t,'')}}return html.join('')};diff_match_patch.prototype.diff_text1=function(diffs){var txt=[];for(var x=0;xthis.Match_MaxBits){return alert('Pattern too long for this browser.')}var s=this.match_alphabet(pattern);var score_text_length=text.length;score_text_length=Math.max(score_text_length,this.Match_MinLength);score_text_length=Math.min(score_text_length,this.Match_MaxLength);var dmp=this;function match_bitapScore(e,x){var d=Math.abs(loc-x);return(e/pattern.length/dmp.Match_Balance)+(d/score_text_length/(1.0-dmp.Match_Balance))}var score_threshold=this.Match_Threshold;var best_loc=text.indexOf(pattern,loc);if(best_loc!=-1){score_threshold=Math.min(match_bitapScore(0,best_loc),score_threshold)}best_loc=text.lastIndexOf(pattern,loc+pattern.length);if(best_loc!=-1){score_threshold=Math.min(match_bitapScore(0,best_loc),score_threshold)}var matchmask=1<<(pattern.length-1);best_loc=null;var bin_min,bin_mid;var bin_max=Math.max(loc+loc,text.length);var last_rd;for(var d=0;d=start;j--){if(d===0){rd[j]=((rd[j+1]<<1)|1)&s[text.charAt(j)]}else{rd[j]=((rd[j+1]<<1)|1)&s[text.charAt(j)]|((last_rd[j+1]<<1)|1)|((last_rd[j]<<1)|1)|last_rd[j+1]}if(rd[j]&matchmask){var score=match_bitapScore(d,j);if(score<=score_threshold){score_threshold=score;best_loc=j;if(j>loc){start=Math.max(0,loc-(j-loc))}else{break}}}}if(match_bitapScore(d+1,loc)>score_threshold){break}last_rd=rd}return best_loc};diff_match_patch.prototype.match_alphabet=function(pattern){var s=Object();for(var i=0;i2){this.diff_cleanupSemantic(diffs);this.diff_cleanupEfficiency(diffs)}}if(diffs.length===0){return[]}var patches=[];var patch=new patch_obj();var char_count1=0;var char_count2=0;var prepatch_text=text1;var postpatch_text=text1;for(var x=0;x=2*this.Patch_Margin){if(patch.diffs.length!==0){this.patch_addContext(patch,prepatch_text);patches.push(patch);patch=new patch_obj();prepatch_text=postpatch_text}}if(diff_type!==DIFF_INSERT){char_count1+=diff_text.length}if(diff_type!==DIFF_DELETE){char_count2+=diff_text.length}}if(patch.diffs.length!==0){this.patch_addContext(patch,prepatch_text);patches.push(patch)}return patches};diff_match_patch.prototype.patch_apply=function(patches,text){this.patch_splitMax(patches);var results=[];var delta=0;for(var x=0;xthis.Match_MaxBits){var bigpatch=patches[x];patches.splice(x,1);var patch_size=this.Match_MaxBits;var start1=bigpatch.start1;var start2=bigpatch.start2;var precontext='';while(bigpatch.diffs.length!==0){var patch=new patch_obj();var empty=true;patch.start1=start1-precontext.length;patch.start2=start2-precontext.length;if(precontext!==''){patch.length1=patch.length2=precontext.length;patch.diffs.push([DIFF_EQUAL,precontext])}while(bigpatch.diffs.length!==0&&patch.length1
+
+
+
+JSSpec results
+
+
+
+
+
+
+
+
+
diff --git a/Support/JSSpec/specs.js b/Support/JSSpec/specs.js
new file mode 100644
index 0000000..d2d90ab
--- /dev/null
+++ b/Support/JSSpec/specs.js
@@ -0,0 +1,163 @@
+(function(){
+ var SubtleSlickParse_Specs = {
+
+ 'should exist': function(){
+ value_of( SubtleSlickParse ).should_not_be_undefined();
+ },
+
+ 'should parse multiple selectors': function(){
+ var s = SubtleSlickParse('a, b, c');
+ value_of( s[0][0].tag ).should_be( 'a' );
+ value_of( s[1][0].tag ).should_be( 'b' );
+ value_of( s[2][0].tag ).should_be( 'c' );
+ },
+
+ 'should parse multiple selectors with class': function(){
+ var s = SubtleSlickParse('a.class, b.class, c.class');
+ value_of( s[0][0].tag ).should_be( 'a' );
+ value_of( s[1][0].tag ).should_be( 'b' );
+ value_of( s[2][0].tag ).should_be( 'c' );
+ value_of( s[0][0].classes[0] ).should_be( 'class' );
+ value_of( s[1][0].classes[0] ).should_be( 'class' );
+ value_of( s[2][0].classes[0] ).should_be( 'class' );
+ },
+
+ 'should parse tag names': function(){
+ for (var i=0; i < tags.length; i++) {var tag = tags[i];
+ value_of( SubtleSlickParse(tag)[0][0].tag ).should_be( tag );
+ }
+ },
+
+ 'should parse tag names with combinators': function(){
+ for (var i=0; i < tags.length; i++) {var tag = tags[i];
+ for (var C=0; C < combinators.length; C++) {var combinator = combinators[C];
+
+ var s = SubtleSlickParse(tag +combinator+ tag);
+
+ value_of( s[0][0].tag ).should_be( tag );
+ value_of( s[0][1].tag ).should_be( tag );
+ value_of( s[0][1].combinator ).should_be( combinator );
+ }
+ }
+ },
+
+ 'should transform odd to 2n+1 in pseudos nth arguments': function(){
+ var nths = [
+ {raw: ":nth-child(odd)", name: "nth-child", argument:"2n+1"},
+ {raw: ":nth-last-child(odd)", name: "nth-last-child", argument:"2n+1"},
+ {raw: ":nth-last-of-type(odd)", name:"nth-last-of-type", argument:"2n+1"},
+ {raw: ":nth-of-type(odd)", name: "nth-of-type", argument:"2n+1"},
+
+ {raw: ":nth-child(odd)", name:'nth-child', argument:"2n+1"},
+ {raw: ":nth-child(2n+1)", name:'nth-child', argument:"2n+1"},
+ {raw: ":nth-child(n)", name:'nth-child', argument:"n" },
+ ];
+ for (var i=0,s, N; N = nths[i]; i++){
+ s = SubtleSlickParse(N.raw);
+ value_of( s[0][0].pseudos[0].name ).should_be( N.name );
+ value_of( s[0][0].pseudos[0].argument ).should_be( N.argument );
+ }
+ },
+
+ 'should transform even to 2n in pseudo nth arguments': function(){
+ var nths = [
+ {raw: ":nth-child(even)", name: "nth-child", argument:"2n"},
+ {raw: ":nth-last-child(even)", name: "nth-last-child", argument:"2n"},
+ {raw: ":nth-last-of-type(even)", name:"nth-last-of-type", argument:"2n"},
+ {raw: ":nth-of-type(even)", name: "nth-of-type", argument:"2n"},
+
+ {raw:":nth-child(even)", name:'nth-child', argument:"2n" },
+ {raw:":nth-child(2n)" , name:'nth-child', argument:"2n" },
+ {raw:":nth-child(n)" , name:'nth-child', argument:"n" },
+ ];
+ for (var i=0,s, N; N = nths[i]; i++){
+ s = SubtleSlickParse(N.raw);
+ value_of( s[0][0].pseudos[0].name ).should_be( N.name );
+ value_of( s[0][0].pseudos[0].argument ).should_be( N.argument );
+ }
+ }
+ ,
+ 'should parse :not(with quoted innards)': function(){
+ var s = SubtleSlickParse(":not()")[0][0];
+ value_of( s.pseudos.length ).should_be(1);
+ value_of( s.pseudos[0].name ).should_be('not');
+ value_of( s.pseudos[0].argument ).should_be("");
+
+ s = SubtleSlickParse(':not([attr])')[0][0];
+ value_of( s.pseudos[0].argument ).should_be('[attr]');
+
+ s = SubtleSlickParse(':not([attr=])')[0][0];
+ value_of( s.pseudos[0].argument ).should_be('[attr=]');
+
+ s = SubtleSlickParse(":not([attr=''])")[0][0];
+ value_of( s.pseudos[0].argument ).should_be("[attr='']");
+
+ s = SubtleSlickParse(':not([attr=""])')[0][0];
+ value_of( s.pseudos[0].argument ).should_be('[attr=""]');
+ }
+ ,
+ 'should parse :pseudo arguments as null': function(){
+ var s = SubtleSlickParse(":pseudo")[0][0];
+ value_of( s.pseudos.length ).should_be(1);
+ value_of( s.pseudos[0].name ).should_be('pseudo');
+ value_of( s.pseudos[0].argument ).should_be_null();
+ }
+ ,
+ 'should parse :pseudo() arguments as ""': function(){
+ var s = SubtleSlickParse(":pseudo()")[0][0];
+ value_of( s.pseudos.length ).should_be(1);
+ value_of( s.pseudos[0].name ).should_be('pseudo');
+ value_of( s.pseudos[0].argument ).should_not_be_null();
+ value_of( s.pseudos[0].argument ).should_be("");
+ }
+ ,
+ 'should parse attributes': function(){ }
+ }
+
+ var combinators = ' ,>,+,~'.split(',');
+ var tags = 'a abbr acronym address applet area b base basefont bdo big blockquote br button caption center cite code col colgroup dd del dfn dir div dl dt em fieldset font form frame frameset h1 h2 h3 h4 h5 h6 head hr html i iframe img input ins isindex kbd label legend li link map menu meta noframes noscript object ol optgroup option p param pre q s samp script select small span strike strong style sub sup table tbody td textarea tfoot th thead title tr tt u ul var'.split(' ');
+ var attribOperators = ' = != *= ^= $= ~= |='.split(' ');
+ var attrs = 'attr html:lang fred-rocks'.split(' ');
+ var vals = 'myValueOfDoom;"double";\'single\';();{};\'thing[]\';"thing[]"'.split(';');
+
+ vals: for (var vi=0; vi < vals.length; vi++) {
+ var val = vals[vi];
+ attrs: for (var ai=0; ai < attrs.length; ai++) {
+ var attr = attrs[ai];
+ operators: for (var i=0; i < attribOperators.length; i++) {
+ var op = attribOperators[i];
+ var ATTRIBUTE = '['+ attr + op + (op&&val) +']';
+
+ SubtleSlickParse_Specs['should parse attributes: '+ ATTRIBUTE] = function(){
+ var s = SubtleSlickParse(ATTRIBUTE)[0][0];
+
+ value_of( s.attributes.length ).should_be( 1 );
+ value_of( s.attributes[0].name||'' ).should_be( attr );
+ value_of( s.attributes[0].operator||'' ).should_be( op );
+ if (!op) {
+ value_of( s.attributes[0].operator ).should_be_undefined();
+ value_of( s.attributes[0].value ).should_be_undefined();
+ value_of( s.attributes[0].regexp ).should_be_null();
+ } else {
+ value_of( s.attributes[0].value ).should_be( val.replace(/^["']|['"]$/g,'') );
+ value_of( s.attributes[0].regexp.toString() ).should_be( SubtleSlickParse.attribValueToRegex(op, op&&val.replace(/^["']|['"]$/g,'')).toString() );
+ }
+ }
+ }
+ }
+ }
+
+ describe('SubtleSlickParse', SubtleSlickParse_Specs);
+})();
+
+
+describe('MooTools-Slick', {
+
+ 'should exist': function(){
+ value_of( slick ).should_not_be_undefined();
+ }
+ ,
+ 'should match attributes': function(){
+
+ }
+});
diff --git a/Support/run_js.js b/Support/run_js.js
new file mode 100644
index 0000000..9764570
--- /dev/null
+++ b/Support/run_js.js
@@ -0,0 +1,19 @@
+(function(window){
+
+ function e_sh(s){
+ return String(s).replace(/'/g,'’').replace(/(?=["\n\\])/g,'\\');
+ };
+
+ window.alert = function(s){
+ console.debug($DIALOG +' -e -p \'{messageTitle="JavaScript";informativeText="'+ e_sh(s) +'";}\'');
+ TextMate.system($DIALOG +' -e -p \'{messageTitle="JavaScript";informativeText="'+ e_sh(s) +'";}\'', null);
+ };
+
+ var __TM_confirm_Status;
+ window.confirm = function(s){
+ TextMate.system($DIALOG +' -e -p \'{messageTitle="JavaScript";informativeText="'+ e_sh(s) +'";buttonTitles=("OK","Cancel");}\'', null)
+ .onreadoutput = function(s){ __TM_confirm_Status = s != 1; };
+ return __TM_confirm_Status;
+ };
+
+})(this);
diff --git a/Support/run_js.rb b/Support/run_js.rb
new file mode 100644
index 0000000..acc551f
--- /dev/null
+++ b/Support/run_js.rb
@@ -0,0 +1,83 @@
+#!/usr/bin/env ruby
+$YOURCODE = STDIN.read
+
+print <<-HTML
+
+
+
+
+Run Javascript #{ENV['TM_FILENAME']}
+
+
+HTML
+
+
+requirements = []
+requirements << ['tm-file://'+ ENV['TM_BUNDLE_SUPPORT']+ '/run_js.js?1234']
+requirements << ENV['TM_WEB_FRAMEWORK'] if ENV['TM_WEB_FRAMEWORK']
+requirements += $YOURCODE.scan(/@require (?:"([^"]*)"|'([^']*)')/).map { |e| e.first || e[1] }
+requirements.flatten!
+requirements.uniq!
+
+requirements = requirements.map do |requirement|
+
+ requirement = 'http://ajax.googleapis.com/ajax/libs/mootools/1.2.1/mootools.js' if requirement == 'mootools'
+ requirement = 'http://ajax.googleapis.com/ajax/libs/mootools/1.2.1/mootools.js' if requirement == 'mootools'
+
+ if requirement == 'jsspec'
+ requirement = [
+ 'tm-file://'+ ENV['TM_BUNDLE_SUPPORT']+ '/JSSpec/'+ 'JSSpec.css',
+ 'tm-file://'+ ENV['TM_BUNDLE_SUPPORT']+ '/JSSpec/'+ 'diff_match_patch.js',
+ 'tm-file://'+ ENV['TM_BUNDLE_SUPPORT']+ '/JSSpec/'+ 'JSSpec.js'
+ ]
+ end
+
+ requirement
+end.flatten
+
+requirements.uniq.each do |requirement|
+ requirement = 'tm-file://' + File.dirname(ENV['TM_FILEPATH']) +'/'+ requirement if requirement =~ /^(?!\/|https?:|(tm-)?file:\/\/)/
+
+ print %{\n} unless requirement =~ /\.css$/
+
+ print <<-CSS if requirement =~ /\.css$/
+
+ CSS
+end
+
+print <<-HTML
+
+
+
+
+HTML
+
+__END__
diff --git a/Tests/run_js.test.js b/Tests/run_js.test.js
new file mode 100644
index 0000000..a426205
--- /dev/null
+++ b/Tests/run_js.test.js
@@ -0,0 +1,25 @@
+// Relative Paths
+// @require 'relative_path.js';
+// @require 'relative_path.css';
+
+// Urls
+// @require 'http://ajax.googleapis.com/ajax/libs/mootools/1.2.1/mootools.js';
+
+// Libraries
+// @require 'mootools';
+// @require 'jsspec';
+
+window.alert('alerts')
+
+window.confirm('confirms')
+ ? window.alert('confirmed')
+ : window.alert('not confirmed')
+;
+
+try{throw {}}catch(e){ console.log(e.line); }; FRED++
+
+
+
+
+
+