175 lines
4.9 KiB
JavaScript
175 lines
4.9 KiB
JavaScript
const dontEnumDesc = { value: 1, writable: true, enumerable: false, configurable: true };
|
|
const testCases = [
|
|
{
|
|
name: "Object",
|
|
createObject: () => Object.create(null, { foo: dontEnumDesc, bar: dontEnumDesc }),
|
|
dontEnumKeys: ["foo", "bar"],
|
|
},
|
|
{
|
|
name: "Error",
|
|
createObject: () => new Error(),
|
|
dontEnumKeys: ["line", "column", "sourceURL", "stack"],
|
|
},
|
|
{
|
|
name: "Array (empty)",
|
|
createObject: () => [],
|
|
dontEnumKeys: ["length"],
|
|
},
|
|
{
|
|
name: "Array (sparse)",
|
|
createObject: () => Object.defineProperties([0, 1, 2], { 0: dontEnumDesc, 2: dontEnumDesc }),
|
|
dontEnumKeys: ["0", "2", "length"],
|
|
},
|
|
{
|
|
name: "Function (strict)",
|
|
createObject: () => function() { "use strict"; },
|
|
dontEnumKeys: ["length", "name", "prototype"],
|
|
},
|
|
{
|
|
name: "Function (non-strict)",
|
|
createObject: () => function() {},
|
|
dontEnumKeys: ["length", "name", "prototype"],
|
|
},
|
|
{
|
|
name: "RegExp",
|
|
createObject: () => /(?:)/g,
|
|
dontEnumKeys: ["lastIndex"],
|
|
},
|
|
{
|
|
name: "String",
|
|
createObject: () => new String("foo"),
|
|
dontEnumKeys: ["length"],
|
|
},
|
|
{
|
|
name: "Arguments (strict)",
|
|
createObject: function(foo) { "use strict"; return arguments; },
|
|
dontEnumKeys: ["length", "callee"],
|
|
},
|
|
{
|
|
name: "Arguments (non-strict)",
|
|
createObject: function(foo) { return arguments; },
|
|
dontEnumKeys: ["length", "callee"],
|
|
},
|
|
{
|
|
name: "Reflect",
|
|
createObject: () => $vm.createGlobalObject().Reflect,
|
|
dontEnumKeys: ["apply", "get", "has", "set"],
|
|
},
|
|
{
|
|
name: "Date.prototype",
|
|
createObject: () => $vm.createGlobalObject().Date.prototype,
|
|
dontEnumKeys: ["toISOString", "getTime", "setYear"],
|
|
},
|
|
];
|
|
|
|
// basic tests
|
|
for (const t of testCases) {
|
|
assert(!contains(forIn(t.createObject()), t.dontEnumKeys), t.name);
|
|
assert(!contains(Object.keys(t.createObject()), t.dontEnumKeys), t.name);
|
|
|
|
assert(contains(Object.getOwnPropertyNames(t.createObject()), t.dontEnumKeys), t.name);
|
|
assert(contains(Reflect.ownKeys(t.createObject()), t.dontEnumKeys), t.name);
|
|
}
|
|
|
|
// shadowing: DontEnum => Enum
|
|
for (const t of testCases) {
|
|
assert(
|
|
!contains(
|
|
forIn(makePrototypeChain(t.createObject(), makeObject(t.dontEnumKeys))),
|
|
t.dontEnumKeys,
|
|
),
|
|
t.name,
|
|
);
|
|
}
|
|
|
|
// shadowing: {} => DontEnum => {} => Enum
|
|
for (const t of testCases) {
|
|
assert(
|
|
!contains(
|
|
forIn(makePrototypeChain({}, t.createObject(), {}, makeObject(t.dontEnumKeys))),
|
|
t.dontEnumKeys,
|
|
),
|
|
t.name,
|
|
);
|
|
}
|
|
|
|
// shadowing: DontEnum => {} => Enum => {} => Enum
|
|
for (const t of testCases) {
|
|
assert(
|
|
!contains(
|
|
forIn(makePrototypeChain(t.createObject(), {}, makeObject(t.dontEnumKeys), {}, makeObject(t.dontEnumKeys))),
|
|
t.dontEnumKeys,
|
|
),
|
|
t.name,
|
|
);
|
|
}
|
|
|
|
// shadowing: {} => DontEnum (enumerable: true => false) => {} => Enum
|
|
for (const t of testCases) {
|
|
const dontEnumObject = t.createObject();
|
|
const target = makePrototypeChain({}, dontEnumObject, {}, makeObject(t.dontEnumKeys));
|
|
assert(!contains(forIn(target), t.dontEnumKeys), t.name);
|
|
|
|
const enumKeys = t.dontEnumKeys.filter(key => Reflect.defineProperty(dontEnumObject, key, { enumerable: true }));
|
|
assert(contains(forIn(target), enumKeys), t.name);
|
|
}
|
|
|
|
// shadowing: {} => DontEnum (delete non-enumerable keys) => {} => Enum
|
|
for (const t of testCases) {
|
|
const dontEnumObject = t.createObject();
|
|
const target = makePrototypeChain({}, dontEnumObject, {}, makeObject(t.dontEnumKeys));
|
|
assert(!contains(forIn(target), t.dontEnumKeys), t.name);
|
|
|
|
const enumKeys = t.dontEnumKeys.filter(key => Reflect.deleteProperty(dontEnumObject, key));
|
|
assert(contains(forIn(target), enumKeys), t.name);
|
|
}
|
|
|
|
// shadowing: {} => DontEnum (materialized) => {} => Enum
|
|
for (const t of testCases) {
|
|
const dontEnumObject = t.createObject();
|
|
const target = makePrototypeChain({}, dontEnumObject, {}, makeObject(t.dontEnumKeys));
|
|
assert(!contains(forIn(target), t.dontEnumKeys), t.name);
|
|
|
|
const dontEnumKeys = t.dontEnumKeys.filter(key => {
|
|
const desc = Object.getOwnPropertyDescriptor(dontEnumObject, key);
|
|
if (!Reflect.deleteProperty(dontEnumObject, key)) return false;
|
|
Object.defineProperty(dontEnumObject, key, desc);
|
|
return true;
|
|
});
|
|
|
|
if (dontEnumKeys.length)
|
|
assert(!contains(forIn(target), dontEnumKeys), t.name);
|
|
}
|
|
|
|
// helpers
|
|
function assert(value, name) {
|
|
if (!value)
|
|
throw new Error(`Bad value: ${value}. Test case: ${name}.`);
|
|
}
|
|
|
|
function contains(array, subarray) {
|
|
return subarray.every(item => array.includes(item));
|
|
}
|
|
|
|
function forIn(object) {
|
|
const keys = [];
|
|
for (const key in object)
|
|
keys.push(key);
|
|
return keys;
|
|
}
|
|
|
|
function makePrototypeChain(...objects) {
|
|
objects.reduce((object, prototype) => {
|
|
Object.setPrototypeOf(object, prototype);
|
|
return prototype;
|
|
});
|
|
return objects[0];
|
|
}
|
|
|
|
function makeObject(keys) {
|
|
const object = {};
|
|
for (const key of keys)
|
|
object[key] = key;
|
|
return object;
|
|
}
|