substack/tape — pull request #51 by grncdr (2013-10-14 19:57:28+00:00)
Title: Refactor nested tests

This is something of a rewrite to the way nested tests are handled.

Functionally, this changes one thing: nested tests now count against the test plan (like in isaacs/node-tap).

Structurally, this changes the way the `Results` and `Test` objects interact. Instead of having the `Results` object be responsible for managing a flattened list of tests-to-be-run, test now signal the results object with the next subtest to be run from their `.end` method. This leads to a (hopefully) easier to follow recursive algorithm for running the tests. This passes against all the current tests (and one I've added), but I'd appreciate people running this branch against their current test suite and reporting failures here, so that I can add more test cases.

Here's the algorithm described in detail:

The top-level `test` function remains the same, but `Results.prototype.push` has been slimmed down:
- The test is pushed onto the the `self.tests` array.
- An `'end'` event listener is attached in `.push` that will run the next test in `self.tests`.
- All other event listeners (`'prerun'`, `'result'`, `'test'`, `'next'` are added to each new `Test` object using `self._watch(test)`.

When a test (hereafter referred to as a parent test) is started by the results object:

1. The results object calls `.run()` on the parent test.
2. The parent test defines a sub-test: `t.test('child', function () {...})`.
- The `'test'` event is emitted with the new child test.
- The `Results` object receives the subtest and attaches event listeners using `self._watch(subtest)`. (Recall from above that no `'end'` listener is attached here).
3. `parentTest.end()` is called, either manually or by performing _n_ assertions, where _n_ is the planned number of tests subtract the number of pending sub-tests. _This is a change in behaviour that matches tap_
4. The parent test shifts off the first sub-test.
5. If no sub-test is found:
- the test finishes by checking the plan, emitting an `'end'` event, etc. etc.
6. If a subtest is found:
- the parent test attaches a listener to the subtests `'end'` event that will re-call it's own `parent.end()` (recurring back to step 4) and then emits the `'next'` event with the subtest.
- The results object receives the `'next'` event, and recurs to step 1, with the subtest becoming the new parent test.