The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
<html>
<head>
<title>Yote Unit Tests</title>

<style>
 .fail { color:red }
 .pass { color:green }
 body { background-color:wheat }
</style>

<script src="/yote/js/jquery-latest.js"></script>
<script src="/yote/js/jquery.cookie.js"></script>
<script src="/yote/js/jquery.base64.min.js"></script>
<script src="/yote/js/json2.js"></script>
<script src="/yote/js/yote.js"></script>
<script src="/yote/js/yote.util.js"></script>

<!-- <script src="/yote/js/yote.unified.min.js"></script>-->
<script>
    var tests = 0;
var failed = 0;
var calls_made = 0;
$.yote.old_message = $.yote.message;
$.yote.message = function( params ) {
    calls_made++;
    return $.yote.old_message( params );
}


function fail( test ) {
    return function(d) {
        ++failed;
        ++tests;
        $('#tests').append( "<span class=fail>FAILED : </span>" + test + '<BR>' );
    }
}
function pass( test ) {
    return function(d) {
        ++tests;
        $('#tests').append( "<span class=pass>PASSED : </span>" + test + '<BR>' );
    }
}
function is( result, expected, msg ) {
    if( result === expected ) {
	pass( msg )();
    }
    else {
	fail( msg + "( expected " + expected + " and got " + result +")" )();
    }
}
function ok( result, msg ) {
    if( result === true ) {
	pass( msg )();
    }
    else { 
	fail( msg )();
    }
}
function wrap_up() {
    $( '#results' ).append( "<BR>Calls made " + calls_made + "<BR>" );
    if( failed > 0 ) {
        $('#results').append( "Failed " + failed + " tests out of " + tests );
    } else {
        $('#results').append( "Passed all " + tests + " tests" );
    }
    var d = new Date();
    var t2 = d.getTime();
    $.yote.fetch_root().remove_login( { l:$.yote.login_obj, p: 'ut' },
			 pass( "remove login" ), fail( "remove login" ) );
    $.yote.logout();
    var d2 = new Date();
    $('#tests').append( "<br>tests completed in " + Math.round(d2.getTime() - t2) + " ms" );
} //wrap_up
var step_count = 0;
function step( msg ) {
    console.log( "step " + step_count + ':' + msg  );
    step_count++;
}
$().ready(function(){ 
    /* check 
     *    account creation   ^
     *    removing account   ^
     *    login              ^
     *    app fetching       ^
     *    object methods     ^
     *    returned scalar    ^
     *    array              
     *    hash
     *    object
     */
    function do_tests() {
        // init yote
        $.yote.init();
        $.yote.logout();
        $.yote.debug = false;

	try {
            //fetch app that doesn't require login and get scalar
            step('get app testappnologin');
            var nologin = $.yote.fetch_app( 'Yote::Test::TestAppNoLogin' );
            nologin._reset();
            step('scalar called on testappnologin');
            var scalar = nologin.scalar( {}, pass("returned login scalar" ), fail("no login scalar returned") );
            is( scalar, "BEEP", "value of login scalar" );
            
            //fetch app that requires login and get scalar
            step('get app testappneedslogin');

            var login = $.yote.fetch_app( 'Yote::Test::TestAppNeedsLogin' );
	    login.purge_app();
            login._reset();
            step('scalar called on testappneedslogin');
            var scalar = login.scalar( {}, 
                                       fail("required login returned scalar without login"),
	                               pass("required login returned nothingwithout login") );
            
            step('create account');
            // account creation. 
            $.yote.fetch_root().create_login( { h: 'unit_test_account0', p: 'ut',e: 'nobodyelse@fenowyn.com' },
				 pass("create first account"), pass("create first account already existed") );

            $.yote.fetch_root().create_login( { h: 'unit_test_account', p: 'ut', e: 'nobody@fenowyn.com'},
				 pass("create first account"), fail("create account") );
	    
            step('create account with same handle');
            $.yote.fetch_root().create_login( { h: 'unit_test_account', p: 'ut', e: 'zobody@fenowyn.com' },
				 fail("created account with same handle"), pass("refused to create account with same handle") );

            $.yote.fetch_root().create_login( { h: 'nunit_test_account', p: 'ut', e: 'nobody@fenowyn.com'},
				 fail("created account with same email"), pass("refused to create account with same email") );
	    
            step('login with wrong password');
	    $.yote.login( 'unit_test_account','uz',
			  fail( "able to log in with wrong password" ),
			  pass( "unable to log in with wrong password" ) );
            step('login with correct password');
            $.yote.login( 'unit_test_account', 'ut',
			  pass( "able to log in" ),
			  fail( "unable to log in" ) );

            step('login has token');
	    ok( $.yote.token != '', 'login has token', 'token missing from login' );
	    ok( $.cookie('yoken') != '', 'cookie was set', 'cookie was not set' );

            login.reset();

	    var token_login = $.yote.fetch_root().token_login( $.yote.token );
	    ok( typeof token_login === 'object', 'has a login with token', 'cannot login with token' );	

            //app that requires login should allow scalar now
            step('scalar called on testappneedslogin to succeed');
            var scalar = login.scalar( {}, pass("required login returned scalar with login"), fail("required login returned scalar with login") );
            ok( typeof scalar !== 'object' && scalar === 'ZEEP', "no login scalar");
            // have a Yote object returned
            step('yote_obj get called on testappneedslogin to succeed');
	    
            var obj = login.get_yote_obj();
            if( typeof obj !== 'object' ) {
		fail( "yote_obj not returned" )();
		return;      
            }
	    is( login.list().length(), 3, " list properly returned " );

            // try passing in a javascript proxy object as a data parameter
            var o1 = login.make_obj( {Text:'foo'}, pass( "created object 1" ), fail( "create object 1" ) );
            var o2 = login.make_obj( {Text:'bar'}, pass( "created object 2" ), fail( "create object 2" ) );
            login.give_obj( o1, pass( "gave object 1" ), fail( "gave object 1" ) );
            is( o1.get_Text(), 'foo', 'correct object 1 text directly a' );
            is( o2.get_Text(), 'bar', 'correct object 2 text directly a' );
            login.give_obj( o2, pass( "gave object 2" ), fail( "gave object 2" ) );
            is( login.obj_text(), 'bar', 'correct object 1 text' ); //should have dirty sent back

            var o3 = login.get_obj(); //<--- STILL FAIL
            is( o3.get_Text(), 'bar', 'correct object 1 text directly' );

            login.give_obj( o1, pass( "gave object 2" ), fail( "gave object 2" ) );
            is( login.obj_text(), 'foo', 'correct object 2 text' );
            o3 = login.get_obj();
            is( o3.get_Text(), 'foo', 'correct object 2 text directly' );

            o3._stage( 'Text', 'baz' );
            is( o3.get_Text(), 'baz', 'correct object 2 text directly' );
            o3._reset();
            is( o3.get_Text(), 'foo', 'correct object 2 text directly' );

            o3._stage( 'Text', 'baz' );
            is( o3.get_Text(), 'baz', 'correct object 2 text directly' );

            o3._send_update();
            is( o3.get_Text(), 'baz', 'correct object 2 text directly' );

	    //Text is writable, zap doesn't exist and so it not, and bext is not writeable
	    is( login.get_Text(), 'inity', "TANL initied text" );
	    is( login.get_zap(), 'zappy', "TANL initied zap" );
	    ok( typeof o3[ 'set_Text' ] !== undefined, "text is there" );
	    ok( typeof o3[ 'set_bext' ] !== undefined, "bext is there" );
	    is( o3[ 'set_zap' ], undefined, "zap is not there" );
            login._send_update({Text:'baf',bext:'Again',zip:'zap'});
	    is( login.get_Text(), 'baf', "TANL changed text" ); 
	    is( login.get_zap(), 'zappy', "TANL unchnaged zap" );
            is( o3.get_Text(), 'baz', 'correct object 2 text directly did not change' );
            is( o3.get_bext(), 'Something else', 'correct object 2 text directly' );
            is( o3.get('zip'), false, 'correct object 2 text directly' );

            pass( "no login returns yote obj" )();      
            step('name get called on testappneedslogin to succeed');
            var initval = obj.get_name();
            is( initval, 'INITY', "yote object inited on server side" );
            
            // have yote array returned
            step('array called on testappneedslogin to succeed');
            var arry = login.array();
            is( arry.length(), 4, 'length of array returned' );
            is( arry.get(0), 'A', 'element 0 correct' );

	    // test sorting functions
	    step( 'testing sorting function' );
	    var tosortarry = arry.get( 3 );
	    is( tosortarry.length(), 6, 'length of sorting array' );
	    is( tosortarry.get(0), 'b', 'first el' );
	    is( tosortarry.get(5), 'c', 'last el' );
	    var sortedarry = tosortarry.sort();
	    is( sortedarry.length, 6, 'sorted array length' );
	    is( sortedarry[0], 'a', 'first el sorted array' );
	    is( sortedarry[5], 'f', 'last el sorted array' );
	    sortedarry = tosortarry.sort(function( a,b ) { if( a > b ) { return -1; } if( a < b ) { return 1; } return 0 ; });
	    is( sortedarry[5], 'a', 'last el rev sorted array' );
	    is( sortedarry[0], 'f', 'first el rev sorted array' );
	    

            //check if el 2 is object
            var inobj = arry.get(2);
            
            step('name get called on testappneedslogin to succeed');
            is( inobj.get_name(), 'INITY', "yote object inited on server side" );

            // have yote hash returned
            step('get hash in array');
            var h = arry.get(1);
            step('get size of hash');
            is( h.length(), 1, 'hash has correct numbers' );
            step('get inner array of hash');
            var inarry = h.get('inner');
            step('inner array correct length');
            is( inarry.length(), 2, 'inner array has correct length' );
            step('first element in inner array');
            is( inarry.get(0), 'Juan', '1st el inner array' );
            step('second element hash in inner array');
            var inh = inarry.get(1);
            step('2nd hash length');
            is( inh.length(), 2, 'inner hash length' );
            step('2nd hash peanut value');
            is( inh.get('peanut'), 'Butter', 'first value inner hash' );
            step('2nd hash ego object with id');
            is( inh.get('ego').id, $.yote.objs[inh.get('ego').id].id, 'object stored in root object cache' ); 
            step('2nd hash ego object with name');
            is( inh.get('ego').get_name(), 'INITY', 'object stored in inner hash' );

            //test javascript object caching and multi loading of objects.
            $.yote._dump_cache();
            is( $.yote._cache_size(), 0, "cache now empty" );
            var login = $.yote.fetch_app( 'Yote::Test::TestAppNeedsLogin' );
            is( $.yote._cache_size(), 3, "cache with loginapp and root and obj in loginapp" );

	    var hello_app = $.yote.fetch_app( 'Yote::Test::Hello' );
	    hello_app.hello();
	    is( hello_app.get_my_hash().get( 'store' ).get( 'AnObject' ).get_flavor(), 'blueberry', "chained objects passed correctly" );

	    // testing pagination

	    var pag = hello_app.wrap_list( { collection_name : 'foo_array', size : 5 } );
	    
	    var pag_list = pag.to_list();

	    is( pag.full_size(), 26, "26 letters of the alphabet" );
	    is( pag.get( 0 ), "A", "first letter" );
	    is( pag_list[ 0 ], "A", "first letter output list" );
	    is( pag.page_size, 5, "Size of 5" );
	    is( pag_list.length, 5, "output list has 5" );
	    is( pag.get( 4 ), "E", "fifth letter" );
	    is( pag_list[ 4 ], "E", "fifth letter output list" );
	    pag.forwards();
	    pag_list = pag.to_list();
	    is( pag_list[ 0 ], "F", "first letter output list after forward" );
	    is( pag.get( 0 ), "F", " first after forward" );
	    is( pag.get( 4 ), "J", " fifth after forward" );
	    is( pag_list[ 4 ], "J", "fifth letter output list after forward" );
	    pag.seek( 22 );
	    pag_list = pag.to_list();
	    is( pag_list[ 0 ], "W", "output list; starting at w" );
	    is( pag.get( 0 ), "W", "Starting at w" );
	    is( pag.get( 3 ), "Z", "Ending with Z" );
	    is( pag_list[ 3 ], "Z", "output list; ending with Z" );
	    pag.last();
	    pag_list = pag.to_list();
	    is( pag_list[ 4 ], "Z", "output list; Z is last" );
	    is( pag.get( 4 ), "Z", "Z is last" );
	    is( pag.get( 0 ), "V", "start with V" );
	    is( pag_list[ 0 ], "V", "output list after last, start with V" );
	    pag.back();
	    pag_list = pag.to_list();
	    is( pag_list[ 0 ], "Q", "output list after last, start with Q" );
	    is( pag.get( 0 ), "Q", "Q is for Rewind" );
	    is( pag.get( 4 ), "U", "U is for U Rewind" );
	    pag.first();
	    is( pag.get( 0 ), "A", "Back to A" );
	    is( pag.get( 4 ), "E", "Back to E" );

	    var bar_hash = hello_app.get_bar_hash().to_hash();
	    var hpag = hello_app.hash_paginator( 'bar_hash', 10 );
	    is( hpag.full_size, 26, "26 letters of the alphabet" );
	    is( hpag.page_size, 10, "Page Size of 10" );
	    is( hpag.page_count(), 10, "10 in buffer" );

	    //go through them all and see if there is a-z but no more than that.
	    var pag_ok = true;
	    var rounds = 0;
	    while( pag_ok && hpag.can_fast_forward() ) {
		if( rounds > 0 ) {hpag.fast_forward(); }
		for( var k in hpag.contents ) {
		    bar_hash[ k ]++;
		    pag_ok = pag_ok && bar_hash[ k ] == 2;
		}
		++rounds;
	    }
	    for( var k in bar_hash ) {
		pag_ok = pag_ok && bar_hash[ k ] == 2;
	    }
	    is( rounds, 3, "took 3 rounds of 10 to go through the alphabet" );
	    ok( pag_ok, "all letters are present and there are no extra ones" );


	    pag = hello_app.wrap_list( { collection_name : '_foo_array', size : 5 } );

	    var pag_list = pag.to_list();

	    is( pag.full_size(), 26, "26 letters of the alphabet" );
	    is( pag.get( 0 ), "A", "first letter" );
	    is( pag_list[ 0 ], "A", "first letter output list" );
	    is( pag.page_size, 5, "Size of 5" );
	    is( pag_list.length, 5, "output list has 5" );
	    is( pag.get( 4 ), "E", "fifth letter" );
	    is( pag_list[ 4 ], "E", "fifth letter output list" );
	    pag.forwards();
	    pag_list = pag.to_list();
	    is( pag_list[ 0 ], "F", "first letter output list after forward" );
	    is( pag.get( 0 ), "F", " first after forward" );
	    is( pag.get( 4 ), "J", " fifth after forward" );
	    is( pag_list[ 4 ], "J", "fifth letter output list after forward" );
	    pag.seek( 22 );
	    pag_list = pag.to_list();
	    is( pag_list[ 0 ], "W", "output list; starting at w" );
	    is( pag.get( 0 ), "W", "Starting at w" );
	    is( pag.get( 3 ), "Z", "Ending with Z" );
	    is( pag_list[ 3 ], "Z", "output list; ending with Z" );
	    pag.last();
	    pag_list = pag.to_list();
	    is( pag_list[ 4 ], "Z", "output list; Z is last" );
	    is( pag.get( 4 ), "Z", "Z is last" );
	    is( pag.get( 0 ), "V", "start with V" );
	    is( pag_list[ 0 ], "V", "output list after last, start with V" );
	    pag.back();
	    pag_list = pag.to_list();
	    is( pag_list[ 0 ], "Q", "output list after last, start with Q" );
	    is( pag.get( 0 ), "Q", "Q is for Rewind" );
	    is( pag.get( 4 ), "U", "U is for U Rewind" );
	    pag.first();
	    is( pag.get( 0 ), "A", "Back to A" );
	    is( pag.get( 4 ), "E", "Back to E" );

	    var bar_hash = hello_app.get_bar_hash().to_hash();
	    var hpag = hello_app.hash_paginator( 'bar_hash', 10 );
	    is( hpag.full_size, 26, "26 letters of the alphabet" );
	    is( hpag.page_size, 10, "Page Size of 10" );
	    is( hpag.page_count(), 10, "10 in buffer" );

	    //go through them all and see if there is a-z but no more than that.
	    var pag_ok = true;
	    var rounds = 0;
	    while( pag_ok && hpag.can_fast_forward() ) {
		if( rounds > 0 ) {hpag.fast_forward(); }
		for( var k in hpag.contents ) {
		    bar_hash[ k ]++;
		    pag_ok = pag_ok && bar_hash[ k ] == 2;
		}
		++rounds;
	    }
	    for( var k in bar_hash ) {
		pag_ok = pag_ok && bar_hash[ k ] == 2;
	    }
	    is( rounds, 3, "took 3 rounds of 10 to go through the alphabet" );
	    ok( pag_ok, "all letters are present and there are no extra ones" );
	    

	    pag = hello_app.wrap_hash( { collection_name : 'foo_hash', size : 5 } );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();
	    // A B  K L   M    N  O  P  Q  R    S  T  C  U  V    W  X  Y  Z   D E  F G H   I J
	    // 0 1 10 11 12   13 14 15 16 17   18 19  2 20 21   22 23 24 25   3 4  5 6 7   8 9
            // 0   * seek 2                                                          22 seek

	    is( pag.full_size(), 26, "26 letters of the alphabet" );
	    is( pag_hash[ pag_keys[ 0 ] ], "A", "first letter" );
	    is( pag_keys[ 0 ], '0', "first index at start" );
	    is( pag.page_size, 5, "Size of 5" );
	    is( pag_hash[ pag_keys[ 4 ] ], "M", "fifth letter" );
	    pag.forwards();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "N", " first after forward" );
	    is( pag_hash[ pag_keys[ 4 ] ], "R", " last after forward" );

	    pag.seek( 2 );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();
	    is( pag_hash[ pag_keys[ 0 ] ], "K", "small seek k start" );
	    is( pag_hash[ pag_keys[ 4 ] ], "O", "small seek o end" );


	    pag.seek( 22 );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();
	    is( pag_hash[ pag_keys[ 0 ] ], "G", "big seek Starting at G" );
	    is( pag_hash[ pag_keys[ 3 ] ], "J", "big seek Ending with J" );
	    is( pag_keys.length, 4, "keys length after seek" );
	    pag.last();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 4 ] ], "J", "J is last" );
	    is( pag_hash[ pag_keys[ 0 ] ], "F", "last start with F" );
	    pag.back();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "X", "X is for Rewind first" );
	    is( pag_hash[ pag_keys[ 4 ] ], "E", "E is for Rewind last" );
	    pag.first();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "A", "Back to A" );
	    is( pag_hash[ pag_keys[ 4 ] ], "M", "Back to M" );

	    // check to see if hash search function works. Search for all 3 keys :
	    //   13 : N, 23 : X, 3 : D
	    pag.set_hashkey_search_criteria( '3' );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();
	    is( pag_keys.length, 3, '3 pag keys matching the string 3' );
	    is( pag_hash[ pag_keys[ 0 ] ], "N", "first matched 3 key" );
	    is( pag_hash[ pag_keys[ 1 ] ], "X", "second matched 3 key" );
	    is( pag_hash[ pag_keys[ 2 ] ], "D", "third matched 3 key" );
	
	    pag = hello_app.wrap_hash( { collection_name : '_foo_hash', size : 5 } );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    // A B  K L   M    N  O  P  Q  R    S  T  C  U  V    W  X  Y  Z   D E  F G H   I J
	    // 0 1 10 11 12   13 14 15 16 17   18 19  2 20 21   22 23 24 25   3 4  5 6 7   8 9
            // 0   * seek 2                                                          22 seek

	    is( pag.full_size(), 26, "26 letters of the alphabet" );
	    is( pag_hash[ pag_keys[ 0 ] ], "A", "first letter" );
	    is( pag_keys[ 0 ], '0', "first index at start" );
	    is( pag.page_size, 5, "Size of 5" );
	    is( pag_hash[ pag_keys[ 4 ] ], "M", "fifth letter" );
	    pag.forwards();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "N", " first after forward" );
	    is( pag_hash[ pag_keys[ 4 ] ], "R", " last after forward" );

	    pag.seek( 2 );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "K", "small seek k start" );
	    is( pag_hash[ pag_keys[ 4 ] ], "O", "small seek o end" );


	    pag.seek( 22 );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "G", "big seek Starting at G" );
	    is( pag_hash[ pag_keys[ 3 ] ], "J", "big seek Ending with J" );
	    is( pag_keys.length, 4, "keys length after seek" );
	    pag.last();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 4 ] ], "J", "J is last" );
	    is( pag_hash[ pag_keys[ 0 ] ], "F", "last start with F" );
	    pag.back();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "X", "X is for Rewind first" );
	    is( pag_hash[ pag_keys[ 4 ] ], "E", "E is for Rewind last" );
	    pag.first();
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );
	    pag_keys.sort();

	    is( pag_hash[ pag_keys[ 0 ] ], "A", "Back to A" );
	    is( pag_hash[ pag_keys[ 4 ] ], "M", "Back to M" );

	    // check to see if hash search function works. Search for all 3 keys :
	    //   13 : N, 23 : X, 3 : D

	    pag.set_hashkey_search_criteria( '3' );
	    pag_hash = pag.to_hash();
	    pag_keys = Object.keys( pag_hash );

	    pag_keys.sort();
	    is( pag_keys.length, 3, '3 pag keys matching the string 3' );
	    is( pag_hash[ pag_keys[ 0 ] ], "N", "first matched 3 key" );
	    is( pag_hash[ pag_keys[ 1 ] ], "X", "second matched 3 key" );
	    is( pag_hash[ pag_keys[ 2 ] ], "D", "third matched 3 key" );

	    var bar_hash = hello_app.get_bar_hash().to_hash();
	    var hpag = hello_app.hash_paginator( 'bar_hash', 10 );
	    is( hpag.full_size, 26, "26 letters of the alphabet" );
	    is( hpag.page_size, 10, "Page Size of 10" );
	    is( hpag.page_count(), 10, "10 in buffer" );
	    
	    

	    //go through them all and see if there is a-z but no more than that.
	    var pag_ok = true;
	    var rounds = 0;
	    while( pag_ok && hpag.can_fast_forward() ) {
		if( rounds > 0 ) {hpag.fast_forward(); }
		for( var k in hpag.contents ) {
		    bar_hash[ k ]++;
		    pag_ok = pag_ok && bar_hash[ k ] == 2;
		}
		++rounds;
	    }
	    for( var k in bar_hash ) {
		pag_ok = pag_ok && bar_hash[ k ] == 2;
	    }
	    is( rounds, 3, "took 3 rounds of 10 to go through the alphabet" );
	    ok( pag_ok, "all letters are present and there are no extra ones" );

	    //timing tests

	    var needs = 4;

	    function meets() {
		needs--;
		if( needs == 0 ) {
		    wrap_up();
		}
	    }


	    login.long_time( '', function( pass_resp ) { pass( "Passed long time test" )(); meets() }, function( fail_resp ) { fail( "Failed long time test" )(); meets();  }, true );
	    login.short_time( '', function( pass_resp ) { pass( "Passed short time test" )(); meets() }, function( fail_resp ) { fail( "Failed short time test" )(); meets(); }, true );
	    login.short_time( '', function( pass_resp ) { pass( "Passed second short time test" )(); meets() }, function( fail_resp ) { fail( "Failed second short time test" )(); meets(); }, true );
	    login.short_time( '', function( pass_resp ) { pass( "Passed third short time test" )(); meets() }, function( fail_resp ) { fail( "Failed third short time test" )(); meets(); }, true );
	}
	catch( err ) {
	    fail( err )();
	    wrap_up();
	}
    } //do_tests

    do_tests();

    //    try {
    /*
      }
      catch( err ) {
      console.dir( 'got err ' + err  );
      if( err.stack ) { console.dir( err.stack ) }
      alert( 'got err ' + err );
      }
    */
} ); //ready
</script>

</head>
<body>
<h1>Yote Unit Tests</h1>
<div id=tests></div>
<div id=results></div>
</body>
</html>