// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

js> load('testsrc/doctests/util.js');

js> Object.defineProperty
function defineProperty() { [native code for Object.defineProperty, arity=3] }

js> expectTypeError(function() { Object.defineProperty() });
js> expectTypeError(function() { Object.defineProperty({}) });
js> expectTypeError(function() { Object.defineProperty({}, 'p') });
js> [undefined, null, true, 1, 'hello'].forEach(function(value) { 
  >   expectTypeError(function() { Object.defineProperty(value, 'p', {}) }) 
  > })

js> Object.defineProperty({}, 'p', {value:1}).p
1
js> var obj = {}
js> Object.defineProperty(obj, 'a', {
  >   value: 1,
  >   enumerable: false,
  >   writable: false,
  >   configurable: false
  > })  
[object Object]
js> for (var p in obj) print(p); // check it has no enumerable properties
js> obj.a = 2; obj.a; // check that the property is not writable
1
js> delete obj.a; obj.a // check that the property is not deletable
1

js> var define = Object.defineProperty;
js> var describe = Object.getOwnPropertyDescriptor;

js> // when define new property with empty descriptor then default values are used for the descriptor
js> var obj = define({}, 'a', {});
js> describe(obj, 'a').toSource()
({value:undefined, writable:false, enumerable:false, configurable:false})

js> // when define new property with data descriptor then those values are used for the descriptor
js> var obj = define({}, 'a', { value: 2, writable: true });
js> var {value:v, writable:w} = describe(obj, 'a'); [v, w].toSource();
[2, true]
js> obj.a
2

js> // when define new property with accessor descriptor then those values are used for the descriptor
js> var obj = define({}, 'a', { get: function() { return 3; }, set: function(value) {} });
js> var {get:g, set:s} = describe(obj, 'a'); [g, s].toSource();
[(function () {return 3;}), (function (value) {})]
js> obj.a
3

js> // when define existing property with empty descriptor then descriptor is left unchanged
js> var descriptor = {value:1, writable:true, enumerable:true, configurable:true};
js> var obj = define({},  'a', descriptor);
js> var obj = define(obj, 'a', {});
js> describe(obj, 'a').toSource()
({value:1, writable:true, enumerable:true, configurable:true})

js> // when define existing property with same descriptor then descriptor is left unchanged
js> var descriptor = {value:1, writable:true, enumerable:true, configurable:true};
js> var obj = define({},  'a', descriptor);
js> var obj = define(obj, 'a', descriptor);
js> describe(obj, 'a').toSource()
({value:1, writable:true, enumerable:true, configurable:true})

js> // may not change configurable from false to true
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {configurable : false});
  >   define(obj, 'a',          {configurable : true});
  > });

js> // may not change enumerable when configurable is false
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {enumerable : true, configurable:false});
  >   define(obj, 'a',          {enumerable : false});
  > });

js> // may not change writable from false to true when configurable is false
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {writable : false, configurable: false});
  >   define(obj, 'a',          {writable : true});
  > });

js> // may not change value when writable is false
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {value : 1, writable:false});
  >   define(obj, 'a',          {value : 2});
  > });

js> // may not change getter when configurable is false
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {get: function() { return 1 }, configurable:false});
  >   define(obj, 'a',          {get: function() { return 1 }});
  > });

js> // may not change setter when configurable is false
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {set: function(val) {}, configurable:false});
  >   define(obj, 'a',          {set: function(val) {}});
  > });

js> // may not change from data property to accessor property when configurable is false
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {value : 1, configurable:false});
  >   define(obj, 'a',          {get   : function() { return 1 }});
  > });

js> // may not change from accessor property to data property when configurable is false
js> expectTypeError(function() {
  >   var obj = define({}, 'a', {get   : function() { return 1 }, configurable:false});
  >   define(obj, 'a',          {value : 1});
  > });

js> // can change writable from true to false when configurable is false
js> var obj = define({},  'a', {writable:true, configurable:false});
js> var obj = define(obj, 'a', {writable:false});

js> // can set enumerable to the same value when configurable is false
js> var obj = define({},  'a', {enumerable:true, configurable:false});
js> var obj = define(obj, 'a', {enumerable:true});

js> // can change from data property to accessor property when configurable is true
js> var obj = define({},  'a', {value : 1, configurable: true});
js> var obj = define(obj, 'a', {get   : function() { return 4 }});
js> obj.a
4
js> describe(obj, 'a').toSource()
({enumerable:false, configurable:true, get:(function () {return 4;})})

js> // can change from accessor property to data property when configurable is true
js> var obj = define({},  'a', {get   : function() { return 2 }, configurable:true});
js> var obj = define(obj, 'a', {value : 5});
js> obj.a
5
js> describe(obj, 'a').toSource()
({value:5, writable:false, enumerable:false, configurable:true})

js> // can change enumerable and writable to true when configurable is true
js> var obj = define({},  'a', {writable : false, enumerable : false, configurable:true});
js> var obj = define(obj, 'a', {writable : true,  enumerable : true, configurable:true});

js> // can change the value if writable is true
js> var obj = define({},  'a', {value:6, writable:true})
js> obj.a
6
js> var obj = define(obj, 'a', {value:7})
js> obj.a
7

js> // defining a new property should fail loudly when object is not extensible
js> var obj = Object.preventExtensions({});
js> expectTypeError(function() { define(obj, 'a', {value:1}) })

js> // defining new property should succeed when object is extensible
js> var obj = {}
js> Object.isExtensible(obj);
true
js> obj.a = 8; obj.a
8

js> // changing existing property should succeed when object is not extensible
js> var obj = define({},  'a', {value:1, writable:true});
js> var obj = Object.preventExtensions(obj);
js> obj.a = 9; obj.a
9

js> // defined getters and setters must be functions
js> expectTypeError(function() { define({}, 'a', {get:1}); })
js> expectTypeError(function() { define({}, 'a', {set:1}); })

js> // make sure defineProperty works properly with numbers as ids
js> Object.defineProperty({}, 0, {value:1, enumerable:true})['0']
1

js> // make sure defineProperty works properly with arrays
js> Object.defineProperty([], 0, {value:1, enumerable:true})[0]
1

js> // make sure defineProperty works properly with arrays
js> Object.defineProperty([], 'a', {value:1, enumerable:true})['a']
1

js> // make sure defineProperty updates length properly for arrays
js> Object.defineProperty([], 0, {value:1}).length
1

js> // make sure that getters and setters are actually used to get and set property
js> Object.defineProperty({}, 'a', {get:function() { return "get called"; }}).a
get called
js> Object.defineProperty({}, 'a', {set:function(val) { print("set called with "+val); }}).a = 1; undefined;
set called with 1

js> // make sure defineProperty works for builtin properties
js> Object.defineProperty(JSON, 'stringify', {value:1}).stringify
1
js> Object.defineProperty(JSON, 'parse', {get:function() {print('do get'); return undefined}}).parse
do get

js> // an accessor property without a setter behaves as if the setter were undefined
js> // and thus the setter can be set to undefined even when configurable is false
js> var obj = Object.defineProperty({}, 'a', {get:function(){return 1}})
js> var _ = Object.defineProperty(obj, 'a', {set:undefined})
js>

