//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// ES6 Generators APIs tests -- verifies built-in API objects and properties

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

var globObj = this;

function checkAttributes(name, o, p, a) {
    var desc = Object.getOwnPropertyDescriptor(o, p);

    var msgPrefix = "Property " + p.toString() + " on " + name + " is ";

    assert.isTrue(!!desc, msgPrefix + "not found; there is no descriptor");

    assert.areEqual(a.writable, desc.writable, msgPrefix + (a.writable ? "" : "not") + " writable");
    assert.areEqual(a.enumerable, desc.enumerable, msgPrefix + (a.enumerable ? "" : "not") + " enumerable");
    assert.areEqual(a.configurable, desc.configurable, msgPrefix + (a.configurable ? "" : "not") + " configurable");
}

var tests = [
    {
        name: "GeneratorFunction is not exposed on the global object",
        body: function () {
            assert.isFalse(globObj.hasOwnProperty("GeneratorFunction"), "Global object does not have property named GeneratorFunction");
        }
    },
    {
        name: "Generator function object instances have length, name, and prototype properties",
        body: function () {
            function* gf() { }

            assert.isTrue(gf.hasOwnProperty("length"), "Generator function objects have a 'length' property");
            assert.isTrue(gf.hasOwnProperty("name"), "Generator function objects have a 'name' property");
            assert.isTrue(gf.hasOwnProperty("prototype"), "Generator function objects have a 'prototype' property");

            checkAttributes("gf", gf, "length", { writable: false, enumerable: false, configurable: true });
            checkAttributes("gf", gf, "name", { writable: false, enumerable: false, configurable: true });
            checkAttributes("gf", gf, "prototype", { writable: true, enumerable: false, configurable: false });

            function gf2(a, b, c) { }
            assert.areEqual(0, gf.length, "Generator function object's 'length' property matches the number of parameters (0)");
            assert.areEqual(3, gf2.length, "Generator function object's 'length' property matches the number of parameters (3)");
            assert.areEqual("gf", gf.name, "Generator function object's 'name' property matches the function's name");

            assert.isFalse(gf.prototype.hasOwnProperty("constructor"), "Generator function prototype objects do not get a 'constructor' property");
        }
    },
    {
        name: "arguments and caller properties are absent regardless of strictness",
        body: function () {
            function* gf() { }

            assert.isFalse(gf.hasOwnProperty("arguments"), "Generator function objects do not have an 'arguments' property");
            assert.isFalse(gf.hasOwnProperty("caller"), "Generator function objects do not have a 'caller' property");

            // Test JavascriptGeneratorFunction APIs that special case PropertyIds::caller and ::arguments
            Object.setPrototypeOf(gf, Object.prototype); // Remove Function.prototype so we don't find its 'caller' and 'arguments' in these operations
            assert.isFalse("arguments" in gf, "Has operation on 'arguments' property returns false initially");
            assert.areEqual(undefined, gf.arguments, "Get operation on 'arguments' property returns undefined initially");
            assert.areEqual(undefined, Object.getOwnPropertyDescriptor(gf, "arguments"), "No property descriptor for 'arguments' initially");
            assert.isTrue(delete gf.arguments, "Delete operation on 'arguments' property returns true");

            assert.areEqual(0, gf.arguments = 0, "Set operation on 'arguments' creates new property with assigned value");
            assert.isTrue("arguments" in gf, "Has operation on 'arguments' property returns true now");
            assert.areEqual(0, gf.arguments, "Get operation on 'arguments' property returns property value now");
            checkAttributes("gf", gf, "arguments", { writable: true, enumerable: true, configurable: true });
            assert.isTrue(delete gf.arguments, "Delete operation on 'arguments' property still returns true");
            assert.isFalse(gf.hasOwnProperty("arguments"), "'arguments' property is gone");

            assert.isFalse("caller" in gf, "Has operation on 'caller' property returns false initially");
            assert.areEqual(undefined, gf.caller, "Get operation on 'caller' property returns undefined initially");
            assert.areEqual(undefined, Object.getOwnPropertyDescriptor(gf, "caller"), "No property descriptor for 'caller' initially");
            assert.isTrue(delete gf.caller, "Delete operation on 'caller' property returns true");

            assert.areEqual(0, gf.caller = 0, "Set operation on 'caller' creates new property with assigned value");
            assert.isTrue("caller" in gf, "Has operation on 'caller' property returns true now");
            assert.areEqual(0, gf.caller, "Get operation on 'caller' property returns property value now");
            checkAttributes("gf", gf, "caller", { writable: true, enumerable: true, configurable: true });
            assert.isTrue(delete gf.caller, "Delete operation on 'caller' property still returns true");
            assert.isFalse(gf.hasOwnProperty("caller"), "'caller' property is gone");

            function* gfstrict() { "use strict"; }

            assert.isFalse(gfstrict.hasOwnProperty("arguments"), "Strict mode generator function objects do not have an 'arguments' property");
            assert.isFalse(gfstrict.hasOwnProperty("caller"), "Strict mode generator function objects do not have a 'caller' property");

            Object.setPrototypeOf(gfstrict, Object.prototype); // Remove Function.prototype so we don't find its 'caller' and 'arguments' in these operations
            assert.isFalse("arguments" in gfstrict, "Has operation on 'arguments' property returns false initially for a strict mode generator function");
            assert.areEqual(undefined, gfstrict.arguments, "Get operation on 'arguments' property returns undefined initially for a strict mode generator function");
            assert.areEqual(undefined, Object.getOwnPropertyDescriptor(gfstrict, "arguments"), "No property descriptor for 'arguments' initially for a strict mode generator function");
            assert.isTrue(delete gfstrict.arguments, "Delete operation on 'arguments' property returns true initially for a strict mode generator function");

            assert.areEqual(0, gfstrict.arguments = 0, "Set operation on 'arguments' creates new property with assigned value for a strict mode generator function");
            assert.isTrue("arguments" in gfstrict, "Has operation on 'arguments' property returns true now for a strict mode generator function");
            assert.areEqual(0, gfstrict.arguments, "Get operation on 'arguments' property returns property value now for a strict mode generator function");
            checkAttributes("gfstrict", gfstrict, "arguments", { writable: true, enumerable: true, configurable: true });
            assert.isTrue(delete gfstrict.arguments, "Delete operation on 'arguments' property still returns true for a strict mode generator function");
            assert.isFalse(gfstrict.hasOwnProperty("arguments"), "'arguments' property is gone for a strict mode generator function");

            assert.isFalse("caller" in gfstrict, "Has operation on 'caller' property returns false initially for a strict mode generator function");
            assert.areEqual(undefined, gfstrict.caller, "Get operation on 'caller' property returns undefined initially for a strict mode generator function");
            assert.areEqual(undefined, Object.getOwnPropertyDescriptor(gfstrict, "caller"), "No property descriptor for 'caller' initially for a strict mode generator function");
            assert.isTrue(delete gfstrict.caller, "Delete operation on 'caller' property returns true initially for a strict mode generator function");

            assert.areEqual(0, gfstrict.caller = 0, "Set operation on 'caller' creates new property with assigned value for a strict mode generator function");
            assert.isTrue("caller" in gfstrict, "Has operation on 'caller' property returns true now for a strict mode generator function");
            assert.areEqual(0, gfstrict.caller, "Get operation on 'caller' property returns property value now for a strict mode generator function");
            checkAttributes("gfstrict", gfstrict, "caller", { writable: true, enumerable: true, configurable: true });
            assert.isTrue(delete gfstrict.caller, "Delete operation on 'caller' property still returns true for a strict mode generator function");
            assert.isFalse(gfstrict.hasOwnProperty("caller"), "'caller' property is gone for a strict mode generator function");
        }
    },
    {
        name: "Generator functions' length property is the number of formal parameters",
        body: function () {
            function* gf0() { }
            function* gf1(a) { }
            function* gf5(a,b,c,d,e) { }

            assert.areEqual(0, gf0.length, "Generator function with no formal parameters has length 0");
            assert.areEqual(1, gf1.length, "Generator function with one formal parameter has length 1");
            assert.areEqual(5, gf5.length, "Generator function with five formal parameters has length 5");
        }
    },
    {
        name: "Generator function instances have GeneratorFunction.prototype as their prototype and it has the specified properties and prototype",
        body: function () {
            function* gf() { }
            var generatorFunctionPrototype = Object.getPrototypeOf(gf);

            assert.areEqual(Function.prototype, Object.getPrototypeOf(generatorFunctionPrototype), "GeneratorFunction.prototype's prototype is Function.prototype");

            assert.isTrue(generatorFunctionPrototype.hasOwnProperty("constructor"), "GeneratorFunction.prototype has 'constructor' property");
            assert.isTrue(generatorFunctionPrototype.hasOwnProperty("prototype"), "GeneratorFunction.prototype has 'prototype' property");
            assert.isTrue(generatorFunctionPrototype.hasOwnProperty(Symbol.toStringTag), "GeneratorFunction.prototype has [Symbol.toStringTag] property");

            checkAttributes("GeneratorFunction.prototype", generatorFunctionPrototype, "constructor", { writable: false, enumerable: false, configurable: true });
            checkAttributes("GeneratorFunction.prototype", generatorFunctionPrototype, "prototype", { writable: false, enumerable: false, configurable: true });
            checkAttributes("GeneratorFunction.prototype", generatorFunctionPrototype, Symbol.toStringTag, { writable: false, enumerable: false, configurable: true });

            assert.areEqual("GeneratorFunction", generatorFunctionPrototype[Symbol.toStringTag], "GeneratorFunction.prototype's [Symbol.toStringTag] property is 'GeneratorFunction'");
        }
    },
    {
        name: "GeneratorFunction constructor is the value of the constructor property of GeneratorFunction.prototype and has the specified properties and prototype",
        body: function () {
            function* gf() { }
            var generatorFunctionPrototype = Object.getPrototypeOf(gf);
            var generatorFunctionConstructor = generatorFunctionPrototype.constructor;

            assert.areEqual(Function, Object.getPrototypeOf(generatorFunctionConstructor), "GeneratorFunction's prototype is Function");

            assert.isTrue(generatorFunctionConstructor.hasOwnProperty("name"), "GeneratorFunction has 'name' property");
            assert.isTrue(generatorFunctionConstructor.hasOwnProperty("length"), "GeneratorFunction has 'length' property");
            assert.isTrue(generatorFunctionConstructor.hasOwnProperty("prototype"), "GeneratorFunction has 'prototype' property");

            checkAttributes("GeneratorFunction", generatorFunctionConstructor, "name", { writable: false, enumerable: false, configurable: true });
            checkAttributes("GeneratorFunction", generatorFunctionConstructor, "length", { writable: false, enumerable: false, configurable: true });
            checkAttributes("GeneratorFunction", generatorFunctionConstructor, "prototype", { writable: false, enumerable: false, configurable: false });

            assert.areEqual("GeneratorFunction", generatorFunctionConstructor.name, "GeneratorFunction's 'name' property is 'GeneratorFunction'");
            assert.areEqual(generatorFunctionPrototype, generatorFunctionConstructor.prototype, "GeneratorFunction's 'prototype' property is GeneratorFunction.prototype");
            assert.areEqual(1, generatorFunctionConstructor.length, "GeneratorFunction's 'length' property is 1");
        }
    },
    {
        name: "Generator prototype is the value of the prototype property of GeneratorFunction.prototype and has the specified properties and prototype",
        body: function () {
            function* gf() { }
            var generatorFunctionPrototype = Object.getPrototypeOf(gf);
            var generatorPrototype = generatorFunctionPrototype.prototype;
            var iteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));

            assert.areEqual(iteratorPrototype, Object.getPrototypeOf(generatorPrototype), "Generator prototype's prototype is %IteratorPrototype%");

            assert.isTrue(generatorPrototype.hasOwnProperty("constructor"), "Generator prototype has 'constructor' property");
            assert.isTrue(generatorPrototype.hasOwnProperty("next"), "Generator prototype has 'next' property");
            assert.isTrue(generatorPrototype.hasOwnProperty("throw"), "Generator prototype has 'throw' property");
            assert.isTrue(generatorPrototype.hasOwnProperty("return"), "Generator prototype has 'return' property");
            assert.isFalse(generatorPrototype.hasOwnProperty(Symbol.iterator), "Generator prototype does not have [Symbol.iterator] property");
            assert.isTrue(generatorPrototype.hasOwnProperty(Symbol.toStringTag), "Generator prototype has [Symbol.toStringTag] property");

            checkAttributes("Generator prototype", generatorPrototype, "constructor", { writable: false, enumerable: false, configurable: true });
            checkAttributes("Generator prototype", generatorPrototype, "next", { writable: true, enumerable: false, configurable: true });
            checkAttributes("Generator prototype", generatorPrototype, "return", { writable: true, enumerable: false, configurable: true });
            checkAttributes("Generator prototype", generatorPrototype, "throw", { writable: true, enumerable: false, configurable: true });
            checkAttributes("Generator prototype", generatorPrototype, Symbol.toStringTag, { writable: false, enumerable: false, configurable: true });

            assert.areEqual(generatorFunctionPrototype, generatorPrototype.constructor, "Generator prototype's 'constructor' property is GeneratorFunction.prototype");
            assert.areEqual("Generator", generatorPrototype[Symbol.toStringTag], "Generator prototype's [Symbol.toStringTag] property is 'Generator'");
        }
    },
    {
        name: "Generator object prototype by default is the function's .prototype property value whose prototype is the Generator prototype",
        body: function () {
            function* gf() { }
            var generatorFunctionPrototype = Object.getPrototypeOf(gf);
            var generatorPrototype = generatorFunctionPrototype.prototype;

            var g = gf();
            assert.areEqual(generatorPrototype, Object.getPrototypeOf(Object.getPrototypeOf(g)), "Generator object's prototype is an object whose prototype is Generator prototype");
            assert.areEqual(Object.getPrototypeOf(g), gf.prototype, "Generator object's prototype comes from generator function's .prototype property");
            assert.isTrue(g instanceof gf, "Generator object is instance of the generator function");
        }
    },
    {
        name: "Generator function's arguments.callee should be equal to the generator function object itself",
        body: function () {
            function* gf() {
                assert.isTrue(arguments.callee === gf, "arguments.callee should be the same function object pointed to by gf");
            }

            gf().next();
        }
    },
];

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