diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a00323c..7e597350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Pending Version +* Brian Fitzpatrick + * Added popping and pushing components/states by dynamic areas + * Tom Dale * Added ability to specify report formats * Supported flags: 'JSON' diff --git a/docs/_pages/components.md b/docs/_pages/components.md index e0ac75ba..3f820457 100644 --- a/docs/_pages/components.md +++ b/docs/_pages/components.md @@ -27,7 +27,7 @@ This section details the different sections of a component * `children` * The computed children from executing the children function * `dynamicArea` - * The dynamicArea given to the component upon creation + * The dynamicArea given to the component upon creation, a given component can have more than one dynamic area * `getFromPage(key)` * Gets a value from the current model of the page * Parameters diff --git a/docs/_pages/expected-state.md b/docs/_pages/expected-state.md index 7d7916f3..9a7cbcc3 100644 --- a/docs/_pages/expected-state.md +++ b/docs/_pages/expected-state.md @@ -78,6 +78,25 @@ This section documents functions for the expected state used throughout the tool ### pop() * Pops the most recent stashed state +### stashDynamicArea(dynamicArea) + * Stashes the components and states of the passed dynamic area + * Parameters + * `dynamicArea` **required** + * A string denoting the dynamic area you wish to stash + +### isDynamicAreaStashed(dynamicArea) + * Checks if the passed dynamic area is stashed + * Parameters + * `dynamicArea` **required** + * A string denoting the dynamic area you wish to check + +### retrieveDynamicArea(dynamicArea) + * Retrieves the components and states from a stashed dynamic area + * Adds the retrieved components back to the dynamic area + * Parameters + * `dynamicArea` **required** + * A String denoting the dynamic area you wish to retrieve + ## eventEmitter * An event emitter instance wherein the events returned from the `events` function are registered * Use this emitter to emit events to other components diff --git a/lib/util/expected-state.js b/lib/util/expected-state.js index 91caa60e..462fd5e8 100644 --- a/lib/util/expected-state.js +++ b/lib/util/expected-state.js @@ -14,6 +14,7 @@ module.exports = ExpectedState = { expectedState._components = new Map(); expectedState._dynamicAreas = new Map(); expectedState._stashedDynamicAreas = []; + expectedState._stashedDynamicAreasComponentsAndStates = new Map(); expectedState._stashedComponents = []; expectedState.eventEmitter = new EventEmitter(); expectedState._dataStore = dataStore; @@ -28,44 +29,75 @@ module.exports = ExpectedState = { clonedState._state= {}; clonedState._pageState = _.cloneDeep(this._pageState); clonedState._components = new Map(); - for (let [type, component] of this._components.entries()) { - let componentState = _.cloneDeep(this._state[type]); - let componentOptions = _.cloneDeep(component.options); - clonedState.createAndAddComponent({ - type: component.type, - name: component.name, - state: componentState, - options: componentOptions, - dynamicArea: component.dynamicArea, - cloning: true, - }); - } + clonedState._stashedStates = _.cloneDeep(this._stashedStates); - for (let stashedComponentMap of this._stashedComponents) { - let components = new Map(); - for (let [name, component] of stashedComponentMap.entries()) { - let componentOptions = _.cloneDeep(component.options); - let myComponent = clonedState.createComponent({ - type: component.type, - name: name, - options: componentOptions, - dynamicArea: component.dynamicArea, - }); - components.set(myComponent.name, myComponent); - } - clonedState._stashedComponents.push(components); - } - for (let dynamicArea of this._stashedDynamicAreas) { - let clonedDynamicArea = new Map(); - for (let [dynamicAreaName, components] of dynamicArea.entries()) { - clonedDynamicArea.set(dynamicAreaName, new Set(components)); - } - clonedState._stashedDynamicAreas.push(clonedDynamicArea); - } + + clonedState._cloneAndAddComponents(this._components, this._state); + clonedState._cloneStashedComponents(this._stashedComponents); + clonedState._cloneStashedDynamicAreas(this._stashedDynamicAreas); + clonedState._cloneStashedDynamicAreasComponentsAndStates(this._stashedDynamicAreasComponentsAndStates); callback(clonedState); }); }, + _cloneComponent(component) { + let componentOptions = _.cloneDeep(component.options); + return this.createComponent({ + type: component.type, + name: component.name, + options: componentOptions, + dynamicArea: component.dynamicArea, + }); + }, + _cloneAndAddComponents(components, state) { + for (let [type, component] of components.entries()) { + let componentState = _.cloneDeep(state[type]); + let componentOptions = _.cloneDeep(component.options); + this.createAndAddComponent({ + type: component.type, + name: component.name, + state: componentState, + options: componentOptions, + dynamicArea: component.dynamicArea, + cloning: true, + }); + } + }, + _cloneStashedComponents(stashedComponents) { + for (let stashedComponentMap of stashedComponents) { + let components = new Map(); + for (let component of stashedComponentMap.values()) { + let myComponent = this._cloneComponent(component); + components.set(myComponent.name, myComponent); + } + this._stashedComponents.push(components); + } + }, + _cloneStashedDynamicAreas(stashedDynamicAreas) { + for (let dynamicArea of stashedDynamicAreas) { + let clonedDynamicArea = new Map(); + for (let [dynamicAreaName, components] of dynamicArea.entries()) { + clonedDynamicArea.set(dynamicAreaName, new Set(components)); + } + this._stashedDynamicAreas.push(clonedDynamicArea); + } + }, + _cloneStashedDynamicAreasComponentsAndStates(stashedDynamicAreasComponentsAndStates) { + for (let [dynamicAreaName, stashedData] of stashedDynamicAreasComponentsAndStates.entries()) { + let components = new Map(); + for (let component of stashedData.components.values()) { + let clonedComponent = this._cloneComponent(component); + components.set(clonedComponent.name, clonedComponent); + } + + let states = new Map(); + for (let [componentName, state] of stashedData.states) { + states.set(componentName, _.cloneDeep(state)); + } + + this._stashedDynamicAreasComponentsAndStates.set(dynamicAreaName, {components, states}); + } + }, createComponent(componentConfig) { let {type, name, options, dynamicArea} = componentConfig; let newComponent; @@ -102,6 +134,9 @@ module.exports = ExpectedState = { newComponent.name = name; newComponent.type = type; newComponent.options = options || {}; + if (dynamicArea) { + newComponent.dynamicArea = dynamicArea; + } newComponent.elements = newComponent.elements(); ExpectedState.emit('validators.validateElements', newComponent.elements, name, type); newComponent.model = newComponent.model(); @@ -120,10 +155,6 @@ module.exports = ExpectedState = { 'validators.validateChildren', newComponent.children, name, type ); } - - if (dynamicArea) { - newComponent.dynamicArea = dynamicArea; - } }); return newComponent; }, @@ -131,7 +162,7 @@ module.exports = ExpectedState = { if (!(typeof state === 'object') || Array.isArray(state)) { throw new SimulatoError.ACTION.EXPECTED_STATE_ERROR( `component with name '${component.name}'` - + `must have a state object to add to expecte state` + + `must have a state object to add to expected state` ); } @@ -263,6 +294,37 @@ module.exports = ExpectedState = { this._components = new Map(); this._dynamicAreas = new Map(); }, + stashDynamicArea(dynamicArea) { + let stashedDynamicAreaComponents = new Map(); + let stashedDynamicAreaStates = new Map(); + let dynamicAreaComponentNames; + if (this._dynamicAreas.get(dynamicArea)) { + dynamicAreaComponentNames = this._dynamicAreas.get(dynamicArea); + + for (let dynamicAreaComponentName of dynamicAreaComponentNames) { + let component = this.getComponent(dynamicAreaComponentName); + stashedDynamicAreaComponents.set(dynamicAreaComponentName, component); + stashedDynamicAreaStates.set(dynamicAreaComponentName, this._state[dynamicAreaComponentName]); + } + + this._stashedDynamicAreasComponentsAndStates.set( + dynamicArea, + { + components: stashedDynamicAreaComponents, + states: stashedDynamicAreaStates, + } + ); + + this.clearDynamicArea(dynamicArea); + } else { + throw new SimulatoError.ACTION.EXPECTED_STATE_ERROR( + `could not find the dynamic area attempting to be stashed` + ); + } + }, + isDynamicAreaStashed(dynamicArea) { + return this._stashedDynamicAreasComponentsAndStates.has(dynamicArea); + }, pop() { if (this._stashedStates.length === 0) { throw new SimulatoError.ACTION.EXPECTED_STATE_ERROR('Failed to pop. No stashed states.'); @@ -274,6 +336,23 @@ module.exports = ExpectedState = { this._dynamicAreas = this._stashedDynamicAreas.pop() || new Map(); [...this._components.values()].map(this._registerEvents, this); }, + retrieveDynamicArea(dynamicArea) { + if (this.isDynamicAreaStashed(dynamicArea) === false) { + throw new SimulatoError.ACTION.EXPECTED_STATE_ERROR(`The dynamic area '${dynamicArea}' is not stashed.`); + } else { + this.clearDynamicArea(dynamicArea); + this._stashedDynamicAreasComponentsAndStates + .get(dynamicArea) + .components + .forEach((value, key) => { + this.addComponent( + value, + this._stashedDynamicAreasComponentsAndStates.get(dynamicArea).states.get(key), + true); + }); + this._stashedDynamicAreasComponentsAndStates.delete(dynamicArea); + } + }, }; Emitter.mixIn(ExpectedState, globalEventDispatch); diff --git a/package-lock.json b/package-lock.json index 5eadf125..2b218e9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "dependencies": { "@sinonjs/formatio": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", "dev": true, "requires": { diff --git a/test/unit/lib/util/expected-state-tests.js b/test/unit/lib/util/expected-state-tests.js index 3c88b376..751b03a6 100644 --- a/test/unit/lib/util/expected-state-tests.js +++ b/test/unit/lib/util/expected-state-tests.js @@ -96,6 +96,7 @@ describe('lib/util/expected-state.js', function() { _components: new Map(), _dynamicAreas: new Map(), _stashedDynamicAreas: [], + _stashedDynamicAreasComponentsAndStates: new Map(), _stashedComponents: [], eventEmitter: EventEmitterInstance, _dataStore: {}, @@ -136,16 +137,23 @@ describe('lib/util/expected-state.js', function() { create: sinon.stub(), _components: new Map(), _stashedComponents: [], + _stashedStates: [{name: 'test'}], _pageState: { name: 'model', }, _stashedDynamicAreas: [], + _stashedDynamicAreasComponentsAndStates: new Map(), }; clonedExpectedState = { createAndAddComponent: sinon.stub(), createComponent: sinon.stub(), + _stashedDynamicAreasComponentsAndStates: new Map(), _stashedComponents: [], _stashedDynamicAreas: [], + _cloneAndAddComponents: sinon.stub(), + _cloneStashedComponents: sinon.stub(), + _cloneStashedDynamicAreas: sinon.stub(), + _cloneStashedDynamicAreasComponentsAndStates: sinon.stub(), }; mockery.registerMock('./emitter.js', Emitter); @@ -199,53 +207,41 @@ describe('lib/util/expected-state.js', function() { }); }); - describe('for each component in the expectedState being cloned', function() { - it('should call _.deepClone with that components state', function() { + it('should call _.deepClone with that components state', function() { myThis._components.set('key', {name: 'test'}); myThis._state = {'key': {name: 'test'}}; myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); expectedState.clone.call(myThis, {}, callback); - expect(_.cloneDeep.args[1]).to.deep.equal([{name: 'test'}]); - }); + expect(_.cloneDeep.args[1]).to.deep.equal([[{name: 'test'}]]); + }); - it('should call _.deepClone with that components options', function() { - myThis._components.set('key', {name: 'test', options: {option1: 'option1'}}); - myThis._state = {'key': {name: 'test'}}; - myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + it('should call _.deepClone with that components options', function() { + myThis._components.set('key', {name: 'test'}); + myThis._state = {'key': {name: 'test'}}; + myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); - expectedState.clone.call(myThis, {}, callback); + expectedState.clone.call(myThis, {}, callback); - expect(_.cloneDeep.args[2]).to.deep.equal([{option1: 'option1'}]); - }); + expect(_.cloneDeep.args[1]).to.deep.equal([[{name: 'test'}]]); + }); - it('should call createAndAddComponent with an object containing: ' + - 'component.type, component.name, componentState, and componentOptions', function() { - myThis._components.set( - 'key', - {type: 'componentName', name: 'instanceName', dynamicArea: 'myDynamicArea'} - ); - myThis._state = {type: 'componentName', name: 'instanceName'}; - _.cloneDeep.onCall(1).returns('myComponentState'); - _.cloneDeep.onCall(2).returns('myComponentOptions'); - myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + it('should call createAndAddComponent with an object containing: ' + + 'component.type, component.name, componentState, and componentOptions', function() { + myThis._components.set( + 'key', + {type: 'componentName', name: 'instanceName', dynamicArea: 'myDynamicArea'} + ); + myThis._state = {type: 'componentName', name: 'instanceName'}; + myThis._cloneAndAddComponents = sinon.stub(); + myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); - expectedState.clone.call(myThis, {}, callback); + expectedState.clone.call(myThis, {}, callback); - expect(clonedExpectedState.createAndAddComponent.args).to.deep.equal( - [ - [{ - type: 'componentName', - name: 'instanceName', - state: 'myComponentState', - options: 'myComponentOptions', - dynamicArea: 'myDynamicArea', - cloning: true, - }], - ] - ); - }); + expect(clonedExpectedState._cloneAndAddComponents.args[0]).to.deep.equal( + [myThis._components, myThis._state] + ); }); it('should call _.cloneDeep with the stashedComponents of the expectedState being cloned', function() { @@ -257,95 +253,456 @@ describe('lib/util/expected-state.js', function() { expect(_.cloneDeep.args[1]).to.deep.equal([['stashedState1', 'stashedState2']]); }); - describe('for each stashed component in the expectedState.stashedComponents being cloned', function() { - it('should call _.cloneDeep with the stashed components options', function() { - let stashedComponents = new Map(); - stashedComponents.set('key', {name: 'stashed1', options: {option1: 'option1'}}); - myThis._stashedComponents = [stashedComponents]; - clonedExpectedState.createComponent.returns({}); - myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + it('should call _cloneStashedComponents with stashed components', function() { + let stashedComponents = new Map(); + stashedComponents.set('key', {name: 'stashed1', options: {option1: 'option1'}}); + myThis._stashedComponents = [stashedComponents]; + clonedExpectedState.createComponent.returns({}); + myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + myThis._cloneStashedComponents = sinon.stub(); - expectedState.clone.call(myThis, {}, callback); + expectedState.clone.call(myThis, {}, callback); - expect(_.cloneDeep.args[2]).to.deep.equal([{option1: 'option1'}]); - }); + expect(clonedExpectedState._cloneStashedComponents.args[0]).to.deep.equal([[stashedComponents]]); + }); - it('should call clonedState.createComponent with and object containing' - + 'component.type, name, and componentOptions', function() { - let stashedComponents = new Map(); - stashedComponents.set( - 'stashed1', - { - type: 'componentName', - name: 'stashed1', - options: {option1: 'option1'}, - dynamicArea: 'myDynamicArea', - } - ); - myThis._stashedComponents = [stashedComponents]; - myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); - clonedExpectedState.createComponent.returns({}); - _.cloneDeep.onCall(2).returns('myComponentOptions'); + it('should call clonedState._cloneAndAddComponent with a component', function() { + let components = new Map(); + components.set( + 'component1', + { + type: 'componentName', + name: 'comp1', + options: {option1: 'option1'}, + dynamicArea: 'myDynamicArea', + } + ); + myThis._components = [components]; + myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + _.cloneDeep.onCall(2).returns('myComponentOptions'); + myThis._cloneAndAddComponents = sinon.stub(); - expectedState.clone.call(myThis, {}, callback); + expectedState.clone.call(myThis, {}, callback); - expect(clonedExpectedState.createComponent.args).to.deep.equal([ - [{ - type: 'componentName', - name: 'stashed1', - options: 'myComponentOptions', - dynamicArea: 'myDynamicArea', - }], - ]); - }); - it('should call clonedState._stashedComponents.push with the new map of components', function() { - let stashedComponents = new Map(); - stashedComponents.set( - 'stashed1', - {type: 'componentName', name: 'stashed1', options: {option1: 'option1'}} - ); - myThis._stashedComponents = [stashedComponents]; - clonedExpectedState.createComponent.onCall(0).returns({name: 'createdComponent'}); - clonedExpectedState._stashedComponents = { - push: sinon.stub(), - }; - myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); - let expectedMap = new Map([['createdComponent', {name: 'createdComponent'}]]); + expect(clonedExpectedState._cloneAndAddComponents.args[0]).to.deep.equal([[components], undefined]); + }); + it('should call clonedState._cloneStashedComponents with the new map of components', function() { + let stashedComponents = new Map(); + stashedComponents.set( + 'stashed1', + {type: 'componentName', name: 'stashed1', options: {option1: 'option1'}} + ); + myThis._stashedComponents = [stashedComponents]; + clonedExpectedState.createComponent.onCall(0).returns({name: 'createdComponent'}); + clonedExpectedState._cloneStashedComponents = sinon.stub(); + myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); - expectedState.clone.call(myThis, {}, callback); + expectedState.clone.call(myThis, {}, callback); - expect(clonedExpectedState._stashedComponents.push.args).to.deep.equal([[expectedMap]]); - }); + expect(clonedExpectedState._cloneStashedComponents.args[0]).to.deep.equal([[stashedComponents]]); }); - describe('for each stashed dynamicArea in this._stashedDynamicAreas', function() { - it('should call clonedState._stashedDynamicArea.push with an identical dynamicArea map', function() { - myThis._stashedDynamicAreas = [ - new Map([ - ['dynamicArea1', new Set(['name1', 'name2'])], - ['dynamicArea2', new Set(['name3', 'name4'])], - ]), - ]; - myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + it('should call clonedState._cloneStashedDynamicArea with an identical dynamicArea map', function() { + myThis._stashedDynamicAreas = [ + new Map([ + ['dynamicArea1', new Set(['name1', 'name2'])], + ['dynamicArea2', new Set(['name3', 'name4'])], + ]), + ]; + clonedExpectedState._cloneStashedDynamicAreas = sinon.stub(); + myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + + expectedState.clone.call(myThis, {}, callback); + + expect(clonedExpectedState._cloneStashedDynamicAreas.args[0][0]).to.deep.equal([ + new Map([ + ['dynamicArea1', new Set(['name1', 'name2'])], + ['dynamicArea2', new Set(['name3', 'name4'])], + ]), + ]); + }); + + it('should call the callback once with the clonedState', function() { + myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + + expectedState.clone.call(myThis, {}, callback); + + expect(callback.args).to.deep.equal([[clonedExpectedState]]); + }); + }); + }); + + describe('_cloneComponent', function() { + let Emitter; + let expectedState; + let component; + let myThis; + let _; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); + + Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + global.SimulatoError = { + ACTION: { + EXPECTED_STATE_ERROR: sinon.stub(), + }, + }; + + component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + + myThis = { + createComponent: sinon.stub(), + }; + + _ = { + get: sinon.stub(), + cloneDeep: sinon.stub(), + }; + + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('events', {}); + mockery.registerMock('lodash', _); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); + + expectedState = require('../../../../lib/util/expected-state.js'); + expectedState._dataStore = {storedData: 'someData'}; + }); + + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + + it('should call clonedeep with the component options', function() { + expectedState._cloneComponent.call(myThis, component); + + expect(_.cloneDeep.args[0][0]).to.deep.equal({option1: 'someOption'}); + }); + }); + + describe('_cloneAndAddComponents', function() { + let Emitter; + let expectedState; + let component; + let myThis; + let componentState; + let componentMap; + let _; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); + + Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + global.SimulatoError = { + ACTION: { + EXPECTED_STATE_ERROR: sinon.stub(), + }, + }; + + component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + componentState = { + type: 'componentType', + name: 'instanceName', + options: { + option1: 'someOption', + }, + }; + + myThis = { + createAndAddComponent: sinon.stub(), + }; + + _ = { + get: sinon.stub(), + cloneDeep: sinon.stub(), + }; + + componentMap = new Map(); + componentMap.set('type', component); + + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('events', {}); + mockery.registerMock('lodash', _); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); + + expectedState = require('../../../../lib/util/expected-state.js'); + expectedState._dataStore = {storedData: 'someData'}; + }); + + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + it('should call clone deep with the state and options', function() { + expectedState._cloneAndAddComponents.call(myThis, componentMap, componentState); + + expect(_.cloneDeep.args).to.deep.equal([['componentType'], [{'option1': 'someOption'}]]); + }); + }); + + describe('_cloneStashedComponents', function() { + let Emitter; + let expectedState; + let component; + let myThis; + let componentMap; + let stashedCompMap; + let _; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); + + Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + global.SimulatoError = { + ACTION: { + EXPECTED_STATE_ERROR: sinon.stub(), + }, + }; + + component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + + myThis = { + _stashedComponents: + { + push: sinon.stub(), + }, + _cloneComponent: sinon.stub().returns(component), + _cloneStashedComponents: sinon.stub(), + }; + + + componentMap = new Map(); + componentMap.set('instanceName', component); + + stashedCompMap = [componentMap]; + + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('events', {}); + mockery.registerMock('lodash', _); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); + + expectedState = require('../../../../lib/util/expected-state.js'); + expectedState._dataStore = {storedData: 'someData'}; + }); + + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + it('should push the cloned stashedComponents as a component map', function() { + expectedState._cloneStashedComponents.call(myThis, stashedCompMap); + + expect(myThis._stashedComponents.push.args[0]).to.deep.equal([componentMap]); + }); + }); + + describe('_cloneStashedDynamicArea', function() { + let Emitter; + let expectedState; + let component; + let myThis; + let componentMap; + let stashedDynamicAreas; + let _; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); + + Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + global.SimulatoError = { + ACTION: { + EXPECTED_STATE_ERROR: sinon.stub(), + }, + }; + + component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + + myThis = { + _stashedDynamicAreas: + { + push: sinon.stub(), + }, + }; + + componentMap = new Map(); + componentMap.set('instanceName', new Set([component])); + + stashedDynamicAreas = [componentMap]; + + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('events', {}); + mockery.registerMock('lodash', _); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); + + expectedState = require('../../../../lib/util/expected-state.js'); + expectedState._dataStore = {storedData: 'someData'}; + }); + + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + it('should call stashedDynamicArea push with the dynamic area to be stashed', function() { + expectedState._cloneStashedDynamicAreas.call(myThis, stashedDynamicAreas); + + expect(myThis._stashedDynamicAreas.push.args[0]).to.deep.equal(stashedDynamicAreas); + }); + }); + + describe('_cloneStashedDynamicAreasComponentsAndStates', function() { + let Emitter; + let expectedState; + let component; + let myThis; + let componentMap; + let stateMap; + let stashedDynamicAreasComponentsAndStates; + let _; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); + + Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + global.SimulatoError = { + ACTION: { + EXPECTED_STATE_ERROR: sinon.stub(), + }, + }; + + component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + + let componentState = { + type: 'componentType', + name: 'instanceName', + options: { + option1: 'someOption', + }, + }; + + componentMap = new Map(); + componentMap.set('instanceName', component); + stateMap = new Map(); + stateMap.set('instanceName', componentState); + + stashedDynamicAreasComponentsAndStates = new Map(); + stashedDynamicAreasComponentsAndStates.set('instanceName', { + components: componentMap, + states: stateMap, + }); + + myThis = { + _stashedDynamicAreasComponentsAndStates: + { + set: sinon.stub(), + }, + _cloneComponent: sinon.stub().returns(component), + }; - expectedState.clone.call(myThis, {}, callback); + _ = { + get: sinon.stub(), + cloneDeep: sinon.stub().onFirstCall().returns(componentState), + }; - expect(clonedExpectedState._stashedDynamicAreas).to.deep.equal([ - new Map([ - ['dynamicArea1', new Set(['name1', 'name2'])], - ['dynamicArea2', new Set(['name3', 'name4'])], - ]), - ]); - }); - }); + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('events', {}); + mockery.registerMock('lodash', _); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); - it('should call the callback once with the clonedState', function() { - myThis.create.callsArgOnWith(1, myThis, clonedExpectedState); + expectedState = require('../../../../lib/util/expected-state.js'); + expectedState._dataStore = {storedData: 'someData'}; + }); - expectedState.clone.call(myThis, {}, callback); + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + it('should call stashedDynamicArea push with the dynamic area to be stashed', function() { + expectedState._cloneStashedDynamicAreasComponentsAndStates + .call(myThis, stashedDynamicAreasComponentsAndStates); - expect(callback.args).to.deep.equal([[clonedExpectedState]]); - }); + expect(myThis._stashedDynamicAreasComponentsAndStates.set.args[0]).to.deep.equal( + ['instanceName', + { + 'components': componentMap, + 'states': stateMap, + }, + ]); }); }); @@ -2150,6 +2507,225 @@ describe('lib/util/expected-state.js', function() { }); }); + describe('stashDynamicArea', function() { + let EventEmitter; + let EventEmitterInstance; + let Emitter; + let expectedState; + let myThis; + let stashedComponent; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('lodash', {}); + mockery.registerMock('events', {}); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); + expectedState = require('../../../../lib/util/expected-state.js'); + + EventEmitter = sinon.stub(); + EventEmitterInstance = { + emit: sinon.stub(), + on: sinon.stub(), + }; + EventEmitter.returns(EventEmitterInstance); + + stashedComponent = new Map([['key', 'value']]); + myThis = { + _stashedStates: [{key: 'value'}], + _stashedComponents: [stashedComponent], + _stashedDynamicAreas: [], + _registerEvents: sinon.stub(), + eventEmitter: { + removeAllListeners: sinon.stub(), + }, + _dynamicAreas: new Map(), + _clearDynamicArea: sinon.stub, + }; + global.SimulatoError = { + ACTION: { + EXPECTED_STATE_ERROR: sinon.stub(), + }, + }; + }); + + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + + describe('for each dynamicArea', function() { + it('should stash the dynamic area components', function() { + let component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + let componentState = { + type: 'componentType', + name: 'instanceName', + options: { + option1: 'someOption', + }, + }; + myThis.getComponent = sinon.stub().returns(component); + myThis._state = {}; + myThis._stashedDynamicAreasComponentsAndStates = new Map(); + myThis.clearDynamicArea = sinon.stub(); + + let componentsMap = new Map(); + let statesMap = new Map(); + + componentsMap.set('instanceName', [component]); + statesMap.set('instanceName', componentState); + myThis._dynamicAreas.set('testDynamicArea', [component.name]); + + expectedState.stashDynamicArea.call(myThis, 'testDynamicArea'); + + expect(myThis._stashedDynamicAreasComponentsAndStates.get('testDynamicArea') + .components.get('instanceName')) + .to.equal(component); + }); + it('should stash the dynamic area states', function() { + let component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + let componentState = { + type: 'componentType', + name: 'instanceName', + options: { + option1: 'someOption', + }, + }; + + myThis.getComponent = sinon.stub().returns(component); + myThis._state = { + 'instanceName': componentState, + }; + myThis._stashedDynamicAreasComponentsAndStates = new Map(); + myThis.clearDynamicArea = sinon.stub(); + + let componentsMap = new Map(); + let statesMap = new Map(); + + componentsMap.set('instanceName', [component]); + statesMap.set('instanceName', componentState); + myThis._dynamicAreas.set('testDynamicArea', [component.name]); + + expectedState.stashDynamicArea.call(myThis, 'testDynamicArea'); + + let compVal = myThis._stashedDynamicAreasComponentsAndStates.get('testDynamicArea') + .states.get('instanceName'); + + expect(compVal) + .to.equal(componentState); + }); + }); + it('should do throw an error if the dynamicArea passed in is invalid', function() { + let error = new Error('My Error'); + SimulatoError.ACTION.EXPECTED_STATE_ERROR.throws(error); + + expect(expectedState.stashDynamicArea.bind(myThis, undefined)).to.throw('My Error'); + }); + it('should clear the dynamic area that was stashed', function() { + let component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + let componentState = { + type: 'componentType', + name: 'instanceName', + options: { + option1: 'someOption', + }, + }; + + myThis.getComponent = sinon.stub().returns(component); + myThis._state = { + componentType: componentState, + }; + myThis._stashedDynamicAreasComponentsAndStates = new Map(); + myThis.clearDynamicArea = sinon.stub(); + + let componentsMap = new Map(); + let statesMap = new Map(); + + + componentsMap.set('instanceName', [component]); + statesMap.set('instanceName', componentState); + myThis._dynamicAreas.set('testDynamicArea', [component.name]); + + expectedState.stashDynamicArea.call(myThis, 'testDynamicArea'); + + expect(myThis.clearDynamicArea.args[0][0]).to.equal('testDynamicArea'); + }); + }); + + describe('isDynamicAreaStashed', function() { + let EventEmitter; + let EventEmitterInstance; + let Emitter; + let expectedState; + let myThis; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('lodash', {}); + mockery.registerMock('events', {}); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); + expectedState = require('../../../../lib/util/expected-state.js'); + + EventEmitter = sinon.stub(); + EventEmitterInstance = { + emit: sinon.stub(), + on: sinon.stub(), + }; + EventEmitter.returns(EventEmitterInstance); + + myThis = {}; + myThis._stashedDynamicAreasComponentsAndStates = new Map(); + myThis._stashedDynamicAreasComponentsAndStates.has = sinon.stub(); + }); + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + it('should check if the stashedDynamicAreasComponentsAndStates has the dynamic area', function() { + expectedState.isDynamicAreaStashed.call(myThis, 'testDynamicArea'); + + expect(myThis._stashedDynamicAreasComponentsAndStates.has.args[0][0]).to.equal('testDynamicArea'); + }); + }); + describe('pop', function() { let Emitter; let expectedState; @@ -2341,4 +2917,122 @@ describe('lib/util/expected-state.js', function() { ]); }); }); + describe('retrieveDynamicArea', function() { + let EventEmitter; + let EventEmitterInstance; + let Emitter; + let expectedState; + let myThis; + let stashedComponent; + + beforeEach(function() { + mockery.enable({useCleanCache: true}); + mockery.registerAllowable('../../../../lib/util/expected-state.js'); + + Emitter = { + mixIn: function(myObject) { + myObject.emit = sinon.stub(); + }, + }; + sinon.spy(Emitter, 'mixIn'); + mockery.registerMock('./emitter.js', Emitter); + mockery.registerMock('lodash', {}); + mockery.registerMock('events', {}); + mockery.registerMock('../global-event-dispatch/global-event-dispatch.js', {}); + expectedState = require('../../../../lib/util/expected-state.js'); + + EventEmitter = sinon.stub(); + EventEmitterInstance = { + emit: sinon.stub(), + on: sinon.stub(), + }; + EventEmitter.returns(EventEmitterInstance); + + let component = { + type: 'componentType', + name: 'instanceName', + elements: sinon.stub().returns(['myElements']), + model: sinon.stub().returns({model: 'modelValue'}), + actions: sinon.stub().returns({ACTION_1: 'someAction'}), + options: {option1: 'someOption'}, + }; + + let componentState = { + type: 'componentType', + name: 'instanceName', + options: { + option1: 'someOption', + }, + }; + + stashedComponent = new Map([['key', 'value']]); + myThis = { + _stashedStates: [{key: 'value'}], + _stashedComponents: [stashedComponent], + _stashedDynamicAreas: [], + _state: {componentType: componentState}, + _stashedDynamicAreasComponentsAndStates: new Map(), + _registerEvents: sinon.stub(), + isDynamicAreaStashed: sinon.stub(), + _addToDynamicArea: sinon.stub(), + eventEmitter: { + removeAllListeners: sinon.stub(), + }, + clearDynamicArea: sinon.stub(), + getComponent: sinon.stub().returns(component), + addComponent: sinon.stub(), + }; + myThis._stashedDynamicAreasComponentsAndStates.delete = sinon.stub(); + + let componentsMap = new Map(); + let statesMap = new Map(); + + global.SimulatoError = { + ACTION: { + EXPECTED_STATE_ERROR: sinon.stub(), + }, + }; + + componentsMap.set('instanceName', component); + statesMap.set('instanceName', componentState); + myThis._stashedDynamicAreasComponentsAndStates.set('testDynamicArea', { + components: componentsMap, + states: statesMap, + }); + }); + + afterEach(function() { + delete global.SimulatoError; + mockery.resetCache(); + mockery.deregisterAll(); + mockery.disable(); + }); + + describe('for each component in the stashed dynamic area components', function() { + it('should add the component to the expected state along with the state', function() { + expectedState.retrieveDynamicArea.call(myThis, 'testDynamicArea'); + + expect(myThis.addComponent.callCount).to.equal(1); + }); + }); + it('should throw an error if the passed in dynamic area is not stashed', function() { + let message = 'The dynamic area testDynamicArea is not stashed.'; + SimulatoError.ACTION.EXPECTED_STATE_ERROR.throws( + {message} + ); + myThis.isDynamicAreaStashed = sinon.stub().returns(false); + + expect(expectedState.retrieveDynamicArea.bind(myThis, 'testDynamicArea')).to.throw(message); + }); + it('should delete the retrieved dynamic area components and states', function() { + expectedState.retrieveDynamicArea.call(myThis, 'testDynamicArea'); + + expect(myThis._stashedDynamicAreasComponentsAndStates.delete.callCount).to.equal(1); + }); + it('should call clear dynamic area on the dynamic area before the rest of the function runs', function() { + expectedState.retrieveDynamicArea.call(myThis, 'testDynamicArea'); + + expect(myThis.clearDynamicArea.args).to.deep.equal([['testDynamicArea']]); + }); + }); });