'use strict';

// This tests the v8.queryObjects() API.

const common = require('../common');
const v8 = require('v8');
const assert = require('assert');
const { inspect } = require('util');

function format(obj) {
  return inspect(obj, { depth: 0 });
}

common.expectWarning(
  'ExperimentalWarning',
  'v8.queryObjects() is an experimental feature and might change at any time',
);

{
  for (const invalid of [undefined, 1, null, false, {}, 'foo']) {
    assert.throws(() => v8.queryObjects(invalid), { code: 'ERR_INVALID_ARG_TYPE' });
  }
  for (const invalid of [1, null, false, 'foo']) {
    assert.throws(() => v8.queryObjects(() => {}, invalid), { code: 'ERR_INVALID_ARG_TYPE' });
  }
  assert.throws(() => v8.queryObjects(() => {}, { format: 'invalid' }), { code: 'ERR_INVALID_ARG_VALUE' });
}

{
  class TestV8QueryObjectsClass {}
  // By default, returns count of objects with the constructor on the prototype.
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass), 0);
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass, { format: 'count' }), 0);
  // 'summary' format returns an array.
  assert.deepStrictEqual(v8.queryObjects(TestV8QueryObjectsClass, { format: 'summary' }), []);

  // Create an instance and check that it shows up in the results.
  const obj = new TestV8QueryObjectsClass();
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass), 1);
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsClass, { format: 'count' }), 1);
  assert.deepStrictEqual(
    v8.queryObjects(TestV8QueryObjectsClass, { format: 'summary' }),
    [ format(obj)]
  );
}

{
  // ES6 class inheritance.
  class TestV8QueryObjectsBaseClass {}
  class TestV8QueryObjectsChildClass extends TestV8QueryObjectsBaseClass {}
  const summary = v8.queryObjects(TestV8QueryObjectsBaseClass, { format: 'summary' });
  // TestV8QueryObjectsChildClass's prototype's [[Prototype]] slot is
  // TestV8QueryObjectsBaseClass's prototype so it shows up in the query.
  assert.deepStrictEqual(summary, [
    format(TestV8QueryObjectsChildClass.prototype),
  ]);
  const obj = new TestV8QueryObjectsChildClass();
  assert.deepStrictEqual(
    v8.queryObjects(TestV8QueryObjectsBaseClass, { format: 'summary' }).sort(),
    [
      format(TestV8QueryObjectsChildClass.prototype),
      format(obj),
    ].sort()
  );
  assert.deepStrictEqual(
    v8.queryObjects(TestV8QueryObjectsChildClass, { format: 'summary' }),
    [ format(obj) ],
  );
}

{
  function TestV8QueryObjectsCtor() {}
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor), 0);
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor, { format: 'count' }), 0);
  assert.deepStrictEqual(v8.queryObjects(TestV8QueryObjectsCtor, { format: 'summary' }), []);

  // Create an instance and check that it shows up in the results.
  const obj = new TestV8QueryObjectsCtor();
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor), 1);
  assert.strictEqual(v8.queryObjects(TestV8QueryObjectsCtor, { format: 'count' }), 1);
  assert.deepStrictEqual(
    v8.queryObjects(TestV8QueryObjectsCtor, { format: 'summary' }),
    [ format(obj)]
  );
}

{
  // Classic inheritance.
  function TestV8QueryObjectsBaseCtor() {}

  function TestV8QueryObjectsChildCtor() {}
  Object.setPrototypeOf(TestV8QueryObjectsChildCtor.prototype, TestV8QueryObjectsBaseCtor.prototype);
  Object.setPrototypeOf(TestV8QueryObjectsChildCtor, TestV8QueryObjectsBaseCtor);

  const summary = v8.queryObjects(TestV8QueryObjectsBaseCtor, { format: 'summary' });
  assert.deepStrictEqual(summary, [
    format(TestV8QueryObjectsChildCtor.prototype),
  ]);
  const obj = new TestV8QueryObjectsChildCtor();
  assert.deepStrictEqual(
    v8.queryObjects(TestV8QueryObjectsChildCtor, { format: 'summary' }),
    [ format(obj) ],
  );
}
