After another days work I found that I didn't have my first pass quite
right.
It turns out that any function that does a config.assertions.push()
[outside
the test fixture itself] can change the order that assertions are made
and need to be included in my "waiting" list.  I'm going to see if I
can
figure out how to post my changes to the SVN server to see if other
people can try it out.

People asked me off line what my reason was for wanting to make these
changes.  I have a number of use cases where I'm writing code that
updates the lifecycle of a loading web page.

Two examples are a dynamic JavaScript loader utility that manages
dynamic loading of new JavaScript by creating <script> tags after the
page
onload events have completed.  Some of the tests I'm writing are to
test
the lifecycle of the page load (in a child iframe) in an automated
way.
Because the iframe itself is loaded async and the JavaScript that it
loads
is async (lots of use of setTimer() everywhere) I can't count on the
specific
ordering of code.  Infact different browsers (IE6, IE7, FF3, Chrome1,
and Safari4Beta have been tested so far) all have very different event
orderings on page load.

The second use case is that I have an iframe manger utility that
uses the JavaScript loader utiltiy to bootstrap itself.  It's job is
to
dynamically create cross domain iframe widgets that communicate
back to the parent page using the # fragment signaling technique.
This code is also inherently async and unordered.

A sample test page I'm using has an iframe loading the actual
page under lifecycle test, which loads 3+ dynamic scripts,
which load 9 grandchild iframes, which each load another 2+
scripts which each load another 9 great-grandchild iframes,
which load another script.  In total the page being tested has
19 iframes, and a minimum of 30 dynamically loaded scripts.
All of this is happening async and has been verified to have
different load orders between browsers and even between
loads on the same browser.  Amazingly with a little enhanced
QUnit testing it seems fairly robust, cross browser computable
and highly performant.

An example of the test that uses this new wait() looks like:

    test("Ensure iframes are properly constructed.", function() {
        expect(3);
        // contentWindow is undefined at first but then becomes
available as the first iframe loads
        wait(function() {
            return $("#test").attr("contentWindow");
        }, "Wait for contentWindow.", 2500, 100);
        // I can now run assertions that only depend on the child
iframe existing
        // ...
        // and eventually this iframe creates 9 grandchild iframes
        wait(function() {
            return $("#test").attr("contentWindow").frames.length ===
9
        }, "Wait for 9 Iframes in content window.", 2500, 100);
        // I can now run tests that depend on the grandchildren having
been created
        // ... but I have to wait longer until they are resized to
>200x200px ...
        wait(function() {
            var test = true,
                iframes = $("iframe", $("#test").attr
("contentWindow").document),
                i;
            $("iframe", $("#test").attr("contentWindow").document)
            for (i=0; i < iframes.length; i+= 1) {
                if (iframes[i].height < 200 || iframes[i].width < 200)
{
                    test = false;
                    break;
                }
            }
            return test;
        }, "Check that all iframes are > 200x200 in size.", 2500,
100);
          // Note when I put ok() tests here they should all wait for the
previous wait()
        // tests to complete as ordering is important in these async
tests.
    });


--
Frederick (Fritz) Staats

On Feb 26, 12:10 pm, Frederick Staats <freder...@staats.org> wrote:
> I've started using QUnit and discovered that it's support for
> asynchronous tests really didn't meet my needs.  After working with it
> for a while I figured out what I needed and implemented it as an
> extension to the existing testrunner.js:
>
> ok() does not take a function as an assertion, the fix to extend ok()
> to accept function callbacks is trivial.
>
> start() and stop() are non-blocking tests in the sense that assertions
> that after a stopped test will be run out of order.  My use cases
> require that the assertions always be run in order because the
> conditions they test for are asynchronous and dependent on each
> other.  Also you have to hand code polling every time you use start()/
> stop() which is a pain.   Multiple pending stop() are not supported.
> Because of the existing tests depend on start()/stop() it is too late
> to propose changing their behavior.
>
> I propose a new function called wait(callback, msg, timeout,
> increment) that implements a polling test/retest that has adjustable
> timeout period and test increment frequency and blocks progress in the
> process() function until the assertion passes or the timeout is
> exceeded.  Multiple pending wait() are supported.
>
> The only problem I have with this solution is that the assertions are
> still reported in the order of callback result processing rather than
> the order as declared in the code.  However asynchronous assertion
> execution is now properly ordered at run time.  I'm open for
> suggestions on how to get the code order of assertions to match the
> reporting order with either start()/stop() or my new wait() function.
> I'm thinking tagging the assertions order at declaration time and then
> sorting the output at report time will be required.
>
> A working proposed implementation of these changes is below.
> config.waiting is added, wait() is exported to the global environment,
> ok() and process() are modified and the implementation of wait() is
> added:
>
> var config = {
>         stats: {
>                 all: 0,
>                 bad: 0
>         },
>         queue: [],
>         // block until document ready
>         blocking: true,
>         waiting: [],
>         //restrict modules/tests by get parameters
>         filters: GETParams,
>         isLocal: !!(window.location.protocol == 'file:')
>
> };
>
> // public API as global methods
> $.extend(window, {
>         test: test,
>         module: module,
>         expect: expect,
>         ok: ok,
>         equals: equals,
>         start: start,
>         stop: stop,
>         wait: wait,
>
> ...
>
> function ok(a, msg) {
>     if (typeof a === "function") {
>         a = a();
>     }
>         config.assertions.push({
>                 result: !!a,
>                 message: msg
>         });
>
> }
>
> ...
>
> function process() {
>     var wait, callback, msg, time, increment, test;
>     while (config.queue.length && !config.blocking) {
>         while (config.waiting.length > 0) {
>             wait = config.waiting[0];
>             callback = wait[0];
>             msg = wait[1];
>             time = wait[2];
>             increment = wait[3];
>             if (time && time < (new Date()).getTime()) {
>                 test = false;
>             } else {
>                 test = callback();
>                 if (!test) {
>                     setTimeout(function () {
>                         process();
>                     }, increment);
>                     return;
>                 }
>             }
>             ok(test, msg);
>             config.waiting = config.waiting.slice(1);
>         }
>         config.queue.shift()();
>     }
>
> }
>
> function wait(callback, msg, timeout, increment) {
>     if (!increment) {
>         increment = 100; // Default increment 1/10 second
>     }
>     var time = (new Date()).getTime() + timeout;
>     config.waiting.push([callback, msg, time, increment]);
>
> }
>
> Here is an example test using the new wait() and ok() with a callback:
>
>     test("Ensure iframe is properly constructed.", function() {
>         expect(5);
>         wait(function() { return $("#test").attr("contentWindow"); },
>             "Wait for contentWindow to exist.", 2500, 250);
>         wait(function() { return $("#test").attr
> ("contentWindow").frames.length === 9; },
>             "Wait for 9 Iframes to be loaded in the content window.",
> 2500, 100);
>         ok(function() { return TestSomeThingInsideThe9Frames() },
> "Test iframe contents.");
>     });
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"jQuery Development" group.
To post to this group, send email to jquery-dev@googlegroups.com
To unsubscribe from this group, send email to 
jquery-dev+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/jquery-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to