/*
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/licenses/publicdomain/
 */

/* These tests are not checking whether an exception is thrown or not for
 * proxies:  those tests should already exist in js/src/tests/ecma_6/Proxy .
 * We expect TypeErrors to be thrown in these tests, with a stringification
 * of the error message showing whatever property name the error is being
 * reported for.
 *
 * Beyond the presence of the property name, these tests do not care about the
 * contents of the message.
 *
 * The reason for requiring the property name is simple:  with ECMAScript
 * proxies, it can be really hard to figure out what little assertion causes a
 * TypeError in the first place.
 */
"use strict";

function assertThrowsTypeErrorIncludes(f, propStr, details) {
  var fullmsg;
  try {
    f();
  }
  catch (exc) {
    if (!(exc instanceof TypeError))
      fullmsg =
        "Assertion failed: expected TypeError, got " + exc;
    else if (!exc.message.includes(propStr))
      fullmsg =
        `Assertion failed: expected TypeError message '${exc.message}' to include '${propStr}'`;
    else if (details && !exc.message.includes(details))
      fullmsg =
        `Assertion failed: expected TypeError message '${exc.message}' to include '${details}'`;
    else
      return;
  }

  if (fullmsg === undefined) {
    fullmsg =
      "Assertion failed: expected TypeError, no exception thrown";
  }

  throw new Error(fullmsg);
}

const STR = "one", STR_NAME = `"one"`;
const SYM = Symbol("two"), SYM_NAME = `'Symbol("two")'`;

function errorHasPropertyTests(test) {
  assertThrowsTypeErrorIncludes(() => test(STR), STR_NAME);
  assertThrowsTypeErrorIncludes(() => test(SYM), SYM_NAME);
}

function errorHasPropertyTestsWithDetails(test) {
  let [throwable, details] = test(STR);
  assertThrowsTypeErrorIncludes(throwable, STR_NAME, details);

  [throwable, details] = test(SYM);
  assertThrowsTypeErrorIncludes(throwable, SYM_NAME, details);
}

// getOwnPropertyDescriptor

function testGetOwnPropertyDescriptor_OBJORUNDEF(propName) {
  // JSMSG_PROXY_GETOWN_OBJORUNDEF
  const h = {
    getOwnPropertyDescriptor: () => 2
  };

  const t = {};
  const p = new Proxy(t, h);

  Reflect.getOwnPropertyDescriptor(p, propName);
}

function testGetOwnPropertyDescriptor_NC_AS_NE(propName) {
  // JSMSG_CANT_REPORT_NC_AS_NE
  const h = {
    getOwnPropertyDescriptor: () => undefined
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  Reflect.getOwnPropertyDescriptor(p, propName);
}

function testGetOwnPropertyDescriptor_E_AS_NE(propName) {
  // JSMSG_CANT_REPORT_E_AS_NE
  const h = {
    getOwnPropertyDescriptor: () => undefined,
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
  });
  Reflect.preventExtensions(t);
  const p = new Proxy(t, h);

  Reflect.getOwnPropertyDescriptor(p, propName);
}

function testGetOwnPropertyDescriptor_NE_AS_NC(propName) {
  // JSMSG_CANT_REPORT_NE_AS_NC
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: false
      };
    }
  };

  const t = {};
  const p = new Proxy(t, h);

  Reflect.getOwnPropertyDescriptor(p, propName);
}

function testGetOwnPropertyDescriptor_C_AS_NC(propName) {
  // JSMSG_CANT_REPORT_C_AS_NC
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: false // here's the difference
      };
    }
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
  });

  const p = new Proxy(t, h);

  Reflect.getOwnPropertyDescriptor(p, propName);
}

function testGetOwnPropertyDescriptor_INVALID_NOT_EXTENSIBLE(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_NOT_EXTENSIBLE
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: true
      };
    }
  };

  const t = {};
  Reflect.preventExtensions(t);

  const p = new Proxy(t, h);
  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy can't report an extensible object as non-extensible"];
}

function testGetOwnPropertyDescriptor_INVALID_C_AS_NC(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_CANT_REPORT_NC_AS_C
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: true
      };
    }
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  });

  const p = new Proxy(t, h);
  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy can't report an existing non-configurable property as configurable"];
}

function testGetOwnPropertyDescriptor_INVALID_ENUM_DIFFERENT_CURRENT(cEnumerable, propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_ENUM_DIFFERENT
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        value: 1,
        writable: true,
        enumerable: !cEnumerable,
        configurable: false
      };
    }
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: cEnumerable,
    configurable: false
  });

  const p = new Proxy(t, h);
  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy can't report a different 'enumerable' from target when target is not configurable"];
}

function testGetOwnPropertyDescriptor_INVALID_CURRENT_NC_DIFF_TYPE(cAccessor, propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_CURRENT_NC_DIFF_TYPE
  const accDesc = {
    get: () => 1,
    enumerable: true,
    configurable: false,
  };
  const dataDesc = {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  };

  const h = {
    getOwnPropertyDescriptor: () => { return (cAccessor ? dataDesc : accDesc); }
  };

  const t = {};
  Reflect.defineProperty(t, propName, cAccessor ? accDesc : dataDesc);
  const p = new Proxy(t, h);
  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy can't report a different descriptor type when target is not configurable"];
}

function testGetOwnPropertyDescriptor_INVALID_NW_AS_W(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_CANT_REPORT_NW_AS_W
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        value: 1,
        writable: true,
        enumerable: true,
        configurable: false
      };
    }
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 2,
    writable: false,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy can't report a non-configurable, non-writable property as writable"];
}

function testGetOwnPropertyDescriptor_INVALID_DIFFERENT_VALUE(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_DIFFERENT_VALUE
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        value: 1,
        writable: false,
        enumerable: true,
        configurable: false
      };
    }
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 2,
    writable: false,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy must report the same value for the non-writable, non-configurable property"];
}

function testGetOwnPropertyDescriptor_INVALID_SETTERS_DIFFERENT(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_SETTERS_DIFFERENT
  const g = () => 1;
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        get: g,
        set: () => 2,
        enumerable: true,
        configurable: false
      };
    }
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    get: g,
    set: () => 2,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy can't report different setters for a currently non-configurable property"];
}

function testGetOwnPropertyDescriptor_INVALID_GETTERS_DIFFERENT(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_GETTERS_DIFFERENT
  const h = {
    getOwnPropertyDescriptor: function() {
      return {
        get: () => 2,
        enumerable: true,
        configurable: false
      };
    }
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    get: () => 2,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.getOwnPropertyDescriptor(p, propName); },
          "proxy can't report different getters for a currently non-configurable property"];
}

// defineProperty
function testDefineProperty_CANT_DEFINE_NEW(propName) {
  // JSMSG_CANT_DEFINE_NEW
  const h = {
    defineProperty: () => true
  };

  const t = {};
  Reflect.preventExtensions(t);

  const p = new Proxy(t, h);
  Reflect.defineProperty(p, propName, {});
}

function testDefineProperty_NE_AS_NC(propName) {
  // JSMSG_CANT_DEFINE_NE_AS_NC
  const h = {
    defineProperty: () => true
  };

  const t = {};

  const p = new Proxy(t, h);
  Reflect.defineProperty(p, propName, {
    value: 1,
    enumerable: true,
    writable: true,
    configurable: false,
  });
}

/* Reflect.defineProperty(proxy, propName, desc) cannot throw
 * JSMSG_CANT_REPORT_INVALID with DETAILS_NOT_EXTENSIBLE.  Here's why:
 *
 * To throw with DETAILS_NOT_EXTENSIBLE, current must be undefined and the
 * target must not be extensible, inside ValidateAndApplyPropertyDescriptor.
 *
 * ValidateAndApplyPropertyDescriptor's current is also
 * IsCompatiblePropertyDescriptor's current, and therefore also
 * targetDesc in [[DefineOwnProperty]] for proxies at step 16b.
 *
 * BUT step 16 is not reached if targetDesc in [[DefineOwnProperty]] is
 * undefined:  instead step 15 is invoked.  QED.
 */

function testDefineProperty_INVALID_NC_AS_C(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_CANT_REPORT_NC_AS_C
  const h = {
    defineProperty: function() {
      return true;
    }
  };

  const newDesc = {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  });

  const p = new Proxy(t, h);
  return [() => { Reflect.defineProperty(p, propName, newDesc); },
          "proxy can't report an existing non-configurable property as configurable"];
}

function testDefineProperty_INVALID_ENUM_DIFFERENT_CURRENT(cEnumerable, propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_ENUM_DIFFERENT
  const h = {
    defineProperty: function() {
      return true;
    }
  };

  const newDesc = {
    value: 1,
    writable: true,
    enumerable: !cEnumerable,
    configurable: false
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: cEnumerable,
    configurable: false
  });

  const p = new Proxy(t, h);
  return [() => { Reflect.defineProperty(p, propName, newDesc); },
          "proxy can't report a different 'enumerable' from target when target is not configurable"];
}

function testDefineProperty_INVALID_CURRENT_NC_DIFF_TYPE(cAccessor, propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_CURRENT_NC_DIFF_TYPE
  const accDesc = {
    get: () => 1,
    enumerable: true,
    configurable: false,
  };
  const dataDesc = {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  };

  const h = {
    defineProperty: () => true,
  };

  const t = {};
  Reflect.defineProperty(t, propName, cAccessor ? accDesc : dataDesc);
  const p = new Proxy(t, h);
  return [() => { Reflect.defineProperty(p, propName, cAccessor ? dataDesc : accDesc); },
          "proxy can't report a different descriptor type when target is not configurable"];
}

function testDefineProperty_INVALID_NW_AS_W(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_CANT_REPORT_NW_AS_W
  const h = {
    defineProperty: function() {
      return true;
    }
  };

  const newDesc = {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 2,
    writable: false,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.defineProperty(p, propName, newDesc); },
          "proxy can't report a non-configurable, non-writable property as writable"];
}

function testDefineProperty_INVALID_DIFFERENT_VALUE(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_DIFFERENT_VALUE
  const h = {
    defineProperty: function() {
      return true;
    }
  };

  const newDesc = {
    value: 1,
    writable: false,
    enumerable: true,
    configurable: false
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 2,
    writable: false,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.defineProperty(p, propName, newDesc); },
          "proxy must report the same value for the non-writable, non-configurable property"];
}

function testDefineProperty_INVALID_SETTERS_DIFFERENT(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_SETTERS_DIFFERENT
  const g = () => 1;
  const h = {
    defineProperty: function() {
      return true;
    }
  };

  const newDesc = {
    get: g,
    set: () => 2,
    enumerable: true,
    configurable: false
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    get: g,
    set: () => 2,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.defineProperty(p, propName, newDesc); },
          "proxy can't report different setters for a currently non-configurable property"];
}

function testDefineProperty_INVALID_GETTERS_DIFFERENT(propName) {
  // JSMSG_CANT_REPORT_INVALID, DETAILS_GETTERS_DIFFERENT
  const h = {
    defineProperty: function() {
      return true;
    }
  };

  const newDesc = {
    get: () => 2,
    enumerable: true,
    configurable: false
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    get: () => 2,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.defineProperty(p, propName, newDesc); },
          "proxy can't report different getters for a currently non-configurable property"];
}

function testDefineProperty_INVALID_C_AS_NC(propName) {
  const h = {
    defineProperty: function() {
      return true;
    }
  };

  const newDesc = {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
  });
  const p = new Proxy(t, h);

  return [() => { Reflect.defineProperty(p, propName, newDesc); },
          "proxy can't define an existing configurable property as non-configurable"];
}

// ownKeys

function testOwnKeys_CANT_SKIP_NC(propName) {
  // JSMSG_CANT_SKIP_NC
  const h = {
    ownKeys: () => []
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  });

  const p = new Proxy(t, h);

  Reflect.ownKeys(p);
}

function testOwnKeys_E_AS_NE(propName) {
  // JSMSG_CANT_REPORT_E_AS_NE
  const h = {
    ownKeys: () => []
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    configurable: true,
    value: 1,
    writable: true,
    enumerable: true
  });

  Reflect.preventExtensions(t);
  const p = new Proxy(t, h);

  Reflect.ownKeys(p);
}

// has

function testHas_NC_AS_NE(propName) {
  // JSMSG_CANT_REPORT_NC_AS_NE
  const h = {
    has: () => undefined
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: false
  });
  const p = new Proxy(t, h);

  Reflect.has(p, propName);
}

function testHas_E_AS_NE(propName) {
  // JSMSG_CANT_REPORT_E_AS_NE
  const h = {
    has: () => undefined
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
  });
  Reflect.preventExtensions(t);
  const p = new Proxy(t, h);

  Reflect.has(p, propName);
}

// get

function testGet_SAME_VALUE(propName) {
  const h = {
    get: () => 2
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: false,
    enumerable: true,
    configurable: false
  });

  const p = new Proxy(t, h);
  Reflect.get(p, propName);
}

function testGet_MUST_REPORT_UNDEFINED(propName) {
  // JSMSG_MUST_REPORT_UNDEFINED
  const h = {
    get: () => 2
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    set: () => { /* do nothing */},
    enumerable: true,
    configurable: false
  });

  const p = new Proxy(t, h);
  Reflect.get(p, propName);
}

// set

function testSet_CANT_SET_NW_NC(propName) {
  // JSMSG_CANT_SET_NW_NC
  const h = {
    set: () => true,
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    value: 1,
    writable: false,
    enumerable: true,
    configurable: false
  });

  const p = new Proxy(t, h);
  Reflect.set(p, propName, 3);
}

function testSet_WO_SETTER(propName) {
  // JSMSG_MUST_REPORT_UNDEFINED
  const h = {
    set: () => true
  };

  const t = {};
  Reflect.defineProperty(t, propName, {
    get: () => { /* do nothing */},
    enumerable: true,
    configurable: false
  });

  const p = new Proxy(t, h);
  Reflect.set(p, propName, 1);
}

// test sequence

[
  testGetOwnPropertyDescriptor_OBJORUNDEF,
  testGetOwnPropertyDescriptor_NC_AS_NE,
  testGetOwnPropertyDescriptor_E_AS_NE,
  testGetOwnPropertyDescriptor_NE_AS_NC,
  testGetOwnPropertyDescriptor_C_AS_NC,

  testDefineProperty_CANT_DEFINE_NEW,
  testDefineProperty_NE_AS_NC,

  testOwnKeys_CANT_SKIP_NC,
  testOwnKeys_E_AS_NE,

  testHas_NC_AS_NE,
  testHas_E_AS_NE,

  testGet_SAME_VALUE,
  testGet_MUST_REPORT_UNDEFINED,

  testSet_CANT_SET_NW_NC,
  testSet_WO_SETTER,
].forEach(errorHasPropertyTests);

[
  testGetOwnPropertyDescriptor_INVALID_NOT_EXTENSIBLE,
  testGetOwnPropertyDescriptor_INVALID_C_AS_NC,
  testGetOwnPropertyDescriptor_INVALID_ENUM_DIFFERENT_CURRENT.bind(null, true),
  testGetOwnPropertyDescriptor_INVALID_ENUM_DIFFERENT_CURRENT.bind(null, false),
  testGetOwnPropertyDescriptor_INVALID_CURRENT_NC_DIFF_TYPE.bind(null, true),
  testGetOwnPropertyDescriptor_INVALID_CURRENT_NC_DIFF_TYPE.bind(null, false),
  testGetOwnPropertyDescriptor_INVALID_NW_AS_W,
  testGetOwnPropertyDescriptor_INVALID_DIFFERENT_VALUE,
  testGetOwnPropertyDescriptor_INVALID_SETTERS_DIFFERENT,
  testGetOwnPropertyDescriptor_INVALID_GETTERS_DIFFERENT,

  testDefineProperty_INVALID_NC_AS_C,
  testDefineProperty_INVALID_ENUM_DIFFERENT_CURRENT.bind(null, true),
  testDefineProperty_INVALID_ENUM_DIFFERENT_CURRENT.bind(null, false),
  testDefineProperty_INVALID_CURRENT_NC_DIFF_TYPE.bind(null, true),
  testDefineProperty_INVALID_CURRENT_NC_DIFF_TYPE.bind(null, false),
  testDefineProperty_INVALID_NW_AS_W,
  testDefineProperty_INVALID_DIFFERENT_VALUE,
  testDefineProperty_INVALID_SETTERS_DIFFERENT,
  testDefineProperty_INVALID_GETTERS_DIFFERENT,
  testDefineProperty_INVALID_C_AS_NC,
].forEach(errorHasPropertyTestsWithDetails);

reportCompare(0, 0, 'ok');
