//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");


function makeTestsFor(ErrorConstructor, options) {
    const name = options ? options.getNameOfConstructor() : ErrorConstructor.name;
    const prototype = options ? options.getPrototypeOfConstructor() : ErrorConstructor.prototype;
    const rawConstructor = options ? options.rawConstructor : ErrorConstructor
    const message = "Test message";
    const o = {};
    return [
        {
            name: `"cause" in ${ name }.prototype.cause === false`,
            body: function () {
                assert.isFalse("cause" in prototype, `Cause property must not exist in ${ name }.prototype`);
            }
        },
        {
            name: `"cause" in ${ name }().cause === false`,
            body: function () {
                assert.isFalse("cause" in ErrorConstructor(), `${ name }().cause should not be defined if not specified by cause property of options parameter in ${ name } constructor`);
            }
        },
        {
            name: `message property is defined when ${ name } called with one argument, not cause`,
            body: function () {
                assert.isTrue("message" in ErrorConstructor(message), `message property is defined when ${ name } called with one argument, not cause`);
                assert.isTrue(ErrorConstructor(message).message === message, `message property is defined when ${ name } called with one argument, not cause`);
                assert.isFalse("cause" in ErrorConstructor(message), `message property is defined when ${ name } called with one argument, not cause`);
            }
        },
        {
            name: `${ name }(${ message }, { cause: o })'s descriptor`,
            body: function () {
                const e = ErrorConstructor(message, { cause: o });
                const desc = Object.getOwnPropertyDescriptor(e, "cause");
                assert.areEqual(desc.configurable, true, "e.cause should be configurable");
                assert.areEqual(desc.writable, true, "e.cause should be writable");
                assert.areEqual(desc.enumerable, false, "e.cause should not be enumerable");
            }
        },
        {
            name: `o === ${ name }(${ message }, { cause: o }).cause`,
            body: function () {
                const e = Error();
                assert.areEqual(ErrorConstructor("", { cause: o }).cause, o, `Cause property value should be kept as-is`);
                assert.areEqual(ErrorConstructor("", { cause: 0 }).cause, 0, `Cause property value should be kept as-is`);
                assert.areEqual(ErrorConstructor("", { cause: e }).cause, e, `Cause property value should be kept as-is`);
                assert.areEqual(ErrorConstructor("", { cause: "A cause" }).cause, "A cause", `Cause property value should be kept as-is`);
            }
        },
        {
            name: "Options with cause property as getter",
            body: function () {
                var getCounter = 0;
                const options = {
                    get cause() {
                        getCounter++;
                        return o;
                    }
                }
                ErrorConstructor(message, options);
                assert.areEqual(getCounter, 1, `getCounter should be 1`);
            }
        },
        {
            name: "Options with cause property as getter which throws",
            body: function () {
                const options = {
                    get cause() {
                        throw ErrorConstructor();
                    }
                }
                assert.throws(() => ErrorConstructor(message, options), rawConstructor);
            }
        },
        {
            name: "Proxy options parameter",
            body: function () {
                const options = new Proxy({ cause: o }, {
                    has(target, p) {
                        hasCounter++;
                        return p in target;
                    },
                    get(target, p) {
                        getCounter++;
                        return target[p];
                    }
                });
                var hasCounter = 0, getCounter = 0;
                const e = ErrorConstructor("test", options);
                assert.areEqual(hasCounter, 1, `hasCounter should be 1`);
                assert.areEqual(getCounter, 1, `getCounter should be 1`);
                assert.areEqual(e.cause, o, `Cause property value should be kept as-is`);
                assert.areEqual(hasCounter, 1, `hasCounter should be 1`);
                assert.areEqual(getCounter, 1, `getCounter should be 1`);
            }
        },
        {
            name: "Cause property is not added to error if options parameter doesn't have the cause property",
            body: function () {
                assert.isFalse('cause' in ErrorConstructor(message, { }), `Cause property must not be added to error if options parameter doesn't have the cause property`);
            }
        },
        {
            name: "Cause property is not added to error if options parameter isn't typeof object",
            body: function () {
                Number.prototype.cause = 0;
                String.prototype.cause = 0;
                assert.isFalse('cause' in ErrorConstructor(message, 0), `Cause property must not be added to error if options parameter isn't typeof object`);
                assert.isFalse('cause' in ErrorConstructor(message, ""), `Cause property must not be added to error if options parameter isn't typeof object`);
            }
        }
    ]
}
const tests = [
    ...makeTestsFor(Error),
    ...makeTestsFor(TypeError),
    ...makeTestsFor(ReferenceError),
    ...makeTestsFor(SyntaxError),
    ...makeTestsFor(RangeError),
    ...makeTestsFor(EvalError),
    ...makeTestsFor((message, options) => AggregateError([], message, options), {
        getNameOfConstructor: () => AggregateError.name,
        getPrototypeOfConstructor: () => AggregateError.prototype,
        rawConstructor: AggregateError
    })
];

testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });
