license import { timeLimit } from './util.js' ; import { Topic } from './topic.js' ; import { Suite } from './suite.js' ; import { ReporterEvent } from './reporter.js' ; /**
 * The basic test configuration object supports a timeout in milliseconds.
 */ export interface TestConfig { timeout ? : number ; } /**
 * A `TestRunContext` represents state that can be shared across test setup
 * and teardown routines in the `Test` classes internal implementation. It
 * is intended to be useful for specializations of the `Test` class, but not
 * directly exposed to the author of a test suite.
 */ export interface TestRunContext { suite : Suite ; implementation : Function ; cancelTimeLimit ? : Function ; } /**
 * A `TestResult` represents the pass or failure of a given test run. The
 * result contains a boolean `passed`, and also an `error` property that
 * contains the relevant error object if the test failed. Note that some
 * kinds of tests cannot report the error object in its original form, so a
 * sparse representation containing only the error stack will be availabe in
 * that case.
 */ export interface TestResult { passed : boolean ; error ? : Error | { stack : string | void ; } ; } ; /**
 * `Test`s are the bread and butter of any good test suite. They represent some
 * chunk of script - referred to as the `implementation` - that asserts some
 * thing related to whatever you are testing. In addition to the test
 * `implementation`, a `Test` also has a related human-readable `description`
 * and a reference to its immediate parent `Topic`. A test can be configured
 * with a `timeout` after which the a test run will automatically fail.
 *
 * A `Test` is typically created when test details are added to a topic. An
 * example of this is whenever `it` is invoked in a test suite:
 *
 * ```javascript
 * it('creates a test', () => {});
 * ```
 *
 * If the `it` invocation is "nested" in a `describe` invocation, it will
 * cause a `Test` to be added to the `Topic` that is created by the `describe`.
 */ export class Test { protected config : TestConfig ; protected implementation : Function ; /**
     * The immediate parent `Topic` of the `Test`.
     */ readonly topic : Topic | void ; /**
     * A human readable description of the `Test`.
     */ readonly description : string ; /**
     * The `behaviorText` of a `Test` is its `description` appended to the
     * `behaviorText` of its immediate parent `Topic`.
     */ get behaviorText ( ) : string { return this . topic != null ? `${ this . topic . behaviorText } ${ this . description }` : this . description ; } /**
     * A time in ms after which a test run will automatically fail. If left
     * unconfigured, the default timeout is 10 seconds.
     */ get timeout ( ) : number { return this . config . timeout || 10000 ; } /**
     * A `Test` is created by providing a human readable `description`, a
     * function implementating the actual test routine, an optional configuration
     * object and an optional reference to a related parent `Topic`.
     */ constructor ( description , implementation , config = { } , topic ? ) { this . config = config ; this . description = description ; this . implementation = implementation ; this . topic = topic ; } /**
     * When preparing to invoke a test `implementation`, there is a "wind-up"
     * phase that allows the `Test` class and any specializations to prepare
     * the `implementation` based on the configured `TestRunContext`. The
     * default `TestRunContext` contains only one key: the current
     * `implementation` of the test. It is important to refer to this
     * `implementation`, as it may be modified by specialized overridden
     * implementations of `windUp`.
     *
     * The `windUp` method should return a modified `TestRunContext`, if
     * any modifications are deemed necessary. The default implementation,
     * for example, modifies the test `implementation` to include a time limit
     * after which the test will automatically fail.
     */ protected async windUp ( context ) { const { timeout } = this ; const { implementation } = context ; const { promise : timeLimitPromise , cancel : cancelTimeLimit } = timeLimit ( timeout ) ; return { ... context , cancelTimeLimit , implementation : ( ... args ) => Promise . race ( [ implementation ( ... args ) , timeLimitPromise ] ) } ; } /**
     * Once a test `implementation` has run and the result has been measured,
     * there is an opportunity to manipulate the result based on the
     * `TestRunContext`. This is useful in cases where the context of the
     * test run may have a specialized effect on the result of the test.
     *
     * For example, a test that has been "skipped" may technically have passed,
     * but it may be desirable to mark the result of such a test as not passed,
     * or to decorate the result with a flag indicating that the test had
     * passed but had been skipped.
     */ protected async postProcess ( _context , result ) { return result ; } /**
     * When a test invokation has completed, there is a "wind-down" phase
     * that allows the `Test` class and any specializations to clean up the
     * `TestRunContext` that was created during the "wind-up" phase. The
     * `windDown` method is always invoked at the end of a test run, regardless
     * of whether the test passed or failed. However, it is not invoked if an
     * exception is thrown during the "wind-up" phase.
     *
     * The default `windDown` method, for example, cancels the time limit
     * created by the default `windUp` method.
     */ protected async windDown ( context ) { context . cancelTimeLimit ! ( ) ; } /**
     * The `run` method initiates a test run. A test run consists of four
     * phases:
     *
     *  1. Wind-up phase, during which the `TestRunContext` is created.
     *  2. Test run phase, when the test `implementation` is invoked and measured.
     *  3. Post-process phase, where `TestResult` may be changed based on the
     *  `TestRunContext`.
     *  4. Wind-down phase, during which the `TestRunContext` is cleaned up.
     *
     * The "wind-up" and "wind-down" phases are described in detail by the
     * related `windUp` and `windDown` methods in this class.
     *
     * The test run phase consists of taking the `implementation` generated
     * by the "wind-up" phase, invoking it, and measuring it for exceptions. If
     * no exceptions are measured, the method returns a passing `TestResult`. If
     * an exception is measured, the method returns a non-passing `TestResult`.
     */ async run ( suite , ... _args ) { const { reporter } = suite ; let context ; let result ; try { context = await this . windUp ( { suite , implementation : this . implementation } ) ; try { const { implementation } = context ; await implementation ( ) ; result = { passed : true } ; } catch ( error ) { result = { passed : false , error } ; } await this . windDown ( context ) ; result = await this . postProcess ( context , result ! ) ; } catch ( error ) { reporter . report ( ReporterEvent . unexpectedError , 'Error preparing test context.' , error , suite ) ; result = { passed : false , error } ; } return result ; } } ;