// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --harmony-proxies --harmony-reflect


var properties =
    ["bla", "0", 1, Symbol(), {[Symbol.toPrimitive]() {return "a"}}];


function TestForwarding(handler, myDelete, shouldThrow) {
  var target = {};
  var proxy = new Proxy(target, handler);

  assertFalse(target.hasOwnProperty("doesnotexist"));
  assertTrue(myDelete(proxy, "doesnotexist"));

  for (p of properties) {
    target[p] = 42;
    assertTrue(myDelete(proxy, p));
    assertFalse(target.hasOwnProperty(p));
  }

  for (p of properties) {
    Object.defineProperty(target, p, {value: 42, configurable: false});
    if (shouldThrow) {
      assertThrows(() => myDelete(proxy, p), TypeError);
    } else {
      assertFalse(myDelete(proxy, p));
    }
    assertTrue(target.hasOwnProperty(p));
  }
};


(function () {
  // No trap.

  var handler = {};

  TestForwarding(handler,
      (o, p) => delete o[p], false);
  TestForwarding(handler,
      (o, p) => Reflect.deleteProperty(o, p), false);
  TestForwarding(handler,
      (o, p) => {"use strict"; return delete o[p]}, true);
  TestForwarding(handler,
      (o, p) => {"use strict"; return Reflect.deleteProperty(o, p)}, false);
})();


(function () {
  // "Undefined" trap.

  var handler = { deleteProperty: null };

  TestForwarding(handler,
      (o, p) => delete o[p], false);
  TestForwarding(handler,
      (o, p) => Reflect.deleteProperty(o, p), false);
  TestForwarding(handler,
      (o, p) => {"use strict"; return delete o[p]}, true);
  TestForwarding(handler,
      (o, p) => {"use strict"; return Reflect.deleteProperty(o, p)}, false);
})();


(function () {
  // Invalid trap.

  var target = {};
  var handler = { deleteProperty: true };
  var proxy = new Proxy(target, handler);

  assertThrows(() => delete proxy[0], TypeError);
  assertThrows(() => Reflect.deleteProperty(proxy, 0), TypeError);
})();


function TestTrappingTrueish(myDelete) {
  var handler = { deleteProperty() {return 42} };
  var target = {};
  var proxy = new Proxy(target, handler);

  // Trap returns trueish and target doesn't own property.
  for (p of properties) {
    assertTrue(myDelete(proxy, p));
  }

  // Trap returns trueish and target property is configurable.
  for (p of properties) {
    target[p] = 42;
    assertTrue(myDelete(proxy, p));
  }

  // Trap returns trueish but target property is not configurable.
  for (p of properties) {
    Object.defineProperty(target, p, {value: 42, configurable: false});
    assertThrows(() => myDelete(proxy, p), TypeError);
  }
};


TestTrappingTrueish(
    (o, p) => delete o[p]);
TestTrappingTrueish(
    (o, p) => Reflect.deleteProperty(o, p));
TestTrappingTrueish(
    (o, p) => {"use strict"; return delete o[p]});
TestTrappingTrueish(
    (o, p) => {"use strict"; return Reflect.deleteProperty(o, p)});


function TestTrappingTrueish2(myDelete) {
  var handler = {
      deleteProperty(target, p) {
          Object.defineProperty(target, p, {configurable: false});
          return 42
      }
  };
  var target = {};
  var proxy = new Proxy(target, handler);

  // Trap returns trueish but target property is not configurable.  In contrast
  // to above, here the target property was configurable before the trap call.
  for (p of properties) {
    target[p] = 42;
    assertThrows(() => myDelete(proxy, p), TypeError);
  }
};


TestTrappingTrueish2(
    (o, p) => delete o[p]);
TestTrappingTrueish2(
    (o, p) => Reflect.deleteProperty(o, p));
TestTrappingTrueish2(
    (o, p) => {"use strict"; return delete o[p]});
TestTrappingTrueish2(
    (o, p) => {"use strict"; return Reflect.deleteProperty(o, p)});


function TestTrappingFalsish(myDelete, shouldThrow) {
  var handler = { deleteProperty() {return ""} };
  var target = {};
  var proxy = new Proxy(target, handler);

  var properties =
      ["bla", "0", 1, Symbol(), {[Symbol.toPrimitive]() {return "a"}}];

  // Trap returns falsish and target doesn't own property.
  for (p of properties) {
    if (shouldThrow) {
      assertThrows(() => myDelete(proxy, p), TypeError);
    } else {
      assertFalse(myDelete(proxy, p));
    }
  }

  // Trap returns falsish and target property is configurable.
  for (p of properties) {
    target[p] = 42;
    if (shouldThrow) {
      assertThrows(() => myDelete(proxy, p), TypeError);
    } else {
      assertFalse(myDelete(proxy, p));
    }
  }

  // Trap returns falsish and target property is not configurable.
  for (p of properties) {
    Object.defineProperty(target, p, {value: 42, configurable: false});
    if (shouldThrow) {
      assertThrows(() => myDelete(proxy, p), TypeError);
    } else {
      assertFalse(myDelete(proxy, p));
    }
  }
};


TestTrappingFalsish(
    (o, p) => delete o[p], false);
TestTrappingFalsish(
    (o, p) => Reflect.deleteProperty(o, p), false);
TestTrappingFalsish(
    (o, p) => {"use strict"; return delete o[p]}, true);
TestTrappingFalsish(
    (o, p) => {"use strict"; return Reflect.deleteProperty(o, p)}, false);
