[Jifty-commit] r4680 - in jifty/branches/jquery: . lib/Jifty lib/Jifty/Action/Record lib/Jifty/Plugin/Authentication/CAS/Action lib/Jifty/Plugin/Authentication/Ldap/Action lib/Jifty/Plugin/Authentication/Password/Mixin/Model lib/Jifty/Plugin/LeakTracker share/web/static/js t/TestApp-JiftyJS/share/web/static/js-test t/TestApp-JiftyJS/share/web/static/js-test/lib t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM t/TestApp-JiftyJS/share/web/static/js-test/lib/Test t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness t/TestApp-JiftyJS/share/web/static/js-test/t t/TestApp-JiftyJS/t

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Thu Dec 13 18:04:01 EST 2007


Author: gugod
Date: Thu Dec 13 18:04:01 2007
New Revision: 4680

Added:
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/01.behaviour.html   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/index.html   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM/
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM/Events.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM/Ready.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/JSAN.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Builder.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness/
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness/Browser.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness/Director.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/More.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Simple.js   (contents, props changed)
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/t/
   jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/t/1-jifty-update.t
   jifty/branches/jquery/t/TestApp-JiftyJS/t/2-behaviour.t
Modified:
   jifty/branches/jquery/   (props changed)
   jifty/branches/jquery/META.yml
   jifty/branches/jquery/Makefile.PL
   jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm
   jifty/branches/jquery/lib/Jifty/DateTime.pm
   jifty/branches/jquery/lib/Jifty/Handler.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogin.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogin.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm
   jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker.pm
   jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker/View.pm
   jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm
   jifty/branches/jquery/share/web/static/js/jifty.js
   jifty/branches/jquery/share/web/static/js/prototype.js

Log:
Bulk merge from trunk. Mainly for test files udner t/.


Modified: jifty/branches/jquery/META.yml
==============================================================================
--- jifty/branches/jquery/META.yml	(original)
+++ jifty/branches/jquery/META.yml	Thu Dec 13 18:04:01 2007
@@ -50,6 +50,7 @@
   Net::Server::Fork: 0
   Net::Server::PreFork: 0
   PAR::Dist::FromCPAN: 0
+  Proc::ProcessTable: 0
   Test::Base: 0.44
   Test::HTML::Lint: 0
   Test::HTTP::Server::Simple: 0.02

Modified: jifty/branches/jquery/Makefile.PL
==============================================================================
--- jifty/branches/jquery/Makefile.PL	(original)
+++ jifty/branches/jquery/Makefile.PL	Thu Dec 13 18:04:01 2007
@@ -166,6 +166,7 @@
         recommends('Devel::Events::Handler::ObjectTracker'),
         recommends('Devel::Events::Generator::Objects'),
         recommends('Devel::Size'),
+        recommends('Proc::ProcessTable'),
     ],
     'OAuth Plugin' => [
         -default => 0,

Modified: jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Action/Record/Update.pm	Thu Dec 13 18:04:01 2007
@@ -124,6 +124,17 @@
             $value = scalar <$value>;
         }
 
+        # Skip fields that have not changed
+        my $old = $self->record->$field;
+        # XXX TODO: This ignore "by" on columns
+        $old = $old->id if blessed($old) and $old->isa( 'Jifty::Record' );
+
+        # ID is sometimes passed in, we want to ignore it if it doesn't change
+        next if $field eq 'id'
+           and defined $old
+           and defined $value
+           and "$old" eq "$value";
+
         # Error on columns we can't update
         # <Sartak> ah ha. I think I know why passing due => undef reports
         #          action success
@@ -144,11 +155,6 @@
             next;
         }
 
-        # Skip fields that have not changed
-        my $old = $self->record->$field;
-        # XXX TODO: This ignore "by" on columns
-        $old = $old->id if blessed($old) and $old->isa( 'Jifty::Record' );
-    
         # if both the new and old values are defined and equal, we don't want to change em
         # XXX TODO "$old" is a cheap hack to scalarize datetime objects
         next if ( defined $old and defined $value and "$old" eq "$value" );

Modified: jifty/branches/jquery/lib/Jifty/DateTime.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/DateTime.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/DateTime.pm	Thu Dec 13 18:04:01 2007
@@ -248,6 +248,26 @@
     return $ymd;
 }
 
+=head2 is_date
+
+Returns whether or not this C<Jifty::DateTime> object represents a date
+(without a specific time). Dates in Jifty are in the floating time zone and
+are set to midnight.
+
+=cut
+
+sub is_date {
+    my $self = shift;
+
+    # all dates are in the floating time zone
+    return 0 unless $self->time_zone->name eq 'floating';
+
+    # all dates are set to midnight
+    return 0 unless $self->hms eq '00:00:00';
+
+    return 1;
+}
+
 =head1 WHY?
 
 There are other ways to do some of these things and some of the decisions here may seem arbitrary, particularly if you read the code. They are.

Modified: jifty/branches/jquery/lib/Jifty/Handler.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Handler.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Handler.pm	Thu Dec 13 18:04:01 2007
@@ -244,6 +244,9 @@
         # Return from the continuation if need be
         Jifty->web->request->return_from_continuation;
         $self->dispatcher->handle_request();
+
+        $self->call_trigger('before_cleanup', $args{cgi});
+
         $self->cleanup_request();
     }
 

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogin.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogin.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogin.pm	Thu Dec 13 18:04:01 2007
@@ -70,6 +70,9 @@
 #    	$ENV{HTTP_HOST}.'/caslogin';
     
     my $service_url = Jifty->web->url.'/caslogin';
+    if ( Jifty->web->request->continuation ) {
+        $service_url .= '?J:C='.Jifty->web->request->continuation_id;
+    };
 
     if (! $ticket) {
         my $login_url = $plugin->CAS->login_url( $service_url );

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogin.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogin.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogin.pm	Thu Dec 13 18:04:01 2007
@@ -79,8 +79,15 @@
     }
 
     # Load up the user
+    my $infos =  $plugin->get_infos($username);
+    my $name = $infos->{name};
+    my $email = $infos->{email};
+ 
     my $current_user = Jifty->app_class('CurrentUser');
-    my $user = $current_user->new( ldap_id => $username );
+    my $user = ($email) 
+        ? $current_user->new( email => $email)    # load by email to mix authentication
+        : $current_user->new( ldap_id => $username );  # else load by ldap_id
+
 
     # Autocreate the user if necessary
     if ( not $user->id ) {
@@ -102,9 +109,6 @@
         $user = $current_user->new( ldap_id => $username );
     }
 
-    my $infos =  $plugin->get_infos($username);
-    my $name = $infos->{name};
-    my $email = $infos->{email};
     my $u = $user->user_object;
 
     # Update, just in case

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm	Thu Dec 13 18:04:01 2007
@@ -48,7 +48,7 @@
 
 column auth_token =>
   render_as 'unrendered',
-  type is 'varchar',
+  type is 'varchar(255)',
   default is '',
   label is _('Authentication token');
     
@@ -57,7 +57,7 @@
 column password =>
   is unreadable,
   label is _('Password'),
-  type is 'varchar',
+  type is 'varchar(255)',
   hints is _('Your password should be at least six characters'),
   render_as 'password',
   filters are 'Jifty::DBI::Filter::SaltHash';

Modified: jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker.pm	Thu Dec 13 18:04:01 2007
@@ -73,6 +73,20 @@
     # XXX: Devel::Size seems to segfault Jifty at END time
     my $size = total_size([ @leaks ]) - $empty_array;
 
+    my $total = '?';
+
+    # report total memory, if able
+    eval {
+        require Proc::ProcessTable;
+        my $proc = Proc::ProcessTable->new;
+        for (@{ $proc->table }) {
+            next unless $_->pid == $$;
+            $total = $_->size;
+            last;
+        }
+    };
+    Jifty->log->warn($@) if $@;
+
     push @requests, {
         id => 1 + @requests,
         url => $cgi->url(-absolute=>1,-path_info=>1),
@@ -80,6 +94,7 @@
         objects => Dumper($leaked),
         time => scalar gmtime,
         leaks => \@leaks,
+        total => $total,
     };
 
     $self->generator(undef);

Modified: jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker/View.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker/View.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/LeakTracker/View.pm	Thu Dec 13 18:04:01 2007
@@ -34,6 +34,7 @@
             th { "ID" }
             th { "Leaks" }
             th { "Bytes leaked" }
+            th { "Total size" }
             th { "Time" }
             th { "URL" }
         };
@@ -49,6 +50,7 @@
 
                 cell { scalar @{$_->{leaks}} }
                 cell { $_->{size} }
+                cell { $_->{total} }
                 cell { $_->{time} }
                 cell { $_->{url} }
             };
@@ -63,8 +65,9 @@
     ul {
         li { "URL: $leak->{url}" }
         li { "Time: $leak->{time}" }
+        li { "Total memory used: $leak->{total} bytes" }
         li { "Objects leaked: " . scalar(@{$leak->{leaks}}) }
-        li { "Total memory leaked: $leak->{size} bytes" }
+        li { "Memory leaked: $leak->{size} bytes" }
     }
     p { a { attr { href => "/__jifty/admin/leaks" } "Table of Contents" } }
     hr {}

Modified: jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm
==============================================================================
--- jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm	(original)
+++ jifty/branches/jquery/lib/Jifty/Plugin/Recorder.pm	Thu Dec 13 18:04:01 2007
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 use base qw/Jifty::Plugin Class::Data::Inheritable/;
-__PACKAGE__->mk_accessors(qw/start path loghandle/);
+__PACKAGE__->mk_accessors(qw/start path loghandle logged_request memory_usage/);
 
 use Time::HiRes 'time';
 use Jifty::Util;
@@ -21,18 +21,29 @@
     my $self = shift;
     my %args = (
         path => 'log/requests',
+        memory_usage => 0,
         @_,
     );
 
     return if $self->_pre_init;
 
     $self->start(time);
+
+    $self->memory_usage($args{memory_usage});
+    if ($args{memory_usage}) {
+        require Proc::ProcessTable;
+    }
+
     $self->path(Jifty::Util->absolute_path( $args{path} ));
     Jifty::Util->make_path($self->path);
 
     Jifty::Handler->add_trigger(
         before_request => sub { $self->before_request(@_) }
     );
+
+    Jifty::Handler->add_trigger(
+        before_cleanup => sub { $self->before_cleanup }
+    );
 }
 
 =head2 before_request
@@ -47,17 +58,59 @@
     my $handler = shift;
     my $cgi     = shift;
 
+    $self->logged_request(0);
+
     eval {
         my $delta = time - $self->start;
         my $request = { cgi => nfreeze($cgi), ENV => \%ENV, time => $delta };
         my $yaml = Jifty::YAML::Dump($request);
 
         print { $self->get_loghandle } $yaml;
+        $self->logged_request(1);
     };
 
     Jifty->log->error("Unable to append to request log: $@") if $@;
 }
 
+=head2 before_cleanup
+
+Append the current user to the request log. This isn't done in one fell swoop
+because if the server explodes during a request, we would lose the request's
+data for logging.
+
+This, strictly speaking, isn't necessary. But we don't always want to lug the
+sessions table around, so this gets us most of the way there.
+
+C<logged_request> is checked to ensure that we don't append the current
+user if the current request couldn't be logged for whatever reason (perhaps
+a serialization error?).
+
+=cut
+
+sub before_cleanup {
+    my $self = shift;
+
+    if ($self->logged_request) {
+        eval {
+            print { $self->get_loghandle } "current_user: " . (Jifty->web->current_user->id || 0) . "\n";
+
+            # get memory usage. yes, we really do need to go through these
+            # motions every request :(
+            if ($self->memory_usage) {
+                my $proc = Proc::ProcessTable->new;
+                for (@{ $proc->table }) {
+                    next unless $_->pid == $$;
+
+                    print { $self->get_loghandle } "memory: " . ($_->size||'?') . "\n";
+                    return;
+                }
+
+                Jifty->log->error("Unable to find myself, pid $$, in Proc::ProcessTable.");
+            }
+        };
+    }
+}
+
 =head2 get_loghandle
 
 Creates the loghandle. The created file is named C<PATH/BOOTTIME-PID.log>.
@@ -79,6 +132,7 @@
             Jifty->log->error("Unable to open $name for writing: $!");
             return;
         };
+        $loghandle->autoflush(1);
 
         Jifty->log->info("Logging all HTTP requests to $name.");
         $self->loghandle($loghandle);
@@ -114,6 +168,11 @@
 The path for creating request logs. Default: log/requests. This directory will
 be created for you, if necessary.
 
+=item memory_usage
+
+Report how much memory (in bytes) Jifty is taking up. This uses
+L<Proc::ProcessTable>. Default is off.
+
 =back
 
 =head1 SEE ALSO

Modified: jifty/branches/jquery/share/web/static/js/jifty.js
==============================================================================
--- jifty/branches/jquery/share/web/static/js/jifty.js	(original)
+++ jifty/branches/jquery/share/web/static/js/jifty.js	Thu Dec 13 18:04:01 2007
@@ -1491,6 +1491,10 @@
      // If the element's text isn't the same as its placeholder text, then the
      // browser screwed up and didn't clear our placeholder. Opera on Mac with
      // VirtueDesktops does this some times, and I lose data.
+     // These are normalized because sometimes one has \r\n and the other has \n
+     elt.value = elt.value.replace(/\r/g, '');
+     elt.placeholderText = elt.placeholderText.replace(/\r/g, '');
+
      if(Jifty.Placeholder.hasPlaceholder(elt) && elt.value == elt.placeholderText) {
        elt.value = '';
        Element.removeClassName(elt, 'placeholder');

Modified: jifty/branches/jquery/share/web/static/js/prototype.js
==============================================================================
--- jifty/branches/jquery/share/web/static/js/prototype.js	(original)
+++ jifty/branches/jquery/share/web/static/js/prototype.js	Thu Dec 13 18:04:01 2007
@@ -3480,9 +3480,16 @@
     if (!element.disabled && element.name) {
       var value = element.getValue();
       if (value != undefined) {
-        var pair = { };
-        pair[element.name] = value;
-        return Object.toQueryString(pair);
+        // XXX: Jifty: this used to be:
+        //     var pair = { };
+        //     pair[element.name] = value
+        //     return Object.toQueryString(pair)
+        // but that included the pair.extend function, which occurred
+        // a lot whenever we validated an action. since we're only encoding
+        // the key (element.name) and value, do what we actually mean
+        return encodeURIComponent(element.name)
+             + '='
+             + encodeURIComponent(value);
       }
     }
     return '';

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/01.behaviour.html
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/01.behaviour.html	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,55 @@
+<html>
+<head>
+<title>behaviour.js test</title>
+<script type="text/javascript" src="/static/js/jsan/JSAN.js" charset="UTF-8"></script>
+
+<script type="text/javascript" src="lib/Test/Builder.js" charset="UTF-8"></script>
+<script type="text/javascript" src="lib/Test/More.js" charset="UTF-8"></script>
+
+<script type="text/javascript" src="/static/js/prototype.js" charset="UTF-8"></script>
+<script type="text/javascript" src="/static/js/cssquery/cssQuery.js" charset="UTF-8"></script>
+<script type="text/javascript" src="/static/js/behaviour.js" charset="UTF-8"></script>
+<script type="text/javascript">
+Behaviour.register({
+    '#foo': function(element) {
+        element.innerHTML = "FOO is HERE";
+    },
+
+    '#bar': function(element) {
+        element.innerHTML = "BAR is HERE";
+    }
+});
+
+var add_bar = function() {
+    document.body.removeChild( document.getElementById("foo") );
+    var d = document.createElement("div");
+    d.setAttribute("id", "bar");
+    document.body.appendChild(d);
+};
+
+</script>
+</head>
+
+<body>
+    <div id="foo"></div>
+    <pre id="test"></pre>
+
+    <script type="text/javascript"><!--
+    JSAN.use('Test.More');
+    plan({ tests: 2 });
+    async_id = beginAsync();
+    
+    setTimeout(function() {
+        is( document.getElementById("foo").innerHTML, "FOO is HERE", "#foo rule is registered and applied");
+        
+        add_bar();
+        
+        Behaviour.apply();
+        
+        is( document.getElementById("bar").innerHTML, "BAR is HERE", "#bar rule is applied manually");
+        endAsync(async_id);
+    }, 1000);
+    //--></script>
+</body>
+</html>
+

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/index.html
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/index.html	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,16 @@
+<html>
+<head>
+<title>Widget.Lightbox - tests index</title>
+<script type="text/javascript" src="lib/JSAN.js" charset="UTF-8"></script>
+<script type="text/javascript" src="lib/Test/Harness.js" charset="UTF-8"></script>
+<script type="text/javascript" src="lib/Test/Harness/Browser.js" charset="UTF-8"></script>
+</head>
+<body>
+<script type="text/javascript"><!--
+    new Test.Harness.Browser().runTests(
+        "01.behaviour.html"
+    );
+// --></script>
+</body>
+</html>
+

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM/Events.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM/Events.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,262 @@
+/**
+
+=head1 NAME
+
+DOM.Events - Event registration abstraction layer
+
+=head1 SYNOPSIS
+
+  JSAN.use("DOM.Events");
+
+  function handleClick(e) {
+      e.currentTarget.style.backgroundColor = "#68b";
+  }
+
+  DOM.Events.addListener(window, "load", function () {
+      alert("The page is loaded.");
+  });
+
+  DOM.Events.addListener(window, "load", function () {
+      // this listener won't interfere with the first one
+      var divs = document.getElementsByTagName("div");
+      for(var i=0; i<divs.length; i++) {
+          DOM.Events.addListener(divs[i], "click", handleClick);
+      }
+  });
+
+=head1 DESCRIPTION
+
+This library lets you use a single interface to listen for and handle all DOM events
+to reduce browser-specific code branching.  It also helps in dealing with Internet
+Explorer's memory leak problem by automatically unsetting all event listeners when
+the page is unloaded (for IE only).
+
+=cut
+
+*/
+
+(function () {
+	if(typeof DOM == "undefined") DOM = {};
+	DOM.Events = {};
+	
+    DOM.Events.VERSION = "0.02";
+	DOM.Events.EXPORT = [];
+	DOM.Events.EXPORT_OK = ["addListener", "removeListener"];
+	DOM.Events.EXPORT_TAGS = {
+		":common": DOM.Events.EXPORT,
+		":all": [].concat(DOM.Events.EXPORT, DOM.Events.EXPORT_OK)
+	};
+	
+	// list of event listeners set by addListener
+	// offset 0 is null to prevent 0 from being used as a listener identifier
+	var listenerList = [null];
+	
+/**
+
+=head2 Functions
+
+All functions are kept inside the namespace C<DOM.Events> and aren't exported
+automatically.
+
+=head3 addListener( S<I<HTMLElement> element,> S<I<string> eventType,>
+S<I<Function> handler> S<[, I<boolean> makeCompatible = true] )>
+
+Registers an event listener/handler on an element.  The C<eventType> string should
+I<not> be prefixed with "on" (e.g. "mouseover" not "onmouseover"). If C<makeCompatible>
+is C<true> (the default), the handler is put inside a wrapper that lets you handle the
+events using parts of the DOM Level 2 Events model, even in Internet Explorer (and
+behave-alikes). Specifically:
+
+=over
+
+=item *
+
+The event object is passed as the first argument to the event handler, so you don't
+have to access it through C<window.event>.
+
+=item *
+
+The event object has the properties C<target>, C<currentTarget>, and C<relatedTarget>
+and the methods C<preventDefault()> and C<stopPropagation()> that behave as described
+in the DOM Level 2 Events specification (for the most part).
+
+=item *
+
+If possible, the event object for mouse events will have the properties C<pageX> and
+C<pageY> that contain the mouse's position relative to the document at the time the
+event occurred.
+
+=item *
+
+If you attempt to set a duplicate event handler on an element, the duplicate will
+still be added (this is different from the DOM2 Events model, where duplicates are
+discarded).
+
+=back
+
+If C<makeCompatible> is C<false>, the arguments are simply passed to the browser's
+native event registering facilities, which means you'll have to deal with event
+incompatibilities yourself. However, if you don't need to access the event information,
+doing it this way can be slightly faster and it gives you the option of unsetting the
+handler with a different syntax (see below).
+
+The return value is a positive integer identifier for the listener that can be used to
+unregister it later on in your script.
+
+=cut
+
+*/
+    
+	DOM.Events.addListener = function(elt, ev, func, makeCompatible) {
+		var usedFunc = func;
+        var id = listenerList.length;
+		if(makeCompatible == true || makeCompatible == undefined) {
+			usedFunc = makeCompatibilityWrapper(elt, ev, func);
+		}
+		if(elt.addEventListener) {
+			elt.addEventListener(ev, usedFunc, false);
+			listenerList[id] = [elt, ev, usedFunc];
+			return id;
+		}
+		else if(elt.attachEvent) {
+			elt.attachEvent("on" + ev, usedFunc);
+			listenerList[id] = [elt, ev, usedFunc];
+			return id;
+		}
+		else return false;
+	};
+	
+/**
+
+=head3 removeListener( S<I<integer> identifier> )
+
+Unregisters the event listener associated with the given identifier so that it will
+no longer be called when the event fires.
+
+  var listener = DOM.Events.addListener(myElement, "mousedown", myHandler);
+  // later on ...
+  DOM.Events.removeListener(listener);
+
+=head3 removeListener( S<I<HTMLElement> element,> S<I<string> eventType,> S<I<Function> handler )>
+
+This alternative syntax can be also be used to unset an event listener, but it can only
+be used if C<makeCompatible> was C<false> when it was set.
+
+=cut
+
+*/
+
+	DOM.Events.removeListener = function() {
+		var elt, ev, func;
+		if(arguments.length == 1 && listenerList[arguments[0]]) {
+			elt  = listenerList[arguments[0]][0];
+			ev   = listenerList[arguments[0]][1];
+			func = listenerList[arguments[0]][2];
+			delete listenerList[arguments[0]];
+		}
+		else if(arguments.length == 3) {
+			elt  = arguments[0];
+			ev   = arguments[1];
+			func = arguments[2];
+		}
+		else return;
+		
+		if(elt.removeEventListener) {
+			elt.removeEventListener(ev, func, false);
+		}
+		else if(elt.detachEvent) {
+			elt.detachEvent("on" + ev, func);
+		}
+	};
+	
+    var rval;
+    
+    function makeCompatibilityWrapper(elt, ev, func) {
+        return function (e) {
+            rval = true;
+            if(e == undefined && window.event != undefined)
+                e = window.event;
+            if(e.target == undefined && e.srcElement != undefined)
+                e.target = e.srcElement;
+            if(e.currentTarget == undefined)
+                e.currentTarget = elt;
+            if(e.relatedTarget == undefined) {
+                if(ev == "mouseover" && e.fromElement != undefined)
+                    e.relatedTarget = e.fromElement;
+                else if(ev == "mouseout" && e.toElement != undefined)
+                    e.relatedTarget = e.toElement;
+            }
+            if(e.pageX == undefined) {
+                if(document.body.scrollTop != undefined) {
+                    e.pageX = e.clientX + document.body.scrollLeft;
+                    e.pageY = e.clientY + document.body.scrollTop;
+                }
+                if(document.documentElement != undefined
+                && document.documentElement.scrollTop != undefined) {
+                    if(document.documentElement.scrollTop > 0
+                    || document.documentElement.scrollLeft > 0) {
+                        e.pageX = e.clientX + document.documentElement.scrollLeft;
+                        e.pageY = e.clientY + document.documentElement.scrollTop;
+                    }
+                }
+            }
+            if(e.stopPropagation == undefined)
+                e.stopPropagation = IEStopPropagation;
+            if(e.preventDefault == undefined)
+                e.preventDefault = IEPreventDefault;
+            if(e.cancelable == undefined) e.cancelable = true;
+            func(e);
+            return rval;
+        };
+    }
+    
+    function IEStopPropagation() {
+        if(window.event) window.event.cancelBubble = true;
+    }
+    
+    function IEPreventDefault() {
+        rval = false;
+    }
+
+	function cleanUpIE () {
+		for(var i=0; i<listenerList.length; i++) {
+			var listener = listenerList[i];
+			if(listener) {
+				var elt = listener[0];
+                var ev = listener[1];
+                var func = listener[2];
+				elt.detachEvent("on" + ev, func);
+			}
+		}
+        listenerList = null;
+	}
+
+	if(!window.addEventListener && window.attachEvent) {
+		window.attachEvent("onunload", cleanUpIE);
+	}
+
+})();
+
+/**
+
+=head1 SEE ALSO
+
+DOM Level 2 Events Specification,
+L<http://www.w3.org/TR/DOM-Level-2-Events/>
+
+Understanding and Solving Internet Explorer Leak Patterns,
+L<http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp>
+
+=head1 AUTHOR
+
+Justin Constantino, <F<goflyapig at gmail.com>>.
+
+=head1 COPYRIGHT
+
+  Copyright (c) 2005 Justin Constantino.  All rights reserved.
+  This module is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public Licence.
+
+=cut
+
+*/
\ No newline at end of file

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM/Ready.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/DOM/Ready.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,169 @@
+if ( typeof DOM == "undefined" ) DOM = {};
+
+DOM.Ready = {};
+
+DOM.Ready.VERSION = '0.14';
+
+DOM.Ready.finalTimeout = 15;
+DOM.Ready.timerInterval = 50;
+
+/* This works for Mozilla */
+if ( document.addEventListener ) {
+    document.addEventListener
+        ( "DOMContentLoaded", function () { DOM.Ready._isDone = 1; }, false );
+}
+
+DOM.Ready._checkDOMReady = function () {
+    if ( DOM.Ready._isReady ) return DOM.Ready._isReady;
+
+    if (    typeof document.getElementsByTagName != 'undefined'
+         && typeof document.getElementById != 'undefined' 
+         && ( document.getElementsByTagName('body')[0] != null
+              || document.body != null ) ) {
+
+        DOM.Ready._isReady = 1;
+    }
+
+    return DOM.Ready._isReady;
+
+};
+
+DOM.Ready._checkDOMDone = function () {
+    /* IE (and Opera?) only */
+    if ( document.readyState
+         && ( document.readyState == "interactive"
+              || document.readyState == "complete" ) ) {
+        return 1;
+    }
+
+    return DOM.Ready._isDone;
+};
+
+DOM.Ready.onDOMReady = function (callback) {
+    if ( DOM.Ready._checkDOMReady() ) {
+        callback();
+    }
+    else {
+        DOM.Ready._onDOMReadyCallbacks.push(callback);
+    }
+}
+
+DOM.Ready.onDOMDone = function (callback) {
+    if ( DOM.Ready._checkDOMDone() ) {
+        callback();
+    }
+    else {
+        DOM.Ready._onDOMDoneCallbacks.push(callback);
+    }
+}
+
+DOM.Ready.onIdReady = function ( id, callback ) {
+    if ( DOM.Ready._checkDOMReady() ) {
+        var elt = document.getElementById(id);
+        if (elt) {
+            callback(elt);
+            return;
+        }
+    }
+
+    var callback_array = DOM.Ready._onIdReadyCallbacks[id];
+    if ( ! callback_array ) {
+        callback_array = [];
+    }
+    callback_array.push(callback);
+
+    DOM.Ready._onIdReadyCallbacks[id] = callback_array;
+}
+
+DOM.Ready._runDOMReadyCallbacks = function () {
+    for ( var i = 0; i < DOM.Ready._onDOMReadyCallbacks.length; i++ ) {
+        DOM.Ready._onDOMReadyCallbacks[i]();
+    }
+
+    DOM.Ready._onDOMReadyCallbacks = [];
+}
+
+DOM.Ready._runDOMDoneCallbacks = function () {
+    for ( var i = 0; i < DOM.Ready._onDOMDoneCallbacks.length; i++ ) {
+        DOM.Ready._onDOMDoneCallbacks[i]();
+    }
+
+    DOM.Ready._onDOMDoneCallbacks = [];
+}
+
+DOM.Ready._runIdCallbacks = function () {
+    for ( var id in DOM.Ready._onIdReadyCallbacks ) {
+        // protect against changes to Object (ala prototype's extend)
+        if ( ! DOM.Ready._onIdReadyCallbacks.hasOwnProperty(id) ) {
+            continue;
+        }
+
+        var elt = document.getElementById(id);
+
+        if (elt) {
+            for ( var i = 0; i < DOM.Ready._onIdReadyCallbacks[id].length; i++) {
+                DOM.Ready._onIdReadyCallbacks[id][i](elt);
+            }
+
+            delete DOM.Ready._onIdReadyCallbacks[id];
+        }
+    }
+}
+
+DOM.Ready._runReadyCallbacks = function () {
+    if ( DOM.Ready._inRunReadyCallbacks ) return;
+
+    DOM.Ready._inRunReadyCallbacks = 1;
+
+    if ( DOM.Ready._checkDOMReady() ) {
+        DOM.Ready._runDOMReadyCallbacks();
+
+        DOM.Ready._runIdCallbacks();
+    }
+
+    if ( DOM.Ready._checkDOMDone() ) {
+        DOM.Ready._runDOMDoneCallbacks();
+    }
+
+    DOM.Ready._timePassed += DOM.Ready._lastTimerInterval;
+
+    if ( ( DOM.Ready._timePassed / 1000 ) >= DOM.Ready.finalTimeout ) {
+        DOM.Ready._stopTimer();
+    }
+
+    DOM.Ready._inRunReadyCallbacks = 0;
+}
+
+DOM.Ready._startTimer = function () {
+    DOM.Ready._lastTimerInterval = DOM.Ready.timerInterval;
+    DOM.Ready._intervalId = setInterval( DOM.Ready._runReadyCallbacks, DOM.Ready.timerInterval );
+};
+
+DOM.Ready._stopTimer = function () {
+    clearInterval( DOM.Ready._intervalId );
+    DOM.Ready._intervalId = null;
+}
+
+DOM.Ready._resetClass = function () {
+    DOM.Ready._stopTimer();
+
+    DOM.Ready._timePassed = 0;
+
+    DOM.Ready._isReady = 0;
+    DOM.Ready._isDone = 0;
+
+    DOM.Ready._onDOMReadyCallbacks = [];
+    DOM.Ready._onDOMDoneCallbacks = [];
+    DOM.Ready._onIdReadyCallbacks = {};
+
+    DOM.Ready._startTimer();
+}
+
+DOM.Ready._resetClass();
+
+DOM.Ready.runCallbacks = function () { DOM.Ready._runReadyCallbacks() };
+
+
+/*
+
+*/

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/JSAN.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/JSAN.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,290 @@
+/*
+
+*/
+
+var JSAN = function () { JSAN.addRepository(arguments) }
+
+JSAN.VERSION = 0.10;
+
+/*
+
+*/
+
+JSAN.globalScope   = self;
+JSAN.includePath   = ['.', 'lib'];
+JSAN.errorLevel    = "none";
+JSAN.errorMessage  = "";
+JSAN.loaded        = {};
+
+/*
+
+*/
+
+JSAN.use = function () {
+    var classdef = JSAN.require(arguments[0]);
+    if (!classdef) return null;
+
+    var importList = JSAN._parseUseArgs.apply(JSAN, arguments).importList;
+    JSAN.exporter(classdef, importList);
+
+    return classdef;
+}
+
+/*
+
+*/
+
+JSAN.require = function (pkg) {
+    var path = JSAN._convertPackageToPath(pkg);
+    if (JSAN.loaded[path]) {
+        return JSAN.loaded[path];
+    }
+
+    try {
+        var classdef = eval(pkg);
+        if (typeof classdef != 'undefined') return classdef;
+    } catch (e) { /* nice try, eh? */ }
+
+
+    for (var i = 0; i < JSAN.includePath.length; i++) {
+        var js;
+        try{
+            var url = JSAN._convertPathToUrl(path, JSAN.includePath[i]);
+                js  = JSAN._loadJSFromUrl(url);
+        } catch (e) {
+            if (i == JSAN.includePath.length - 1) throw e;
+        }
+        if (js != null) {
+            var classdef = JSAN._createScript(js, pkg);
+            JSAN.loaded[path] = classdef;
+            return classdef;
+        }
+    }
+    return false;
+
+}
+
+/*
+
+*/
+
+JSAN.exporter = function () {
+    JSAN._exportItems.apply(JSAN, arguments);
+}
+
+/*
+
+*/
+
+JSAN.addRepository = function () {
+    var temp = JSAN._flatten( arguments );
+    // Need to go in reverse to do something as simple as unshift( @foo, @_ );
+    for ( var i = temp.length - 1; i >= 0; i-- )
+        JSAN.includePath.unshift(temp[i]);
+    return JSAN;
+}
+
+JSAN._flatten = function( list1 ) {
+    var list2 = new Array();
+    for ( var i = 0; i < list1.length; i++ ) {
+        if ( typeof list1[i] == 'object' ) {
+            list2 = JSAN._flatten( list1[i], list2 );
+        }
+        else {
+            list2.push( list1[i] );
+        }
+    }
+    return list2;
+};
+
+JSAN._findMyPath = function () {
+    if (document) {
+        var scripts = document.getElementsByTagName('script');
+        for ( var i = 0; i < scripts.length; i++ ) {
+            var src = scripts[i].getAttribute('src');
+            if (src) {
+                var inc = src.match(/^(.*?)\/?JSAN.js/);
+                if (inc && inc[1]) {
+                    var repo = inc[1];
+                    for (var j = 0; j < JSAN.includePath.length; j++) {
+                        if (JSAN.includePath[j] == repo) {
+                            return;
+                        }
+                    }
+                    JSAN.addRepository(repo);
+                }
+            }
+        }
+    }
+}
+JSAN._findMyPath();
+
+JSAN._convertPathToUrl = function (path, repository) {
+    return repository.concat('/' + path);
+};
+    
+
+JSAN._convertPackageToPath = function (pkg) {
+    var path = pkg.replace(/\./g, '/');
+        path = path.concat('.js');
+    return path;
+}
+
+JSAN._parseUseArgs = function () {
+    var pkg        = arguments[0];
+    var importList = [];
+
+    for (var i = 1; i < arguments.length; i++)
+        importList.push(arguments[i]);
+
+    return {
+        pkg:        pkg,
+        importList: importList
+    }
+}
+
+JSAN._loadJSFromUrl = function (url) {
+    return new JSAN.Request().getText(url);
+}
+
+JSAN._findExportInList = function (list, request) {
+    if (list == null) return false;
+    for (var i = 0; i < list.length; i++)
+        if (list[i] == request)
+            return true;
+    return false;
+}
+
+JSAN._findExportInTag = function (tags, request) {
+    if (tags == null) return [];
+    for (var i in tags)
+        if (i == request)
+            return tags[i];
+    return [];
+}
+
+JSAN._exportItems = function (classdef, importList) {
+    var exportList  = new Array();
+    var EXPORT      = classdef.EXPORT;
+    var EXPORT_OK   = classdef.EXPORT_OK;
+    var EXPORT_TAGS = classdef.EXPORT_TAGS;
+    
+    if (importList.length > 0) {
+       importList = JSAN._flatten( importList );
+
+       for (var i = 0; i < importList.length; i++) {
+            var request = importList[i];
+            if (   JSAN._findExportInList(EXPORT,    request)
+                || JSAN._findExportInList(EXPORT_OK, request)) {
+                exportList.push(request);
+                continue;
+            }
+            var list = JSAN._findExportInTag(EXPORT_TAGS, request);
+            for (var i = 0; i < list.length; i++) {
+                exportList.push(list[i]);
+            }
+        }
+    } else {
+        exportList = EXPORT;
+    }
+    JSAN._exportList(classdef, exportList);
+}
+
+JSAN._exportList = function (classdef, exportList) {
+    if (typeof(exportList) != 'object') return null;
+    for (var i = 0; i < exportList.length; i++) {
+        var name = exportList[i];
+
+        if (JSAN.globalScope[name] == null)
+            JSAN.globalScope[name] = classdef[name];
+    }
+}
+
+JSAN._makeNamespace = function(js, pkg) {
+    var spaces = pkg.split('.');
+    var parent = JSAN.globalScope;
+    eval(js);
+    var classdef = eval(pkg);
+    for (var i = 0; i < spaces.length; i++) {
+        var name = spaces[i];
+        if (i == spaces.length - 1) {
+            if (typeof parent[name] == 'undefined') {
+                parent[name] = classdef;
+                if ( typeof classdef['prototype'] != 'undefined' ) {
+                    parent[name].prototype = classdef.prototype;
+                }
+            }
+        } else {
+            if (parent[name] == undefined) {
+                parent[name] = {};
+            }
+        }
+
+        parent = parent[name];
+    }
+    return classdef;
+}
+
+JSAN._handleError = function (msg, level) {
+    if (!level) level = JSAN.errorLevel;
+    JSAN.errorMessage = msg;
+
+    switch (level) {
+        case "none":
+            break;
+        case "warn":
+            alert(msg);
+            break;
+        case "die":
+        default:
+            throw new Error(msg);
+            break;
+    }
+}
+
+JSAN._createScript = function (js, pkg) {
+    try {
+        return JSAN._makeNamespace(js, pkg);
+    } catch (e) {
+        JSAN._handleError("Could not create namespace[" + pkg + "]: " + e);
+    }
+    return null;
+}
+
+
+JSAN.prototype = {
+    use: function () { JSAN.use.apply(JSAN, arguments) }
+};
+
+
+// Low-Level HTTP Request
+JSAN.Request = function (jsan) {
+    if (JSAN.globalScope.XMLHttpRequest) {
+        this._req = new XMLHttpRequest();
+    } else {
+        this._req = new ActiveXObject("Microsoft.XMLHTTP");
+    }
+}
+
+JSAN.Request.prototype = {
+    _req:  null,
+    
+    getText: function (url) {
+        this._req.open("GET", url, false);
+        try {
+            this._req.send(null);
+            if (this._req.status == 200 || this._req.status == 0)
+                return this._req.responseText;
+        } catch (e) {
+            JSAN._handleError("File not found: " + url);
+            return null;
+        };
+
+        JSAN._handleError("File not found: " + url);
+        return null;
+    }
+};
+
+/*
+
+*/

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Builder.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Builder.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,835 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+// Set up namespace.
+if (typeof self != 'undefined') {
+    // Browser
+    if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
+    else Test.PLATFORM = 'browser';
+} else if (typeof _global != 'undefined') {
+    //Director
+    if (typeof _global.Test == "undefined") _global.Test = {PLATFORM: 'director'};
+    else _global.Test.PLATFORM = 'director';
+} else {
+    throw new Error("Test.More does not support your platform");
+}
+
+// Constructor.
+Test.Builder = function () {
+    Test.Builder.Instances.push(this.reset());
+    if (!Test.Builder.Test) Test.Builder.Test = this;
+};
+
+// Static variables.
+Test.Builder.globalScope = typeof JSAN != 'undefined'
+  ? JSAN.globalScope
+  :  typeof window != 'undefined'
+    ? window
+    : typeof _global != 'undefined'
+      ? _global
+      : null;
+
+Test.Builder.VERSION = '0.21';
+Test.Builder.Instances = [];
+Test.Builder.lineEndingRx = /\r?\n|\r/g;
+Test.Builder.StringOps = {
+    eq: '==',
+    ne: '!=',
+    lt: '<',
+    gt: '>',
+    ge: '>=',
+    le: '<='
+};
+
+// Stoopid IE.
+Test.Builder.LF = typeof navigator != "undefined"
+    && navigator.userAgent.toLowerCase().indexOf('msie') + 1
+    && Test.Builder.globalScope.opera == undefined
+  ? "\r"
+  : "\n";
+
+// Static methods.
+Test.Builder.die = function (msg) {
+    throw new Error(msg);
+};
+
+Test.Builder._whoa = function (check, desc) {
+    if (!check) return;
+    Test.Builder.die("WHOA! " + desc + Test.Builder.LF +
+                     + "This should never happen! Please contact the author "
+                     + "immediately!");
+};
+
+Test.Builder.typeOf = function (object) {
+    var c = Object.prototype.toString.apply(object);
+    var name = c.substring(8, c.length - 1);
+    if (name != 'Object') return name;
+    // It may be a non-core class. Try to extract the class name from
+    // the constructor function. This may not work in all implementations.
+    if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
+        return RegExp.$1;
+    }
+    // No idea. :-(
+    return name;
+};
+
+Test.Builder.instance = function () {
+    if (!Test.Builder.Test) return new Test.Builder();
+    return Test.Builder.Test;
+};
+
+Test.Builder.create = function () {
+    var ret = new Test.Builder();
+    ret.diag(
+        "Test.Builder.create() has been deprecated. "
+        + "Use new Test.Builder() instead"
+    );
+    return ret;
+};
+
+// Instance methods.
+
+Test.Builder.prototype.reset = function () {
+    this.TestDied      = false;
+    this.HavePlan      = false;
+    this.NoPlan        = false;
+    this.CurrTest      = 0;
+    this.ExpectedTests = 0;
+    this.UseNums       = true;
+    this.NoHeader      = false;
+    this.NoEnding      = false;
+    this.TestResults   = [];
+    this.ToDo          = [];
+    this.Buffer        = [];
+    this.asyncs        = [0];
+    this.asyncID       = 0;
+    return this._setupOutput();
+};
+
+Test.Builder.prototype._print = function (msg) {
+    this.output().call(this, msg);
+};
+
+Test.Builder.prototype.warn = function (msg) {
+    this.warnOutput().apply(this, arguments);
+};
+
+Test.Builder.prototype.plan = function (arg) {
+    if (!arg) return;
+    if (this.HavePlan) Test.Builder.die("You tried to plan twice!");
+
+    if (!(arg instanceof Object))
+        Test.Builder.die("plan() doesn't understand " + arg);
+    for (var cmd in arg) {
+        if (cmd == 'tests') {
+            if (arg[cmd] == null) {
+                TestBulder.die(
+                    "Got an undefined number of tests. Looks like you tried to "
+                    + "say how many tests you plan to run but made a mistake."
+                    + Test.Builder.LF
+                );
+            } else if (!arg[cmd]) {
+                Test.Builder.die(
+                    "You said to run 0 tests! You've got to run something."
+                    + Test.Builder.LF
+                );
+            } else {
+                this.expectedTests(arg[cmd]);
+            }
+        } else if (cmd == 'skipAll') {
+            this.skipAll(arg[cmd]);
+        } else if (cmd == 'noPlan' && arg[cmd]) {
+            this.noPlan();
+        } else {
+            // Do nothing, since Object.prototype might have been changed.
+            // Too bad JS doesn't have real hashes!
+            // Test.Builder.die("plan() doesn't understand "
+            // + cmd + (arg[cmd] ? (" " + arg[cmd]) : ''));
+        }
+    }
+};
+
+Test.Builder.prototype.expectedTests = function (max) {
+    if (max) {
+        if (isNaN(max)) {
+            Test.Builder.die(
+                "Number of tests must be a postive integer. You gave it '"
+                + max + "'." + Test.Builder.LF
+            );
+        }
+
+        this.ExpectedTests = max.valueOf();
+        this.HavePlan       = true;
+        if (!this.noHeader()) this._print("1.." + max + Test.Builder.LF);
+    }
+    return this.ExpectedTests;
+};
+
+Test.Builder.prototype.noPlan = function () {
+    this.NoPlan   = 1;
+    this.HavePlan = 1;
+};
+
+Test.Builder.prototype.hasPlan = function () {
+    if (this.ExpectedTests) return this.ExpectedTests;
+    if (this.NoPlan) return 'noPlan';
+};
+
+Test.Builder.prototype.skipAll = function (reason) {
+    var out = "1..0";
+    if (reason) out += " # Skip " + reason;
+    out += Test.Builder.LF;
+    this.SkipAll = 1;
+    if (!this.noHeader()) this.output()(out);
+    // Just throw and catch an exception.
+    Test.Builder.globalScope.onerror = function () { return true; }
+    throw new Error("__SKIP_ALL__");
+};
+
+Test.Builder.prototype.ok = function (test, desc) {
+    // test might contain an object that we don't want to accidentally
+    // store, so we turn it into a boolean.
+    test = !!test;
+
+    if (!this.HavePlan) {
+        Test.Builder.die(
+            "You tried to run a test without a plan! Gotta have a plan."
+        );
+    }
+
+    // Append any output to the previous test's results.
+    if (this.Buffer.length && this.TestResults.length) {
+        this.TestResults[this.TestResults.length - 1].output +=
+            this.Buffer.splice(0, this.Buffer.length).join('');
+    
+    }
+
+    // I don't think we need to worry about threading in JavaScript.
+    this.CurrTest++;
+
+    // In case desc is a string overloaded object, force it to stringify.
+    if (desc) desc = desc.toString();
+
+    var startsNumber
+    if (desc != null && /^[\d\s]+$/.test(desc)) {
+        this.diag( "Your test description is '" + desc + "'. You shouldn't use",
+                   Test.Builder.LF,
+                   "numbers for your test names. Very confusing.");
+    }
+
+    var todo = this._todo();
+    // I don't think we need to worry about result beeing shared between
+    // threads.
+    var out = '';
+    var result = {};
+
+    if (test) {
+        result.ok        = true;
+        result.actual_ok = test;
+    } else {
+        out += 'not ';
+        result.ok        = todo ? true : false;
+        result.actual_ok = false;
+    }
+
+    out += 'ok';
+    if (this.useNumbers) out += ' ' + this.CurrTest;
+
+    if (desc == null) {
+        result.desc = '';
+    } else {
+        desc = desc.replace(Test.Builder.lineEndingRx, Test.Builder.LF + "# ");
+        // XXX Does this matter since we don't have a TestHarness?
+        desc.split('#').join('\\#'); // # # in a desc can confuse TestHarness.
+        out += ' - ' + desc;
+        result.desc = desc;
+    }
+
+    if (todo) {
+        todo = todo.replace(Test.Builder.lineEndingRx, Test.Builder.LF + "# ");
+        out += " # TODO " + todo;
+        result.reason = todo;
+        result.type   = 'todo';
+    } else {
+        result.reason = '';
+        result.type   = '';
+    }
+
+    this.TestResults[this.CurrTest - 1] = result;
+
+    out += Test.Builder.LF;
+    this._print(out);
+
+    if (!test) {
+        var msg = todo ? "Failed (TODO)" : "Failed";
+        this.diag("    " + msg + " test");
+    }
+    result.output = this.Buffer.splice(0, this.Buffer.length).join('');
+    return test;
+};
+
+Test.Builder.prototype.isEq = function (got, expect, desc) {
+    if (got == null || expect == null) {
+        // undefined only matches undefined and nothing else
+        return this.isUndef(got, '==', expect, desc);
+    }
+    return this.cmpOK(got, '==', expect, desc);
+};
+
+Test.Builder.prototype.isNum = function (got, expect, desc) {
+    if (got == null || expect == null) {
+        // undefined only matches undefined and nothing else
+        return this.isUndef(got, '==', expect, desc);
+    }
+    return this.cmpOK(Number(got), '==', Number(expect), desc);
+};
+
+Test.Builder.prototype.isntEq = function (got, dontExpect, desc) {
+    if (got == null || dontExpect == null) {
+        // undefined only matches undefined and nothing else
+        return this.isUndef(got, '!=', dontExpect, desc);
+    }
+    return this.cmpOK(got, '!=', dontExpect, desc);
+};
+
+Test.Builder.prototype.isntNum = function (got, dontExpect, desc) {
+    if (got == null || dontExpect == null) {
+        // undefined only matches undefined and nothing else
+        return this.isUndef(got, '!=', dontExpect, desc);
+    }
+    return this.cmpOK(Number(got), '!=', Number(dontExpect), desc);
+};
+
+Test.Builder.prototype.like = function (val, regex, desc) {
+    return this._regexOK(val, regex, '=~', desc);
+};
+
+Test.Builder.prototype.unlike = function (val, regex, desc) {
+    return this._regexOK(val, regex, '!~', desc);
+};
+
+Test.Builder.prototype._regexOK = function (val, regex, cmp, desc) {
+    // Create a regex object.
+    var type = Test.Builder.typeOf(regex);
+    var ok;
+    if (type.toLowerCase() == 'string') {
+        // Create a regex object.
+        regex = new RegExp(regex);
+    } else {
+        if (type != 'RegExp') {
+            ok = this.ok(false, desc);
+            this.diag("'" + regex + "' doesn't look much like a regex to me.");
+            return ok;
+        }
+    }
+
+    if (val == null || typeof val != 'string') {
+        if (cmp == '=~') {
+            // The test fails.
+            ok = this.ok(false, desc);
+            this._diagLike(val, regex, cmp);
+        } else {
+            // undefined matches nothing (unlike in Perl, where undef =~ //).
+            ok = this.ok(true, desc);
+        }
+        return ok;
+    }
+
+    // Use val.match() instead of regex.test() in case they've set g.
+    var test = val.match(regex);
+    if (cmp == '!~') test = !test;
+    ok = this.ok(test, desc);
+    if (!ok) this._diagLike(val, regex, cmp);
+    return ok;
+};
+
+Test.Builder.prototype._diagLike = function (val, regex, cmp) {
+    var match = cmp == '=~' ? "doesn't match" : "      matches";
+    return this.diag(
+        "                  '" + val + "" + Test.Builder.LF +
+        "    " + match + " /" + regex.source + "/"
+    );
+};
+
+Test.Builder.prototype.cmpOK = function (got, op, expect, desc) {
+
+    var test;
+    if (Test.Builder.StringOps[op]) {
+        // Force string context.
+        test = eval("got.toString() " + Test.Builder.StringOps[op] + " expect.toString()");
+    } else {
+        test = eval("got " + op + " expect");
+    }
+
+    var ok = this.ok(test, desc);
+    if (!ok) {
+        if (/^(eq|==)$/.test(op)) {
+            this._isDiag(got, op, expect);
+        } else {
+            this._cmpDiag(got, op, expect);
+        }
+    }
+    return ok;
+};
+
+Test.Builder.prototype._cmpDiag = function (got, op, expect) {
+    if (got != null) got = "'" + got.toString() + "'";
+    if (expect != null) expect = "'" + expect.toString() + "'";
+    return this.diag("    " + got + Test.Builder.LF + "        " + op
+                     + Test.Builder.LF + "    " + expect);
+};
+
+Test.Builder.prototype._isDiag = function (got, op, expect) {
+    var args = [got, expect];
+    for (var i = 0; i < args.length; i++) {
+        if (args[i] != null) {
+            args[i] = op == 'eq' ? "'" + args[i].toString() + "'" : args[i].valueOf();
+        }
+    }
+
+    return this.diag(
+        "         got: " + args[0] + Test.Builder.LF +
+        "    expected: " + args[1] + Test.Builder.LF
+    );
+};
+
+Test.Builder.prototype.BAILOUT = function (reason) {
+    this._print("Bail out! " + reason);
+    // Just throw and catch an exception.
+    Test.Builder.globalScope.onerror = function () {
+        // XXX Do something to tell TestHarness it was a bailout?
+        return true;
+    }
+    throw new Error("__BAILOUT__");
+};
+
+Test.Builder.prototype.skip = function (why) {
+    if (!this.HavePlan)
+        Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
+
+    // In case desc is a string overloaded object, force it to stringify.
+    if (why) why = why.toString().replace(Test.Builder.lineEndingRx,
+                                          Test.Builder.LF+ "# ");
+
+    this.CurrTest++;
+    this.TestResults[this.CurrTest - 1] = {
+        ok:        true,
+        actual_ok: true,
+        desc:      '',
+        type:      'skip',
+        reason:    why
+    };
+
+    var out = "ok";
+    if (this.useNumbers) out += ' ' + this.CurrTest;
+    out    += " # skip " + why + Test.Builder.LF;
+    this._print(out);
+    this.TestResults[this.CurrTest - 1].output =
+    this.Buffer.splice(0, this.Buffer.length).join('');
+    return true;
+};
+
+Test.Builder.prototype.todoSkip = function (why) {
+    if (!this.HavePlan)
+        Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
+
+    // In case desc is a string overloaded object, force it to stringify.
+    if (why) why = why.toString().replace(Test.Builder.lineEndingRx,
+                                          Test.Builder.LF + "# ");
+    
+
+    this.CurrTest++;
+    this.TestResults[this.CurrTest - 1] = {
+        ok:        true,
+        actual_ok: false,
+        desc:      '',
+        type:      'todo_skip',
+        reason:    why
+    };
+
+    var out = "not ok";
+    if (this.useNumbers) out += ' ' + this.CurrTest;
+    out    += " # TODO & SKIP " + why + Test.Builder.LF;
+    this._print(out);
+    this.TestResults[this.CurrTest - 1].output =
+    this.Buffer.splice(0, this.Buffer.length).join('');
+    return true;
+};
+
+Test.Builder.prototype.skipRest = function (reason) {
+    var out = "# Skip";
+    if (reason) out += " " + reason;
+    out += Test.Builder.LF;
+    if (this.NoPlan) this.skip(reason);
+    else {
+        for (var i = this.CurrTest; i < this.ExpectedTests; i++) {
+            this.skip(reason);
+        }
+    }
+    // Just throw and catch an exception.
+    Test.Builder.globalScope.onerror = function () { return true; }
+    throw new Error("__SKIP_REST__");
+};
+
+Test.Builder.prototype.useNumbers = function (useNums) {
+    if (useNums != null) this.UseNums = useNums;
+    return this.UseNums;
+};
+
+Test.Builder.prototype.noHeader = function (noHeader) {
+    if (noHeader != null) this.NoHeader = !!noHeader;
+    return this.NoHeader;
+};
+
+Test.Builder.prototype.noEnding = function (noEnding) {
+    if (noEnding != null) this.NoEnding = !!noEnding;
+    return this.NoEnding;
+};
+
+Test.Builder.prototype.diag = function () {
+    if (!arguments.length) return;
+
+    var msg = '# ';
+    // Join each agument and escape each line with a #.
+    for (var i = 0; i < arguments.length; i++) {
+        // Replace any newlines.
+        msg += arguments[i].toString().replace(Test.Builder.lineEndingRx,
+                                               Test.Builder.LF + "# ");
+    }
+
+    // Append a new line to the end of the message if there isn't one.
+    if (!(new RegExp(Test.Builder.LF + '$').test(msg)))
+        msg += Test.Builder.LF;
+    // Append the diag message to the most recent result.
+    return this._printDiag(msg);
+};
+
+Test.Builder.prototype._printDiag = function () {
+    var fn = this.todo() ? this.todoOutput() : this.failureOutput();
+    fn.apply(this, arguments);
+    return false;
+};
+
+Test.Builder.prototype.output = function (fn) {
+    if (fn != null) {
+        var buffer = this.Buffer;
+        this.Output = function (msg) { buffer.push(msg); fn(msg) };
+    }
+    return this.Output;
+};
+
+Test.Builder.prototype.failureOutput = function (fn) {
+    if (fn != null) {
+        var buffer = this.Buffer;
+        this.FailureOutput = function (msg) { buffer.push(msg); fn(msg) };
+    }
+    return this.FailureOutput;
+};
+
+Test.Builder.prototype.todoOutput = function (fn) {
+    if (fn != null) {
+        var buffer = this.Buffer;
+        this.TodoOutput = function (msg) { buffer.push(msg); fn(msg) };
+    }
+    return this.TodoOutput;
+};
+
+Test.Builder.prototype.endOutput = function (fn) {
+    if (fn != null) {
+        var buffer = this.Buffer;
+        this.EndOutput = function (msg) { buffer.push(msg); fn(msg) };
+    }
+    return this.EndOutput;
+};
+
+Test.Builder.prototype.warnOutput = function (fn) {
+    if (fn != null) {
+        var buffer = this.Buffer;
+        this.WarnOutput = function (msg) { buffer.push(msg); fn(msg) };
+    }
+    return this.WarnOutput;
+};
+
+Test.Builder.prototype._setupOutput = function () {
+    if (Test.PLATFORM == 'browser') {
+        var top = Test.Builder.globalScope;
+        var doc = top.document;
+        var writer = function (msg) {
+            // I'm sure that there must be a more efficient way to do this,
+            // but if I store the node in a variable outside of this function
+            // and refer to it via the closure, then things don't work right
+            // --the order of output can become all screwed up (see
+            // buffer.html).  I have no idea why this is.
+            var node = doc.getElementById("test");
+            var body = doc.body || doc.getElementsByTagName("body")[0];
+            if (node) {
+                // This approach is neater, but causes buffering problems when
+                // mixed with document.write. See tests/buffer.html.
+                //node.appendChild(document.createTextNode(msg));
+                //return;
+                for (var i = 0; i < node.childNodes.length; i++) {
+                    if (node.childNodes[i].nodeType == 3 /* Text Node */) {
+                        // Append to the node and scroll down.
+                        node.childNodes[i].appendData(msg);
+                        top.scrollTo(
+                            0, body.offsetHeight || body.scrollHeight
+                        );
+                        return;
+                    }
+                }
+
+                // If there was no text node, add one.
+                node.appendChild(doc.createTextNode(msg));
+                top.scrollTo(0, body.offsetHeight || body.scrollHeight);
+                return;
+            }
+
+            // Default to the normal write and scroll down...
+            doc.write(msg);
+            // IE 6 Service Pack 2 requires that we re-cache the object. Bill
+            // Gates only knows why!
+            body = doc.body || doc.getElementsByTagName("body")[0];
+            if (body) top.scrollTo(0, body.offsetHeight || body.scrollHeight);
+        };
+
+        this.output(writer);
+        this.failureOutput(function (msg) {
+            writer('<span style="color: red; font-weight: boldvv">'
+                   + msg + '</span>')
+        });
+        this.todoOutput(writer);
+        this.endOutput(writer);
+
+        if (top.alert.apply) {
+            this.warnOutput(top.alert, top);
+        } else {
+            this.warnOutput(function (msg) { top.alert(msg); });
+        }
+
+    } else if (Test.PLATFORM == 'director') {
+        // Macromedia-Adobe:Director MX 2004 Support
+        // XXX Is _player a definitive enough object?
+        // There may be an even more explicitly Director object.
+        this.output(trace);       
+        this.failureOutput(trace);
+        this.todoOutput(trace);
+        this.warnOutput(trace);
+    }
+
+    return this;
+};
+
+Test.Builder.prototype.currentTest = function (num) {
+    if (num == null) return this.CurrTest;
+
+    if (!this.HavePlan)
+        Test.Builder.die("Can't change the current test number without a plan!");
+    this.CurrTest = num;
+    if (num > this.TestResults.length ) {
+        var reason = 'incrementing test number';
+        for (var i = this.TestResults.length; i < num; i++) {
+            this.TestResults[i] = {
+                ok:        true, 
+                actual_ok: null,
+                reason:    reason,
+                type:      'unknown', 
+                name:      null,
+                output:    'ok - ' + reason + Test.Builder.LF
+            };
+        }
+    } else if (num < this.TestResults.length) {
+        // IE requires the second argument to truncate the array.
+        this.TestResults.splice(num, this.TestResults.length);
+    }
+    return this.CurrTest;
+};
+
+Test.Builder.prototype.summary = function () {
+    var results = new Array(this.TestResults.length);
+    for (var i = 0; i < this.TestResults.length; i++) {
+        results[i] = this.TestResults[i]['ok'];
+    }
+    return results
+};
+
+Test.Builder.prototype.details = function () {
+    return this.TestResults;
+};
+
+Test.Builder.prototype.todo = function (why, howMany) {
+    if (howMany) this.ToDo = [why, howMany];
+    return this.ToDo[1];
+};
+
+Test.Builder.prototype._todo = function () {
+    if (this.ToDo[1]) {
+        if (this.ToDo[1]--) return this.ToDo[0];
+        this.ToDo = [];
+    }
+    return false;
+};
+
+Test.Builder.prototype._sanity_check = function () {
+    Test.Builder._whoa(
+        this.CurrTest < 0,
+        'Says here you ran a negative number of tests!'
+    );
+
+    Test.Builder._whoa(
+        !this.HavePlan && this.CurrTest, 
+        'Somehow your tests ran without a plan!'
+    );
+
+    Test.Builder._whoa(
+        this.CurrTest != this.TestResults.length,
+        'Somehow you got a different number of results than tests ran!'
+    );
+};
+
+Test.Builder.prototype._notifyHarness = function () {
+    var top = Test.Builder.globalScope;
+    // Special treatment for the browser harness.
+    if (top.parent && top.parent.Test && top.parent.Test.Harness) {
+        top.parent.Test.Harness.Done++;
+    }
+};
+
+Test.Builder.prototype._ending = function () {
+    if (this.Ended) return;
+    this.Ended = true;
+    if (this.noEnding()) {
+        this._notifyHarness();
+        return;
+    }
+    this._sanity_check();
+    var out = this.endOutput();
+
+    // Figure out if we passed or failed and print helpful messages.
+    if( this.TestResults.length ) {
+        // The plan?  We have no plan.
+        if (this.NoPlan) {
+            if (!this.noHeader())
+                this._print("1.." + this.CurrTest + Test.Builder.LF);
+            this.ExpectedTests = this.CurrTest;
+        }
+
+        var numFailed = 0;
+        for (var i = 0; i < this.TestResults.length; i++) {
+            if (!this.TestResults[i]) numFailed++;
+        }
+        numFailed += Math.abs(
+            this.ExpectedTests - this.TestResults.length
+        );
+
+        if (this.CurrTest < this.ExpectedTests) {
+            var s = this.ExpectedTests == 1 ? '' : 's';
+            out(
+                "# Looks like you planned " + this.ExpectedTests + " test"
+                + s + " but only ran " + this.CurrTest + "." + Test.Builder.LF
+            );
+        } else if (this.CurrTest > this.ExpectedTests) {
+           var numExtra = this.CurrTest - this.ExpectedTests;
+            var s = this.ExpectedTests == 1 ? '' : 's';
+            out(
+                "# Looks like you planned " + this.ExpectedTests + " test"
+                + s + " but ran " + numExtra + " extra." + Test.Builder.LF
+            );
+        } else if (numFailed) {
+            var s = numFailed == 1 ? '' : 's';
+            out(
+                "# Looks like you failed " + numFailed + "test" + s + " of "
+                + this.ExpectedTests + "." + Test.Builder.LF
+            );
+        }
+
+        if (this.TestDied) {
+            out(
+                "# Looks like your test died just after " 
+                + this.CurrTest + "." + Test.Builder.LF
+            );
+        }
+
+    } else if (!this.SkipAll) {
+        // skipAll requires no status output.
+        if (this.TestDied) {
+            out(
+                "# Looks like your test died before it could output anything."
+                + Test.Builder.LF
+            );
+        } else {
+            out("# No tests run!" + Test.Builder.LF);
+        }
+    }
+    this._notifyHarness();
+};
+
+Test.Builder.prototype.isUndef = function (got, op, expect, desc) {
+    // Undefined only matches undefined, so we don't need to cast anything.
+    var test = eval("got " + (Test.Builder.StringOps[op] || op) + " expect");
+    this.ok(test, desc);
+    if (!test) this._isDiag(got, op, expect);
+    return test;
+};
+
+Test.Builder._finish = function (pkg) {
+    if (!pkg) pkg = Test;
+    for (var i = 0; i < pkg.Builder.Instances.length; i++) {
+        // The main process is always async ID 0.
+        if (!pkg.Builder.Instances[i].Ended)
+            pkg.Builder.Instances[i].endAsync(0);
+    }
+};
+
+if (Test.Builder.globalScope) {
+    // Set up an onload function to end all tests.
+    Test.Builder.globalScope.onload = function (event, pkg) {
+        // The package may be passed in if onload() is called explicitly.
+        // This is to get around a very weird scoping bug in my version of
+        // Firefox. See Test.Harness.Browser.runTest() for this usage.
+        Test.Builder._finish(pkg)
+    };
+
+    // Set up an exception handler. This is so that we can capture deaths but
+    // still output information for TestHarness to pick up.
+    Test.Builder.globalScope.onerror = function (msg, url, line) {
+        // Output the exception.
+        Test.Builder.Test.TestDied = true;
+        Test.Builder.Test.diag("Error in " + url + " at line " + line + ": " + msg);
+        return true;
+    };
+};
+
+Test.Builder.prototype.beginAsync = function (timeout) {
+	var id = ++this.asyncID;
+    var top = Test.Builder.globalScope;
+    if (timeout && top && top.setTimeout) {
+        // Are there other ways of setting timeout in non-browser settings?
+        var aTest = this;
+        this.asyncs[id] = top.setTimeout(
+            function () { aTest.endAsync(id) }, timeout
+        );
+    } else {
+        // Make sure it's defined.
+        this.asyncs[id] = 0;
+    }
+	return id;
+};
+
+Test.Builder.prototype.endAsync = function (id) {
+    if (this.asyncs[id] == undefined) return;
+    if (this.asyncs[id]) {
+		// Remove the timeout
+		Test.Builder.globalScope.clearTimeout(this.asyncs[id]);
+	}
+    if (--this.asyncID < 0) this._ending();
+};
+
+Test.Builder.exporter = function (pkg, root) {
+    if (typeof root == 'undefined') {
+        root = Test.Builder.globalScope;
+        if (!root) throw new Error("Platform unknown");
+    }
+    for (var i = 0; i < pkg.EXPORT.length; i++) {
+        if (typeof root[pkg.EXPORT[i]] == 'undefined')
+            root[pkg.EXPORT[i]] = pkg[pkg.EXPORT[i]];
+    }
+};

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,277 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+// Set up namespace.
+if (typeof self != 'undefined') {
+    //Browser
+    if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
+    else Test.PLATFORM = 'browser';
+} else if (typeof _player != 'undefined'){
+    //Director
+    if (typeof _global.Test != "object") _global.Test = {PLATFORM: 'director'};
+    else _global.Test.PLATFORM = 'director';
+} else {
+    throw new Error("Test.Harness does not support your platform");
+}
+
+Test.Harness = function () {};
+Test.Harness.VERSION = '0.21';
+Test.Harness.Done = 0;
+
+// Stoopid IE.
+Test.Harness.LF = typeof navigator != "undefined"
+    && navigator.userAgent.toLowerCase().indexOf('msie') + 1
+    && ( ( typeof JSAN != "undefined" && JSAN.globalScope.opera == undefined )
+         || ( Test.PLATFORM == 'browser' && window.opera == undefined ) )
+  ? "\r"
+  : "\n";
+
+Test.Harness.prototype.isDone = Test.Harness.isDone;
+
+/*
+
+    bonus           Number of individual todo tests unexpectedly passed
+    ran             Number of individual tests ran
+    ok              Number of individual tests passed
+    subSkipped      Number of individual tests skipped
+    todo            Number of individual todo tests
+
+    files           Number of test files ran
+    good            Number of test files passed
+    bad             Number of test files failed
+    tests           Number of test files originally given
+    skipped         Number of test files skipped
+
+*/
+
+Test.Harness.prototype.bonus      = 0;
+Test.Harness.prototype.ran        = 0;
+Test.Harness.prototype.ok         = 0;
+Test.Harness.prototype.subSkipped = 0;
+Test.Harness.prototype.todo       = 0;
+Test.Harness.prototype.files      = 0;
+Test.Harness.prototype.good       = 0;
+Test.Harness.prototype.bad        = 0;
+Test.Harness.prototype.tests      = 0;
+Test.Harness.prototype.skipped    = 0;
+Test.Harness.prototype.failures   = [];
+Test.Harness.prototype._encoding  = null;
+
+Test.Harness.prototype.encoding = function (enc) {
+    if (!enc) return this._encoding;
+    this._encoding = enc;
+    return this;
+};
+
+Test.Harness.runTests = function () {
+    // XXX Can't handle inheritance, right? Or can we?
+    var harness = new Test.Harness();
+    harness.runTests.apply(harness, arguments);
+};
+
+Test.Harness.prototype.outFileNames = function (files) {
+    var len = 0;
+    for (var i = 0; i < files.length; i++) {
+        if (files[i].length > len) len = files[i].length;
+    }
+    len += 3;
+    var ret = [];
+    for (var i = 0; i < files.length; i++) {
+        var outName = files[i];
+        var add = len - files[i].length;
+        // Where is Perl's x operator when I need it??
+        for (var j = 0; j < add; j++) {
+            outName += '.';
+        }
+        ret.push(outName);
+    }
+    return ret;
+};
+
+Test.Harness.prototype.outputResults = function (test, file, out, attrs) {
+    this.tests++;
+    this.ran += test.TestResults.length;
+    var fails = [];
+    var track = {
+        fn:       file,
+        total:    test.expectedTests(),
+        ok:       0,
+        failList: []
+    };
+
+    if (test.TestResults.length) {
+        this.files++;
+        var pass = true;
+        for (var i = 0; i < test.TestResults.length; i++) {
+            // Start out assuming passage.
+            if (test.TestResults[i].ok) {
+                if (attrs.verbose) out.pass(test.TestResults[i].output);
+                this.ok++;
+                track.ok++
+                if (test.TestResults[i].type == 'todo') {
+                    // Handle unexpected pass.
+                    if (test.TestResults[i].actualOK) this.bonus++;
+                    this.todo ++;
+                } else if (test.TestResults[i].type == 'skip') this.subSkipped++;
+            } else {
+                if (test.TestResults[i].type == 'todo') {
+                    // Expected failure.
+                    this.todo++;
+                    if (attrs.verbose) out.pass(test.TestResults[i].output);
+                } else {
+                    pass = false;
+                    track.failList.push(i + 1);
+                    out.fail(test.TestResults[i].output);
+                }
+            }
+            
+        }
+        
+        if (pass) {
+            this.good++;
+            out.pass("ok" + Test.Harness.LF);
+        } else {
+            this.bad++;
+            var err = "NOK # Failed ";
+            if (track.failList.length == 1) {
+                err += "test " + track.failList[0];
+            } else {
+                err += "tests " + this._failList(track.failList);
+            }
+            out.fail(err + " in " + file + Test.Harness.LF);
+        }
+    } else if (test.SkipAll){
+        // All tests skipped.
+        this.skipped++;
+        this.good++;
+        out.pass(test.Buffer.join('').replace(/[^#]+#\s+Skip /, 'all skipped: '));
+    } else {
+        // Wha happened? Tests ran, but no results!
+        this.files++;
+        this.bad++;
+        out.fail("FAILED before any test output arrived" + Test.Harness.LF);
+    }
+    if (track.failList.length) this.failures.push(track);
+};
+
+Test.Harness.prototype._allOK = function () {
+    return this.bad == 0 && (this.ran || this.skipped) ? true : false;
+};
+
+Test.Harness.prototype.outputSummary = function (fn, time) {
+    var bonusmsg = this._bonusmsg();
+    var pct;
+    if (this._allOK()) {
+        fn("All tests successful" + bonusmsg + '.' + Test.Harness.LF);
+    } else if (!this.tests) {
+        fn("FAILED—no tests were run for some reason." + Test.Harness.LF);
+    } else if (!this.ran) {
+        var blurb = this.tests == 1 ? "file" : "files";
+        fn("FAILED—" + this.tests + " test " + blurb + " could be run, "
+           + "alas—no output ever seen." + Test.Harness.LF);
+    } else {
+        pct = this.good / this.tests * 100;
+        var pctOK = 100 * this.ok / this.ran;
+        var subpct = (this.ran - this.ok) + "/" + this.ran
+          + " subtests failed, " + pctOK.toPrecision(4) + "% okay.";
+
+        if (this.bad) {
+            bonusmsg = bonusmsg.replace(/^,?\s*/, '');
+            if (bonusmsg) fn(bonusmsg + '.' + Test.Harness.LF);
+            fn("Failed " + this.bad + "/" + this.tests + " test scripts, "
+               + pct.toPrecision(4) + "% okay. " + subpct + Test.Harness.LF);
+        }
+        this.formatFailures(fn);
+    }
+
+    fn("Files=" + this.tests + ", Tests=" + this.ran + ", " + (time / 1000)
+       + " seconds" + Test.Harness.LF);
+};
+
+Test.Harness.prototype.formatFailures = function () {
+    var table = '';
+    var failedStr = "Failed Test";
+    var middleStr = " Total Fail  Failed  ";
+    var listStr = "List of Failed";
+    var cols = 80;
+
+    // Figure out our longest name string for formatting purposes.
+    var maxNamelen = failedStr.length;
+    for (var i = 0; i < this.failures.length; i++) {
+        var len = this.failures[i].length;
+        if (len > maxNamelen) maxNamelen = len;
+    }
+
+    var listLen = cols - middleStr.length - maxNamelen.length;
+    if (listLen < listStr.length) {
+        listLen = listStr.length;
+        maxNamelen = cols - middleStr.length - listLen;
+        if (maxNamelen < failedStr.length) {
+            maxNamelen = failedStr.length;
+            cols = maxNamelen + middleStr.length + listLen;
+        }
+    }
+
+    var out = failedStr;
+    if (out.length < maxNamelen) {
+        for (var j = out.length; j < maxNameLength; j++) {
+            out += ' ';
+        }
+    }
+    out += '  ' + middleStr;
+    // XXX Need to finish implementing the text-only version of the failures
+    // table.
+};
+
+Test.Harness.prototype._bonusmsg = function () {
+    var bonusmsg = '';
+    if (this.bonus) {
+        bonusmsg = (" (" + this.bonus + " subtest" + (this.bonus > 1 ? 's' : '')
+          + " UNEXPECTEDLY SUCCEEDED)");
+    }
+
+    if (this.skipped) {
+        bonusmsg += ", " + this.skipped + " test"
+          + (this.skipped != 1 ? 's' : '');
+        if (this.subSkipped) {
+            bonusmsg += " and " + this.subSkipped + " subtest"
+              + (this.subSkipped != 1 ? 's' : '');
+        }
+        bonusmsg += ' skipped';
+    } else if (this.subSkipped) {
+        bonusmsg += ", " + this.subSkipped + " subtest"
+          + (this.subSkipped != 1 ? 's' : '') + " skipped";
+    }
+
+    return bonusmsg;
+}
+
+Test.Harness.prototype._failList = function (fails) {
+    var last = -1;
+    var dash = '';
+    var list = [];
+    for (var i = 0; i < fails.length; i++) {
+        if (dash) {
+            // We're in a series of numbers.
+            if (fails[i] - 1 == last) {
+                // We're still in it.
+                last = fails[i];
+            } else {
+                // End of the line.
+                list[list.length-1] += dash + last;
+                last = -1;
+                list.push(fails[i]);
+                dash = '';
+            }
+        } else if (fails[i] - 1 == last) {
+            // We're in a new series.
+            last = fails[i];
+            dash = '-';
+        } else {
+            // Not in a sequence.
+            list.push(fails[i]);
+            last = fails[i];
+        }
+    }
+    if (dash) list[list.length-1] += dash + last;
+    return list.join(' ');
+}

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness/Browser.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness/Browser.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,353 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+if (typeof JSAN != 'undefined') JSAN.use('Test.Harness');
+else {
+    if (typeof Test == 'undefined') Test = {};
+    if (!Test.Harness) Test.Harness = {};
+}
+
+if (window.parent != window &&
+    location.href.replace(/[?#].+/, "") == parent.location.href.replace(/[?#].+/, ""))
+{
+
+    // Build fake T.H.B so original script from this file doesn't throw
+    // exception. This is a bit of a hack...
+    Test.Harness.Browser = function() {
+        this.runTests = function() {},
+        this.encoding = function () { return this }
+    };
+
+    // We're in a test iframe. Set up the necessary parts and load the
+    // test script with XMLHttpRequest (to support Safari and Opera).
+    var __MY = {};
+    __MY.pre = document.createElement("pre");
+    __MY.pre.id = "test";
+    if (window.parent.Test.Harness.Browser._encoding) {
+        // Set all scripts to use the appropriate encoding.
+        __MY.scripts = document.getElementsByTagName('script');
+        for (var j = 0; j < __MY.scripts.length; j++) {
+            __MY.scripts[j].charset =
+                window.parent.Test.Harness.Browser._encoding;
+        }
+    }
+
+    // XXX replace with a script element at some point? Safari is due to
+    // have this working soon (not sure about IE or Opera):
+    // http://bugzilla.opendarwin.org/show_bug.cgi?id=3748
+    __MY.inc = window.parent.Test.Harness.Browser.includes;
+    __MY.req = typeof XMLHttpRequest != 'undefined'
+      ? new XMLHttpRequest()
+      : new ActiveXObject("Microsoft.XMLHTTP");
+
+    for (var k = 0; k < __MY.inc.length; k++) {
+        __MY.req.open("GET", __MY.inc[k], false);
+        __MY.req.send(null);
+        var stat = __MY.req.status;
+        //           OK   Not Modified    IE Cached   Safari cached
+        if (stat == 200 || stat == 304 || stat == 0 || stat == null) {
+            eval(__MY.req.responseText);
+        } else {
+            throw new Error(
+                "Unable to load " + __MY.inc[k]
+                + ': Status ' + __MY.req.status
+            );
+        }
+    }
+
+    // IE 6 SP 2 doesn't seem to run the onload() event, so we force the
+    // issue.
+    Test.Builder._finish(Test);
+
+    // XXX Opera throws a DOM exception here, but I don't know what to do
+    // about that.
+    __MY.body = document.body
+        || document.getElementsByTagName("body")[0].appendChild(__MY.pre);
+    if (__MY.body) __MY.body.appendChild(__MY.pre);
+    else if (document.appendChild) document.appendChild(__MY.pre);
+
+} else {
+    Test.Harness.Browser = function () {
+        this.includes = Test.Harness.Browser.includes = [];
+        Array.prototype.push.apply(Test.Harness.Browser.includes, arguments);
+        this.includes.push('');
+    };
+
+    Test.Harness.Browser.VERSION = '0.21';
+
+    Test.Harness.Browser.runTests = function () {
+        var harness = new Test.Harness.Browser();
+        harness.runTests.apply(harness, arguments);
+    };
+
+    Test.Harness.Browser.prototype = new Test.Harness();
+    Test.Harness.Browser.prototype.interval = 100;
+
+    Test.Harness.Browser.prototype._setupFrame = function () {
+        // Setup the iFrame to run the tests.
+        var node = document.getElementById('buffer');
+        if (node) return node.contentWindow || frames.buffer;
+        node = document.createElement("iframe");
+        node.setAttribute("id", "buffer");
+        node.setAttribute("name", "buffer");
+        // Safari makes it impossible to do anything with the iframe if it's
+        // set to display:none. See:
+        // http://www.quirksmode.org/bugreports/archives/2005/02/hidden_iframes.html
+        if (/Safari/.test(navigator.userAgent)) {
+            node.style.visibility = "hidden";
+            node.style.height = "0"; 
+            node.style.width = "0";
+        } else
+            node.style.display = "none";
+        document.body.appendChild(node);
+        return node.contentWindow || frames.buffer;
+    };
+
+    Test.Harness.Browser.prototype._setupOutput = function () {
+        // Setup the pre element for test output.
+        var node = document.createElement("pre");
+        node.setAttribute("id", "output");
+        document.body.appendChild(node);
+        return {
+            pass: function (msg) {
+                node.appendChild(document.createTextNode(msg));
+                window.scrollTo(0, document.body.offsetHeight
+                                || document.body.scrollHeight);
+            },
+            fail: function (msg) {
+                var red = document.createElement("span");
+                red.setAttribute("style", "color: red; font-weight: bold");
+                node.appendChild(red);
+                red.appendChild(document.createTextNode(msg));
+                window.scrollTo(0, document.body.offsetHeight
+                                || document.body.scrollHeight);
+            }
+        };
+    };
+
+    Test.Harness.Browser.prototype._setupSummary = function () {
+        // Setup the div for the summary.
+        var node = document.createElement("div");
+        node.setAttribute("id", "summary");
+        node.setAttribute(
+            "style", "white-space:pre; font-family: Verdana,Arial,serif;"
+        );
+        document.body.appendChild(node);
+        return function (msg) {
+            node.appendChild(document.createTextNode(msg));
+            window.scrollTo(0, document.body.offsetHeight
+                            || document.body.scrollHeight);
+        };
+};
+
+    Test.Harness.Browser.prototype.runTests = function () {
+        Test.Harness.Browser._encoding = this.encoding();
+        var files = this.args.file
+        ? typeof this.args.file == 'string' ? [this.args.file] : this.args.file
+        : arguments;
+        if (!files.length) return;
+        var outfiles = this.outFileNames(files);
+        var buffer = this._setupFrame();
+        var harness = this;
+        var ti = 0;
+        var start;
+        var output = this._setupOutput();
+        var summaryOutput = this._setupSummary();
+        // These depend on how we're watching for a test to finish.
+        var finish = function () {}, runNext = function () {};
+
+        // This function handles most of the work of outputting results and
+        // running the next test, if there is one.
+        var runner = function () {
+            harness.outputResults(
+                buffer.Test.Builder.Test,
+                files[ti],
+                output,
+                harness.args
+            );
+
+            if (files[++ti]) {
+                output.pass(
+                    outfiles[ti]
+                    + (harness.args.verbose ? Test.Harness.LF : '')
+                );
+                harness.runTest(files[ti], buffer);
+                runNext();
+            } else {
+                harness.outputSummary(summaryOutput, new Date() - start);
+                finish();
+            }
+        };
+
+        if (Object.watch) {
+            // We can use the cool watch method, and avoid setting timeouts!
+            // We just need to unwatch() when all tests are finished.
+            finish = function () { Test.Harness.unwatch('Done') };
+            Test.Harness.watch('Done', function (attr, prev, next) {
+                if (next < buffer.Test.Builder.Instances.length) return next;
+                runner();
+                return 0;
+            });
+        } else {
+            // Damn. We have to set timeouts. :-(
+            var pkg;
+            var wait = function () {
+                // Check Test.Harness.Done. If it's non-zero, then we know
+                // that the buffer is fully loaded, because it has incremented
+                // Test.Harness.Done. Grrr.. IE 6 SP 2 seems to delete
+                // buffer.Test after all the tests have finished running, but
+                // before this code executes for the correct number of
+                // completed tests. So we cache it in a variable outside of
+                // the function on previous calls to the function.
+                if (!pkg) pkg = buffer.Test;
+                if (Test.Harness.Done > 0
+                    && Test.Harness.Done >= pkg.Builder.Instances.length)
+                {
+                    Test.Harness.Done = 0;
+                    // Avoid race condition by resetting the instances, too. I
+                    // have no idea why this might remain set from a previous
+                    // test, but such can be the case in IE 6 SP 2.
+                    pkg.Builder.Instances = [];
+                    runner();
+                } else {
+                    window.setTimeout(wait, harness.interval);
+                }
+            };
+            // We'll just have to set a timeout for the next test.
+            runNext = function () {
+                window.setTimeout(wait, harness.interval);
+            };
+            window.setTimeout(wait, this.interval);
+        }
+
+        // Now start the first test.
+        output.pass(outfiles[ti] + (this.args.verbose ? Test.Harness.LF : ''));
+        start = new Date();
+        this.runTest(files[ti], buffer);
+    };
+
+    Test.Harness.Browser.prototype.runTest = function (file, buffer) {
+        if (/\.html$/.test(file)) {
+            buffer.location.replace(file);
+        } else { // if (/\.js$/.test(file)) {
+            if (/MSIE/.test(navigator.userAgent)
+                || /Opera/.test(navigator.userAgent)
+                || /Safari/.test(navigator.userAgent))
+            {
+                // These browsers have problems with the DOM solution. It
+                // simply doesn't work in Safari, and Opera considers its
+                // handling of buffer.document to be a security violation. So
+                // have them use the XML hack, instead.
+                this.includes[this.includes.length-1] = file;
+                buffer.location.replace(location.pathname + "?xml-hack=1");
+                return;
+            }
+            // document.write() simply doesn't work here. Thanks to
+            // Pawel Chmielowski for figuring that out!
+            var doc = buffer.document;
+            doc.open("text/html");
+            doc.close();
+            var el;
+
+            // XXX Opera chokes on this line. It thinks that using the doc
+            // element like this is a security violation, never mind that we
+            // were the ones who actually created it. Whatever!
+            var body = doc.body || doc.getElementsByTagName("body")[0];
+            var head = doc.getElementsByTagName("head")[0];
+
+            // Safari seems to be headless at this point.
+            if (!head) {
+                head = doc.createElement('head');
+                doc.appendChild(head);
+            }
+
+            // Add script elements for all includes.
+            for (var i = 0; i < this.includes.length - 1; i++) {
+                el = doc.createElement("script");
+                el.setAttribute("src", this.includes[i]);
+                head.appendChild(el);
+            }
+
+
+            // Create the pre and script element for the test file.
+            var pre = doc.createElement("pre");
+            pre.id = "test";
+            el = doc.createElement("script");
+            el.type = "text/javascript";
+            if (this.encoding()) el.charset = this.encoding();
+
+            // XXX This doesn't work in Safari right now. See
+            // http://bugzilla.opendarwin.org/show_bug.cgi?id=3748
+            el.src = file;
+            pre.appendChild(el);
+
+            // Create a script element to finish the tests.
+            el = doc.createElement("script");
+            el.type = "text/javascript";
+            var text = "window.onload(null, Test)";
+
+            // IE doesn't let script elements have children.
+            if (null != el.canHaveChildren) el.text = text;
+            // But most other browsers do.
+            else el.appendChild(document.createTextNode(text));
+
+            pre.appendChild(el);
+
+            // IE 6 SP 2 Requires getting the body element again.
+            body = doc.body || doc.getElementsByTagName("body")[0];
+            body.appendChild(pre);
+        /* Let's just assume that if it's not .html, it's JavaScript.
+        } else {
+            // Who are you, man??
+            alert("I don't know what kind of file '" + file + "' is");
+        */
+        }
+    };
+
+    Test.Harness.Browser.prototype.args = {};
+    var pairs = location.search.substring(1).split(/[;&]/);
+    for (var i = 0; i < pairs.length; i++) {
+        var parts = pairs[i].split('=');
+        if (parts[0] == null) continue;
+        var key = unescape(parts[0]), val = unescape(parts[1]);
+        if (Test.Harness.Browser.prototype.args[key] == null) {
+            Test.Harness.Browser.prototype.args[key] = unescape(val);
+        } else {
+            if (typeof Test.Harness.Browser.prototype.args[key] == 'string') {
+                Test.Harness.Browser.prototype.args[key] =
+                    [Test.Harness.Browser.prototype.args[key], unescape(val)];
+            } else {
+                Test.Harness.Browser.prototype.args[key].push(unescape(val));
+            }
+        }
+    }
+    delete pairs;
+
+    Test.Harness.Browser.prototype.formatFailures = function (fn) {
+        // XXX Switch to DOM?
+        var failedStr = "Failed Test";
+        var middleStr = " Total Fail  Failed  ";
+        var listStr = "List of Failed";
+        var table =
+            '<style>table {padding: 0; border-collapse: collapse; }'
+          + 'tr { height: 2em; }'
+          + 'th { background: lightgrey; }'
+          + 'td, th { padding: 2px 5px; text-align: left; border: solid #000000 1px;}'
+          + '.odd { background: #e8e8cd }'
+          + '</style>'
+          + '<table style="padding: 0"><tr><th>Failed Test</th><th>Total</th>'
+          + '<th>Fail</th><th>Failed</th></tr>';
+        for (var i = 0; i < this.failures.length; i++) {
+            var track = this.failures[i];
+            var style = i % 2 ? 'even' : 'odd';
+            table += '<tr class="' + style + '"><td>' + track.fn + '</td>'
+              + '<td>' + track.total + '</td>'
+              + '<td>' + (track.total - track.ok) + '</td>'
+              + '<td>' + this._failList(track.failList) + '</td></tr>';
+        };
+        table += '</table>' + Test.Harness.LF;
+        var node = document.getElementById('summary');
+        node.innerHTML += table;
+        window.scrollTo(0, document.body.offsetHeight
+                        || document.body.scrollHeight);
+    };
+}
\ No newline at end of file

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness/Director.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Harness/Director.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,65 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+Test.Harness.Director = function () {};
+Test.Harness.Director.VERSION = '0.21';
+
+Test.Harness.Director.runTests = function () {
+    var harness = new Test.Harness.Director();
+    harness.runTests.apply(harness, arguments);
+};
+
+Test.Harness.Director.prototype = new Test.Harness();
+Test.Harness.Director.prototype.verbose = true;
+Test.Harness.Director.prototype.args = {};
+
+Test.Harness.Director.prototype.runTests = function () {
+    // Allow for an array or a simple list in arguments.
+    // XXX args.file isn't quite right since it's more function names, but
+    // that is still to be ironed out.
+
+    var functionNames = this.args.file
+      ? typeof this.args.file == 'string' ? [this.args.file] : this.args.file
+      : arguments;
+    if (!functionNames.length) return;
+    var outfunctions = this.outFileNames(functionNames);
+    var harness = this;
+    var start = new Date();
+    var newLineRx = /(?:\r?\n|\r)+$/;
+    var output = {
+        pass: function (msg) { trace(msg.replace(newLineRx, '')) }
+    }
+    output.fail = output.pass;
+
+    for (var x = 0; x < functionNames.length; x++){
+        output(outfunctions[x]);
+        eval(functionNames[x] + "()");
+        harness.outputResults(
+            Test.Builder.Test,
+            functionNames[x],
+            output,
+            harness.args
+        );
+    }
+    harness.outputSummary(
+        output,
+        new Date() - start
+    );
+};
+
+Test.Harness.Director.prototype.formatFailures = function (fn) {
+    // XXX Delete once the all-text version is implemented in Test.Harness.
+    var failedStr = "Failed Test";
+    var middleStr = " Total Fail  Failed  ";
+    var listStr = "List of Failed";
+    var table = '<table style=""><tr><th>Failed Test</th><th>Total</th>'
+      + '<th>Fail</th><th>Failed</th></tr>';
+    for (var i = 0; i < this.failures.length; i++) {
+        var track = this.failures[i];
+        table += '<tr><td>' + track.fn + '</td>'
+          + '<td>' + track.total + '</td>'
+          + '<td>' + track.total - track.ok + '</td>'
+          + '<td>' + this._failList(track.failList) + '</td></tr>'
+    };
+    table += '</table>';
+    output(table);
+};
\ No newline at end of file

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/More.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/More.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,438 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+// Create a namespace for ourselves.
+
+// Set up package.
+if (typeof JSAN != 'undefined') JSAN.use('Test.Builder');
+else {
+    if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
+        throw new Error(
+            "You must load either JSAN or Test.Builder "
+            + "before loading Test.More"
+        );
+}
+
+Test.More = {};
+Test.More.EXPORT = [
+    'plan',
+    'ok', 'is', 'isnt',
+    'like', 'unlike',
+    'cmpOK', 'canOK', 'isaOK',
+    'pass', 'fail', 'diag', 'loadOK',
+    'skip', 'todo', 'todoSkip', 'skipRest',
+    'isDeeply', 'isSet', 'isa',
+    'beginAsync', 'endAsync'
+];
+Test.More.EXPORT_TAGS = { ':all': Test.More.EXPORT };
+Test.More.VERSION     = '0.21';
+
+Test.More.ShowDiag = true;
+Test.Builder.DNE = { dne: 'Does not exist' };
+Test.More.Test = Test.Builder.instance();
+Test.More.builder = function () { return Test.More.Test; };
+
+Test.More.plan = function (cmds) {
+    if (cmds.noDiag) {
+        Test.More.ShowDiag = false;
+        delete cmds.noDiag;
+    }
+    return Test.More.Test.plan(cmds);
+};
+
+Test.More.ok = function (test, desc) {
+    return Test.More.Test.ok(test, desc);
+};
+
+Test.More.is = function (got, expect, desc) {
+    return Test.More.Test.isEq(got, expect, desc);
+};
+
+Test.More.isnt = function (got, expect, desc) {
+    return Test.More.Test.isntEq(got, expect, desc);
+};
+
+Test.More.like = function (val, regex, desc) {
+    return Test.More.Test.like(val, regex, desc);
+};
+
+Test.More.unlike = function (val, regex, desc) {
+    return Test.More.Test.unlike(val, regex, desc);
+};
+
+Test.More.cmpOK = function (got, op, expect, desc) {
+    return Test.More.Test.cmpOK(got, op, expect, desc);
+};
+
+Test.More.canOK = function (proto) {
+    var ok;
+    // Make sure they passed some method names for us to check.
+    if (!arguments.length > 1) {
+        ok = Test.More.Test.ok(false, clas + '.can(...)');
+        Test.More.Test.diag('    canOK() called with no methods');
+        return ok;
+    }
+
+    // Get the class name and the prototype.
+    var clas;
+    if (typeof proto == 'string') {
+        // We just have a class name.
+        clas = proto;
+        proto = eval(clas + '.prototype');
+    } else {
+        // We have an object or something that can be converted to an object.
+        clas = Test.Builder.typeOf(proto);
+        proto = proto.constructor.prototype;
+    }
+
+    var nok = [];
+    for (var i = 1; i < arguments.length; i++) {
+        var method = arguments[i];
+        if (typeof proto[method] != 'function') nok.push(method);
+    }
+
+    // There'es no can() method in JavaScript, but what the hell!
+    var desc = clas + ".can('" + (arguments.length == 2 ? arguments[1] : '...') + "')";
+    ok = Test.More.Test.ok(!nok.length, desc);
+    for (var i = 0; i < nok.length; i++) {
+        Test.More.Test.diag('    ' + clas + ".can('" + nok[i] + "') failed");
+    }
+    return ok;
+};
+
+Test.More.isaOK = function (object, clas, objName) {
+    var mesg;
+    if (objName == null) objName = 'The object';
+    var name = objName + ' isa ' + clas;
+    if (object == null) {
+        mesg = objName + " isn't defined";
+    } else if (!Test.More._isRef(object)) {
+        mesg = objName + " isn't a reference";
+    } else {
+        var ctor = eval(clas);
+        if (Object.isPrototypeOf) {
+            // With JavaScript 1.5, we can determine inheritance.
+            if (!ctor.prototype.isPrototypeOf(object)) {
+                mesg = objName + " isn't a '" + clas + "' it's a '"
+                  + Test.Builder.typeOf(object) + "'";
+            }
+        } else {
+            // We can just determine what constructor was used. This will
+            // not work for inherited constructors.
+            if (object.constructor != ctor)
+                mesg = objName + " isn't a '" + clas + "' it's a '"
+                  + Test.Builder.typeOf(object) + '"';
+        }
+    }
+    
+    var ok;
+    if (mesg) {
+        ok = Test.More.Test.ok(false, name);
+        Test.More.Test.diag('    ' + mesg);
+    } else {
+        ok = Test.More.Test.ok(true, name);
+    }
+
+    return ok;
+};
+
+Test.More.pass = function (name) {
+    return Test.More.Test.ok(true, name);
+};
+
+Test.More.fail = function (name) {
+    return Test.More.Test.ok(false, name);
+};
+
+Test.More.diag = function () {
+    if (!Test.More.ShowDiag) return;
+    return Test.More.Test.diag.apply(Test.More.Test, arguments);
+};
+
+// Use this instead of use_ok and require_ok.
+Test.More.loadOK = function () {
+    // XXX What do I do here? Eval?
+    // XXX Just always fail for now, to keep people from using it just yet.
+    return false;
+};
+
+Test.More.skip = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("skip() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+    for (var i = 0; i < howMany; i++) {
+        Test.More.Test.skip(why);
+    }
+};
+
+Test.More.todo = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("todo() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+    return Test.More.Test.todo(why, howMany);
+};
+
+Test.More.todoSkip = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("todoSkip() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+
+    for (var i = 0; i < howMany; i++) {
+        Test.More.Test.todoSkip(why);
+    }
+};
+
+Test.More.skipRest = function (why) {
+    Test.More.Test.skipRest(why);
+};
+
+Test.More.isDeeply = function (it, as, name) {
+    if (arguments.length != 2 && arguments.length != 3) {
+        Test.More.Test.warn(
+            'isDeeply() takes two or three args, you gave '
+            + arguments.length + "."
+        );
+    }
+
+    var ok;
+    // ^ is the XOR operator.
+    if (Test.More._isRef(it) ^ Test.More._isRef(as)) {
+        // One's a reference, one isn't.
+        ok = false;
+    } else if (!Test.More._isRef(it) && !Test.More._isRef(as)) {
+        // Neither is an object.
+        ok = Test.More.Test.isEq(it, as, name);
+    } else {
+        // We have two objects. Do a deep comparison.
+        var stack = [], seen = [];
+        if ( Test.More._deepCheck(it, as, stack, seen)) {
+            ok = Test.More.Test.ok(true, name);
+        } else {
+            ok = Test.More.Test.ok(false, name);
+            Test.More.Test.diag(Test.More._formatStack(stack));
+        }
+    }
+    return ok;
+};
+
+Test.More._deepCheck = function (e1, e2, stack, seen) {
+    var ok = false;
+    // Either they're both references or both not.
+    var sameRef = !(!Test.More._isRef(e1) ^ !Test.More._isRef(e2));
+    if (e1 == null && e2 == null) {
+        ok = true;
+    } else if (e1 != null ^ e2 != null) {
+        ok = false;
+    } else if (e1 == Test.More.DNE ^ e2 == Test.More.DNE) {
+        ok = false;
+    } else if (sameRef && e1 == e2) {
+        // Handles primitives and any variables that reference the same
+        // object, including functions.
+        ok = true;
+    } else if (isa(e1, 'Array') && isa(e2, 'Array')) {
+        ok = Test.More._eqArray(e1, e2, stack, seen);
+    } else if (typeof e1 == "object" && typeof e2 == "object") {
+        ok = Test.More._eqAssoc(e1, e2, stack, seen);
+    } else {
+        // If we get here, they're not the same (function references must
+        // always simply rererence the same function).
+        stack.push({ vals: [e1, e2] });
+        ok = false;
+    }
+    return ok;
+};
+
+Test.More._isRef = function (object) {
+    var type = typeof object;
+    return type == 'object' || type == 'function';
+};
+
+Test.More._formatStack = function (stack) {
+    var variable = '$Foo';
+    for (var i = 0; i < stack.length; i++) {
+        var entry = stack[i];
+        var type = entry['type'];
+        var idx = entry['idx'];
+        if (idx != null) {
+            if (/^\d+$/.test(idx)) {
+                // Numeric array index.
+                variable += '[' + idx + ']';
+            } else {
+                // Associative array index.
+                idx = idx.replace("'", "\\'");
+                variable += "['" + idx + "']";
+            }
+        }
+    }
+
+    var vals = stack[stack.length-1]['vals'].slice(0, 2);
+    var vars = [
+        variable.replace('$Foo',     'got'),
+        variable.replace('$Foo',     'expected')
+    ];
+
+    var out = "Structures begin differing at:" + Test.Builder.LF;
+    for (var i = 0; i < vals.length; i++) {
+        var val = vals[i];
+        if (val == null) {
+            val = 'undefined';
+        } else {
+             val == Test.More.DNE ? "Does not exist" : "'" + val + "'";
+        }
+    }
+
+    out += vars[0] + ' = ' + vals[0] + Test.Builder.LF;
+    out += vars[1] + ' = ' + vals[1] + Test.Builder.LF;
+    
+    return '    ' + out;
+};
+
+/* Commented out per suggestion from Michael Schwern. It turned out to be
+   confusing to Test::More users because it isn't atually a test. Use
+   isDeeply() instead and don't worry about it.
+
+Test.More.eqArray = function (a1, a2) {
+    if (!isa(a1, 'Array') || !isa(a2, 'Array')) {
+        Test.More.Test.warn("Non-array passed to eqArray()");
+        return false;
+    }
+    return Test.More._eqArray(a1, a2, [], []);
+};
+
+*/
+
+Test.More._eqArray = function (a1, a2, stack, seen) {
+    // Return if they're the same object.
+    if (a1 == a2) return true;
+
+    // JavaScript objects have no unique identifiers, so we have to store
+    // references to them all in an array, and then compare the references
+    // directly. It's slow, but probably won't be much of an issue in
+    // practice. Start by making a local copy of the array to as to avoid
+    // confusing a reference seen more than once (such as [a, a]) for a
+    // circular reference.
+    for (var j = 0; j < seen.length; j++) {
+        if (seen[j][0] == a1) {
+            return seen[j][1] == a2;
+        }
+    }
+
+    // If we get here, we haven't seen a1 before, so store it with reference
+    // to a2.
+    seen.push([ a1, a2 ]);
+
+    var ok = true;
+    // Only examines enumerable attributes. Only works for numeric arrays!
+    // Associative arrays return 0. So call _eqAssoc() for them, instead.
+    var max = a1.length > a2.length ? a1.length : a2.length;
+    if (max == 0) return Test.More._eqAssoc(a1, a2, stack, seen);
+    for (var i = 0; i < max; i++) {
+        var e1 = i > a1.length - 1 ? Test.More.DNE : a1[i];
+        var e2 = i > a2.length - 1 ? Test.More.DNE : a2[i];
+        stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
+        if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+/* Commented out per suggestion from Michael Schwern. It turned out to be
+   confusing to Test::More users because it isn't atually a test. Use
+   isDeeply() instead and don't worry about it.
+
+Test.More.eqHash = function () {
+    return eqAssoc.apply(this, arguments);
+};
+
+Test.More.eqAssoc = function (o1, o2) {
+    if (typeof o1 != "object" || typeof o2 != "object") {
+        Test.More.Test.warn("Non-object passed to eqAssoc()");
+        return false;
+    } else if (   (isa(o1, 'Array') && o1.length > 0)
+               || (isa(o2, 'Array') && o2.length > 0))
+    {
+        Test.More.Test.warn("Ordered array passed to eqAssoc()");
+        return false;
+    }
+    return Test.More._eqAssoc(o1, o2, [], []);
+};
+
+*/
+
+Test.More._eqAssoc = function (o1, o2, stack, seen) {
+    // Return if they're the same object.
+    if (o1 == o2) return true;
+
+    // JavaScript objects have no unique identifiers, so we have to store
+    // references to them all in an array, and then compare the references
+    // directly. It's slow, but probably won't be much of an issue in
+    // practice. Start by making a local copy of the array to as to avoid
+    // confusing a reference seen more than once (such as [a, a]) for a
+    // circular reference.
+    seen = seen.slice(0);
+    for (var j = 0; j < seen.length; j++) {
+        if (seen[j][0] == o1) {
+            return seen[j][1] == o2;
+        }
+    }
+
+    // If we get here, we haven't seen o1 before, so store it with reference
+    // to o2.
+    seen.push([ o1, o2 ]);
+
+    // They should be of the same class.
+
+    var ok = true;
+    // Only examines enumerable attributes.
+    var o1Size = 0; for (var i in o1) o1Size++;
+    var o2Size = 0; for (var i in o2) o2Size++;
+    var bigger = o1Size > o2Size ? o1 : o2;
+    for (var i in bigger) {
+        var e1 = o1[i] == undefined ? Test.More.DNE : o1[i];
+        var e2 = o2[i] == undefined ? Test.More.DNE : o2[i];
+        stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
+        if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+Test.More._eqSet = function (a1, a2, stack, seen) {
+    return Test.More._eqArray(a1.slice(0).sort(), a2.slice(0).sort(), stack, seen);
+};
+
+Test.More.isSet = function (a1, a2, desc) {
+    var stack = [], seen = [], ok = true;
+    if (Test.More._eqSet(a1, a2, stack, seen)) {
+        ok = Test.More.Test.ok(true, desc);
+    } else {
+        ok = Test.More.Test.ok(false, desc);
+        Test.More.Test.diag(Test.More._formatStack(stack));
+    }
+    return ok;
+};
+
+Test.More.isa = function (object, clas) {
+    return Test.Builder.typeOf(object) == clas;
+};
+
+Test.More.beginAsync = function (timeout) {
+    return Test.More.Test.beginAsync(timeout);
+}
+
+Test.More.endAsync = function (id) {
+    return Test.More.Test.endAsync(id);
+}
+
+// Handle exporting.
+if (typeof JSAN == 'undefined') Test.Builder.exporter(Test.More);

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Simple.js
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/lib/Test/Simple.js	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,29 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+// Set up package.
+if (typeof JSAN != 'undefined') JSAN.use('Test.Builder');
+else {
+    if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
+        throw new Error(
+            "You must load either JSAN or Test.Builder "
+            + "before loading Test.Simple"
+        );
+}
+
+Test.Simple = {};
+Test.Simple.EXPORT      = ['plan', 'ok'];
+Test.Simple.EXPORT_TAGS = { ':all': Test.Simple.EXPORT };
+Test.Simple.VERSION     = '0.21';
+
+Test.Simple.plan = function (cmds) {
+    return Test.Simple.Test.plan(cmds);
+};
+
+Test.Simple.ok = function (val, desc) {
+    return Test.Simple.Test.ok(val, desc);
+};
+
+// Handle exporting.
+if (typeof JSAN == 'undefined') Test.Builder.exporter(Test.Simple);
+
+Test.Simple.Test = Test.Builder.instance();

Added: jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/t/1-jifty-update.t
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/share/web/static/js-test/t/1-jifty-update.t	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,42 @@
+# This test is for testing Jifty.update() javascript function.
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 12;
+use Jifty::Test::WWW::Selenium;
+use utf8;
+
+my $sel    = Jifty::Test::WWW::Selenium->rc_ok($server);
+
+$sel->open_ok("/1-jifty-update.html");
+
+my $html = $sel->get_html_source;
+
+like $html, qr{<h1>Jifty\.update\(\) tests</h1>}is;
+
+$sel->click_ok("region1");
+sleep 2;
+$html = $sel->get_html_source;
+like $html, qr{<p>Region One</p>}is;
+
+$sel->click_ok("region2");
+sleep 2;
+$html = $sel->get_html_source;
+like $html, qr{<p>Region Two</p>}is;
+
+
+# Update the same region path with different argument
+$sel->click_ok("region3");
+sleep 2;
+$html = $sel->get_html_source;
+like $html, qr{<p>Hello, John</p>}is;
+
+$sel->click_ok("region4");
+sleep 2;
+$html = $sel->get_html_source;
+like $html, qr{<p>Hello, Smith</p>}is;
+
+$sel->stop;
+

Added: jifty/branches/jquery/t/TestApp-JiftyJS/t/2-behaviour.t
==============================================================================
--- (empty file)
+++ jifty/branches/jquery/t/TestApp-JiftyJS/t/2-behaviour.t	Thu Dec 13 18:04:01 2007
@@ -0,0 +1,19 @@
+# This test is for testing Jifty.update() javascript function.
+
+use strict;
+use warnings;
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 4;
+use Jifty::Test::WWW::Selenium;
+use utf8;
+
+my $server = Jifty::Test->make_server;
+my $sel    = Jifty::Test::WWW::Selenium->rc_ok($server);
+my $URL    = $server->started_ok;
+
+$sel->open_ok("/static/js-test/index.html");
+
+$sel->wait_for_text_present_ok("All tests successful.");
+$sel->stop;
+


More information about the Jifty-commit mailing list