Source: globals/test.js

"use strict";

const path = require("path");

const _ = require("lodash");
const U = require("glace-utils");

const CONF = require("../config");
const TestCase = require("../testing").TestCase;
const ScopeType = require("../testing").ScopeType;
const setLog = require("../utils").setLog;

const _test = (names => {
    return (name, fixtures, opts, func) => {
        CONF.test.id++;
        CONF.chunk.id = 0;

        let preloaded_passed_chunk_ids;

        if (CONF.filter.testIds && !CONF.filter.testIds.includes(CONF.test.id)) return;

        if (CONF.filter.include) {
            let isIncluded = false;
            for (const include of CONF.filter.include) {
                if (isFilterMatched(name, include.id)) {
                    preloaded_passed_chunk_ids = include.passed_chunk_ids;
                    isIncluded = true;
                    break;
                }
            }
            if (!isIncluded) return;
        }

        if (CONF.filter.exclude) {
            for (const exclude of CONF.filter.exclude) {
                if (isFilterMatched(name, exclude.id)) return;
            }
        }

        if (CONF.test.checkNames && !CONF.retry.id) {
            if (names.includes(name)) {
                throw new Error(`Test case '${name}' is added already`);
            }
            names.push(name);
        }

        const skip = !!opts.skip;
        const skipReason = _.isString(opts.skip) ? opts.skip : null;
        const retries = U.defVal(opts.retry, CONF.test.retries, 0);

        opts.chunkRetry = U.defVal(opts.chunkRetry, CONF.chunk.retries, 0);
        opts.chunkTimeout = U.defVal(opts.chunkTimeout);

        let testCase;
        if (!CONF.retry.id) {
            testCase = new TestCase(name, CONF.test.id);
            testCase.addPassedChunkIds(preloaded_passed_chunk_ids || []);
            CONF.test.cases.push(testCase);
            if (skip) {
                testCase.status = TestCase.SKIPPED;
                if (skipReason) testCase.addDetails(skipReason);
            }
        } else {
            testCase = CONF.test.cases.filter(t => t.id === CONF.test.id)[0];
            expect(testCase, "Oops! Testcase isn't found by id").to.exist;
        }

        if (testCase.status === TestCase.SKIPPED) return;

        if (!CONF.retry.chunkIds[retries]) CONF.retry.chunkIds[retries] = [];
        CONF.retry.curChunkIds = CONF.retry.chunkIds[retries];

        testFunc({ testCase, fixtures, opts, func });
    };
})([]);

const testFunc = ({ testCase, fixtures, opts, func }) => {
    fixtures = [initTestFixture(testCase)].concat(fixtures);
    const scopeType = new ScopeType(testCase.name).setType("test");

    scope(scopeType, fixtures, opts, () => {
        func();
    });
};

const initTestFixture = testCase => {
    return U.makeFixture({ before: beforeCb(testCase), after: afterCb });
};

const beforeCb = testCase => ctx => () => {
    ctx.testCase = testCase;
    ctx.testCase.reset();
    ctx.testCase.start();
    CONF.test.curCase = ctx.testCase;
    CONF.report.testDir = path.resolve(
        CONF.report.dir, "tests", U.toKebab(ctx.testCase.name));
    setLog(); // Current test case is started, need to reinit log
};

const afterCb = ctx => () => {
    if (ctx.testCase.errors.length) {
        ctx.testCase.end(TestCase.FAILED);
    } else {
        ctx.testCase.end(TestCase.PASSED);
    }
};

const isFilterMatched = (testName, filterId) => {
    if (filterId == CONF.test.id) return true;
    filterId = filterId.toString();
    if (CONF.filter.precise) {
        return testName.toLowerCase() === filterId.toLowerCase();
    } else {
        return testName.toLowerCase().includes(filterId.toLowerCase());
    }
};

/**
 * Global function, existing in `glace` tests, which creates test case.
 *
 * @global
 * @function
 * @arg {string} name - Name of test case. By default, should be unique in session.
 * But uniqueness check can be skipped with CLI option `--dont-check-names`.
 * @arg {object} [opts] - Options.
 * @arg {boolean|string} [opts.skip=false] - Flag to skip test or skip reason message.
 * @arg {?number} [opts.retry=null] - Number of test retries on failure. Overrides
 * [config](GlaceConfig.html#test-retry) settings.
 * @arg {?number} [opts.chunkRetry=null] - <a name="test-chunk-retry" href="#test-chunk-retry">#</a>
 * Number of chunk retries on failure. Overrides [config](GlaceConfig.html#test-chunk-retry) settings.
 * @arg {?number} [opts.chunkTimeout=null] - <a name="test-chunk-timeout" href="#test-chunk-timeout">#</a>
 * Time to execute chunk or hook, **sec**. Overrides [config](GlaceConfig.html#test-chunk-timeout) settings.
 * @arg {function[]} [fixtures] - Involved [fixtures](tutorial-common-used-funcs.html#fixtures) list.
 * @arg {function} func - Сallback function with [chunks](#chunk__anchor) and hooks.
 *
 * @example <caption><b>Simple test</b></caption>
 *
 * test("Some test", () => {
 *     chunk("Some chunk", () => {
 *         someFunc();
 *     });
 * });
 *
 * @example <caption><b>Test with retry</b></caption>
 *
 * test("Test with retry", { retry: 2 }, () => {
 *     chunk(() => {
 *         someFunc();
 *     });
 * });
 *
 * @example <caption><b>Test with fixtures</b></caption>
 *
 * test("Test with fixtures", null, [fix_func_1, fix_func_2], () => {
 *     chunk(() => {
 *         someFunc();
 *     });
 * });
 */
const test = (name, fixtures, opts, func) => {

    if (_.isFunction(opts)) [func, opts] = [opts];
    if (_.isPlainObject(fixtures)) [opts, fixtures] = [fixtures];
    if (_.isFunction(fixtures)) [func, fixtures] = [fixtures];

    fixtures = fixtures || [];
    opts = opts || {};

    _test(name, fixtures, opts, func);
};

module.exports = test;