forked from mirrors/gecko-dev
The highlevel architecture:
- Each observable array attribute has corresponding ES proxy exotic object stored
in DOM reflector's reserved slot. It use the ObservableArrayProxyHandler as
proxy handler which implements the behaviour defined in the spec.
- Each observable array exotic objects has 2 reserved slots:
* one is to store the raw pointer of the interface, it is cleaned up when the
DOM reflector is finalized. It is used to run the SetAlgorithm and
DeleteAlgorithm defined in the spec.
* one is to store the backing list used to store the indexed value. The additional
properties are stored in the proxy target instead.
Depends on D112279
Differential Revision: https://phabricator.services.mozilla.com/D112280
859 lines
30 KiB
HTML
859 lines
30 KiB
HTML
<!-- Any copyright is dedicated to the Public Domain.
|
|
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
|
<!DOCTYPE HTML>
|
|
<html>
|
|
<head>
|
|
<title>Test Observable Array Type</title>
|
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
|
</head>
|
|
<body>
|
|
<script>
|
|
/* global TestInterfaceObservableArray */
|
|
|
|
add_task(async function init() {
|
|
await SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_defineProperty() {
|
|
let setCallbackCount = 0;
|
|
let deleteCallbackCount = 0;
|
|
let setCallbackTests = null;
|
|
let deleteCallbackTests = null;
|
|
|
|
let m = new TestInterfaceObservableArray({
|
|
setBooleanCallback(value, index) {
|
|
setCallbackCount++;
|
|
if (typeof setCallbackTests === 'function') {
|
|
setCallbackTests(value, index);
|
|
}
|
|
},
|
|
deleteBooleanCallback(value, index) {
|
|
deleteCallbackCount++;
|
|
if (typeof deleteCallbackTests === 'function') {
|
|
deleteCallbackTests(value, index);
|
|
}
|
|
},
|
|
});
|
|
m.observableArrayBoolean = [true, true, true];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 3, "length of observable array should be 0");
|
|
|
|
// Test length
|
|
[
|
|
// [descriptor, shouldThrow, expectedResult]
|
|
// Invalid descriptor
|
|
[{configurable: true, value: 0}, false, false],
|
|
[{enumerable: true, value: 0}, false, false],
|
|
[{writable: false, value: 0}, false, false],
|
|
[{get: ()=>{}}, false, false],
|
|
[{set: ()=>{}}, false, false],
|
|
[{get: ()=>{}, set: ()=>{}}, false, false],
|
|
[{get: ()=>{}, value: 0}, true],
|
|
// Invalid length value
|
|
[{value: 1.9}, true],
|
|
[{value: "invalid"}, true],
|
|
[{value: {}}, true],
|
|
// length value should not greater than current length
|
|
[{value: b.length + 1}, false, false],
|
|
// descriptor without value
|
|
[{configurable: false, enumerable: false, writable: true}, false, true],
|
|
// Success
|
|
[{value: b.length}, false, true],
|
|
[{value: b.length - 1}, false, true],
|
|
[{value: 0}, false, true],
|
|
].forEach(function([descriptor, shouldThrow, expectedResult]) {
|
|
// Initialize
|
|
let oldLen = b.length;
|
|
let oldValues = b.slice();
|
|
let deleteCallbackIndex = oldLen - 1;
|
|
let success = expectedResult && "value" in descriptor;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
setCallbackTests = null;
|
|
deleteCallbackTests = function(_value, _index) {
|
|
is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
|
|
is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
|
|
deleteCallbackIndex--;
|
|
};
|
|
|
|
// Test
|
|
info(`defining "length" property with ${JSON.stringify(descriptor)}`);
|
|
try {
|
|
is(Reflect.defineProperty(b, "length", descriptor), expectedResult,
|
|
`Reflect.defineProperty should return ${expectedResult}`);
|
|
ok(!shouldThrow, "Reflect.defineProperty should not throw");
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, success ? oldLen - descriptor.value : 0, "deleteCallback count");
|
|
isDeeply(b, success ? oldValues.slice(0, descriptor.value) : oldValues, "property values");
|
|
is(b.length, success ? descriptor.value : oldLen, "length of observable array");
|
|
});
|
|
|
|
// Test indexed value
|
|
[
|
|
// [index, descriptor, shouldThrow, expectedResult]
|
|
// Invalid descriptor
|
|
[0, {configurable: false, value: true}, false, false],
|
|
[0, {enumerable: false, value: true}, false, false],
|
|
[0, {writable: false, value: true}, false, false],
|
|
[0, {get: ()=>{}}, false, false],
|
|
[0, {set: ()=>{}}, false, false],
|
|
[0, {get: ()=>{}, set: ()=>{}}, false, false],
|
|
[0, {get: ()=>{}, value: true}, true],
|
|
// Index could not greater than last index + 1.
|
|
[b.length + 1, {configurable: true, enumerable: true, value: true}, false, false],
|
|
// descriptor without value
|
|
[b.length, {configurable: true, enumerable: true}, false, true],
|
|
// Success
|
|
[b.length, {configurable: true, enumerable: true, value: true}, false, true],
|
|
[b.length + 1, {configurable: true, enumerable: true, value: true}, false, true],
|
|
].forEach(function([index, descriptor, shouldThrow, expectedResult]) {
|
|
// Initialize
|
|
let oldLen = b.length;
|
|
let oldValue = b[index];
|
|
let success = expectedResult && "value" in descriptor;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
setCallbackTests = function(_value, _index) {
|
|
is(_value, descriptor.value, "setCallbackTests: test value argument");
|
|
is(_index, index, "setCallbackTests: test index argument");
|
|
};
|
|
deleteCallbackTests = function(_value, _index) {
|
|
is(_value, oldValue, "deleteCallbackTests: test value argument");
|
|
is(_index, index, "deleteCallbackTests: test index argument");
|
|
};
|
|
|
|
// Test
|
|
info(`defining ${index} property with ${JSON.stringify(descriptor)}`);
|
|
try {
|
|
is(Reflect.defineProperty(b, index, descriptor), expectedResult,
|
|
`Reflect.defineProperty should return ${expectedResult}`);
|
|
ok(!shouldThrow, "Reflect.defineProperty should not throw");
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, success ? 1 : 0, "setCallback count");
|
|
is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
|
|
is(b[index], success ? descriptor.value : oldValue, "property value");
|
|
is(b.length, success ? Math.max(index + 1, oldLen) : oldLen, "length of observable array");
|
|
});
|
|
|
|
// Test other property
|
|
[
|
|
// [property, descriptor, shouldThrow, expectedResult]
|
|
["prop1", {configurable: false, value: "value1"}, false, true],
|
|
["prop1", {configurable: true, value: "value2"}, false, false],
|
|
["prop2", {enumerable: false, value: 5}, false, true],
|
|
["prop3", {enumerable: false, value: []}, false, true],
|
|
["prop4", {enumerable: false, value: {}}, false, true],
|
|
["prop5", {get: ()=>{}, value: true}, true, false],
|
|
["prop6", {get: ()=>{}, set: ()=>{}}, false, true],
|
|
].forEach(function([property, descriptor, shouldThrow, expectedResult]) {
|
|
// Initialize
|
|
let oldValue = b[property];
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
setCallbackTests = null;
|
|
deleteCallbackTests = null;
|
|
|
|
// Test
|
|
info(`defining ${property} property with ${JSON.stringify(descriptor)}`);
|
|
try {
|
|
is(Reflect.defineProperty(b, property, descriptor), expectedResult,
|
|
`Reflect.defineProperty should return ${expectedResult}`);
|
|
ok(!shouldThrow, "Reflect.defineProperty should not throw");
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, 0, "deleteCallback count");
|
|
is(b[property], expectedResult ? descriptor.value : oldValue, "property value");
|
|
is(b.length, oldLen, "length of observable array");
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_defineProperty_callback_throw() {
|
|
let setCallbackCount = 0;
|
|
let deleteCallbackCount = 0;
|
|
|
|
const minLen = 3;
|
|
let m = new TestInterfaceObservableArray({
|
|
setBooleanCallback(value, index) {
|
|
setCallbackCount++;
|
|
if (value) {
|
|
throw new Error("setBooleanCallback");
|
|
}
|
|
},
|
|
deleteBooleanCallback(value, index) {
|
|
deleteCallbackCount++;
|
|
if (index < minLen) {
|
|
throw new Error("deleteBooleanCallback");
|
|
}
|
|
},
|
|
});
|
|
m.observableArrayBoolean = [false, false, false, false, false];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 5, "length of observable array should be 3");
|
|
|
|
// Test length
|
|
[
|
|
// [length, shouldThrow]
|
|
[b.length, false],
|
|
[b.length - 1, false],
|
|
[0, true],
|
|
].forEach(function([length, shouldThrow]) {
|
|
// Initialize
|
|
let oldValues = b.slice();
|
|
let oldLen = b.length;
|
|
let descriptor = {value: length};
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`defining "length" property with ${JSON.stringify(descriptor)}`);
|
|
try {
|
|
ok(Reflect.defineProperty(b, "length", descriptor),
|
|
"Reflect.defineProperty should return true");
|
|
ok(!shouldThrow, "Reflect.defineProperty should not throw");
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count");
|
|
isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values");
|
|
is(b.length, shouldThrow ? minLen : length, "length of observable array");
|
|
});
|
|
|
|
// Test indexed value
|
|
[
|
|
// [index, value, shouldThrow]
|
|
[b.length, true, true],
|
|
[b.length, false, false],
|
|
[b.length + 1, false, false],
|
|
[b.length + 1, true, true],
|
|
[0, true, true],
|
|
[0, false, true],
|
|
].forEach(function([index, value, shouldThrow]) {
|
|
// Initialize
|
|
let oldValue = b[index];
|
|
let oldLen = b.length;
|
|
let descriptor = {configurable: true, enumerable: true, value};
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`defining ${index} property with ${JSON.stringify(descriptor)}`);
|
|
try {
|
|
ok(Reflect.defineProperty(b, index, descriptor), "Reflect.defineProperty should return true");
|
|
ok(!shouldThrow, "Reflect.defineProperty should not throw");
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.defineProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, (index < minLen) ? 0 : 1, "setCallback count");
|
|
is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
|
|
is(b[index], shouldThrow ? oldValue : value, "property value");
|
|
is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), "length of observable array");
|
|
});
|
|
|
|
// Test other property
|
|
[
|
|
// [property, descriptor, expectedResult]
|
|
["prop1", {configurable: false, value: "value1"}, true],
|
|
["prop1", {configurable: true, value: "value2"}, false],
|
|
["prop2", {enumerable: false, value: 5}, true],
|
|
["prop3", {enumerable: false, value: []}, true],
|
|
["prop4", {enumerable: false, value: {}}, true],
|
|
].forEach(function([property, descriptor, expectedResult]) {
|
|
// Initialize
|
|
let oldValue = b[property];
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`defining ${property} property with ${JSON.stringify(descriptor)}`);
|
|
try {
|
|
is(Reflect.defineProperty(b, property, descriptor), expectedResult,
|
|
`Reflect.defineProperty should return ${expectedResult}`);
|
|
ok(true, "Reflect.defineProperty should not throw");
|
|
} catch(e) {
|
|
ok(false, `Reflect.defineProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, 0, "deleteCallback count");
|
|
is(b[property], expectedResult ? descriptor.value : oldValue, "property value");
|
|
is(b.length, oldLen, "length of observable array");
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_deleteProperty() {
|
|
let setCallbackCount = 0;
|
|
let deleteCallbackCount = 0;
|
|
let deleteCallbackTests = null;
|
|
|
|
let m = new TestInterfaceObservableArray({
|
|
setBooleanCallback(value, index) {
|
|
setCallbackCount++;
|
|
},
|
|
deleteBooleanCallback(value, index) {
|
|
deleteCallbackCount++;
|
|
if (typeof deleteCallbackTests === 'function') {
|
|
deleteCallbackTests(value, index);
|
|
}
|
|
},
|
|
});
|
|
m.observableArrayBoolean = [true, true];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 2, "length of observable array should be 2");
|
|
|
|
// Test length
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
info("deleting length property");
|
|
ok(!Reflect.deleteProperty(b, "length"), "test result of deleting length property");
|
|
is(setCallbackCount, 0, "setCallback should not be called");
|
|
is(deleteCallbackCount, 0, "deleteCallback should not be called");
|
|
is(b.length, 2, "length should still be 2");
|
|
|
|
// Test indexed value
|
|
[
|
|
// [index, expectedResult]
|
|
[2, false],
|
|
[0, false],
|
|
[1, true],
|
|
].forEach(function([index, expectedResult]) {
|
|
// Initialize
|
|
let oldLen = b.length;
|
|
let oldValue = b[index];
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
deleteCallbackTests = function(_value, _index) {
|
|
is(_value, oldValue, "deleteCallbackTests: test value argument");
|
|
is(_index, index, "deleteCallbackTests: test index argument");
|
|
};
|
|
|
|
// Test
|
|
info(`deleting ${index} property`);
|
|
is(Reflect.deleteProperty(b, index), expectedResult,
|
|
`Reflect.deleteProperty should return ${expectedResult}`);
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, expectedResult ? 1 : 0, "deleteCallback count");
|
|
is(b[index], expectedResult ? undefined : oldValue, "property value");
|
|
is(b.length, expectedResult ? oldLen - 1 : oldLen,
|
|
"length of observable array");
|
|
});
|
|
|
|
// Test other property
|
|
[
|
|
// [property, value]
|
|
["prop1", "value1"],
|
|
["prop2", 5],
|
|
["prop3", []],
|
|
["prop4", {}],
|
|
].forEach(function([property, value]) {
|
|
// Initialize
|
|
b[property] = value;
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
deleteCallbackTests = null;
|
|
|
|
// Test
|
|
info(`deleting ${property} property`);
|
|
is(b[property], value, `property value should be ${value} before deleting`);
|
|
ok(Reflect.deleteProperty(b, property), "Reflect.deleteProperty should return true");
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, 0, "deleteCallback count");
|
|
is(b[property], undefined, "property value should be undefined after deleting");
|
|
is(b.length, oldLen, "length of observable array");
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_deleteProperty_callback_throw() {
|
|
let setCallbackCount = 0;
|
|
let deleteCallbackCount = 0;
|
|
|
|
let m = new TestInterfaceObservableArray({
|
|
setBooleanCallback(value, index) {
|
|
setCallbackCount++;
|
|
},
|
|
deleteBooleanCallback(value, index) {
|
|
deleteCallbackCount++;
|
|
if (value) {
|
|
throw new Error("deleteBooleanCallback");
|
|
}
|
|
},
|
|
});
|
|
m.observableArrayBoolean = [true, false];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 2, "length of observable array should be 2");
|
|
|
|
// Test indexed value
|
|
let index = b.length;
|
|
while (index--) {
|
|
// Initialize
|
|
let oldValue = b[index];
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`deleting index ${index}`);
|
|
try {
|
|
ok(Reflect.deleteProperty(b, index), "Reflect.deleteProperty should return true");
|
|
ok(!oldValue, "Reflect.deleteProperty should not throw");
|
|
} catch(e) {
|
|
ok(oldValue, `Reflect.deleteProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, 1, "deleteCallback count");
|
|
is(b[index], oldValue ? oldValue : undefined, "property value");
|
|
is(b.length, oldValue ? oldLen : oldLen - 1, "length of observable array");
|
|
}
|
|
|
|
// Test other property
|
|
[
|
|
// [property, value]
|
|
["prop1", "value1"],
|
|
["prop2", 5],
|
|
["prop3", []],
|
|
["prop4", {}],
|
|
["prop5", false],
|
|
].forEach(function([property, value]) {
|
|
// Initialize
|
|
b[property] = value;
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`deleting ${property} property`);
|
|
is(b[property], value, `property value should be ${JSON.stringify(value)} before deleting`);
|
|
try {
|
|
ok(Reflect.deleteProperty(b, property), `Reflect.deleteProperty should return true`);
|
|
ok(true, "Reflect.deleteProperty should not throw");
|
|
} catch(e) {
|
|
ok(false, `Reflect.deleteProperty throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, 0, "deleteCallback count");
|
|
is(b[property], undefined, `property value should be undefined after deleting`);
|
|
is(b.length, oldLen, "length of observable array");
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_get() {
|
|
let m = new TestInterfaceObservableArray();
|
|
m.observableArrayBoolean = [true, false];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 2, "length of observable array should be 2");
|
|
|
|
// Test length
|
|
is(Reflect.get(b, "length"), 2, "test result of getting length property");
|
|
|
|
// Test indexed value
|
|
is(Reflect.get(b, 0), true, "test result of getting index 0");
|
|
is(Reflect.get(b, 1), false, "test result of getting index 1");
|
|
is(Reflect.get(b, 2), undefined, "test result of getting index 2");
|
|
|
|
// Test other property
|
|
[
|
|
// [property, value]
|
|
["prop1", "value1"],
|
|
["prop2", 5],
|
|
["prop3", []],
|
|
["prop4", {}],
|
|
].forEach(function([property, value]) {
|
|
is(Reflect.get(b, property), undefined, `test ${property} property before setting property value`);
|
|
b[property] = value;
|
|
is(Reflect.get(b, property), value, `test ${property} property after setting property value`);
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_getOwnPropertyDescriptor() {
|
|
function TestDescriptor(object, property, exist, configurable, enumerable,
|
|
writable, value) {
|
|
let descriptor = Reflect.getOwnPropertyDescriptor(object, property);
|
|
if (!exist) {
|
|
is(descriptor, undefined, `descriptor of ${property} property should be undefined`);
|
|
return;
|
|
}
|
|
|
|
is(descriptor.configurable, configurable, `test descriptor of ${property} property (configurable)`);
|
|
is(descriptor.enumerable, enumerable, `test descriptor of ${property} property (enumerable)`);
|
|
is(descriptor.writable, writable, `test descriptor of ${property} property (writable)`);
|
|
is(descriptor.value, value, `test descriptor of ${property} property (value)`);
|
|
}
|
|
|
|
let m = new TestInterfaceObservableArray();
|
|
m.observableArrayBoolean = [true, false];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 2, "length of observable array should be 2");
|
|
|
|
// Test length
|
|
TestDescriptor(b, "length", true, false /* configurable */,
|
|
false /* enumerable */, true /* writable */ , 2 /* value */);
|
|
|
|
// Test indexed value
|
|
TestDescriptor(b, 0, true, true /* configurable */, true /* enumerable */,
|
|
true /* writable */ , true /* value */);
|
|
TestDescriptor(b, 1, true, true /* configurable */, true /* enumerable */,
|
|
true /* writable */ , false /* value */);
|
|
TestDescriptor(b, 2, false);
|
|
|
|
// Test other property
|
|
[
|
|
// [property, value, configurable, enumerable, writable]
|
|
["prop1", "value1", true, true, true],
|
|
["prop2", 5, true, true, false],
|
|
["prop3", [], true, false, false],
|
|
["prop4", {}, false, false, false],
|
|
].forEach(function([property, value, configurable, enumerable, writable]) {
|
|
Object.defineProperty(b, property, {
|
|
value,
|
|
configurable,
|
|
enumerable,
|
|
writable,
|
|
});
|
|
TestDescriptor(b, property, true, configurable, enumerable, writable , value);
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_has() {
|
|
let m = new TestInterfaceObservableArray();
|
|
m.observableArrayBoolean = [true, false];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 2, "length of observable array should be 2");
|
|
|
|
// Test length
|
|
ok(Reflect.has(b, "length"), `test length property`);
|
|
|
|
// Test indexed value
|
|
ok(Reflect.has(b, 0), `test 0 property`);
|
|
ok(Reflect.has(b, 1), `test 1 property`);
|
|
ok(!Reflect.has(b, 2), `test 2 property`);
|
|
|
|
// Test other property
|
|
[
|
|
// [property, value]
|
|
["prop1", "value1"],
|
|
["prop2", 5],
|
|
["prop3", []],
|
|
["prop4", {}],
|
|
].forEach(function([property, value]) {
|
|
ok(!Reflect.has(b, property), `test ${property} property before setting property value`);
|
|
b[property] = value;
|
|
ok(Reflect.has(b, property), `test ${property} property after setting property value`);
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_ownKeys() {
|
|
let m = new TestInterfaceObservableArray();
|
|
m.observableArrayBoolean = [true, false];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 2, "length of observable array should be 2");
|
|
|
|
// Add other properties
|
|
b.prop1 = "value1";
|
|
b.prop2 = 5;
|
|
b.prop3 = [];
|
|
b.prop4 = {};
|
|
|
|
let keys = Reflect.ownKeys(b);
|
|
SimpleTest.isDeeply(keys, ["0", "1", "length", "prop1", "prop2", "prop3", "prop4"], `test property keys`);
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_preventExtensions() {
|
|
let m = new TestInterfaceObservableArray();
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 0, "length of observable array should be 0");
|
|
|
|
// Test preventExtensions
|
|
ok(Reflect.isExtensible(b), "test isExtensible before preventExtensions");
|
|
ok(!Reflect.preventExtensions(b), "test preventExtensions");
|
|
ok(Reflect.isExtensible(b), "test isExtensible after preventExtensions");
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_set() {
|
|
let setCallbackCount = 0;
|
|
let deleteCallbackCount = 0;
|
|
let setCallbackTests = null;
|
|
let deleteCallbackTests = null;
|
|
|
|
let m = new TestInterfaceObservableArray({
|
|
setBooleanCallback(value, index) {
|
|
setCallbackCount++;
|
|
if (typeof setCallbackTests === 'function') {
|
|
setCallbackTests(value, index);
|
|
}
|
|
},
|
|
deleteBooleanCallback(value, index) {
|
|
deleteCallbackCount++;
|
|
if (typeof deleteCallbackTests === 'function') {
|
|
deleteCallbackTests(value, index);
|
|
}
|
|
},
|
|
});
|
|
m.observableArrayBoolean = [true, true, true];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 3, "length of observable array should be 3");
|
|
|
|
// Test length
|
|
[
|
|
// [length, shouldThrow, expectedResult]
|
|
// Invalid length value
|
|
[1.9, true],
|
|
['invalid', true],
|
|
[{}, true],
|
|
// length value should not greater than current length
|
|
[b.length + 1, false, false],
|
|
// Success
|
|
[b.length, false, true],
|
|
[b.length - 1, false, true],
|
|
[0, false, true],
|
|
].forEach(function([length, shouldThrow, expectedResult]) {
|
|
// Initialize
|
|
let oldLen = b.length;
|
|
let oldValues = b.slice();
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
setCallbackTests = null;
|
|
let deleteCallbackIndex = oldLen - 1;
|
|
deleteCallbackTests = function(_value, _index) {
|
|
is(_value, oldValues[deleteCallbackIndex], "deleteCallbackTests: test value argument");
|
|
is(_index, deleteCallbackIndex, "deleteCallbackTests: test index argument");
|
|
deleteCallbackIndex--;
|
|
};
|
|
|
|
// Test
|
|
info(`setting "length" property value to ${length}`);
|
|
try {
|
|
is(Reflect.set(b, "length", length), expectedResult, `Reflect.set should return ${expectedResult}`);
|
|
ok(!shouldThrow, "Reflect.set should not throw");
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.set throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, expectedResult ? oldLen - length : 0, "deleteCallback count");
|
|
isDeeply(b, expectedResult ? oldValues.slice(0, length) : oldValues, "property values");
|
|
is(b.length, expectedResult ? length : oldLen, "length of observable array");
|
|
});
|
|
|
|
// Test indexed value
|
|
[
|
|
// [index, value, shouldThrow, expectedResult]
|
|
// Index could not greater than last index.
|
|
[b.length + 1, true, false, false],
|
|
// Success
|
|
[b.length, true, false, true],
|
|
[b.length + 1, true, false, true],
|
|
].forEach(function([index, value, shouldThrow, expectedResult]) {
|
|
// Initialize
|
|
let oldLen = b.length;
|
|
let oldValue = b[index];
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
setCallbackTests = function(_value, _index) {
|
|
is(_value, value, "setCallbackTests: test value argument");
|
|
is(_index, index, "setCallbackTests: test index argument");
|
|
};
|
|
deleteCallbackTests = function(_value, _index) {
|
|
is(_value, oldValue, "deleteCallbackTests: test value argument");
|
|
is(_index, index, "deleteCallbackTests: test index argument");
|
|
};
|
|
|
|
// Test
|
|
info(`setting ${index} property to ${value}`);
|
|
try {
|
|
is(Reflect.set(b, index, value), expectedResult, `Reflect.set should return ${expectedResult}`);
|
|
ok(!shouldThrow, "Reflect.set should not throw");
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.set throws ${e}`);
|
|
}
|
|
is(setCallbackCount, expectedResult ? 1 : 0, "setCallback count");
|
|
is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
|
|
is(b[index], expectedResult ? value : oldValue, "property value");
|
|
is(b.length, expectedResult ? Math.max(index + 1, oldLen) : oldLen, "length of observable array");
|
|
});
|
|
|
|
// Test other property
|
|
[
|
|
// [property, value]
|
|
["prop1", "value1"],
|
|
["prop1", "value2"],
|
|
["prop2", 5],
|
|
["prop3", []],
|
|
["prop4", {}],
|
|
].forEach(function([property, value]) {
|
|
// Initialize
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
setCallbackTests = null;
|
|
deleteCallbackTests = null;
|
|
|
|
// Test
|
|
info(`setting ${property} property to ${value}`);
|
|
ok(Reflect.set(b, property, value), "Reflect.defineProperty should return true");
|
|
is(setCallbackCount, 0, "setCallback count");
|
|
is(deleteCallbackCount, 0, "deleteCallback count");
|
|
is(b[property], value, "property value");
|
|
is(b.length, oldLen, "length of observable array");
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_set_callback_throw() {
|
|
let setCallbackCount = 0;
|
|
let deleteCallbackCount = 0;
|
|
|
|
const minLen = 3;
|
|
let m = new TestInterfaceObservableArray({
|
|
setBooleanCallback(value, index) {
|
|
setCallbackCount++;
|
|
if (value) {
|
|
throw new Error("setBooleanCallback");
|
|
}
|
|
},
|
|
deleteBooleanCallback(value, index) {
|
|
deleteCallbackCount++;
|
|
if (index < minLen) {
|
|
throw new Error("deleteBooleanCallback");
|
|
}
|
|
},
|
|
});
|
|
m.observableArrayBoolean = [false, false, false, false, false];
|
|
|
|
let b = m.observableArrayBoolean;
|
|
ok(Array.isArray(b), "observable array should be an array type");
|
|
is(b.length, 5, "length of observable array should be 3");
|
|
|
|
// Test length
|
|
[
|
|
// [value, shouldThrow]
|
|
[b.length, false],
|
|
[b.length - 1, false],
|
|
[0, true],
|
|
].forEach(function([length, shouldThrow]) {
|
|
// Initialize
|
|
let oldValues = b.slice();
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`setting "length" property to ${length}`);
|
|
try {
|
|
ok(Reflect.set(b, "length", length), "Reflect.set should return true");
|
|
ok(!shouldThrow, `Reflect.set should not throw`);
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.set throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback should not be called");
|
|
is(deleteCallbackCount, oldLen - (shouldThrow ? minLen - 1 : length), "deleteCallback count");
|
|
isDeeply(b, oldValues.slice(0, shouldThrow ? minLen : length), "property values");
|
|
is(b.length, shouldThrow ? minLen : length, "length of observable array");
|
|
});
|
|
|
|
// Test indexed value
|
|
[
|
|
// [index, value, shouldThrow]
|
|
[b.length, true, true],
|
|
[b.length, false, false],
|
|
[b.length + 1, false, false],
|
|
[b.length + 1, true, true],
|
|
[0, false, true],
|
|
[0, true, true],
|
|
].forEach(function([index, value, shouldThrow]) {
|
|
// Initialize
|
|
let oldValue = b[index];
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`setting ${index} property to ${value}`);
|
|
try {
|
|
ok(Reflect.set(b, index, value), "Reflect.set should return true");
|
|
ok(!shouldThrow, `Reflect.set should not throw`);
|
|
} catch(e) {
|
|
ok(shouldThrow, `Reflect.set throws ${e}`);
|
|
}
|
|
is(setCallbackCount, (index < minLen) ? 0 : 1, "setCallback count");
|
|
is(deleteCallbackCount, (oldLen > index) ? 1 : 0, "deleteCallback count");
|
|
is(b[index], shouldThrow ? oldValue : value, "property value");
|
|
is(b.length, shouldThrow ? oldLen : Math.max(oldLen, index + 1), "length of observable array");
|
|
});
|
|
|
|
// Test other property
|
|
[
|
|
["prop1", "value1"],
|
|
["prop1", "value2"],
|
|
["prop2", 5],
|
|
["prop3", []],
|
|
["prop4", {}],
|
|
].forEach(function([property, value]) {
|
|
// Initialize
|
|
let oldLen = b.length;
|
|
setCallbackCount = 0;
|
|
deleteCallbackCount = 0;
|
|
|
|
// Test
|
|
info(`setting ${property} property to ${JSON.stringify(value)}`);
|
|
try {
|
|
ok(Reflect.set(b, property, value), "Reflect.set should return true");
|
|
ok(true, `Reflect.set should not throw`);
|
|
} catch(e) {
|
|
ok(false, `Reflect.set throws ${e}`);
|
|
}
|
|
is(setCallbackCount, 0, "setCallback should not be called");
|
|
is(deleteCallbackCount, 0, "deleteCallback should be called");
|
|
is(b[property], value, "property value");
|
|
is(b.length, oldLen, "length of observable array");
|
|
});
|
|
});
|
|
|
|
add_task(function testObservableArrayExoticObjects_invalidtype() {
|
|
let m = new TestInterfaceObservableArray();
|
|
let i = m.observableArrayInterface;
|
|
ok(Array.isArray(i), "Observable array should be an array type");
|
|
is(i.length, 0, "length should be 0");
|
|
|
|
[true, "invalid"].forEach(function(value) {
|
|
SimpleTest.doesThrow(() => {
|
|
let descriptor = {configurable: true, enumerable: true, writable: true, value};
|
|
Reflect.defineProperty(i, i.length, descriptor);
|
|
}, `defining ${i.length} property with ${JSON.stringify(value)} should throw`);
|
|
|
|
SimpleTest.doesThrow(() => {
|
|
Reflect.set(i, i.length, value);
|
|
}, `setting ${i.length} property to ${JSON.stringify(value)} should throw`);
|
|
});
|
|
|
|
is(i.length, 0, "length should still be 0");
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|