/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/licenses/publicdomain/ */

// Reflect.defineProperty defines properties.
var obj = {};
assertEq(Reflect.defineProperty(obj, "x", {value: 7}), true);
assertEq(obj.x, 7);
var desc = Reflect.getOwnPropertyDescriptor(obj, "x");
assertDeepEq(desc, {value: 7,
                    writable: false,
                    enumerable: false,
                    configurable: false});

// Reflect.defineProperty can define a symbol-keyed property.
var key = Symbol(":o)");
assertEq(Reflect.defineProperty(obj, key, {value: 8}), true);
assertEq(obj[key], 8);

// array .length property
obj = [1, 2, 3, 4, 5];
assertEq(Reflect.defineProperty(obj, "length", {value: 4}), true);
assertDeepEq(obj, [1, 2, 3, 4]);

// The target can be a proxy.
obj = {};
var proxy = new Proxy(obj, {
    defineProperty(t, id, desc) {
        t[id] = 1;
        return true;
    }
});
assertEq(Reflect.defineProperty(proxy, "prop", {value: 7}), true);
assertEq(obj.prop, 1);
assertEq(delete obj.prop, true);
assertEq("prop" in obj, false);

// The attributes object is re-parsed, not passed through to the
// handler.defineProperty method.
obj = {};
var attributes = {
    configurable: 17,
    enumerable: undefined,
    value: null
};
proxy = new Proxy(obj, {
    defineProperty(t, id, desc) {
        assertEq(desc !== attributes, true);
        assertEq(desc.configurable, true);
        assertEq(desc.enumerable, false);
        assertEq(desc.value, null);
        assertEq("writable" in desc, false);
        return 15;  // and the return value here is coerced to boolean
    }
});
assertEq(Reflect.defineProperty(proxy, "prop", attributes), true);


// === Failure and error cases
//
// Reflect.defineProperty behaves much like Object.defineProperty, which has
// extremely thorough tests elsewhere, and the implementation is largely
// shared. Duplicating those tests with Reflect.defineProperty would be a
// big waste.
//
// However, certain failures cause Reflect.defineProperty to return false
// without throwing a TypeError (unlike Object.defineProperty). So here we test
// many error cases to check that behavior.

// missing attributes argument
assertThrowsInstanceOf(() => Reflect.defineProperty(obj, "y"),
                       TypeError);

// non-object attributes argument
for (var attributes of SOME_PRIMITIVE_VALUES) {
    assertThrowsInstanceOf(() => Reflect.defineProperty(obj, "y", attributes),
                           TypeError);
}

// inextensible object
obj = Object.preventExtensions({});
assertEq(Reflect.defineProperty(obj, "prop", {value: 4}), false);

// inextensible object with irrelevant inherited property
obj = Object.preventExtensions(Object.create({"prop": 3}));
assertEq(Reflect.defineProperty(obj, "prop", {value: 4}), false);

// redefine nonconfigurable to configurable
obj = Object.freeze({prop: 1});
assertEq(Reflect.defineProperty(obj, "prop", {configurable: true}), false);

// redefine enumerability of nonconfigurable property
obj = Object.freeze(Object.defineProperties({}, {
    x: {enumerable: true,  configurable: false, value: 0},
    y: {enumerable: false, configurable: false, value: 0},
}));
assertEq(Reflect.defineProperty(obj, "x", {enumerable: false}), false);
assertEq(Reflect.defineProperty(obj, "y", {enumerable: true}), false);

// redefine nonconfigurable data to accessor property, or vice versa
obj = Object.seal({x: 1, get y() { return 2; }});
assertEq(Reflect.defineProperty(obj, "x", {get() { return 2; }}), false);
assertEq(Reflect.defineProperty(obj, "y", {value: 1}), false);

// redefine nonwritable, nonconfigurable property as writable
obj = Object.freeze({prop: 0});
assertEq(Reflect.defineProperty(obj, "prop", {writable: true}), false);
assertEq(Reflect.defineProperty(obj, "prop", {writable: false}), true);  // no-op

// change value of nonconfigurable nonwritable property
obj = Object.freeze({prop: 0});
assertEq(Reflect.defineProperty(obj, "prop", {value: -0}), false);
assertEq(Reflect.defineProperty(obj, "prop", {value: +0}), true);  // no-op

// change getter or setter
function g() {}
function s(x) {}
obj = {};
Object.defineProperty(obj, "prop", {get: g, set: s, configurable: false});
assertEq(Reflect.defineProperty(obj, "prop", {get: s}), false);
assertEq(Reflect.defineProperty(obj, "prop", {get: g}), true);  // no-op
assertEq(Reflect.defineProperty(obj, "prop", {set: g}), false);
assertEq(Reflect.defineProperty(obj, "prop", {set: s}), true);  // no-op

// Proxy defineProperty handler method that returns false
var falseValues = [false, 0, -0, "", NaN, null, undefined];
if (typeof objectEmulatingUndefined === "function")
    falseValues.push(objectEmulatingUndefined());
var value;
proxy = new Proxy({}, {
    defineProperty(t, id, desc) {
        return value;
    }
});
for (value of falseValues) {
    assertEq(Reflect.defineProperty(proxy, "prop", {value: 1}), false);
}

// Proxy defineProperty handler method returns true, in violation of invariants.
// Per spec, this is a TypeError, not a false return.
obj = Object.freeze({x: 1});
proxy = new Proxy(obj, {
    defineProperty(t, id, desc) {
        return true;
    }
});
assertThrowsInstanceOf(() => Reflect.defineProperty(proxy, "x", {value: 2}), TypeError);
assertThrowsInstanceOf(() => Reflect.defineProperty(proxy, "y", {value: 0}), TypeError);
assertEq(Reflect.defineProperty(proxy, "x", {value: 1}), true);

// The second argument is converted ToPropertyKey before any internal methods
// are called on the first argument.
var poison =
  (counter => new Proxy({}, new Proxy({}, { get() { throw counter++; } })))(42);
assertThrowsValue(() => {
    Reflect.defineProperty(poison, {
        toString() { throw 17; },
        valueOf() { throw 8675309; }
    }, poison);
}, 17);


// For more Reflect.defineProperty tests, see target.js and propertyKeys.js.

reportCompare(0, 0);