// META: global=jsshell
// META: script=/wasm/jsapi/assertions.js

function test_operations(object, object_name, operations) {
  for (const [name, length] of operations) {
    test(() => {
      const propdesc = Object.getOwnPropertyDescriptor(object, name);
      assert_equals(typeof propdesc, "object");
      assert_true(propdesc.writable, "writable");
      assert_true(propdesc.enumerable, "enumerable");
      assert_true(propdesc.configurable, "configurable");
      assert_equals(propdesc.value, object[name]);
    }, `${object_name}.${name}`);

    test(() => {
      assert_function_name(object[name], name, `${object_name}.${name}`);
    }, `${object_name}.${name}: name`);

    test(() => {
      assert_function_length(object[name], length, `${object_name}.${name}`);
    }, `${object_name}.${name}: length`);
  }
}

function test_attributes(object, object_name, attributes) {
  for (const [name, mutable] of attributes) {
    test(() => {
      const propdesc = Object.getOwnPropertyDescriptor(object, name);
      assert_equals(typeof propdesc, "object");
      assert_true(propdesc.enumerable, "enumerable");
      assert_true(propdesc.configurable, "configurable");
    }, `${object_name}.${name}`);

    test(() => {
      const propdesc = Object.getOwnPropertyDescriptor(object, name);
      assert_equals(typeof propdesc, "object");
      assert_equals(typeof propdesc.get, "function");
      assert_function_name(propdesc.get, "get " + name, `getter for "${name}"`);
      assert_function_length(propdesc.get, 0, `getter for "${name}"`);
    }, `${object_name}.${name}: getter`);

    test(() => {
      const propdesc = Object.getOwnPropertyDescriptor(object, name);
      assert_equals(typeof propdesc, "object");
      if (mutable) {
        assert_equals(typeof propdesc.set, "function");
        assert_function_name(propdesc.set, "set " + name, `setter for "${name}"`);
        assert_function_length(propdesc.set, 1, `setter for "${name}"`);
      } else {
        assert_equals(typeof propdesc.set, "undefined");
      }
    }, `${object_name}.${name}: setter`);
  }
}

test(() => {
  const propdesc = Object.getOwnPropertyDescriptor(this, "WebAssembly");
  assert_equals(typeof propdesc, "object");
  assert_true(propdesc.writable, "writable");
  assert_false(propdesc.enumerable, "enumerable");
  assert_true(propdesc.configurable, "configurable");
  assert_equals(propdesc.value, this.WebAssembly);
}, "WebAssembly: property descriptor");

test(() => {
  assert_throws(new TypeError(), () => WebAssembly());
}, "WebAssembly: calling");

test(() => {
  assert_throws(new TypeError(), () => new WebAssembly());
}, "WebAssembly: constructing");

const interfaces = [
  "Module",
  "Instance",
  "Memory",
  "Table",
  "Global",
  "CompileError",
  "LinkError",
  "RuntimeError",
];

for (const name of interfaces) {
  test(() => {
    const propdesc = Object.getOwnPropertyDescriptor(WebAssembly, name);
    assert_equals(typeof propdesc, "object");
    assert_true(propdesc.writable, "writable");
    assert_false(propdesc.enumerable, "enumerable");
    assert_true(propdesc.configurable, "configurable");
    assert_equals(propdesc.value, WebAssembly[name]);
  }, `WebAssembly.${name}: property descriptor`);

  test(() => {
    const interface_object = WebAssembly[name];
    const propdesc = Object.getOwnPropertyDescriptor(interface_object, "prototype");
    assert_equals(typeof propdesc, "object");
    assert_false(propdesc.writable, "writable");
    assert_false(propdesc.enumerable, "enumerable");
    assert_false(propdesc.configurable, "configurable");
  }, `WebAssembly.${name}: prototype`);

  test(() => {
    const interface_object = WebAssembly[name];
    const interface_prototype_object = interface_object.prototype;
    const propdesc = Object.getOwnPropertyDescriptor(interface_prototype_object, "constructor");
    assert_equals(typeof propdesc, "object");
    assert_true(propdesc.writable, "writable");
    assert_false(propdesc.enumerable, "enumerable");
    assert_true(propdesc.configurable, "configurable");
    assert_equals(propdesc.value, interface_object);
  }, `WebAssembly.${name}: prototype.constructor`);
}

test_operations(WebAssembly, "WebAssembly", [
  ["validate", 1],
  ["compile", 1],
  ["instantiate", 1],
]);


test_operations(WebAssembly.Module, "WebAssembly.Module", [
  ["exports", 1],
  ["imports", 1],
  ["customSections", 2],
]);


test_attributes(WebAssembly.Instance.prototype, "WebAssembly.Instance", [
  ["exports", false],
]);


test_operations(WebAssembly.Memory.prototype, "WebAssembly.Memory", [
  ["grow", 1],
]);

test_attributes(WebAssembly.Memory.prototype, "WebAssembly.Memory", [
  ["buffer", false],
]);


test_operations(WebAssembly.Table.prototype, "WebAssembly.Table", [
  ["grow", 1],
  ["get", 1],
  ["set", 2],
]);

test_attributes(WebAssembly.Table.prototype, "WebAssembly.Table", [
  ["length", false],
]);


test_operations(WebAssembly.Global.prototype, "WebAssembly.Global", [
  ["valueOf", 0],
]);

test_attributes(WebAssembly.Global.prototype, "WebAssembly.Global", [
  ["value", true],
]);
