Permalink
Browse files

wycats: Partial browser emulation, for JQuery-related testing.

  • Loading branch information...
1 parent 1793f95 commit 770041fde84b6270873fe04c911564a867dd1b1a @matthewd committed Apr 6, 2008
Showing with 3,835 additions and 0 deletions.
  1. +26 −0 env/env.js
  2. +223 −0 env/env.rb
  3. +16 −0 env/html.html
  4. +3,381 −0 env/jquery.js
  5. +189 −0 jspec/jspec.js
View
26 env/env.js
@@ -0,0 +1,26 @@
+window.load = function(file) {
+ var text = File.read(file);
+ eval(text);
+};
+
+window.navigator = {
+ get userAgent(){
+ return "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3";
+ }
+};
+
+window.__defineSetter__("location", function(url) {
+ var text = File.read(url);
+ window.document = text;
+});
+
+window.__defineSetter__("document", function(text) {
+ window.__document__ = DOMDocument.create(text);
+});
+
+window.__defineGetter__("document", function() {
+ return window.__document__;
+});
+
+load("env/jquery.js")
+window.location = "env/html.html"
View
223 env/env.rb
@@ -0,0 +1,223 @@
+require 'spidermonkey'
+require 'rubygems'
+require 'hpricot'
+require 'xml/libxml'
+require 'delegate'
+
+class SpiderMonkey::Context
+
+ def eval_file(file)
+ text = File.read(file)
+ begin
+ self.eval(text)
+ rescue SpiderMonkey::EvalError => e
+ puts "An error has occured in #{file} on line #{e.lineno}"
+ puts e.message
+ end
+ end
+
+end
+
+module JavaScript
+ class Console
+
+ def initialize
+ @level = 0
+ end
+
+ def print_level
+ print " " * @level
+ end
+
+ def top_level(msg)
+ puts msg
+ end
+
+ def describe(msg)
+ @level += 1
+ print_level
+ puts msg
+ end
+
+ def pass(msg)
+ print_level
+ puts "* #{msg}"
+ end
+
+ def fail(msg)
+ print_level
+ puts "X #{msg}"
+ @level += 1
+ end
+
+ def failure(msg)
+ print_level
+ puts "E #{msg}"
+ # @level -= 1
+ end
+
+ def groupEnd(*args)
+ @level -= 1
+ end
+ end
+end
+
+require 'rubygems'
+require 'hpricot'
+
+class String
+ # "FooBar".snake_case #=> "foo_bar"
+ def snake_case
+ gsub(/\B[A-Z]/, '_\&').downcase
+ end
+
+ # "foo_bar".camel_case #=> "FooBar"
+ def camel_case
+ split('_').map{|e| e.capitalize}.join
+ end
+end
+
+class XML::Node::Set
+ def array_like?() true end
+end
+
+module HtmlDom
+ module Events
+ def events
+ @events ||= Hash.new {|h,k| h[k] = []}
+ end
+
+ def addEventListener(type, fn, *args)
+ events[type] << fn
+ end
+
+ def removeEventListener(type, fn, *args)
+ events[type].delete(fn)
+ end
+
+ def dispatchEvent(event, *args)
+ events[event["type"]].each {|e| e.call_function("call", self, event)}
+ end
+ end
+end
+
+class XML::Node
+ include HtmlDom::Events
+
+ def getElementById(theId)
+ find("//*[@id='#{theId}']").set[0]
+ end
+
+ def getElementsByTagName(name)
+ find("//#{name}").set
+ end
+
+ def tagName
+ name.upcase
+ end
+
+ def nodeType() node_type end
+ def nodeValue
+ case node_type
+ when 3, 4, 7, 8 then content # TEXT_NODE, CDATA_SECTION, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE
+ else nil
+ end
+ end
+
+ alias_method :old_brackets, :[]
+ alias_method :set_old_brackets, :[]=
+
+ def key?(key)
+ @props ||= {}
+ !!@props[key] || (!self.methods.include?(key) && !self.methods.include?(key.to_sym))
+ end
+
+ def [](key) @props ||= {}; @props[key] || "" end
+ def []=(key, value) @props ||= {}; @props[key] = value || "" end
+
+ def getAttribute(key) self[key] end
+ def setAttribute(key, value) self[key] = value end
+ def removeAttribute(key) self[key] = nil end
+
+ def id() self["id"] || "" end
+ def id=(theId) self["id"] = theId || "" end
+
+ def className() self["class"] || "" end
+ def className=(name) self["class"] = name || "" end
+
+ def cloneNode(deep = true) copy(deep) end
+ def nodeName() tagName end
+ def ownerDocument() doc.root end
+ def documentElement() doc.root end
+
+ def parentNode() parent end
+ def nextSibling() next_sibling end
+ def previousSibling() prev end
+ def childNodes
+ x = []
+ self.each_child {|c| x << c }
+ x
+ end
+ def firstChild() childNodes[0] end
+ def lastChild() childNodes[-1] end
+
+ def appendChild(node)
+ if child then last.next = node
+ else child = node end
+ end
+
+ def insertBefore(node) prev = node end
+ def removeChild(node) node.remove! if childNodes.include?(node) end
+
+ def toString
+ if node_type == 1
+ "<#{tagName}#{" id=#{self["id"]}" unless self["id"].empty?}#{" class=#{className}" unless className.empty?}>"
+ else
+ "\"#{nodeValue}\""
+ end
+ end
+
+ def outerHTML() to_s end
+ def innerHTML() childNodes.to_s end
+
+ def innerHTML=(html)
+ node = XML::Parser.string(html).parse.root;
+ children.remove!;
+ child_add(node)
+ end
+
+ def textContent() content end
+ def textContent=(content) content = content end
+
+ def disabled() (this["disabled"] != "false") && !!this["disabled"] end
+
+ def body() find("//body").set[0] end
+
+end
+
+class DOMDocument
+ def self.create(text)
+ XML::Parser.string(Hpricot(text).to_s).parse.root
+ end
+end
+
+
+THREADS = []
+
+CX = cx = SpiderMonkey::Context.new
+JS_NULL = cx.eval("null")
+cx.set_property("console", JavaScript::Console.new)
+cx.set_property("window", cx.global)
+cx.set_property("HTMLElement", XML::Node)
+cx.set_property("DOMDocument", DOMDocument)
+cx.global.function("print") {|x| puts x}
+cx.global.function("setTimeout") {|f,t| Thread.new { sleep(t / 1000.0); f.call_function } }
+cx.global.function("setInterval") {|f,t| THREADS.push(Thread.new { loop { sleep(t / 1000.0); f.call_function } }); THREADS.size - 1 }
+cx.global.function("clearInterval") {|i| THREADS[i].kill }
+
+cx.eval_file(File.dirname(__FILE__) + "/env.js")
+
+# require 'ruby-debug'
+# debugger
+
+# cx.eval_file(File.dirname(__FILE__) + "/jspec.js")
View
16 env/html.html
@@ -0,0 +1,16 @@
+<html>
+
+ <head>
+ <title>Conforming XHTML 1.0 Transitional Template</title>
+ </head>
+ <body>
+ <div id="hello">
+ <span>Hey!</span>
+ <ul>
+ <li>Hey</li>
+ <li>Whaaaa?</li>
+ </ul>
+ </div>
+ </body>
+
+</html>
View
3,381 env/jquery.js
3,381 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
189 jspec/jspec.js
@@ -0,0 +1,189 @@
+jspec = {
+ fn_contents: function(fn) {
+ return fn.toString().match(/^[^\{]*{((.*\n*)*)}/m)[1];
+ },
+ TOP_LEVEL: 0, DESCRIBE: 1, IT_SHOULD_PASS: 2, IT_SHOULD_FAIL: 3,
+ FAILURE: 4, DONE_EXAMPLE: 5, DONE_GROUP: 6,
+ logger: function(state, message) {
+ switch(state) {
+ case jspec.TOP_LEVEL:
+ console.group(message);
+ break;
+ case jspec.DESCRIBE:
+ console.group(message);
+ break;
+ case jspec.IT_SHOULD_PASS:
+ console.info(message);
+ break;
+ case jspec.IT_SHOULD_FAIL:
+ console.group(message);
+ break;
+ case jspec.FAILURE:
+ console.error(message);
+ console.groupEnd();
+ break;
+ case jspec.DONE_EXAMPLE:
+ console.groupEnd();
+ break;
+ case jspec.DONE_GROUP:
+ console.groupEnd();
+ }
+
+ },
+ describe: function(str, desc) {
+ jspec.logger(jspec.TOP_LEVEL, str);
+ var it = function(str, fn) {
+ jspec.logger(jspec.DESCRIBE, str);
+ fn();
+ jspec.logger(jspec.DONE_EXAMPLE);
+ };
+ var Expectation = function(p) { this.expectation = p; };
+ Expectation.prototype.to = function(fn_str, to_compare, not) {
+ try {
+ var pass = jspec.matchers[fn_str].matches(this.expectation, to_compare);
+ if(not) var pass = !pass;
+ } catch(e) {
+ var pass = null;
+ }
+ var should_string = (jspec.matchers[fn_str].describe &&
+ jspec.matchers[fn_str].describe(this.expectation, to_compare, not)) ||
+ this.toString() + " should " + (not ? "not " : "") + fn_str + " " + to_compare;
+ if(pass) {
+ jspec.logger(jspec.IT_SHOULD_PASS, should_string + " (PASS)");
+ } else {
+ jspec.logger(jspec.IT_SHOULD_FAIL, should_string + (pass == false ? " (FAIL)" : " (ERROR)"));
+ jspec.logger(jspec.FAILURE, jspec.matchers[fn_str].failure_message(this.expectation, to_compare, not))
+ }
+ }
+ Expectation.prototype.not_to = function(fn_str, to_compare) { this.to(fn_str, to_compare, true) }
+ var expect = function(p) { return new Expectation(p) };
+ x = desc.toString()
+ var fn_body = this.fn_contents(desc);
+ var fn = new Function("it", "expect", fn_body);
+ fn.call(this, it, expect);
+ jspec.logger(jspec.DONE_GROUP);
+ }
+}
+
+// Helper for
+
+jspec.print_object = function(obj) {
+ if(obj instanceof Function) {
+ return obj.toString().match(/^([^\{]*) {/)[1];
+ } else if(obj instanceof Array) {
+ return obj.toSource();
+ // } else if(obj instanceof HTMLElement) {
+ // return "<" + obj.tagName + " " + (obj.className != "" ? "class='" + obj.className + "'" : "") +
+ // (obj.id != "" ? "id='" + obj.id + "'" : "") + ">";
+ } else if(obj) {
+ return obj.toString().replace(/\n\s*/g, "");
+ }
+}
+
+// Matchers begin here
+
+jspec.matchers = {};
+
+jspec.matchers["=="] = {
+ describe: function(self, target, not) {
+ return jspec.print_object(self) + " should " + (not ? "not " : "") + "equal " + jspec.print_object(target)
+ },
+ matches: function(self, target) {
+ return self == target;
+ },
+ failure_message: function(self, target, not) {
+ if (not)
+ return "Expected " + jspec.print_object(self) + " not to equal " + jspec.print_object(target);
+ else
+ return "Expected " + jspec.print_object(self) + ". Got " + jspec.print_object(target);
+ }
+}
+
+jspec.matchers["include"] = {
+ matches: function(self, target) {
+ for(i=0,j=self.length;i<j;i++) {
+ if(target == self[i]) return true;
+ }
+ return false;
+ },
+ failure_message: function(self, target, not) {
+ return "Expected " + jspec.print_object(self) + " " + (not ? "not " : "") + "to include " + target;
+ }
+}
+
+jspec.matchers["exist"] = {
+ describe: function(self, target, not) {
+ return jspec.print_object(self) + " should " + (not ? "not " : "") + "exist."
+ },
+ matches: function(self, target) {
+ return !!this;
+ },
+ failure_message: function(self, target, not) {
+ return "Expected " + (not ? "not " : "") + "to exist, but was " + jspec.print_object(self);
+ }
+}
+
+jspec.logger = function(state, message) {
+ switch(state) {
+ case jspec.TOP_LEVEL:
+ console.top_level(message);
+ break;
+ case jspec.DESCRIBE:
+ console.describe(message);
+ break;
+ case jspec.IT_SHOULD_PASS:
+ console.pass(message);
+ break;
+ case jspec.IT_SHOULD_FAIL:
+ console.fail(message);
+ break;
+ case jspec.FAILURE:
+ console.failure(message);
+ console.groupEnd();
+ break;
+ case jspec.DONE_EXAMPLE:
+ console.groupEnd();
+ break;
+ case jspec.DONE_GROUP:
+ console.groupEnd();
+ }
+}
+
+jspec.describe("JSpec", function() {
+ it("should support ==", function() {
+ expect(1).to("==", 1);
+ var arr = [];
+ expect(arr).to("==", arr);
+ var obj = new Object;
+ expect(obj).to("==", obj);
+ expect(document).to("==", document);
+ });
+
+ it("should support include", function() {
+ expect([1,2,3,4,5]).to("include", 3);
+ expect([1,2,3,4,5]).not_to("include", 6);
+ expect(document.getElementsByTagName("div")).to("include", document.getElementById("hello"))
+ });
+
+ it("should support exists", function() {
+ expect(document).to("exist");
+ });
+
+ jspec.matchers["have_tag_name"] = {
+ describe: function(self, target, not) {
+ return jspec.print_object(self) + " should " + (not ? "not " : "") + "have " + target + " as its tag name."
+ },
+ matches: function(self, target) {
+ return (self.tagName && self.tagName == target) ? true : false;
+ },
+ failure_message: function(self, target, not) {
+ return "Expected " + jspec.print_object(self) + (not ? " not " : " ") + "to have " + target + " as its tag name," +
+ " but was " + self.tagName;
+ }
+ };
+
+ it("should support custom matchers", function() {
+ expect(document.getElementById("wrapper")).to("have_tag_name", "DIV");
+ expect(document.getElementById("wrapper")).not_to("have_tag_name", "SPAN");
+ });
+});

0 comments on commit 770041f

Please sign in to comment.