diff --git a/src/validate/ValidStateMonitor.js b/src/validate/ValidStateMonitor.js index 2eae4ac..a2b530f 100644 --- a/src/validate/ValidStateMonitor.js +++ b/src/validate/ValidStateMonitor.js @@ -1,7 +1,7 @@ /** * Field validity monitor * - * Copyright (C) 2016 LoVullo Associates, Inc. + * Copyright (C) 2016, 2017 LoVullo Associates, Inc. * * This file is part of liza. * @@ -40,22 +40,29 @@ module.exports = Class( 'ValidStateMonitor' ) /** * Mark fields as updated and detect failures and fixes * - * The field data DATA should be a key-value store with an array as the - * value for each key. If the data are not present, then it is assumed - * to have been left unchanged, and will not contribute to a - * fix. Otherwise, any field in FAILURES but not in DATA will count as - * a fix. + * The field data `data` should be a key-value store with an array as + * the value for each key. If the data are not present, then it is + * assumed to have been left unchanged, and will not contribute to a + * fix. Otherwise, any field in `failures` but not in `data` will count + * as a fix. * - * FAILURES should follow the same structure as DATA. Indexes should - * omitted from the value if they are not failures. + * `failures` should follow the same structure as `data`. Indexes + * should omitted from the value if they are not failures. + * + * The return value is a promise that is accepted once all fix checks + * have been performed (after which the `fix` event is emitted if + * appropriate). The `failure` event is emitted synchronously if any + * additional failures are detected. * * @param {Object} data key-value field data * @param {Object} failures key-value field errors * - * @return {ValidStateMonitor} self + * @return {Promise.} self after fix checks */ 'public update': function( data, failures ) { + var _self = this; + var fixed = this.detectFixes( data, this._failures, failures ), count_new = this.mergeFailures( this._failures, failures ); @@ -64,12 +71,16 @@ module.exports = Class( 'ValidStateMonitor' ) this.emit( 'failure', this._failures ); } - if ( fixed !== null ) + // failures is synchronous, fixes async + return fixed.then( function( fixes ) { - this.emit( 'fix', fixed ); - } + if ( fixes !== null ) + { + _self.emit( 'fix', fixes ); + } - return this; + return _self.__inst; + } ); }, @@ -182,26 +193,35 @@ module.exports = Class( 'ValidStateMonitor' ) * @param {Object} data validated data * @param {Object} failures new failures * - * @return {!Object} fixed list of fixed indexes for each fixed field + * @return {Promise.} fixed list of fixed indexes for each fixed field */ 'virtual protected detectFixes': function( data, past, failures ) { - var fixed = {}, - has_fixed = false; + var _self = this, + fixed = {}; - for ( var name in past ) - { - var past_fail = past[ name ], - fail = failures[ name ]; + return Promise.all( + Object.keys( past ).map( function( name ) + { + var past_fail = past[ name ], + fail = failures[ name ]; - has_fixed = has_fixed || this._checkFailureFix( - name, fail, past_fail, data, fixed - ); - } + return _self._checkFailureFix( + name, fail, past_fail, data, fixed + ); + } ) + ) + .then( function( fixes ) + { + var has_fixed = fixes.some( function( value ) + { + return value === true; + } ); - return ( has_fixed ) - ? fixed - : null; + return ( has_fixed ) + ? fixed + : null; + } ); }, @@ -214,7 +234,7 @@ module.exports = Class( 'ValidStateMonitor' ) * @param {Object} data validated data * @param {Object} fixed destination for fixed field data * - * @return {boolean} whether a field was fixed + * @return {Promise.} whether a field was fixed */ 'private _checkFailureFix': function( name, fail, past_fail, data, fixed ) { @@ -261,6 +281,7 @@ module.exports = Class( 'ValidStateMonitor' ) } } - return has_fixed; + // preparation for future use of Store, which is async + return Promise.resolve( has_fixed ); } } ); diff --git a/test/validate/ValidStateMonitorTest.js b/test/validate/ValidStateMonitorTest.js index fa3db01..c129cdd 100644 --- a/test/validate/ValidStateMonitorTest.js +++ b/test/validate/ValidStateMonitorTest.js @@ -1,7 +1,7 @@ /** * Test field validity monitor * - * Copyright (C) 2016 LoVullo Associates, Inc. + * Copyright (C) 2016, 2017 LoVullo Associates, Inc. * * This file is part of liza. * @@ -21,10 +21,13 @@ var root = require( '../../' ), Sut = root.validate.ValidStateMonitor, - expect = require( 'chai' ).expect, + chai = require( 'chai' ), + expect = chai.expect, Failure = root.validate.Failure, Field = root.field.BucketField; +chai.use( require( 'chai-as-promised' ) ); + var nocall = function( type ) { @@ -51,7 +54,7 @@ describe( 'ValidStateMonitor', function() { it( 'does nothing with no data or failures', function() { - Sut() + return Sut() .on( 'failure', nocall( 'failure' ) ) .on( 'fix', nocall( 'fix' ) ) .update( {}, {} ); @@ -60,7 +63,7 @@ describe( 'ValidStateMonitor', function() it( 'does nothing with data but no failures', function() { - Sut() + return Sut() .on( 'failure', nocall( 'failure' ) ) .on( 'fix', nocall( 'fix' ) ) .update( { foo: mkfail( 'foo', [ 'bar' ] ) }, {} ); @@ -128,7 +131,10 @@ describe( 'ValidStateMonitor', function() .once( 'failure', test_first ) .on( 'fix', nocall( 'fix' ) ) .update( {}, { foo: [ undefined, fail[ 1 ] ] } ) - .update( {}, { foo: [ fail[ 0 ] ] } ); + .then( () => + { + return sut.update( {}, { foo: [ fail[ 0 ] ] } ); + } ); } ); @@ -172,24 +178,28 @@ describe( 'ValidStateMonitor', function() // leading to fewer caues sut .update( {}, { foo: [ fail1 ] } ) - .update( {}, { foo: [ fail2 ] } ); - - // if cause1 wasn't removed, then this will fix it - sut - .once( 'fix', function( fixed ) + .then( () => { - expect( fixed ) - .to.deep.equal( { foo: [ 'causefix1' ] } ); - - // and then we should have no failures - expect( sut.hasFailures() ).to.be.false; - - done(); + return sut.update( {}, { foo: [ fail2 ] } ); } ) - .update( - { foo: [ 'moo' ], cause1: [ 'causefix1' ] }, - {} - ); + .then( () => + { + return sut + .once( 'fix', function( fixed ) + { + expect( fixed ) + .to.deep.equal( { foo: [ 'causefix1' ] } ); + + // and then we should have no failures + expect( sut.hasFailures() ).to.be.false; + + done(); + } ) + .update( + { foo: [ 'moo' ], cause1: [ 'causefix1' ] }, + {} + ); + } ); } ); } ); @@ -201,7 +211,9 @@ describe( 'ValidStateMonitor', function() var data = { foo: [ 'bardata', 'baz' ] }, fail = mkfail( 'foo', [ 'bar', 'baz' ] ); - Sut() + var sut = Sut(); + + sut .on( 'fix', function( fixed ) { expect( fixed ) @@ -209,7 +221,10 @@ describe( 'ValidStateMonitor', function() done(); } ) .update( data, { foo: [ fail[ 0 ], fail[ 1 ] ] } ) - .update( data, { foo: [ undefined, fail[ 1 ] ] } ); + .then( () => + { + return sut.update( data, { foo: [ undefined, fail[ 1 ] ] } ); + } ); } ); @@ -230,7 +245,10 @@ describe( 'ValidStateMonitor', function() foo: fail_foo, // does not exist in data bar: fail_bar, } ) - .update( data, {} ); + .then( sut => + { + return sut.update( data, {} ); + } ); } ); @@ -244,9 +262,14 @@ describe( 'ValidStateMonitor', function() called++; } ) .update( {}, { foo: mkfail( 'foo', [ 'bar' ] ) } ) - .update( {}, {} ); // do not trigger failure event - - expect( called ).to.equal( 1 ); + .then( sut => + { + return sut.update( {}, {} ); // do not trigger failure event + } ) + .then( sut => + { + expect( called ).to.equal( 1 ); + } ); } ); @@ -269,7 +292,10 @@ describe( 'ValidStateMonitor', function() done(); } ) .update( data, { foo: [ fail ] } ) - .update( data, {} ); + .then( sut => + { + return sut.update( data, {} ); + } ); } ); @@ -290,7 +316,10 @@ describe( 'ValidStateMonitor', function() done(); } ) .update( data, { foo: [ fail ] } ) - .update( data, {} ); + .then( sut => + { + return sut.update( data, {} ); + } ); } ); @@ -316,7 +345,10 @@ describe( 'ValidStateMonitor', function() done(); } ) .update( data, { foo: [ fail ] } ) - .update( data, {} ); + .then( sut => + { + return sut.update( data, {} ); + } ); } ); @@ -336,7 +368,10 @@ describe( 'ValidStateMonitor', function() Sut() .on( 'fix', nocall ) .update( data, { foo: [ fail ] } ) - .update( data, {} ); + .then( sut => + { + return sut.update( data, {} ); + } ); } ); } ); } ); @@ -351,22 +386,25 @@ describe( 'ValidStateMonitor', function() .update( data, { bar: mkfail( 'bar', [ 'moo', 'cow' ] ) // fail } ) - .on( 'failure', function( failed ) + .then( sut => { - expect( failed ) - .to.deep.equal( { - foo: fail_foo, - } ); - } ) - .on( 'fix', function( fixed ) - { - expect( fixed ) - .to.deep.equal( { bar: [ 'baz', 'quux' ] } ); - done(); - } ) - .update( data, { - foo: fail_foo, // fail - // fixes bar + return sut.on( 'failure', function( failed ) + { + expect( failed ) + .to.deep.equal( { + foo: fail_foo, + } ); + } ) + .on( 'fix', function( fixed ) + { + expect( fixed ) + .to.deep.equal( { bar: [ 'baz', 'quux' ] } ); + done(); + } ) + .update( data, { + foo: fail_foo, // fail + // fixes bar + } ); } ); } ); } ); @@ -387,11 +425,14 @@ describe( 'ValidStateMonitor', function() { var fail = mkfail( 'foo', [ 'fail' ] ); - expect( + return expect( Sut() .update( {}, { foo: fail } ) - .getFailures() - ).to.deep.equal( { foo: fail } ); + .then( sut => + { + return sut.getFailures() + } ) + ).to.eventually.deep.equal( { foo: fail } ); } ); } ); @@ -407,11 +448,14 @@ describe( 'ValidStateMonitor', function() it( 'is true when failures exist', function() { - expect( + return expect( Sut() .update( {}, { foo: mkfail( 'foo', [ 'bar' ] ) } ) - .hasFailures() - ).to.be.true; + .then( sut => + { + return sut.hasFailures(); + } ) + ).to.eventually.be.true; } ); } ); } );