<!DOCTYPE html>

<html>

  <head>

    <title>Virtual World Framework</title>

    <script type="text/javascript" src="qunit.js"></script>

    <script type="text/javascript" src="../lib/async.js"></script>

    <script type="text/javascript" src="../lib/crypto.js"></script>
    <script type="text/javascript" src="../lib/md5.js"></script>

    <script type="text/javascript" src="../lib/alea.js"></script>
    <script type="text/javascript" src="../lib/mash.js"></script>

    <script type="text/javascript" src="../lib/vwf.js"></script>

    <script type="text/javascript" src="../lib/require.js"></script>

    <script type="text/javascript">

      require( {

        baseUrl: "../lib",
        paths: {
          jquery: "jquery-1.10.2.min",
        },

      }, [

        "domReady",
        "utility.js",

        "jquery",
        "vwf/configuration",
        "vwf/kernel/model",
        "vwf/model/javascript",
        "vwf/model/object",
        "vwf/model/stage/log",
        "vwf/kernel/view",
        "vwf/view/document",
        "vwf/kernel/utility",
        "vwf/utility",
        "logger",

      ], function( ready, testUtility ) {

        // Test the kernel "future" interface.

        ready( function() {

          vwf.initialize(
            /* models */ [ "vwf/model/javascript", "vwf/model/object" ],
            /*  views */ [ "vwf/view/document" ]
          );

          var kernel = require( "vwf/kernel/model" ).create( vwf ); // connect through the kernel's model interface

          // Time passage from future actions and the periodic interval.

          asyncTest( "Time passage", function() {

            createFixture( function( fixtureID, cleanup ) {

              // Calculate times. We need to find two system tick times and additional times between
              // the system ticks. This calculation will need to be adjusted if the single-user tick
              // interval changes.

              var interval = 10; // assumed periodic tick interval

              var timeStart = ( Math.floor( Math.round( vwf.time() * 1000 ) / interval ) *
                interval + interval ) / 1000; // next periodic tick

              var timeStop = ( Math.floor( Math.round( vwf.time() * 1000 ) / interval ) *
                interval + interval + interval ) / 1000; // the following periodic tick

              var time1 = fixed3( timeStart + 0.001 );
              var time2 = fixed3( timeStart + 0.002 );
              var time3 = fixed3( timeStart + 0.003 );

              timeStart = fixed3( timeStart );
              timeStop = fixed3( timeStop );

              // Create a function to drop tags by timestamp in this.property.

              kernel.execute( fixtureID, "this.mark = function( tag ) { " +
                "var time = this.time.toFixed(3); " +
                "this.property[time] || ( this.property[time] = [] ); " +
                "this.property[time].push( tag ); " +
              "}" );

              kernel.execute( fixtureID, "this.property = {}" );

              // Schedule the actions.

              kernel.execute( fixtureID, "", undefined, time1 ); // nothing

              kernel.execute( fixtureID, "this.mark( 'one' )", undefined, time2 ); // one thing

              kernel.execute( fixtureID, "this.mark( 'two' )", undefined, time3 ); // two things
              kernel.execute( fixtureID, "this.mark( 'three' )", undefined, time3 );

              kernel.execute( fixtureID, "", undefined, timeStop ); // nothing, exactly on the tick

              // Wait and test.

              testUtility.runFutureAssertions( function( time ) {

                vwf.execute( fixtureID, "this.mark( 'tick' )" ); // tick, just before assertions
                
              }, [

                { absolute: timeStart, assertion: function() {
                    deepEqual( vwf.execute( fixtureID, "this.property" )[ timeStart ], [ "tick" ], "tick at the periodic interval" ) } },

                { absolute: time1, assertion: function() {
                    deepEqual( vwf.execute( fixtureID, "this.property" )[ time1 ], [ "tick" ], "tick when a future action executes" ) } },

                { absolute: time2, assertion: function() {
                    deepEqual( vwf.execute( fixtureID, "this.property" )[ time2 ], [ "tick", "one" ], "tick is before the future action" ) } },

                { absolute: time3, assertion: function() {
                    deepEqual( vwf.execute( fixtureID, "this.property" )[ time3 ], [ "tick", "two", "three" ], "one tick per group of future actions" ) } },

                { absolute: timeStop, assertion: function() {
                    deepEqual( vwf.execute( fixtureID, "this.property" )[ timeStop ], [ "tick" ], "one tick with the future action at the periodic interval time" ) } },

              ], cleanup );

            } );

          } );

          // Future JavaScript API

          asyncTest( "Future JavaScript", function() {

            createFixture( function( fixtureID, cleanup ) {

              vwf.setProperty( fixtureID, "property", undefined );

              var time0 = fixed3( vwf.time() );
              var time1 = fixed3( time0 + 0.011 );
              var time2 = fixed3( time0 + 0.012 );
              var time3 = fixed3( time0 + 0.013 );

              vwf.execute( fixtureID, "this.in( " + "0.011" + " ).property = 'in'" );
              vwf.execute( fixtureID, "this.at( " + time2 + " ).property = 'at'" );
              vwf.execute( fixtureID, "this.future( " + "0.013" + " ).property = 'future'" );

              testUtility.runFutureAssertions( [

                { absolute: time1, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "in", "relative" ) } },

                { absolute: time2, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "at", "absolute" ) } },

                { absolute: time3, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "future", "relative alias" ) } },

              ], cleanup );

            } );

          } );

          // Immediate actions directly to the kernel

          asyncTest( "Immediate kernel", function() {

            createFixture( function( fixtureID, cleanup ) {

              kernel.setProperty( fixtureID, "property", "property", undefined );
              equal( vwf.getProperty( fixtureID, "property" ), "property", "immediate property" );

              kernel.callMethod( fixtureID, "method", [ "method" ], undefined );
              equal( vwf.getProperty( fixtureID, "property" ), "method", "immediate method" );

              kernel.fireEvent( fixtureID, "event", [ "event" ], undefined );
              equal( vwf.getProperty( fixtureID, "property" ), "event", "immediate event" );

              cleanup();
              start();

            } );

          } );

          // Immediate actions through JavaScript

          asyncTest( "Immediate JavaScript", function() {

            createFixture( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.property = 'property'" );
              equal( vwf.getProperty( fixtureID, "property" ), "property", "immediate property" );

              vwf.execute( fixtureID, "this.method( 'method' )" );
              equal( vwf.getProperty( fixtureID, "property" ), "method", "immediate method" );

              vwf.execute( fixtureID, "this.event( 'event' )" );
              equal( vwf.getProperty( fixtureID, "property" ), "event", "immediate event" );

              cleanup();
              start();

            } );

          } );

          // Future actions directly to the kernel

          asyncTest( "Future kernel", function() {

            createFixture( function( fixtureID, cleanup ) {

              vwf.setProperty( fixtureID, "property", undefined );
              equal( vwf.getProperty( fixtureID, "property" ), undefined, "future initialized" );

              kernel.setProperty( fixtureID, "property", "property", -0.001 );
              kernel.callMethod( fixtureID, "method", [ "method" ], -0.002 );
              kernel.fireEvent( fixtureID, "event", [ "event" ], -0.003 );

              equal( vwf.getProperty( fixtureID, "property" ), undefined, "future queued" );

              testUtility.runFutureAssertions( [

                { relative: 0.001, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "property", "future property" ) } },

                { relative: 0.002, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "method", "future method" ) } },

                { relative: 0.003, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "event", "future event" ) } },

              ], cleanup );

            } );

          } );

          // Future actions through JavaScript

          asyncTest( "Future JavaScript", function() {

            createFixture( function( fixtureID, cleanup ) {

              vwf.setProperty( fixtureID, "property", undefined );
              equal( vwf.getProperty( fixtureID, "property" ), undefined, "future initialized" );

              vwf.execute( fixtureID, "this.future( 0.001 ).property = 'property'" );
              vwf.execute( fixtureID, "this.future( 0.002 ).method( 'method' )" );
              vwf.execute( fixtureID, "this.future( 0.003 ).event( 'event' )" );

              equal( vwf.getProperty( fixtureID, "property" ), undefined, "future queued" );

              testUtility.runFutureAssertions( [

                { relative: 0.001, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "property", "future property" ) } },

                { relative: 0.002, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "method", "future method" ) } },

                { relative: 0.003, assertion: function() {
                    equal( vwf.getProperty( fixtureID, "property" ), "event", "future event" ) } },

              ], cleanup );

            } );

          } );

          // Future inherited actions through JavaScript direct properties

          asyncTest( "Future JavaScript inherited direct", function() {

            createFixtureDerivedBase( function( derivedID, baseID, cleanup ) {

              equal( vwf.getProperty( derivedID, "derived" ), "derived", "derived in derived initialized" );
              equal( vwf.getProperty( derivedID, "base" ), "base", "base in derived initialized" ); // inherited
              equal( vwf.getProperty( baseID, "base" ), "base", "base in base" );

              vwf.execute( derivedID, "this.future( 0.001 ).derived = 'derived updated'" );
              vwf.execute( derivedID, "this.future( 0.002 ).base = 'base updated'" ); // inherit from base, assign to derived
              
              equal( vwf.getProperty( derivedID, "derived" ), "derived", "derived in derived queued" );
              equal( vwf.getProperty( derivedID, "base" ), "base", "base in derived queued" ); // still inherited
              equal( vwf.getProperty( baseID, "base" ), "base", "base in base unchanged" );

              testUtility.runFutureAssertions( [

                { relative: 0.001, assertion: function() {
                    equal( vwf.getProperty( derivedID, "derived" ), "derived updated", "derived in derived executed" ) } },

                { relative: 0.002, assertion: function() {
                    equal( vwf.getProperty( derivedID, "base" ), "base updated", "base in derived executed" ) // no longer inherited
                    equal( vwf.getProperty( baseID, "base" ), "base", "base in base unchanged" ) } },

              ], cleanup );

            } );

          } );

          // Future inherited actions through JavaScript collection properties

          asyncTest( "Future JavaScript inherited collection", function() {

            createFixtureDerivedBase( function( derivedID, baseID, cleanup ) {

              equal( vwf.getProperty( derivedID, "derived" ), "derived", "derived in derived initialized" );
              equal( vwf.getProperty( derivedID, "base" ), "base", "base in derived initialized" ); // inherited
              equal( vwf.getProperty( baseID, "base" ), "base", "base in base" );

              vwf.execute( derivedID, "this.future( 0.001 ).properties.derived = 'derived updated'" );
              vwf.execute( derivedID, "this.future( 0.002 ).properties.base = 'base updated'" ); // inherit from base, assign to derived
              
              equal( vwf.getProperty( derivedID, "derived" ), "derived", "derived in derived queued" );
              equal( vwf.getProperty( derivedID, "base" ), "base", "base in derived queued" ); // still inherited
              equal( vwf.getProperty( baseID, "base" ), "base", "base in base unchanged" );

              testUtility.runFutureAssertions( [

                { relative: 0.001, assertion: function() {
                    equal( vwf.getProperty( derivedID, "derived" ), "derived updated", "derived in derived executed" ) } },

                { relative: 0.002, assertion: function() {
                    equal( vwf.getProperty( derivedID, "base" ), "base updated", "base in derived executed" ) // no longer inherited
                    equal( vwf.getProperty( baseID, "base" ), "base", "base in base unchanged" ) } },

              ], cleanup );

            } );

          } );

          // -- Interactions between reflector messages and future messages ------------------------

          // The queue is must retain a well-defined order when future actions and reflector actions
          // share the same time, even when reflector messages have not arrived at the client yet.
          // 
          // For a sequence of messages having the same time:
          // 
          //   - New future messages are inserted after other future messages and before any
          //     reflector messages.
          //     
          //   - New reflector messages are appended after any future messages and after any other
          //     reflector messages.

          // r0( f0 ) => r0 f0

          asyncTest( "Reflector action generating a future action for the same time", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0, "f0" ] ] ], 0 );

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "r0 executed",      // at time 0
                      "- f0 scheduled",   // at time 0, for time 0
                      "f0 executed",      // at time 0
                    ], "reflector action then future action" ) } },

              ], cleanup );

            } );

          } );

          // r0( f1 ) => r0 f1

          asyncTest( "Reflector action generating a future action for a later time", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "f1" ] ] ], 0 );

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "r0 executed",      // at time 0
                      "- f1 scheduled",   // at time 0, for time 1
                      "f1 executed"       // at time 1
                    ], "reflector action then future action" ) } },

              ], cleanup );

            } );

          } );

          // r0( f0a, f0b ) => r0 f0a f0b

          asyncTest( "Reflector action generating multiple future actions for the same time", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0, "f0a", 0, "f0b" ] ] ], 0 );

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "r0 executed",      // at time 0
                      "- f0a scheduled",  // at time 0, for time 0
                      "- f0b scheduled",  // at time 0, for time 0
                      "f0a executed",     // at time 0
                      "f0b executed",     // at time 0
                    ], "reflector action then future actions in the order of arrival" ) } },

              ], cleanup );

            } );

          } );

          // r0( f1a, f1b ) => r0 f1a f1b

          asyncTest( "Reflector action generating multiple future actions for the same later time", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "f1a", 0.001, "f1b" ] ] ], 0 );

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "r0 executed",      // at time 0
                      "- f1a scheduled",  // at time 0, for time 1
                      "- f1b scheduled",  // at time 0, for time 1
                      "f1a executed",     // at time 1
                      "f1b executed",     // at time 1
                    ], "reflector action then future actions in the order of arrival" ) } },

              ], cleanup );

            } );

          } );

          // r0( f1 ) r1 ...  => r0 f1 r1

          asyncTest( "Reflector action generating a future action for a later time, with a reflector " +
            "action for that same time arriving beforehand", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "f1" ] ] ], 0 );

              vwf.execute( fixtureID, "this.methods.log( '- r1 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1" ] ], -0.001 );

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "- r1 scheduled",   // at time 0, for time 1
                      "r0 executed",      // at time 0
                      "- f1 scheduled",   // at time 0, for time 1
                      "f1 executed",      // at time 1
                      "r1 executed",      // at time 1
                    ], "reflector action, later future action, then bounding reflector action" ) } },

              ], cleanup );

            } );

          } );

          // r0( f1 ) ... r1 => r0 f1 r1

          asyncTest( "Reflector action generating a future action for a later time, with a reflector " +
              "action for that same time arriving afterwards", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "f1" ] ] ], 0 );

              testUtility.runFutureAssertions( [

                { relative: 0.000, assertion: function() {
                    vwf.execute( fixtureID, "this.methods.log( '- r1 scheduled' )" );
                    vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1" ] ], -0.001 ) } },

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "r0 executed",      // at time 0
                      "- f1 scheduled",   // at time 0, for time 1
                      "- r1 scheduled",   // at time 0+, for time 1
                      "f1 executed",      // at time 1
                      "r1 executed",      // at time 1
                    ], "reflector action, later future action, then bounding reflector action" ) } },

              ], cleanup );

            } );

          } );

          // r0( f1a, f1b ) r1a r1b ... => r0 f1a f1b r1a r1b

          asyncTest( "Reflector action generating multiple future actions for the same later time, " +
            "with multiple reflector actions for that time arriving beforehand", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "f1a", 0.001, "f1b" ] ] ], 0 );

              vwf.execute( fixtureID, "this.methods.log( '- r1a scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1a" ] ], -0.001 );

              vwf.execute( fixtureID, "this.methods.log( '- r1b scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1b" ] ], -0.001 );

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "- r1a scheduled",  // at time 0, for time 1
                      "- r1b scheduled",  // at time 0, for time 1
                      "r0 executed",      // at time 0
                      "- f1a scheduled",  // at time 0, for time 1
                      "- f1b scheduled",  // at time 0, for time 1
                      "f1a executed",     // at time 1
                      "f1b executed",     // at time 1
                      "r1a executed",     // at time 1
                      "r1b executed",     // at time 1
                    ], "reflector action, later future actions, then bounding reflector actions" ) } },

              ], cleanup );

            } );

          } );

          // r0( f1a, f1b ) ... r1a r1b => r0 f1a f1b r1a r1b

          asyncTest( "Reflector action generating multiple future actions for the same later time, " +
            "with multiple reflector actions for that time arriving afterwards", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "f1a", 0.001, "f1b" ] ] ], 0 );

              testUtility.runFutureAssertions( [

                { relative: 0.000, assertion: function() {
                    vwf.execute( fixtureID, "this.methods.log( '- r1a scheduled' )" );
                    vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1a" ] ], -0.001 ) } },

                { relative: 0.000, assertion: function() {
                    vwf.execute( fixtureID, "this.methods.log( '- r1b scheduled' )" );
                    vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1b" ] ], -0.001 ) } },

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "r0 executed",      // at time 0
                      "- f1a scheduled",  // at time 0, for time 1
                      "- f1b scheduled",  // at time 0, for time 1
                      "- r1a scheduled",  // at time 0+, for time 1
                      "- r1b scheduled",  // at time 0+, for time 1
                      "f1a executed",     // at time 1
                      "f1b executed",     // at time 1
                      "r1a executed",     // at time 1
                      "r1b executed",     // at time 1
                    ], "reflector action, later future actions, then bounding reflector actions" ) } },

              ], cleanup );

            } );

          } );

          // r0( f1 ) r1a( f1a ) r1b ... => r0 f1 r1a f1a r1b

          asyncTest( "Reflector action generating a future action for a later time, " +
            "with a reflector action for that same time generating a future action for the same time, " +
            "followed by another reflector action for that same time", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "f1" ] ] ], 0 );

              vwf.execute( fixtureID, "this.methods.log( '- r1a scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1a", [ 0, "f1a" ] ] ], -0.001 );

              vwf.execute( fixtureID, "this.methods.log( '- r1b scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1b" ] ], -0.001 );

              testUtility.runFutureAssertions( [

                { relative: 0.020, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "- r1a scheduled",  // at time 0, for time 1
                      "- r1b scheduled",  // at time 0, for time 1
                      "r0 executed",      // at time 0
                      "- f1 scheduled",   // at time 0, for time 1
                      "f1 executed",      // at time 1
                      "r1a executed",     // at time 1
                      "- f1a scheduled",  // at time 1, for time 1
                      "f1a executed",     // at time 1
                      "r1b executed",     // at time 1
                    ], "reflector action, future action, first bounding reflector action, " +
                        "its future action, then second bounding reflector action" ) } },

              ], cleanup );

            } );

          } );

          // r0( ff1( ff1a ) ) r1 ... => r0 ff1 ff1a r1

          asyncTest( "Reflector action generating a future action for a later time, " +
            "which generates a future action for that same time, " +
            "followed by another reflector action for the same time", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              vwf.execute( fixtureID, "this.methods.log( '- r0 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r0", [ 0.001, "ff1" ] ] ], 0 );

              vwf.execute( fixtureID, "this.methods.log( '- r1 scheduled' )" );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1" ] ], -0.001 );

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "- r0 scheduled",   // at time 0, for time 0
                      "- r1 scheduled",   // at time 0, for time 1
                      "r0 executed",      // at time 0
                      "- ff1 scheduled",  // at time 0, for time 1
                      "ff1 executed",     // at time 1
                      "- ff1a scheduled",  // at time 1, for time 1
                      "ff1a executed",     // at time 1
                      "r1 executed",      // at time 1
                    ], "reflector action, future action, its future action, then bounding reflector action" ) } },

              ], cleanup );

            } );

          } );

          // setState should remove any messages from the queue that pre-date the setState action,
          // remove messages generated while initializing nodes during the setState action, insert
          // messages from the setState incoming queue, and leave messages in place that arrived
          // after the setState message.

          asyncTest( "setState queue", function() {

            createFixtureQueue( function( fixtureID, cleanup ) {

              // Add reflector and future messages that pre-date the setState message but that would
              // execute after it at times 2 and 3, respectively. These represent the prior state of
              // the queue. setState should remove them.

              vwf.plan( fixtureID, "callMethod", "future_", [ [ "f2" ] ], -0.002 );
              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r3" ] ], -0.003 );

              // Add the setState message at time 1.

              vwf.send( undefined, "setState", undefined, [ {

                // The created node generates a future message during initialize that would run at
                // time 1, immediately following the setState action. setState should remove it.

                nodes: [
                  testUtility.dataURIFromDescriptor( {
                    extends:
                      fixtureID,
                    scripts: [
                      "this.initialize = function() { " +
                        "Object.getPrototypeOf( this ).future( 0 ).future_( 'f1' ); " +
                      "}"
                    ],
                  } )
                ],

                // Include a future message at time 4 and a reflector message at time 5 in the
                // incoming queue. These should be retained.

                queue: {
                  time:
                    vwf.time() + 0.001,
                  queue: [ {
                    time: vwf.time() + 0.004,
                    node: fixtureID,
                    action: "callMethod",
                    member: "future_",
                    parameters: [ [ "f4" ] ],
                    origin: "future"
                  }, {
                    time: vwf.time() + 0.005,
                    node: fixtureID,
                    action: "callMethod",
                    member: "reflector",
                    parameters: [ [ "r5" ] ],
                    origin: "reflector"
                  } ]
                }

              } ], -0.001 );

              // Add a reflector message at time 1. This represents a new action that the reflector
              // sent following the setState action. It should be retained and should execute after
              // setState.

              vwf.send( fixtureID, "callMethod", "reflector", [ [ "r1" ] ], -0.001 );

              // The expected result is that setState runs and doesn't leave a trace (other than the
              // transformed system state), the following reflector action runs, then the actions
              // from the incoming queue scheduled later run.

              testUtility.runFutureAssertions( [

                { relative: 0.010, assertion: function() {
                    deepEqual( vwf.getProperty( fixtureID, "log" ), [
                      "r1 executed",      // second reflector action at time 1
                      "f4 executed",      // future action from the incoming queue at time 4
                      "r5 executed",      // reflector action from the incoming queue at time 5
                    ], "setState discarded prior actions, added incoming actions, and retained following actions" ) } },

              ], cleanup );

            } );

          } );

          // == Helper functions =====================================================================

          // Create a node with a property, a method and an event.

          function createFixture( callback /* fixtureID, cleanup */ ) {

            vwf.createChild( 0, testUtility.uniqueName( "fixture" ), {

              extends:
                "http://vwf.example.com/node.vwf",
              
              properties: {
                property: undefined,
              },

              methods: {
                method: "this.property = arguments[0]",
              },

              events: {
                event: undefined,
              },

              scripts: [
                "this.event = this.events.add( function() { this.property = arguments[0] }, this )",
              ],

            }, undefined, function( fixtureID ) {

              callback( fixtureID, function() {
                vwf.deleteNode( fixtureID );
              } );

            } );

          }

          // Create a node with two levels of inheritance and properties to manipulate.

          function createFixtureDerivedBase( callback /* derivedID, baseID, cleanup */ ) {

            vwf.createNode( {

              extends:
                "http://vwf.example.com/node.vwf",
              
              properties: {
                base: "base",
              },

            }, function( baseID ) {

              vwf.createNode( {

                extends:
                  baseID,
                
                properties: {
                  derived: "derived",
                },

              }, function( derivedID ) {

                callback( derivedID, baseID, function() {
                  vwf.deleteNode( derivedID );
                  vwf.deleteNode( baseID );
                } );

              } );

            } );

          }

          // Create a node with methods to be called as reflector and future actions that will keep
          // a log.

          function createFixtureQueue( callback /* fixtureID, cleanup */ ) {

            vwf.createChild( 0, testUtility.uniqueName( "fixture" ), {

              extends:
                "http://vwf.example.com/node.vwf",
              
              properties: {
                log: [], // log of methods called on the node
              },

              methods: {

                // A method to be called as a reflector action. Append the first parameter to the
                // log to record that this call occurred, then schedule calls to `future` for each
                // time/tag pair in the `futures` array.

                reflector: {

                  parameters:
                    [ "tag", "futures" ],

                  body:
                    "this.methods.log( tag + ' executed' ); " + // the reflector action executes
                    "while ( ( futures || [] ).length ) { " +
                      "var time = futures.shift(); " +
                      "var tag = futures.shift(); " +
                      "this.methods.log( '- ' + tag + ' scheduled' ); " + // the future action is scheduled
                      "tag.match( /ff/ ) ? " +
                        "this.future( time ).methods.futurefuture( tag, tag + 'a' ) : " + // special case for future to call future
                        "this.future( time ).methods.future_( tag ); " +
                    "}"

                },

                // A method to be called as a future action. Append the parameter to the log to
                // record that this called occurred.

                future_: {

                  parameters:
                    [ "tag" ],
 
                  body:
                    "this.methods.log( tag + ' executed' )" // the future action executes
 
                },

                // A method to be called as a future action that generates another future action.
                // Append the first parameter to the log to record that this call occurred, then
                // schedule an additional call to `future` with the second tag.

                futurefuture: {

                  parameters:
                    [ "tag1", "tag2" ],
 
                  body:
                    "this.methods.log( tag1 + ' executed' ); " + // the future action executes
                    "this.methods.log( '- ' + tag2 + ' scheduled' ); " + // another future action is scheduled
                    "this.future( 0 ).methods.future_( tag2 )"
 
                },

                // Append a tag to the log.

                log: {
                  parameters: [ "tag" ],
                  body: "this.properties.log = this.properties.log.concat( tag )"
                },

              },

            }, undefined, function( fixtureID ) {

              callback( fixtureID, function() {
                vwf.deleteNode( fixtureID );
              } );

            } );

          }

          // Convert a number to a Number object that expresses itself in 3-point fixed precision
          // when referenced in string context.

          function fixed3( number ) {

            var number3 = new Number( number );

            number3.toString = function() {
              return this.toFixed( 3 )
            }

            return number3;            
          }

        } );

      } );

    </script>

    <link rel="stylesheet" type="text/css" href="qunit.css" />

  </head>

  <body>

    <h1 id="qunit-header">Virtual World Framework</h1>
    <h2 id="qunit-banner"></h2>
    <div id="qunit-testrunner-toolbar"></div>
    <h2 id="qunit-userAgent"></h2>
    <ol id="qunit-tests"></ol>

    <div id="qunit-fixture">test markup, will be hidden</div>

  </body>

</html>