From ed217079200766837306ccb8d13abf49fa91da58 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Fri, 3 Feb 2017 16:50:40 -0500 Subject: [PATCH] DataValidator, ValidStateMonitor: Add #clearFailures argument This allows clearing only the specified failures. * src/validate/ValidStateMonitor.js (clearFailures): Add `fields' argument. Make method more concise. (_fixFailure): Handle clearing `_failures' record. * src/validate/DataValidator.js (clearFailures): Add `fields' argument. * test/validate/ValidStateMonitorTest.js: Add test. * test/validate/DataValidatorTest.js: Add test. --- src/validate/DataValidator.js | 14 ++++-- src/validate/ValidStateMonitor.js | 59 +++++++++++++++----------- test/validate/DataValidatorTest.js | 11 +++-- test/validate/ValidStateMonitorTest.js | 50 +++++++++++++++++++++- 4 files changed, 101 insertions(+), 33 deletions(-) diff --git a/src/validate/DataValidator.js b/src/validate/DataValidator.js index 4651fcb..12e663f 100644 --- a/src/validate/DataValidator.js +++ b/src/validate/DataValidator.js @@ -159,13 +159,21 @@ module.exports = Class( 'DataValidator', /** - * Clear all recorded failures + * Clear specified failures, or otherwise all recorded failures + * + * `fields` must be a key-value map with the field name as the key and + * an array of indexes as the value. Any field in `fields` that has no + * failure is ignored. + * + * See `ValidStateMonitor#clearFailures` for more information. + * + * @param {Object} fields key-value names of fields/indexes to clear * * @return {DataValidator} self */ - 'public clearFailures'() + 'public clearFailures'( failures ) { - this._field_monitor.clearFailures(); + this._field_monitor.clearFailures( failures ); return this; }, diff --git a/src/validate/ValidStateMonitor.js b/src/validate/ValidStateMonitor.js index afe8545..a25a79a 100644 --- a/src/validate/ValidStateMonitor.js +++ b/src/validate/ValidStateMonitor.js @@ -263,7 +263,6 @@ module.exports = Class( 'ValidStateMonitor' ) // looks like it has been resolved this._fixFailure( fixed, name, fail_i, result ); - delete past_fail[ fail_i ]; return true; } ); } ) ).then( fixes => fixes.some( fix => fix === true ) ); @@ -328,48 +327,58 @@ module.exports = Class( 'ValidStateMonitor' ) 'private _fixFailure'( fixed, name, index, value ) { ( fixed[ name ] = fixed[ name ] || [] )[ index ] = value; + + // caller is expected to have ensured that this exists + delete this._failures[ name ][ index ]; + return fixed; }, /** - * Clear all recorded failures + * Clear specified failures, or otherwise all recorded failures * - * For each recorded failure, a `fix` even is emitted. All failure - * records are then cleared. + * `fields` must be a key-value map with the field name as the key and + * an array of indexes as the value. Any field in `fields` that has no + * failure is ignored. + * + * For each specified failure, a `fix` event is emitted. If no failures + * are specified by `fields`, all recorded failures are marked as + * fixed. If a field in `fields` is not known, it is ignored. * * Normally the resulting fix object contains the values that triggered - * the fix. Instead, each fixed index will contain `undefined`. + * the fix. Instead, each fixed index will contain `null`. * * This process is synchronous, and only a single `fix` event is emitted * after all failures have been cleared. * + * @param {Object} fields key-value names of fields/indexes to clear + * * @return {ValidStateMonitor} self */ - 'public clearFailures'() + 'public clearFailures'( fields ) { + const failures = this._failures; + let fixed = {}; - for ( let name in this._failures ) - { - const failure = this._failures[ name ]; + const isRequestedIndex = ( fields ) + ? field => ( fields[ field.getName() ] || [] ).indexOf( + field.getIndex() + ) !== -1 + : () => true; - for ( let cause_i in failure ) - { - const cause = failure[ cause_i ]; - - for ( let cause_i in cause ) - { - let fail_i = cause.getField().getIndex(); - - this._fixFailure( fixed, name, fail_i, undefined ); - } - } - } - - // clear _before_ emitting the fixes (listeners might trigger - // additional failures, for example, or call `#hasFailures`) - this._failures = {}; + Object.keys( failures ) + .reduce( + ( all_fields, name ) => all_fields.concat( + failures[ name ].map( cause => cause.getField() ) + ), + [] + ) + .filter( isRequestedIndex ) + .forEach( field => this._fixFailure( + fixed, field.getName(), field.getIndex(), null + ) ); this.emit( 'fix', fixed ); diff --git a/test/validate/DataValidatorTest.js b/test/validate/DataValidatorTest.js index 8ff4af9..b31a93e 100644 --- a/test/validate/DataValidatorTest.js +++ b/test/validate/DataValidatorTest.js @@ -217,7 +217,7 @@ describe( 'DataValidator', () => describe( '#clearFailures', () => { - it( 'marks all failures as fixed', () => + it( 'proxies to validator', () => { const bvalidator = createMockBucketValidator(); const vmonitor = ValidStateMonitor(); @@ -229,9 +229,14 @@ describe( 'DataValidator', () => bvalidator, vmonitor, dep_factory, createStubStore() ); - mock_vmonitor.expects( 'clearFailures' ).once(); + const failures = [ 'foo', 'bar' ]; - expect( sut.clearFailures() ) + mock_vmonitor + .expects( 'clearFailures' ) + .once() + .withExactArgs( failures ); + + expect( sut.clearFailures( failures ) ) .to.equal( sut ); mock_vmonitor.verify(); diff --git a/test/validate/ValidStateMonitorTest.js b/test/validate/ValidStateMonitorTest.js index 5c44cb4..b57ff07 100644 --- a/test/validate/ValidStateMonitorTest.js +++ b/test/validate/ValidStateMonitorTest.js @@ -596,7 +596,7 @@ describe( 'ValidStateMonitor', function() describe( '#clearFailures', () => { - it( 'clears all failures', () => + it( 'clears all failures when provided no arguments', () => { return new Promise( ( accept, reject ) => { @@ -608,7 +608,7 @@ describe( 'ValidStateMonitor', function() .on( 'fix', fixed => { expect( fixed ) - .to.deep.equal( { foo: [ undefined ] } ); + .to.deep.equal( { foo: [ null ] } ); expect( sut.hasFailures() ).to.be.false; @@ -620,6 +620,52 @@ describe( 'ValidStateMonitor', function() .catch( e => reject( e ) ); } ); } ); + + + it( 'clears only provided failures when provided array argument', () => + { + return new Promise( ( accept, reject ) => + { + mkstore( {} ).then( empty => + { + const sut = Sut(); + + return sut + .on( 'fix', fixed => + { + debugger; + // `bar' not cleared + expect( fixed ) + .to.deep.equal( { + foo: [ null ], + baz: [ , null ], + } ); + + // still has `bar' + expect( sut.hasFailures() ).to.be.true; + + accept( true ); + } ) + .update( empty, { + foo: mkfail( 'foo', [ 'bar1', 'bar2' ] ), + bar: mkfail( 'bar', [ 'baz' ] ), + baz: mkfail( 'baz', [ 'quux', 'quuux' ] ), + } ) + .then( sut => sut.clearFailures( { + foo: [ 0 ], + baz: [ 1 ], + } ) ); + } ) + .catch( e => reject( e ) ); + } ); + } ); + + + it( 'does not error on non-existent failure', () => + { + expect( () => Sut().clearFailures( [ 'foo', 'baz' ] ) ) + .to.not.throw( Error ); + } ); } ); } );