From fa26e7ccf4a2ca85c22c1c3d6c8b5fbc1ca7d14a Mon Sep 17 00:00:00 2001 From: Maira Bello Date: Fri, 21 Oct 2016 15:54:42 -0300 Subject: [PATCH] Stops allowing State to receive new properties dynamically --- packages/metal-component/src/Component.js | 9 +- .../src/ComponentDataManager.js | 18 +- .../metal-component/src/ComponentRenderer.js | 8 +- .../test/ComponentDataManager.js | 10 - packages/metal-jsx/src/JSXDataManager.js | 2 +- packages/metal-jsx/test/JSXDataManager.js | 29 --- packages/metal-soy/src/Soy.js | 17 +- packages/metal-soy/test/Soy.js | 6 +- packages/metal-state/src/State.js | 241 +++++++----------- packages/metal-state/test/State.js | 241 ++++++++---------- 10 files changed, 213 insertions(+), 368 deletions(-) diff --git a/packages/metal-component/src/Component.js b/packages/metal-component/src/Component.js index 457db9b0..e1934753 100644 --- a/packages/metal-component/src/Component.js +++ b/packages/metal-component/src/Component.js @@ -9,7 +9,8 @@ import { isFunction, isObject, isString, - mergeSuperClassesProperty + mergeSuperClassesProperty, + object } from 'metal'; import { toElement } from 'metal-dom'; import ComponentDataManager from './ComponentDataManager'; @@ -123,7 +124,6 @@ class Component extends EventEmitter { this.renderer_.on('rendered', this.handleRendererRendered_.bind(this)); this.dataManager_ = this.createDataManager(); - this.renderer_.handleDataManagerCreated_(); this.on('stateChanged', this.handleStateChanged_); this.on('eventsChanged', this.onEventsChanged_); @@ -223,7 +223,10 @@ class Component extends EventEmitter { */ createDataManager() { mergeSuperClassesProperty(this.constructor, 'DATA_MANAGER', array.firstDefinedValue); - return new this.constructor.DATA_MANAGER_MERGED(this, Component.DATA); + return new this.constructor.DATA_MANAGER_MERGED( + this, + object.mixin({}, Component.DATA, this.getRenderer().getExtraDataConfig()) + ); } /** diff --git a/packages/metal-component/src/ComponentDataManager.js b/packages/metal-component/src/ComponentDataManager.js index 93e0731e..30d7e718 100644 --- a/packages/metal-component/src/ComponentDataManager.js +++ b/packages/metal-component/src/ComponentDataManager.js @@ -24,16 +24,6 @@ class ComponentDataManager extends EventEmitter { this.createState_(data, this.component_); } - /** - * Adds a state property to the component. - * @param {string} name - * @param {!Object} config - * @param {*} opt_initialValue - */ - add() { - this.state_.addToState(...arguments); - } - /** * Builds the configuration data that will be passed to the `State` instance. * @param {!Object} data @@ -51,13 +41,9 @@ class ComponentDataManager extends EventEmitter { * @protected */ createState_(data, holder, define) { - const state = new State({}, holder, this.component_); + const state = new State(this.component_.getInitialConfig(), holder, this.component_); state.setKeysBlacklist_(this.constructor.BLACKLIST_MERGED); - state.addToState( - this.buildStateInstanceData_(data), - this.component_.getInitialConfig(), - define - ); + state.configState(this.buildStateInstanceData_(data), define); this.state_ = state; } diff --git a/packages/metal-component/src/ComponentRenderer.js b/packages/metal-component/src/ComponentRenderer.js index c8e2e0c4..4c937a59 100644 --- a/packages/metal-component/src/ComponentRenderer.js +++ b/packages/metal-component/src/ComponentRenderer.js @@ -50,8 +50,12 @@ class ComponentRenderer extends EventEmitter { return this.component_; } - handleDataManagerCreated_() { - + /** + * Returns extra configuration for data that should be added to the manager. + * @return {Object} + */ + getExtraDataConfig() { + return null; } /** diff --git a/packages/metal-component/test/ComponentDataManager.js b/packages/metal-component/test/ComponentDataManager.js index 49a3d28c..20b29ef1 100644 --- a/packages/metal-component/test/ComponentDataManager.js +++ b/packages/metal-component/test/ComponentDataManager.js @@ -60,16 +60,6 @@ describe('ComponentDataManager', function() { assert.strictEqual('fooValue', component.foo); }); - it('should add the state properties via the "add" function', function() { - component = new Component(); - manager = new ComponentDataManager(component, {}); - manager.add('foo', { - value: 'fooValue' - }); - - assert.strictEqual('fooValue', component.foo); - }); - it('should replace all non internal data with given values or default', function() { class TestComponent extends Component { } diff --git a/packages/metal-jsx/src/JSXDataManager.js b/packages/metal-jsx/src/JSXDataManager.js index 8b79e5bc..b4e97c82 100644 --- a/packages/metal-jsx/src/JSXDataManager.js +++ b/packages/metal-jsx/src/JSXDataManager.js @@ -77,7 +77,7 @@ class JSXDataManager extends ComponentDataManager { this.state_.setEventData({ type: 'state' }); - this.state_.addToState(this.component_.constructor.STATE_MERGED, {}, define); + this.state_.configState(this.component_.constructor.STATE_MERGED, define); } /** diff --git a/packages/metal-jsx/test/JSXDataManager.js b/packages/metal-jsx/test/JSXDataManager.js index 521b2ee3..52de8be1 100644 --- a/packages/metal-jsx/test/JSXDataManager.js +++ b/packages/metal-jsx/test/JSXDataManager.js @@ -56,21 +56,6 @@ describe('JSXDataManager', function() { assert.strictEqual('defaultFoo', component.state.foo); }); - it('should automatically make all STATE properties "internal"', function() { - class TestComponent extends Component { - } - TestComponent.DATA_MANAGER = JSXDataManager; - TestComponent.STATE = { - foo: { - value: 'defaultFoo' - } - }; - - component = new TestComponent(); - var stateInstance = component.getDataManager().getStateInstance(); - assert.ok(stateInstance.getStateKeyConfig('foo').internal); - }); - it('should not include default component data in "state"', function() { class TestComponent extends Component { } @@ -166,20 +151,6 @@ describe('JSXDataManager', function() { assert.strictEqual('defaultPropsFoo', manager.get('foo')); }); - it('should add value to "props" when "add" is called', function() { - class TestComponent extends Component { - } - TestComponent.DATA_MANAGER = JSXDataManager; - - component = new TestComponent(); - - var manager = component.getDataManager(); - manager.add('foo', { - value: 'defaultFoo' - }); - assert.strictEqual('defaultFoo', component.props.foo); - }); - it('should return keys from "props" when "getSyncKeys" is called', function() { class TestComponent extends Component { } diff --git a/packages/metal-soy/src/Soy.js b/packages/metal-soy/src/Soy.js index 07bc60d9..8a16d430 100644 --- a/packages/metal-soy/src/Soy.js +++ b/packages/metal-soy/src/Soy.js @@ -15,7 +15,7 @@ class Soy extends IncrementalDomRenderer { * Adds the template params to the component's state, if they don't exist yet. * @protected */ - addMissingStateKeys_() { + getExtraDataConfig() { var elementTemplate = this.component_.constructor.TEMPLATE; if (!isFunction(elementTemplate)) { return; @@ -26,12 +26,13 @@ class Soy extends IncrementalDomRenderer { var keys = elementTemplate.params || []; var component = this.component_; - var state = component.getDataManager().getStateInstance(); + var configs = {}; for (var i = 0; i < keys.length; i++) { - if (!state.hasStateKey(keys[i]) && !component[keys[i]]) { - state.addToState(keys[i], {}, component.getInitialConfig()[keys[i]]); + if (!component[keys[i]]) { + configs[keys[i]] = {}; } } + return configs; } /** @@ -80,14 +81,6 @@ class Soy extends IncrementalDomRenderer { }; } - /** - * @inheritDoc - */ - handleDataManagerCreated_() { - super.handleDataManagerCreated_(); - this.addMissingStateKeys_(); - } - /** * Handles an intercepted soy template call. If the call is for a component's * main template, then it will be replaced with a call that incremental dom diff --git a/packages/metal-soy/test/Soy.js b/packages/metal-soy/test/Soy.js index d8e04b6e..7ca9ff2b 100644 --- a/packages/metal-soy/test/Soy.js +++ b/packages/metal-soy/test/Soy.js @@ -78,9 +78,7 @@ describe('Soy', function() { it('should not trigger update when changed state key is not used by template', function(done) { comp = new HelloWorldComponent(); - comp.getDataManager().getStateInstance().addToState('foo'); - - comp.foo = 'Bar'; + comp.visible = false; comp.once('stateSynced', function() { assert.strictEqual(0, IncrementalDOM.patchOuter.callCount); done(); @@ -105,7 +103,7 @@ describe('Soy', function() { return true; }; comp = new HelloWorldComponent(); - comp.getDataManager().getStateInstance().addToState('foo'); + comp.visible = false; comp.foo = 'Bar'; comp.once('stateSynced', function() { diff --git a/packages/metal-state/src/State.js b/packages/metal-state/src/State.js index 4f838f32..08b8e862 100644 --- a/packages/metal-state/src/State.js +++ b/packages/metal-state/src/State.js @@ -15,7 +15,7 @@ import { EventEmitter } from 'metal-events'; /** * State adds support for having object properties that can be watched for * changes, as well as configured with validators, setters and other options. - * See the `addToState` method for a complete list of available configuration + * See the `configState` method for a complete list of available configuration * options for each state key. * @extends {EventEmitter} */ @@ -29,19 +29,10 @@ class State extends EventEmitter { * instead. * @param {Object=} opt_context Optional context to call functions (like * validators and setters) on. Defaults to `this`. - * @param {Object=} opt_commonOpts Optional common option values to be used - * by all this instance's state properties. */ - constructor(opt_config, opt_obj, opt_context, opt_commonOpts) { + constructor(opt_config, opt_obj, opt_context) { super(); - /** - * Common option values to be used by all this instance's state properties. - * @type {Object} - * @protected - */ - this.commonOpts_ = opt_commonOpts; - /** * Context to call functions (like validators and setters) on. * @type {!Object} @@ -82,11 +73,11 @@ class State extends EventEmitter { this.stateConfigs_ = {}; - this.initialValues_ = opt_config || {}; + this.initialValues_ = object.mixin({}, opt_config); this.setShouldUseFacade(true); this.mergeInvalidKeys_(); - this.addToStateFromStaticHint_(opt_config); + this.configStateFromStaticHint_(); Object.defineProperty(this.obj_, State.STATE_REF_KEY, { configurable: true, @@ -95,122 +86,6 @@ class State extends EventEmitter { }); } - /** - * Adds the given key to the state. - * @param {string} name The name of the new state key. - * @param {Object.=} config The configuration object for the new - * key. See `addToState` for supported settings. - * @param {*} initialValue The initial value of the new key. - */ - addKeyToState(name, config, initialValue) { - this.buildKeyInfo_(name, config, initialValue, arguments.length > 2); - Object.defineProperty( - this.obj_, - name, - this.buildKeyPropertyDef_(name) - ); - this.validateInitialValue_(name); - this.assertGivenIfRequired_(name); - } - - /** - * Adds the given key(s) to the state, together with its(their) configs. - * Config objects support the given settings: - * required - When set to `true`, causes errors to be printed (via - * `console.error`) if no value is given for the property. - * - * setter - Function for normalizing state key values. It receives the new - * value that was set, and returns the value that should be stored. - * - * validator - Function that validates state key values. When it returns - * false, the new value is ignored. When it returns an instance of Error, - * it will emit the error to the console. - * - * value - The default value for the state key. Note that setting this to - * an object will cause all class instances to use the same reference to - * the object. To have each instance use a different reference for objects, - * use the `valueFn` option instead. - * - * valueFn - A function that returns the default value for a state key. - * - * writeOnce - Ignores writes to the state key after it's been first - * written to. That is, allows writes only when setting the value for the - * first time. - * @param {!Object.|string} configsOrName An object that maps - * configuration options for keys to be added to the state or the name of - * a single key to be added. - * @param {Object.=} opt_initialValuesOrConfig An object that maps - * state keys to their initial values. These values have higher precedence - * than the default values specified in the configurations. If a single - * key name was passed as the first param instead though, then this should - * be the configuration object for that key. - * @param {boolean|Object|*=} opt_contextOrInitialValue If the first - * param passed to this method was a config object, this should be the - * context where the added state keys will be defined (defaults to `this`), - * or false if they shouldn't be defined at all. If the first param was a - * single key name though, this should be its initial value. - */ - addToState(configsOrName, opt_initialValuesOrConfig, opt_contextOrInitialValue) { - if (isString(configsOrName)) { - return this.addKeyToState(...arguments); - } - - var initialValues = opt_initialValuesOrConfig || {}; - var names = Object.keys(configsOrName); - var shouldDefine = opt_contextOrInitialValue !== false; - - var props = {}; - for (let i = 0; i < names.length; i++) { - var name = names[i]; - this.buildKeyInfo_( - name, - configsOrName[name], - initialValues[name], - initialValues.hasOwnProperty(name) - ); - if (shouldDefine) { - props[name] = this.buildKeyPropertyDef_(name); - } - this.assertGivenIfRequired_(name); - } - - if (shouldDefine) { - Object.defineProperties( - opt_contextOrInitialValue || this.obj_, - props - ); - } - - // Validate initial values after all properties have been defined, otherwise - // it won't be possible to access those properties within validators. - for (let i = 0; i < names.length; i++) { - this.validateInitialValue_(names[i]); - } - } - - /** - * Adds state keys from super classes static hint `MyClass.STATE = {};`. - * @param {Object.=} opt_config An object that maps all the - * configurations for state keys. - * @protected - */ - addToStateFromStaticHint_(opt_config) { - var ctor = this.constructor; - var defineContext; - var merged = State.mergeStateStatic(ctor); - if (this.obj_ === this) { - defineContext = merged ? ctor.prototype : false; - } - this.addToState(ctor.STATE_MERGED, opt_config, defineContext); - } - - getStateInfo(name) { - if (!this.stateInfo_[name]) { - this.stateInfo_[name] = {}; - } - return this.stateInfo_[name]; - } - /** * Logs an error if the given property is required but wasn't given. * @param {string} name @@ -245,27 +120,6 @@ class State extends EventEmitter { } } - /** - * Builds the info object for the specified state key. - * @param {string} name The name of the key. - * @param {Object} config The config object for the key. - * @param {*} initialValue The initial value of the key. - * @param {boolean} hasInitialValue Flag indicating if an initial value was - * given or not (important since `initialValue` can also be `undefined`). - * @protected - */ - buildKeyInfo_(name, config, initialValue, hasInitialValue) { - this.assertValidStateKeyName_(name); - config = (config && config.config) ? config.config : (config || {}); - if (this.commonOpts_) { - config = object.mixin({}, config, this.commonOpts_); - } - this.stateConfigs_[name] = config; - if (hasInitialValue) { - this.initialValues_[name] = initialValue; - } - } - /** * Builds the property definition object for the specified state key. * @param {string} name The name of the key. @@ -352,11 +206,82 @@ class State extends EventEmitter { return !this.stateConfigs_[name].writeOnce || !info.written; } + /** + * Adds the given key(s) to the state, together with its(their) configs. + * Config objects support the given settings: + * required - When set to `true`, causes errors to be printed (via + * `console.error`) if no value is given for the property. + * + * setter - Function for normalizing state key values. It receives the new + * value that was set, and returns the value that should be stored. + * + * validator - Function that validates state key values. When it returns + * false, the new value is ignored. When it returns an instance of Error, + * it will emit the error to the console. + * + * value - The default value for the state key. Note that setting this to + * an object will cause all class instances to use the same reference to + * the object. To have each instance use a different reference for objects, + * use the `valueFn` option instead. + * + * valueFn - A function that returns the default value for a state key. + * + * writeOnce - Ignores writes to the state key after it's been first + * written to. That is, allows writes only when setting the value for the + * first time. + * @param {!Object.|string} configs An object that maps + * configuration options for keys to be added to the state. + * @param {boolean|Object|*=} opt_context The context where the added state + * keys will be defined (defaults to `this`), or false if they shouldn't + * be defined at all. + */ + configState(configs, opt_context) { + if (opt_context !== false) { + const props = {}; + const names = Object.keys(configs); + for (let i = 0; i < names.length; i++) { + const name = names[i]; + this.assertValidStateKeyName_(name); + props[name] = this.buildKeyPropertyDef_(name); + } + Object.defineProperties( + opt_context || this.obj_, + props + ); + } + + this.stateConfigs_ = configs; + const names = Object.keys(configs); + for (let i = 0; i < names.length; i++) { + const name = names[i]; + configs[name] = configs[name].config ? configs[name].config : configs[name]; + this.assertGivenIfRequired_(names[i]); + this.validateInitialValue_(names[i]); + } + } + + /** + * Adds state keys from super classes static hint `MyClass.STATE = {};`. + * @param {Object.=} opt_config An object that maps all the + * configurations for state keys. + * @protected + */ + configStateFromStaticHint_() { + var ctor = this.constructor; + var defineContext; + var merged = State.mergeStateStatic(ctor); + if (this.obj_ === this) { + defineContext = merged ? ctor.prototype : false; + } + this.configState(ctor.STATE_MERGED, defineContext); + } + /** * @inheritDoc */ disposeInternal() { super.disposeInternal(); + this.initialValues_ = null; this.stateInfo_ = null; this.stateConfigs_ = null; this.scheduledBatchData_ = null; @@ -403,6 +328,18 @@ class State extends EventEmitter { return state; } + /** + * Gets information about the specified state property. + * @param {string} name + * @return {!Object} + */ + getStateInfo(name) { + if (!this.stateInfo_[name]) { + this.stateInfo_[name] = {}; + } + return this.stateInfo_[name]; + } + /** * Gets the config object for the requested state key. * @param {string} name The key's name. @@ -605,6 +542,10 @@ class State extends EventEmitter { } } + /** + * Sets data to be sent with all events emitted from this instance. + * @param {Object} + */ setEventData(data) { this.eventData_ = data; } diff --git a/packages/metal-state/test/State.js b/packages/metal-state/test/State.js index 15a09533..00b5fb1a 100644 --- a/packages/metal-state/test/State.js +++ b/packages/metal-state/test/State.js @@ -4,18 +4,9 @@ import { async } from 'metal'; import State from '../src/State'; describe('State', function() { - it('should add a key to the state', function() { + it('should add keys to the state', function() { var state = new State(); - state.addToState('key1'); - - var keys = Object.keys(state.getState()); - assert.strictEqual(1, keys.length); - assert.strictEqual('key1', keys[0]); - }); - - it('should add multiple keys to the state', function() { - var state = new State(); - state.addToState({ + state.configState({ key1: {}, key2: {} }); @@ -28,7 +19,7 @@ describe('State', function() { it('should make state keys enumerable', function() { var state = new State(); - state.addToState({ + state.configState({ key1: {}, key2: {} }); @@ -42,7 +33,7 @@ describe('State', function() { var state = new State(); assert.throws(function() { - state.addToState({ + state.configState({ state: {} }); }); @@ -52,7 +43,7 @@ describe('State', function() { var state = new State(); assert.throws(function() { - state.addToState({ + state.configState({ stateKey: {} }); }); @@ -65,7 +56,7 @@ describe('State', function() { var test = new Test(); assert.throws(function() { - test.addToState({ + test.configState({ invalid: {} }); }); @@ -80,7 +71,7 @@ describe('State', function() { invalid: true }); assert.throws(function() { - test.addToState({ + test.configState({ invalid: {} }); }); @@ -93,7 +84,7 @@ describe('State', function() { var test = new Test(); assert.throws(function() { - test.addToState({ + test.configState({ state: {} }); }); @@ -101,7 +92,7 @@ describe('State', function() { it('should get a state key\'s config object', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { a: 2 } @@ -115,7 +106,7 @@ describe('State', function() { it('should use config object from "config" key', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { config: { a: 2 @@ -136,7 +127,7 @@ describe('State', function() { it('should set and get state values', function() { var state = new State(); - state.addToState({ + state.configState({ key1: {}, key2: {} }); @@ -153,7 +144,7 @@ describe('State', function() { it('should get state key value through "get" method', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { value: 2 } @@ -164,7 +155,7 @@ describe('State', function() { it('should set state key value through "set" method', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { value: 2 } @@ -183,7 +174,7 @@ describe('State', function() { it('should set default state key value with raw value', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { value: 1 } @@ -194,7 +185,7 @@ describe('State', function() { it('should set default state key value from function', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { valueFn: function() { return 1; @@ -210,7 +201,7 @@ describe('State', function() { state.returns1 = function() { return 1; }; - state.addToState({ + state.configState({ key1: { valueFn: 'returns1' } @@ -221,7 +212,7 @@ describe('State', function() { it('should ignore invalid valueFn function', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { valueFn: 1 } @@ -232,7 +223,7 @@ describe('State', function() { it('should not use valueFn function if value is also defined', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { value: '', valueFn: function() { @@ -245,8 +236,11 @@ describe('State', function() { }); it('should override default state key value', function() { - var state = new State(); - state.addToState( + var state = new State({ + key1: 10, + key2: 20 + }); + state.configState( { key1: { value: 1 @@ -254,10 +248,6 @@ describe('State', function() { key2: { value: 2 } - }, - { - key1: 10, - key2: 20 } ); @@ -276,7 +266,7 @@ describe('State', function() { it('should initialize state values lazily', function() { var state = new State(); var valueFn = sinon.stub().returns(2); - state.addToState({ + state.configState({ key1: { valueFn: valueFn } @@ -292,7 +282,7 @@ describe('State', function() { var state = new State(); var keyName = 'key1'; var value = 2; - state.addToState({ + state.configState({ [keyName]: { validator: function(val, name, context) { assert.strictEqual(value, val); @@ -309,7 +299,7 @@ describe('State', function() { it('should validate new state values', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { validator: function(val) { return val > 0; @@ -330,7 +320,7 @@ describe('State', function() { state.isPositive = function(val) { return val > 0; }; - state.addToState({ + state.configState({ key1: { validator: 'isPositive', value: 1 @@ -345,8 +335,10 @@ describe('State', function() { }); it('should validate initial state values', function() { - var state = new State(); - state.addToState( + var state = new State({ + key1: -10 + }); + state.configState( { key1: { validator: function(val) { @@ -354,9 +346,6 @@ describe('State', function() { }, value: 1 } - }, - { - key1: -10 } ); @@ -364,8 +353,10 @@ describe('State', function() { }); it('should allow accessing other state properties in validator', function() { - var state = new State(); - state.addToState( + var state = new State({ + key1: 1 + }); + state.configState( { key1: { validator: function(val) { @@ -375,9 +366,6 @@ describe('State', function() { key2: { value: 2 } - }, - { - key1: 1 } ); assert.strictEqual(1, state.key1); @@ -391,7 +379,7 @@ describe('State', function() { it('should not validate default state values', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { validator: function(val) { return val > 0; @@ -407,7 +395,7 @@ describe('State', function() { var originalConsoleFn = console.error; console.error = sinon.stub(); var state = new State(); - state.addToState( + state.configState( { key1: { validator: function(val) { @@ -449,7 +437,7 @@ describe('State', function() { it('should change state new value through setter', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { setter: Math.abs, value: -1 @@ -468,7 +456,7 @@ describe('State', function() { it('should change state new value through setter name', function() { var state = new State(); state.makePositive = Math.abs; - state.addToState({ + state.configState({ key1: { setter: 'makePositive', value: -1 @@ -486,7 +474,7 @@ describe('State', function() { it('should pass the state key\'s current value to setter', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { setter: (newValue, currentValue) => { return currentValue ? currentValue + ':' + newValue : newValue; @@ -505,16 +493,15 @@ describe('State', function() { }); it('should allow setting a writeOnce with initial value', function() { - var state = new State(); - state.addToState( + var state = new State({ + key1: 2 + }); + state.configState( { key1: { value: 1, writeOnce: true } - }, - { - key1: 2 } ); @@ -523,7 +510,7 @@ describe('State', function() { it('should allow setting a writeOnce state value before it has been written', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { value: 1, writeOnce: true @@ -537,7 +524,7 @@ describe('State', function() { it('should not allow changing a writeOnce state value after it has been written', function() { var state = new State(); - state.addToState({ + state.configState({ key1: { value: 1, writeOnce: true @@ -561,26 +548,25 @@ describe('State', function() { console.error = originalConsoleFn; }); - it('should log error if required property gets no initial value via addToState', function() { - var state = new State(); - state.addToState({ + it('should log error if required property gets no initial value via configState', function() { + var state = new State({ + key2: 'initialValue' + }); + state.configState({ key1: {} }); assert.strictEqual(0, console.error.callCount); - state.addToState( + state.configState( { key2: { required: true } - }, - { - key2: 'initialValue' } ); assert.strictEqual(0, console.error.callCount); - state.addToState({ + state.configState({ key3: { required: true } @@ -588,35 +574,16 @@ describe('State', function() { assert.strictEqual(1, console.error.callCount); }); - it('should log error if required property gets no initial value via addKeyToState', function() { - var state = new State(); - state.addKeyToState('key1'); - assert.strictEqual(0, console.error.callCount); - - state.addKeyToState( - 'key2', - { - required: true - }, - 'initialValue' - ); - assert.strictEqual(0, console.error.callCount); - - state.addKeyToState('key3', { - required: true - } - ); - assert.strictEqual(1, console.error.callCount); - }); - it('should log error if required property is set to null or undefined', function() { - var state = new State(); + var state = new State({ + key: 'initialValue' + }); - state.addToState('key', { + state.configState({ + key: { required: true - }, - 'initialValue' - ); + } + }); assert.strictEqual(0, console.error.callCount); state.key = 'value'; @@ -631,14 +598,14 @@ describe('State', function() { }); it('should emit event when a state key\'s value changes', function() { - var state = new State(); - state.addToState({ + var state = new State({ + key1: 10 + }); + state.configState({ key1: { value: 1, writeOnce: true } - }, { - key1: 10 }); var listener = sinon.stub(); @@ -654,14 +621,14 @@ describe('State', function() { }); it('should emit stateKeyChanged event when a state key\'s value changes', function() { - var state = new State(); - state.addToState({ + var state = new State({ + key1: 10 + }); + state.configState({ key1: { value: 1, writeOnce: true } - }, { - key1: 10 }); var listener = sinon.stub(); @@ -800,9 +767,13 @@ describe('State', function() { }); it('should check if a state key\'s value has already been set', function() { - var state = new State(); - state.addToState('key1', {}, 1); - state.addToState('key2', {}); + var state = new State({ + key1: 1 + }); + state.configState({ + key1: {}, + key2: {} + }); assert.ok(state.hasBeenSet('key1')); assert.ok(!state.hasBeenSet('key2')); @@ -813,12 +784,14 @@ describe('State', function() { it('should not run setter, validator or events for removed state keys', function() { var state = new State(); - state.addToState('key1', { - setter: function(val) { - return val + 10; - }, - validator: function(val) { - return val > 0; + state.configState({ + key1: { + setter: function(val) { + return val + 10; + }, + validator: function(val) { + return val > 0; + } } }); var listener = sinon.stub(); @@ -958,8 +931,10 @@ describe('State', function() { it('should add state properties to given object', function() { var obj = {}; var state = new State({}, obj); - state.addToState('key1'); - state.addToState('key2'); + state.configState({ + key1: {}, + key2: {} + }); var keys = Object.keys(obj); assert.strictEqual(2, keys.length); @@ -1024,34 +999,14 @@ describe('State', function() { assert.strictEqual(context, validator.args[0][2]); }); - it('should share given common options with all state properties', function() { - class Test extends State { - } - Test.STATE = { - key1: { - value: 1 - }, - key2: { - config: { - value: 2 - } - } - }; - - var obj = {}; - new Test({}, obj, obj, { - setter: val => val + 1 - }); - assert.strictEqual(2, obj.key1); - assert.strictEqual(3, obj.key2); - }); - it('should remove state properties from object under given name', function() { var obj = {}; var state = new State({}, obj); - state.addToState('key1'); - state.addToState('key2'); + state.configState({ + key1: {}, + key2: {} + }); state.removeStateKey('key1'); var keys = Object.keys(obj); @@ -1062,8 +1017,10 @@ describe('State', function() { it('should create new object with same state properties when getState is called', function() { var obj = {}; var state = new State({}, obj); - state.addToState('key1'); - state.addToState('key2'); + state.configState({ + key1: {}, + key2: {} + }); assert.deepEqual(obj, state.getState()); assert.notStrictEqual(obj, state.getState()); @@ -1072,8 +1029,10 @@ describe('State', function() { it('should emit event when state property changes', function() { var obj = {}; var state = new State({}, obj); - state.addToState('key1'); - state.addToState('key2'); + state.configState({ + key1: {}, + key2: {} + }); var listener = sinon.stub(); state.on('key1Changed', listener); @@ -1148,7 +1107,7 @@ describe('State', function() { function createStateInstance() { var state = new State(); - state.addToState({ + state.configState({ key1: { value: 1 },