Dynamic property (#749)
* Add DynamicProperty class * Add tests for DynamicProperty
This commit is contained in:
parent
f0c974d319
commit
a96e1c20a7
@ -108,6 +108,7 @@
|
||||
"deferPromise": "readonly",
|
||||
"clone": "readonly",
|
||||
"generateId": "readonly",
|
||||
"DynamicProperty": "readonly",
|
||||
"EventDispatcher": "readonly",
|
||||
"EventListenerCollection": "readonly"
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ function promiseTimeout(delay, resolveValue) {
|
||||
|
||||
|
||||
/*
|
||||
* Common events
|
||||
* Common classes
|
||||
*/
|
||||
|
||||
class EventDispatcher {
|
||||
@ -348,3 +348,106 @@ class EventListenerCollection {
|
||||
this._eventListeners = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing a generic value with an override stack.
|
||||
* Changes can be observed by listening to the 'change' event.
|
||||
*/
|
||||
class DynamicProperty extends EventDispatcher {
|
||||
/**
|
||||
* Creates a new instance with the specified value.
|
||||
* @param value The value to assign.
|
||||
*/
|
||||
constructor(value) {
|
||||
super();
|
||||
this._value = value;
|
||||
this._defaultValue = value;
|
||||
this._overrides = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default value for the property, which is assigned to the
|
||||
* public value property when no overrides are present.
|
||||
*/
|
||||
get defaultValue() {
|
||||
return this._defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the default value for the property. If no overrides are present
|
||||
* and if the value is different than the current default value,
|
||||
* the 'change' event will be triggered.
|
||||
* @param value The value to assign.
|
||||
*/
|
||||
set defaultValue(value) {
|
||||
this._defaultValue = value;
|
||||
if (this._overrides.length === 0) { this._updateValue(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current value for the property, taking any overrides into account.
|
||||
*/
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of overrides added to the property.
|
||||
*/
|
||||
get overrideCount() {
|
||||
return this._overrides.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an override value with the specified priority to the override stack.
|
||||
* Values with higher priority will take precedence over those with lower.
|
||||
* For tie breaks, the override value added first will take precedence.
|
||||
* If the newly added override has the highest priority of all overrides
|
||||
* and if the override value is different from the current value,
|
||||
* the 'change' event will be fired.
|
||||
* @param value The override value to assign.
|
||||
* @param priority The priority value to use, as a number.
|
||||
* @returns A string token which can be passed to the clearOverride function
|
||||
* to remove the override.
|
||||
*/
|
||||
setOverride(value, priority=0) {
|
||||
const overridesCount = this._overrides.length;
|
||||
let i = 0;
|
||||
for (; i < overridesCount; ++i) {
|
||||
if (priority > this._overrides[i].priority) { break; }
|
||||
}
|
||||
const token = generateId(16);
|
||||
this._overrides.splice(i, 0, {value, priority, token});
|
||||
if (i === 0) { this._updateValue(); }
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific override value. If the removed override
|
||||
* had the highest priority, and the new value is different from
|
||||
* the previous value, the 'change' event will be fired.
|
||||
* @param token The token for the corresponding override which is to be removed.
|
||||
* @returns true if an override was returned, false otherwise.
|
||||
*/
|
||||
clearOverride(token) {
|
||||
for (let i = 0, ii = this._overrides.length; i < ii; ++i) {
|
||||
if (this._overrides[i].token === token) {
|
||||
this._overrides.splice(i, 1);
|
||||
if (i === 0) { this._updateValue(); }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current value using the current overrides and default value.
|
||||
* If the new value differs from the previous value, the 'change' event will be fired.
|
||||
*/
|
||||
_updateValue() {
|
||||
const value = this._overrides.length > 0 ? this._overrides[0].value : this._defaultValue;
|
||||
if (this._value === value) { return; }
|
||||
this._value = value;
|
||||
this.trigger('change', {value});
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
"build": "node ./dev/build.js",
|
||||
"test": "npm run test-lint && npm run test-code && npm run test-manifest",
|
||||
"test-lint": "eslint . && node ./test/lint/global-declarations.js",
|
||||
"test-code": "node ./test/test-schema.js && node ./test/test-dictionary.js && node ./test/test-database.js && node ./test/test-document-util.js && node ./test/test-object-property-accessor.js && node ./test/test-japanese.js && node ./test/test-text-source-map.js && node ./test/test-dom-text-scanner.js && node ./test/test-cache-map.js && node ./test/test-profile-conditions.js",
|
||||
"test-code": "node ./test/test-schema.js && node ./test/test-dictionary.js && node ./test/test-database.js && node ./test/test-document-util.js && node ./test/test-object-property-accessor.js && node ./test/test-japanese.js && node ./test/test-text-source-map.js && node ./test/test-dom-text-scanner.js && node ./test/test-cache-map.js && node ./test/test-profile-conditions.js && node ./test/test-core.js",
|
||||
"test-manifest": "node ./test/test-manifest.js"
|
||||
},
|
||||
"repository": {
|
||||
|
169
test/test-core.js
Normal file
169
test/test-core.js
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Yomichan Authors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
const crypto = require('crypto');
|
||||
const {VM} = require('./yomichan-vm');
|
||||
|
||||
const vm = new VM({
|
||||
crypto: {
|
||||
getRandomValues: (array) => {
|
||||
const buffer = crypto.randomBytes(array.byteLength);
|
||||
buffer.copy(array);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
});
|
||||
vm.execute([
|
||||
'mixed/js/core.js'
|
||||
]);
|
||||
const [DynamicProperty] = vm.get(['DynamicProperty']);
|
||||
|
||||
|
||||
function testDynamicProperty() {
|
||||
const data = [
|
||||
{
|
||||
initialValue: 0,
|
||||
operations: [
|
||||
{
|
||||
operation: null,
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 0,
|
||||
expectedOverrideCount: 0,
|
||||
expeectedEventOccurred: false
|
||||
},
|
||||
{
|
||||
operation: 'set.defaultValue',
|
||||
args: [1],
|
||||
expectedDefaultValue: 1,
|
||||
expectedValue: 1,
|
||||
expectedOverrideCount: 0,
|
||||
expeectedEventOccurred: true
|
||||
},
|
||||
{
|
||||
operation: 'set.defaultValue',
|
||||
args: [1],
|
||||
expectedDefaultValue: 1,
|
||||
expectedValue: 1,
|
||||
expectedOverrideCount: 0,
|
||||
expeectedEventOccurred: false
|
||||
},
|
||||
{
|
||||
operation: 'set.defaultValue',
|
||||
args: [0],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 0,
|
||||
expectedOverrideCount: 0,
|
||||
expeectedEventOccurred: true
|
||||
},
|
||||
{
|
||||
operation: 'setOverride',
|
||||
args: [8],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 8,
|
||||
expectedOverrideCount: 1,
|
||||
expeectedEventOccurred: true
|
||||
},
|
||||
{
|
||||
operation: 'setOverride',
|
||||
args: [16],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 8,
|
||||
expectedOverrideCount: 2,
|
||||
expeectedEventOccurred: false
|
||||
},
|
||||
{
|
||||
operation: 'setOverride',
|
||||
args: [32, 1],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 32,
|
||||
expectedOverrideCount: 3,
|
||||
expeectedEventOccurred: true
|
||||
},
|
||||
{
|
||||
operation: 'setOverride',
|
||||
args: [64, -1],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 32,
|
||||
expectedOverrideCount: 4,
|
||||
expeectedEventOccurred: false
|
||||
},
|
||||
{
|
||||
operation: 'clearOverride',
|
||||
args: [-4],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 32,
|
||||
expectedOverrideCount: 3,
|
||||
expeectedEventOccurred: false
|
||||
},
|
||||
{
|
||||
operation: 'clearOverride',
|
||||
args: [-3],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 32,
|
||||
expectedOverrideCount: 2,
|
||||
expeectedEventOccurred: false
|
||||
},
|
||||
{
|
||||
operation: 'clearOverride',
|
||||
args: [-2],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 64,
|
||||
expectedOverrideCount: 1,
|
||||
expeectedEventOccurred: true
|
||||
},
|
||||
{
|
||||
operation: 'clearOverride',
|
||||
args: [-1],
|
||||
expectedDefaultValue: 0,
|
||||
expectedValue: 0,
|
||||
expectedOverrideCount: 0,
|
||||
expeectedEventOccurred: true
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
for (const {initialValue, operations} of data) {
|
||||
const property = new DynamicProperty(initialValue);
|
||||
const overrideTokens = [];
|
||||
let eventOccurred = false;
|
||||
const onChange = () => { eventOccurred = true; };
|
||||
property.on('change', onChange);
|
||||
for (const {operation, args, expectedDefaultValue, expectedValue, expectedOverrideCount, expeectedEventOccurred} of operations) {
|
||||
eventOccurred = false;
|
||||
switch (operation) {
|
||||
case 'set.defaultValue': property.defaultValue = args[0]; break;
|
||||
case 'setOverride': overrideTokens.push(property.setOverride(...args)); break;
|
||||
case 'clearOverride': property.clearOverride(overrideTokens[overrideTokens.length + args[0]]); break;
|
||||
}
|
||||
assert.strictEqual(eventOccurred, expeectedEventOccurred);
|
||||
assert.strictEqual(property.defaultValue, expectedDefaultValue);
|
||||
assert.strictEqual(property.value, expectedValue);
|
||||
assert.strictEqual(property.overrideCount, expectedOverrideCount);
|
||||
}
|
||||
property.off('change', onChange);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function main() {
|
||||
testDynamicProperty();
|
||||
}
|
||||
|
||||
|
||||
if (require.main === module) { main(); }
|
Loading…
Reference in New Issue
Block a user