1'use strict';
2const common = require('../../common');
3const assert = require('assert');
4
5// Testing api calls for objects
6const test_object = require(`./build/${common.buildType}/test_object`);
7
8
9const object = {
10  hello: 'world',
11  array: [
12    1, 94, 'str', 12.321, { test: 'obj in arr' },
13  ],
14  newObject: {
15    test: 'obj in obj',
16  },
17};
18
19assert.strictEqual(test_object.Get(object, 'hello'), 'world');
20assert.strictEqual(test_object.GetNamed(object, 'hello'), 'world');
21assert.deepStrictEqual(test_object.Get(object, 'array'),
22                       [1, 94, 'str', 12.321, { test: 'obj in arr' }]);
23assert.deepStrictEqual(test_object.Get(object, 'newObject'),
24                       { test: 'obj in obj' });
25
26assert(test_object.Has(object, 'hello'));
27assert(test_object.HasNamed(object, 'hello'));
28assert(test_object.Has(object, 'array'));
29assert(test_object.Has(object, 'newObject'));
30
31const newObject = test_object.New();
32assert(test_object.Has(newObject, 'test_number'));
33assert.strictEqual(newObject.test_number, 987654321);
34assert.strictEqual(newObject.test_string, 'test string');
35
36{
37  // Verify that napi_get_property() walks the prototype chain.
38  function MyObject() {
39    this.foo = 42;
40    this.bar = 43;
41  }
42
43  MyObject.prototype.bar = 44;
44  MyObject.prototype.baz = 45;
45
46  const obj = new MyObject();
47
48  assert.strictEqual(test_object.Get(obj, 'foo'), 42);
49  assert.strictEqual(test_object.Get(obj, 'bar'), 43);
50  assert.strictEqual(test_object.Get(obj, 'baz'), 45);
51  assert.strictEqual(test_object.Get(obj, 'toString'),
52                     Object.prototype.toString);
53}
54
55{
56  // Verify that napi_has_own_property() fails if property is not a name.
57  [true, false, null, undefined, {}, [], 0, 1, () => { }].forEach((value) => {
58    assert.throws(() => {
59      test_object.HasOwn({}, value);
60    }, /^Error: A string or symbol was expected$/);
61  });
62}
63
64{
65  // Verify that napi_has_own_property() does not walk the prototype chain.
66  const symbol1 = Symbol();
67  const symbol2 = Symbol();
68
69  function MyObject() {
70    this.foo = 42;
71    this.bar = 43;
72    this[symbol1] = 44;
73  }
74
75  MyObject.prototype.bar = 45;
76  MyObject.prototype.baz = 46;
77  MyObject.prototype[symbol2] = 47;
78
79  const obj = new MyObject();
80
81  assert.strictEqual(test_object.HasOwn(obj, 'foo'), true);
82  assert.strictEqual(test_object.HasOwn(obj, 'bar'), true);
83  assert.strictEqual(test_object.HasOwn(obj, symbol1), true);
84  assert.strictEqual(test_object.HasOwn(obj, 'baz'), false);
85  assert.strictEqual(test_object.HasOwn(obj, 'toString'), false);
86  assert.strictEqual(test_object.HasOwn(obj, symbol2), false);
87}
88
89{
90  // test_object.Inflate increases all properties by 1
91  const cube = {
92    x: 10,
93    y: 10,
94    z: 10,
95  };
96
97  assert.deepStrictEqual(test_object.Inflate(cube), { x: 11, y: 11, z: 11 });
98  assert.deepStrictEqual(test_object.Inflate(cube), { x: 12, y: 12, z: 12 });
99  assert.deepStrictEqual(test_object.Inflate(cube), { x: 13, y: 13, z: 13 });
100  cube.t = 13;
101  assert.deepStrictEqual(
102    test_object.Inflate(cube), { x: 14, y: 14, z: 14, t: 14 });
103
104  const sym1 = Symbol('1');
105  const sym2 = Symbol('2');
106  const sym3 = Symbol('3');
107  const sym4 = Symbol('4');
108  const object2 = {
109    [sym1]: '@@iterator',
110    [sym2]: sym3,
111  };
112
113  assert(test_object.Has(object2, sym1));
114  assert(test_object.Has(object2, sym2));
115  assert.strictEqual(test_object.Get(object2, sym1), '@@iterator');
116  assert.strictEqual(test_object.Get(object2, sym2), sym3);
117  assert(test_object.Set(object2, 'string', 'value'));
118  assert(test_object.SetNamed(object2, 'named_string', 'value'));
119  assert(test_object.Set(object2, sym4, 123));
120  assert(test_object.Has(object2, 'string'));
121  assert(test_object.HasNamed(object2, 'named_string'));
122  assert(test_object.Has(object2, sym4));
123  assert.strictEqual(test_object.Get(object2, 'string'), 'value');
124  assert.strictEqual(test_object.Get(object2, sym4), 123);
125}
126
127{
128  // Wrap a pointer in a JS object, then verify the pointer can be unwrapped.
129  const wrapper = {};
130  test_object.Wrap(wrapper);
131
132  assert(test_object.Unwrap(wrapper));
133}
134
135{
136  // Verify that wrapping doesn't break an object's prototype chain.
137  const wrapper = {};
138  const protoA = { protoA: true };
139  Object.setPrototypeOf(wrapper, protoA);
140  test_object.Wrap(wrapper);
141
142  assert(test_object.Unwrap(wrapper));
143  assert(wrapper.protoA);
144}
145
146{
147  // Verify the pointer can be unwrapped after inserting in the prototype chain.
148  const wrapper = {};
149  const protoA = { protoA: true };
150  Object.setPrototypeOf(wrapper, protoA);
151  test_object.Wrap(wrapper);
152
153  const protoB = { protoB: true };
154  Object.setPrototypeOf(protoB, Object.getPrototypeOf(wrapper));
155  Object.setPrototypeOf(wrapper, protoB);
156
157  assert(test_object.Unwrap(wrapper));
158  assert(wrapper.protoA, true);
159  assert(wrapper.protoB, true);
160}
161
162{
163  // Verify that objects can be type-tagged and type-tag-checked.
164  const obj1 = test_object.TypeTaggedInstance(0);
165  const obj2 = test_object.TypeTaggedInstance(1);
166  const obj3 = test_object.TypeTaggedInstance(2);
167  const obj4 = test_object.TypeTaggedInstance(3);
168  const external = test_object.TypeTaggedExternal(2);
169  const plainExternal = test_object.PlainExternal();
170
171  // Verify that we do not allow type tag indices greater than the largest
172  // available index.
173  assert.throws(() => test_object.TypeTaggedInstance(39), {
174    name: 'RangeError',
175    message: 'Invalid type index',
176  });
177  assert.throws(() => test_object.TypeTaggedExternal(39), {
178    name: 'RangeError',
179    message: 'Invalid type index',
180  });
181
182  // Verify that type tags are correctly accepted.
183  assert.strictEqual(test_object.CheckTypeTag(0, obj1), true);
184  assert.strictEqual(test_object.CheckTypeTag(1, obj2), true);
185  assert.strictEqual(test_object.CheckTypeTag(2, obj3), true);
186  assert.strictEqual(test_object.CheckTypeTag(3, obj4), true);
187  assert.strictEqual(test_object.CheckTypeTag(2, external), true);
188
189  // Verify that wrongly tagged objects are rejected.
190  assert.strictEqual(test_object.CheckTypeTag(0, obj2), false);
191  assert.strictEqual(test_object.CheckTypeTag(1, obj1), false);
192  assert.strictEqual(test_object.CheckTypeTag(0, obj3), false);
193  assert.strictEqual(test_object.CheckTypeTag(1, obj4), false);
194  assert.strictEqual(test_object.CheckTypeTag(2, obj4), false);
195  assert.strictEqual(test_object.CheckTypeTag(3, obj3), false);
196  assert.strictEqual(test_object.CheckTypeTag(4, obj3), false);
197  assert.strictEqual(test_object.CheckTypeTag(0, external), false);
198  assert.strictEqual(test_object.CheckTypeTag(1, external), false);
199  assert.strictEqual(test_object.CheckTypeTag(3, external), false);
200  assert.strictEqual(test_object.CheckTypeTag(4, external), false);
201
202  // Verify that untagged objects are rejected.
203  assert.strictEqual(test_object.CheckTypeTag(0, {}), false);
204  assert.strictEqual(test_object.CheckTypeTag(1, {}), false);
205  assert.strictEqual(test_object.CheckTypeTag(0, plainExternal), false);
206  assert.strictEqual(test_object.CheckTypeTag(1, plainExternal), false);
207  assert.strictEqual(test_object.CheckTypeTag(2, plainExternal), false);
208  assert.strictEqual(test_object.CheckTypeTag(3, plainExternal), false);
209  assert.strictEqual(test_object.CheckTypeTag(4, plainExternal), false);
210}
211
212{
213  // Verify that normal and nonexistent properties can be deleted.
214  const sym = Symbol();
215  const obj = { foo: 'bar', [sym]: 'baz' };
216
217  assert.strictEqual('foo' in obj, true);
218  assert.strictEqual(sym in obj, true);
219  assert.strictEqual('does_not_exist' in obj, false);
220  assert.strictEqual(test_object.Delete(obj, 'foo'), true);
221  assert.strictEqual('foo' in obj, false);
222  assert.strictEqual(sym in obj, true);
223  assert.strictEqual('does_not_exist' in obj, false);
224  assert.strictEqual(test_object.Delete(obj, sym), true);
225  assert.strictEqual('foo' in obj, false);
226  assert.strictEqual(sym in obj, false);
227  assert.strictEqual('does_not_exist' in obj, false);
228}
229
230{
231  // Verify that non-configurable properties are not deleted.
232  const obj = {};
233
234  Object.defineProperty(obj, 'foo', { configurable: false });
235  assert.strictEqual(test_object.Delete(obj, 'foo'), false);
236  assert.strictEqual('foo' in obj, true);
237}
238
239{
240  // Verify that prototype properties are not deleted.
241  function Foo() {
242    this.foo = 'bar';
243  }
244
245  Foo.prototype.foo = 'baz';
246
247  const obj = new Foo();
248
249  assert.strictEqual(obj.foo, 'bar');
250  assert.strictEqual(test_object.Delete(obj, 'foo'), true);
251  assert.strictEqual(obj.foo, 'baz');
252  assert.strictEqual(test_object.Delete(obj, 'foo'), true);
253  assert.strictEqual(obj.foo, 'baz');
254}
255
256{
257  // Verify that napi_get_property_names gets the right set of property names,
258  // i.e.: includes prototypes, only enumerable properties, skips symbols,
259  // and includes indices and converts them to strings.
260
261  const object = Object.create({
262    inherited: 1,
263  });
264
265  const fooSymbol = Symbol('foo');
266
267  object.normal = 2;
268  object[fooSymbol] = 3;
269  Object.defineProperty(object, 'unenumerable', {
270    value: 4,
271    enumerable: false,
272    writable: true,
273    configurable: true,
274  });
275  Object.defineProperty(object, 'writable', {
276    value: 4,
277    enumerable: true,
278    writable: true,
279    configurable: false,
280  });
281  Object.defineProperty(object, 'configurable', {
282    value: 4,
283    enumerable: true,
284    writable: false,
285    configurable: true,
286  });
287  object[5] = 5;
288
289  assert.deepStrictEqual(test_object.GetPropertyNames(object),
290                         ['5',
291                          'normal',
292                          'writable',
293                          'configurable',
294                          'inherited']);
295
296  assert.deepStrictEqual(test_object.GetSymbolNames(object),
297                         [fooSymbol]);
298
299  assert.deepStrictEqual(test_object.GetEnumerableWritableNames(object),
300                         ['5',
301                          'normal',
302                          'writable',
303                          fooSymbol,
304                          'inherited']);
305
306  assert.deepStrictEqual(test_object.GetOwnWritableNames(object),
307                         ['5',
308                          'normal',
309                          'unenumerable',
310                          'writable',
311                          fooSymbol]);
312
313  assert.deepStrictEqual(test_object.GetEnumerableConfigurableNames(object),
314                         ['5',
315                          'normal',
316                          'configurable',
317                          fooSymbol,
318                          'inherited']);
319
320  assert.deepStrictEqual(test_object.GetOwnConfigurableNames(object),
321                         ['5',
322                          'normal',
323                          'unenumerable',
324                          'configurable',
325                          fooSymbol]);
326}
327
328// Verify that passing NULL to napi_set_property() results in the correct
329// error.
330assert.deepStrictEqual(test_object.TestSetProperty(), {
331  envIsNull: 'Invalid argument',
332  objectIsNull: 'Invalid argument',
333  keyIsNull: 'Invalid argument',
334  valueIsNull: 'Invalid argument',
335});
336
337// Verify that passing NULL to napi_has_property() results in the correct
338// error.
339assert.deepStrictEqual(test_object.TestHasProperty(), {
340  envIsNull: 'Invalid argument',
341  objectIsNull: 'Invalid argument',
342  keyIsNull: 'Invalid argument',
343  resultIsNull: 'Invalid argument',
344});
345
346// Verify that passing NULL to napi_get_property() results in the correct
347// error.
348assert.deepStrictEqual(test_object.TestGetProperty(), {
349  envIsNull: 'Invalid argument',
350  objectIsNull: 'Invalid argument',
351  keyIsNull: 'Invalid argument',
352  resultIsNull: 'Invalid argument',
353});
354
355{
356  const obj = { x: 'a', y: 'b', z: 'c' };
357
358  test_object.TestSeal(obj);
359
360  assert.strictEqual(Object.isSealed(obj), true);
361
362  assert.throws(() => {
363    obj.w = 'd';
364  }, /Cannot add property w, object is not extensible/);
365
366  assert.throws(() => {
367    delete obj.x;
368  }, /Cannot delete property 'x' of #<Object>/);
369
370  // Sealed objects allow updating existing properties,
371  // so this should not throw.
372  obj.x = 'd';
373}
374
375{
376  const obj = { x: 10, y: 10, z: 10 };
377
378  test_object.TestFreeze(obj);
379
380  assert.strictEqual(Object.isFrozen(obj), true);
381
382  assert.throws(() => {
383    obj.x = 10;
384  }, /Cannot assign to read only property 'x' of object '#<Object>/);
385
386  assert.throws(() => {
387    obj.w = 15;
388  }, /Cannot add property w, object is not extensible/);
389
390  assert.throws(() => {
391    delete obj.x;
392  }, /Cannot delete property 'x' of #<Object>/);
393}
394