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 -~----------~----~----~----~------~----~------~--~---