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

if (typeof (WScript) != "undefined") {
  WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js", "self");
}

var tests = {
  // Note: each test has name (string) and body (function) properties. 
  //       Success is when the body does not throw, failure -- when it throws.

  test01: {
    name: "8.12.9.4.a (variation 1): define generic property, check default attrbitues",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo02";
      var pd = {};
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: undefined, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test02: {
    name: "8.12.9.4.a (variation 2): define data property, check default attrbitues",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo03";
      var pd = { value: 0 };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 0, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test03: {
    name: "8.12.9.4.a (variation 3): define generic property by specifying some attributes, check attrbitues",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo02_v3";
      var pd = { configurable: true, writable: false };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: undefined, configurable: true, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test04: {
    name: "8.12.9.4.b: define accessor property, check default attrbitues",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo04";
      var getter = function () { return this.Value };
      var pd = { get: getter };
      Object.defineProperty(o, propertyName, pd);
      var expected = { get: getter, set: undefined, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test05: {
    name: "8.12.9.5: re-define property: use descriptor with all fields absent, check that nothing happens to previous descriptor",
    body: function () {
      var propertyName = "foo05";
      var o = { foo05: 1 };
      var pd = {};
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: true, value: 1, configurable: true, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test06: {
    name: "8.12.9.6: re-define property: use equal descriptor with data field, check that nothing happens to previous descriptor",
    body: function () {
      var propertyName = "foo06";
      var o = { foo06: 1 };
      var pd = { value: 1 };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: true, value: 1, configurable: true, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  // where we are:
  // - re-define
  // - desc is not empty
  // - desc and current are not the same

  test07: {
    name: "8.12.9.7.a: re-define property: current descriptor is not configurable and descriptor is configurable, check that it throws TypeError",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo07";
      var pd = { value: 0, configurable: false };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1, configurable: true };
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
    }
  },

  test08: {
    name: "8.12.9.7.b (variation 1): re-define property: current descriptor is not configurable and descriptor enumerable is specified and it's negation of current enumerable, check that it throws TypeError",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo08";
      var pd = { value: 0 };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1, enumerable: true };
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
    }
  },

  test09: {
    name: "8.12.9.7.b (variation 2): re-define property: current descriptor is not configurable and descriptor enumerable is not specified, check that it does not throw",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo09";
      var pd = { value: 0, writable: true }; // set writable to true to avoid throw code path.
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1, writable: false };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 1, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test10: {
    name: "8.12.9.7.b (variation 3): re-define property: current descriptor is not configurable and descriptor enumerable is same as current enumerable, check that it does not throw",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo10";
      var pd = { value: 0, writable: true }; // set writable to true to avoid throw code path.
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1, enumerable: false, writable: false };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 1, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test11: {
    name: "8.12.9.8: re-define property: descriptor is not empty, generic and is different from current",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo11";
      var pd = { value: 0, configurable: true };
      Object.defineProperty(o, propertyName, pd);
      pd = { enumerable: true }; // change enumerable to make sure that descriptor is different from current.
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 0, configurable: true, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  // where we are:
  // - re-define
  // - desc is not empty
  // - desc and current are not the same
  // - descriptor.IsData != current.IsData

  test12: {
    name: "8.12.9.9.a: re-define property: descriptor.IsData != current.IsData and current is not configurable, check that it throws TypeError",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo12";
      var pd = { value: 0, configurable: false };
      Object.defineProperty(o, propertyName, pd);
      pd = { get: function () { return this.Value; } };
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
    }
  },

  test13: {
    name: "8.12.9.9.b (variation 1): re-define property: convert from data to accessor descriptor, check that configurable/enumerable (true) are preserved",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo13";
      var pd = { value: 0, configurable: true, enumerable: true };
      Object.defineProperty(o, propertyName, pd);
      var getter = function() { return this.Value; };
      pd = { get: getter };
      Object.defineProperty(o, propertyName, pd);
      var expected = { get: getter, set: undefined, configurable: true, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test14: {
    name: "8.12.9.9.b (variation 2): re-define property: convert from data to accessor descriptor, check that enumerable (false) is preserved",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo14";
      var pd = { value: 0, configurable: true, enumerable: false };
      Object.defineProperty(o, propertyName, pd);
      var getter = function () { return this.Value; };
      pd = { get: getter };
      Object.defineProperty(o, propertyName, pd);
      var expected = { get: getter, set: undefined, configurable: true, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test15: {
    name: "8.12.9.9.b (variation 3): re-define property: convert from data to accessor descriptor, check that configurable/enumerable not preserved when specified by descriptor",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo15";
      var pd = { value: 0, configurable: true, enumerable: true };
      Object.defineProperty(o, propertyName, pd);
      var getter = function () { return this.Value; };
      pd = { get: getter, configurable: false };
      Object.defineProperty(o, propertyName, pd);
      var expected = { get: getter, set: undefined, configurable: false, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test16: {
    name: "8.12.9.9.c (variation 1): re-define property: convert from accessor to data descriptor, check that configurable/enumerable (true) are preserved",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo16";
      var pd = {
        set: function (arg) { helpers.writeln("setter was called"); this.Value = arg; },
        configurable: true,
        enumerable: true
      };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1 };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 1, configurable: true, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test17: {
    name: "8.12.9.9.c (variation 2): re-define property: convert from accessor to data descriptor, check that enumerable (false) is preserved",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo17";
      var pd = {
        set: function (arg) { helpers.writeln("setter was called"); this.Value = arg; },
        configurable: true,
        enumerable: false
      };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1 };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 1, configurable: true, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test18: {
    name: "8.12.9.9.c (variation 3): re-define property: convert from accessor to data descriptor, check that configurable/enumerable (true/false) not preserved when specified by descriptor (false/absent)",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo18";
      var pd = {
        set: function (arg) { helpers.writeln("setter was called"); this.Value = arg; },
        configurable: true,
        enumerable: false
      };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1, configurable: false };
      Object.defineProperty(o, propertyName, pd);
      // expected: configurable/enumerable = false/false.
      var expected = { writable: false, value: 1, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test19: {
    name: "8.12.9.9.c (variation 4): re-define property: convert from accessor to data descriptor, check that configurable/enumerable (true/true) not preserved when specified by descriptor (absent/false)",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo19";
      var pd = {
        set: function (arg) { helpers.writeln("setter was called"); this.Value = arg; },
        configurable: true,
        enumerable: true
      };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1, enumerable: false };
      Object.defineProperty(o, propertyName, pd);
      // expected: configurable/enumerable = true/false.
      var expected = { writable: false, value: 1, configurable: true, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  // where we are:
  // - re-define
  // - desc is not empty
  // - desc and current are not the same
  // - descriptor is data, current is data

  test20: {
    name: "8.12.9.10.a (variation 1): re-define data property: current is not configurable/not writable and descriptor writable is absent/value is same",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo20";
      var pd = { value: 1 };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 1 };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 1, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test21: {
    name: "8.12.9.10.a.i: re-define data property: current is not configurable/not writable and descriptor is writable, check that it throws TypeError",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo21";
      var pd = { value: 1 };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 2, writable: true };
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
      return true;
    }
  },

  test22: {
    name: "8.12.9.10.a.ii: re-define data property: current is not configurable/not writable and descriptor writable is false and value is different, check that it throws TypeError",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo22";
      var pd = { value: 1 };
      Object.defineProperty(o, propertyName, pd);
      pd = { value: 2, writable: false };
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
    }
  },

  test23: {
    name: "8.12.9.10.a (variation 2): re-define data property: current is configurable",
    body: function () {
      var propertyName = "foo23";
      var o = { foo23: 1 };
      var pd = { value: 2, writable: false };
      Object.defineProperty(o, propertyName, pd);
      var expected = { writable: false, value: 2, configurable: true, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

// where we are:
// - re-define
// - desc is not empty
// - desc and current are not the same
// - descriptor is accessor, current is accessor

  test24: {
    name: "Test: 8.12.9.11 (variation 1): re-define accessor property: current configurable is true: valid case",
    body: function () {
      var propertyName = "foo24";
      var o = { 
        get foo24() { return this.Value; }, 
        set foo24(arg) { helpers.writeln("old setter"); this.Value = arg; } 
      };
      var newGetter = function() { return 2; };
      var newSetter = function(arg) { helpers.writeln("new setter"); }
      var pd = { get: newGetter, set: newSetter };
      Object.defineProperty(o, propertyName, pd);
      var expected = { get: newGetter,  set: newSetter, configurable: true, enumerable: true };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test25: {
    name: "8.12.9.11.a.i: re-define accessor property: current configurable is false, descriptor specifies setter as different, expect TypeError",
    body: function () {
      var propertyName = "foo25";
      var o = helpers.getDummyObject();
      var pd = { set: function(arg) { helpers.writeln("old setter"); this.Value = arg; } };
      Object.defineProperty(o, propertyName, pd);

      pd = { set: function(arg) { helpers.writeln("new setter"); } };
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
    }
  },

  test26: {
    name: "8.12.9.11.a.ii: re-define accessor property: current configurable is false, descriptor specifies getter as different, expect TypeError",
    body: function () {
      var propertyName = "foo26";
      var o = helpers.getDummyObject();
      var pd = { get: function() { return this.Value; }, };
      Object.defineProperty(o, propertyName, pd);

      pd = { get: function() { helpers.writeln("new getter"); return 2; } };
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
    }
  },

  test27: {
    name: "8.12.9.11 (variation 2): re-define accessor property: current configurable is true and no getter, descriptor specifies getter as undefined, setter as same",
    body: function () {
      var propertyName = "foo27";
      var o = helpers.getDummyObject();
      var setter = function(arg) { helpers.writeln("setter") }; 
      var pd = { set: setter };
      Object.defineProperty(o, propertyName, pd);

      pd = { get: undefined, set: setter };
      Object.defineProperty(o, propertyName, pd);
      var expected = { get: undefined, set: setter, configurable: false, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
    }
  },

  test28: {
    name: "Re-define property from data to accessor property. Make sure that setter is called when setting the value.",
    body: function () {
      // define a data property.
      var propertyName = "foo28";
      var o = helpers.getDummyObject();
      var pd = { value: 1, configurable: true };
      Object.defineProperty(o, propertyName, pd);

      // re-define the property to be accessor property.
      var log = "";
      var getter = function() { log += "getter was called."; return this.Value; }
      var setter = function(arg) { log += "setter was called."; this.Value = arg; }; 
      pd = { get: getter, set: setter };
      Object.defineProperty(o, propertyName, pd);

      // set the value and get it.
      var newValue = 2;
      o[propertyName] = newValue;
      var actualValue = o[propertyName];

      // validate.
      var expected = { get: getter, set: setter, configurable: true, enumerable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
      assert.areEqual("setter was called.getter was called.", log, "wrong log");
      assert.areEqual(newValue, actualValue, "wrong value");
    }
  },

  test29: {
    name: "Define property 'length' as accessor property on array: check that it throws TypeError.",
    body: function () {
      assert.throws(
        function() { Object.defineProperty([], "length", {configurable: false, get: function() {return 2;}}); },
        TypeError);
      assert.throws(
        function() { Object.defineProperty(Array.prototype, "length", {configurable: false, get: function() {return 2;}}); },
        TypeError);
    }
  },

  // Where we are: some tests for specific issues.
  test30: {
    name: "Define property with getter specified as undefined, then access the property (WOOB bug 1123281)",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo30";
      var pd = { get: undefined };
      Object.defineProperty(o, propertyName, pd);
      assert.areEqual(undefined, o[propertyName]);
    }
  },
  
  test31: {
    name: "Define property with setter specified as undefined, then set the property (WOOB bug 1123281)",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo31";
      var pd = { set: undefined };
      Object.defineProperty(o, propertyName, pd);
      o[propertyName] = 1; // Make sure this does not throw.
      assert.areEqual(undefined, o[propertyName]); // Just in case try to access the property.
    }
  },

  test32: {
    name: "Convert data to accessor property with getter specified as undefined, then access the property (WOOB bug 1123281)",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo32";
      var pd = { configurable: true, value: 0 };
      Object.defineProperty(o, propertyName, pd);

      pd = { get: undefined };
      Object.defineProperty(o, propertyName, pd);
      assert.areEqual(undefined, o[propertyName]);
    }
  },

  test33: {
    name: "Convert data to accessor property with setter specified as undefined, then set the property (WOOB bug 1123281)",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "foo33";
      var pd = { configurable: true, value: 0 };
      Object.defineProperty(o, propertyName, pd);

      pd = { set: undefined };
      Object.defineProperty(o, propertyName, pd);
      o[propertyName] = 1; // Make sure this does not throw.
      assert.areEqual(undefined, o[propertyName]); // Just in case try to access the property.
    }
  },

  test34: {
    name: "Convert accessor to a data property for non-extensible object (WIN8 bug 463559)",
    body: function () {
      var o = helpers.getDummyObject();
      var propertyName = "x";

      Object.defineProperty(o, propertyName, { 
        get: function() { return 0; },
        set: function(val) { helpers.writeln("setter was called although it shouldn't"); },
        configurable: true
      });
      Object.preventExtensions(o);
      var val = 1;
      Object.defineProperty(o, propertyName, { value: val, });

      var expected = { value: val, configurable: true, enumerable: false, writable: false };
      assert.areEqual(expected, Object.getOwnPropertyDescriptor(o, propertyName), "wrong value of getOwnPropertyDescriptor");
      assert.areEqual(val, o[propertyName], "the property value is wrong");
      assert.areEqual(false, Object.isExtensible(o), "isExtensible() changed");
    }
  },

  // Note: this test irreversibly changes the dummy object (that's important when dummy object is document/window), 
  //       it should in the very end.
  test_last_01: {
    name: "8.12.9.3: define property for non-extensible object, check that it throws TypeError",
    body: function () {
      var o = helpers.getDummyObject();
      Object.preventExtensions(o);
      var propertyName = "foo01";
      var pd = {};
      assert.throws(function() { Object.defineProperty(o, propertyName, pd); }, TypeError);
    }
  }

}; // tests.

testRunner.runTests(tests);
