fune/dom/bindings/test/test_observablearray_proxyhandler.html
Edgar Chen ebd37b74c8 Bug 1683281 - Part 5: Generate binding code for ObservableArray type; r=peterv
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
2022-03-10 22:44:28 +00:00

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>