Expand appropriate option when DataAPI results return
commit
8888749059
|
@ -239,6 +239,12 @@ module.exports = Class( 'Client' )
|
||||||
*/
|
*/
|
||||||
'private _rootContext': null,
|
'private _rootContext': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataApi Manager
|
||||||
|
* @type {DataApiManager}
|
||||||
|
*/
|
||||||
|
'private _dapiManager': null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User-visible validation error messages
|
* User-visible validation error messages
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
|
@ -338,6 +344,13 @@ module.exports = Class( 'Client' )
|
||||||
|
|
||||||
this.ui = this._createUi( this.nav );
|
this.ui = this._createUi( this.nav );
|
||||||
|
|
||||||
|
this._factory.createDataApiMediator(
|
||||||
|
this.ui,
|
||||||
|
this._dataValidator,
|
||||||
|
this.program.dapimap,
|
||||||
|
() => this.getQuote()
|
||||||
|
).monitor( this._dapiManager );
|
||||||
|
|
||||||
this._eventHandler = this._factory.createClientEventHandler(
|
this._eventHandler = this._factory.createClientEventHandler(
|
||||||
this, this._dataValidator, this.elementStyler, this.dataProxy, jQuery
|
this, this._dataValidator, this.elementStyler, this.dataProxy, jQuery
|
||||||
);
|
);
|
||||||
|
@ -756,11 +769,11 @@ module.exports = Class( 'Client' )
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dapi_manager = this._factory.createDataApiManager();
|
this._dapiManager = this._factory.createDataApiManager();
|
||||||
|
|
||||||
var program = this._factory.createProgram(
|
var program = this._factory.createProgram(
|
||||||
this.programId,
|
this.programId,
|
||||||
dapi_manager
|
this._dapiManager
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
catch ( e )
|
catch ( e )
|
||||||
|
@ -779,98 +792,7 @@ module.exports = Class( 'Client' )
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// handle field updates
|
// handle field updates
|
||||||
dapi_manager
|
this._dapiManager
|
||||||
.on( 'fieldLoading', function( name, index )
|
|
||||||
{
|
|
||||||
var group = _self.getUi().getCurrentStep().getElementGroup(
|
|
||||||
name
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( !group )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -1 represents "all indexes"
|
|
||||||
if ( index === -1 )
|
|
||||||
{
|
|
||||||
index = undefined;
|
|
||||||
}
|
|
||||||
} )
|
|
||||||
.on( 'updateFieldData', function( name, index, data, fdata )
|
|
||||||
{
|
|
||||||
var group = _self.getUi().getCurrentStep().getElementGroup(
|
|
||||||
name
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( !group )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cur_data = _self._quote.getDataByName( name );
|
|
||||||
if ( +index === -1 )
|
|
||||||
{
|
|
||||||
// -1 is the "combined" index, representing every field
|
|
||||||
indexes = cur_data;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
indexes = [];
|
|
||||||
indexes[ index ] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var update = [];
|
|
||||||
for ( var i in indexes )
|
|
||||||
{
|
|
||||||
var cur = undefined;
|
|
||||||
|
|
||||||
if ( data.length )
|
|
||||||
{
|
|
||||||
cur = cur_data[ i ];
|
|
||||||
|
|
||||||
update[ i ] = ( fdata[ cur ] )
|
|
||||||
? cur
|
|
||||||
: data[ 0 ].value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
update[ i ] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate and enable field *only if* results were returned
|
|
||||||
// and if the quote has not been locked; but first, give the
|
|
||||||
// UI a chance to finish updating
|
|
||||||
( function( index, cur )
|
|
||||||
{
|
|
||||||
setTimeout( function()
|
|
||||||
{
|
|
||||||
group.setOptions( name, index, data, cur );
|
|
||||||
}, 0 );
|
|
||||||
} )( i, cur );
|
|
||||||
}
|
|
||||||
|
|
||||||
update.length && _self._quote.setDataByName( name, update );
|
|
||||||
} )
|
|
||||||
.on( 'clearFieldData', function( name, index )
|
|
||||||
{
|
|
||||||
if ( !_self.getUi().getCurrentStep().getElementGroup( name ) )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear and disable the field (if there's no value, then there
|
|
||||||
// is no point in allowing them to do something with it)
|
|
||||||
_self.getUi().getCurrentStep().getElementGroup( name )
|
|
||||||
.clearOptions( name, index );
|
|
||||||
} )
|
|
||||||
.on( 'fieldLoaded', ( name, index ) =>
|
|
||||||
{
|
|
||||||
_self._dataValidator.clearFailures( {
|
|
||||||
[name]: [ index ],
|
|
||||||
} );
|
|
||||||
} )
|
|
||||||
.on( 'error', function( e )
|
.on( 'error', function( e )
|
||||||
{
|
{
|
||||||
_self.handleError( e );
|
_self.handleError( e );
|
||||||
|
|
|
@ -97,6 +97,8 @@ var Step = require( '../step/Step' ),
|
||||||
|
|
||||||
diffStore = require( 'liza/system/client' ).data.diffStore,
|
diffStore = require( 'liza/system/client' ).data.diffStore,
|
||||||
|
|
||||||
|
DataApiMediator = require( './dapi/DataApiMediator' ),
|
||||||
|
|
||||||
Class = require( 'easejs' ).Class;
|
Class = require( 'easejs' ).Class;
|
||||||
|
|
||||||
|
|
||||||
|
@ -371,4 +373,6 @@ module.exports = Class( 'ClientDependencyFactory',
|
||||||
'action$cvv2Dialog': requireh( 'Cvv2DialogEventHandler' )( jquery )
|
'action$cvv2Dialog': requireh( 'Cvv2DialogEventHandler' )( jquery )
|
||||||
} );
|
} );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createDataApiMediator: DataApiMediator,
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -0,0 +1,315 @@
|
||||||
|
/**
|
||||||
|
* Data API mediator
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Class } = require( 'easejs' );
|
||||||
|
const MissingDataError = require( '../../dapi/MissingDataError' );
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mediate updates to system state based on DataAPI request status and
|
||||||
|
* results
|
||||||
|
*
|
||||||
|
* The UI will be updated to reflect the options returned by DataAPI
|
||||||
|
* requests. When a field is cleared of all options, any errors on that
|
||||||
|
* field will be cleared.
|
||||||
|
*/
|
||||||
|
module.exports = Class( 'DataApiMediator',
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* UI
|
||||||
|
* @type {Ui}
|
||||||
|
*/
|
||||||
|
'private _ui': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data validator for clearing failures
|
||||||
|
* @type {DataValidator}
|
||||||
|
*/
|
||||||
|
'private _data_validator': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataAPI source/destination field map
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
'private _dapi_map': null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function returning active quote
|
||||||
|
* @type {function():Quote}
|
||||||
|
*/
|
||||||
|
'private _quotef': null,
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize mediator
|
||||||
|
*
|
||||||
|
* The provided DataValidator DATA_VALIDATOR must be the same validator
|
||||||
|
* used to produce errors on fields to ensure that its state can be
|
||||||
|
* appropriately cleared.
|
||||||
|
*
|
||||||
|
* DAPI_MAP stores destination:source field mappings, where source is
|
||||||
|
* the result of the DataAPI call and destination is the target field in
|
||||||
|
* which to store those data.
|
||||||
|
*
|
||||||
|
* Since the active quote changes at runtime, this constructor accepts a
|
||||||
|
* quote function QUOTEF to return the active quote.
|
||||||
|
*
|
||||||
|
* @param {Ui} ui UI
|
||||||
|
* @param {DataValidator} data_validator data validator
|
||||||
|
* @param {Object} dapi_map field source and destination map
|
||||||
|
* @param {function():Quote} quotef nullary function returning quote
|
||||||
|
*/
|
||||||
|
constructor( ui, data_validator, dapi_map, quotef )
|
||||||
|
{
|
||||||
|
if ( typeof dapi_map !== 'object' )
|
||||||
|
{
|
||||||
|
throw TypeError( "dapi_map must be a key/value object" );
|
||||||
|
}
|
||||||
|
|
||||||
|
this._ui = ui;
|
||||||
|
this._data_validator = data_validator;
|
||||||
|
this._dapi_map = dapi_map;
|
||||||
|
this._quotef = quotef;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook given DataApiManager
|
||||||
|
*
|
||||||
|
* Handled events are updateFieldData, clearFieldData, and fieldLoaded.
|
||||||
|
*
|
||||||
|
* @param {DataApiManager} dapi_manager manager to hook
|
||||||
|
*
|
||||||
|
* @return {DataApiMediator} self
|
||||||
|
*/
|
||||||
|
'public monitor'( dapi_manager )
|
||||||
|
{
|
||||||
|
const handlers = [
|
||||||
|
[ 'updateFieldData', this._updateFieldData ],
|
||||||
|
[ 'clearFieldData', this._clearFieldOptions ],
|
||||||
|
[ 'fieldLoaded', this._clearFieldFailures ],
|
||||||
|
]
|
||||||
|
|
||||||
|
handlers.forEach( ( [ event, handler ] ) =>
|
||||||
|
dapi_manager.on( event, handler.bind( this, dapi_manager ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set field options
|
||||||
|
*
|
||||||
|
* If the bucket value associated with NAME and INDEX are in the result
|
||||||
|
* set RESULTS, then it will be selected. Otherwise, the first result
|
||||||
|
* in RESULTS will be selected, if any. If there are no results in
|
||||||
|
* RESULTS, the set value will be the empty string.
|
||||||
|
*
|
||||||
|
* @param {DataApiManager} dapi_manager DataAPI manager
|
||||||
|
* @param {string} name field name
|
||||||
|
* @param {number} index field index
|
||||||
|
* @param {Object<value,label>} val_label value and label
|
||||||
|
* @param {Object} results DataAPI result set
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _updateFieldData'( dapi_manager, name, index, val_label, results )
|
||||||
|
{
|
||||||
|
const group = this._ui.getCurrentStep().getElementGroup( name );
|
||||||
|
|
||||||
|
if ( !group )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quote = this._quotef();
|
||||||
|
const existing = quote.getDataByName( name ) || [];
|
||||||
|
|
||||||
|
let indexes = [];
|
||||||
|
|
||||||
|
// index of -1 indicates that all indexes should be affected
|
||||||
|
if ( index === -1 )
|
||||||
|
{
|
||||||
|
indexes = existing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indexes[ index ] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep existing value if it exists in the result set, otherwise
|
||||||
|
// use the first value of the set
|
||||||
|
const field_update = indexes.map( ( _, i ) =>
|
||||||
|
( results[ existing[ i ] ] )
|
||||||
|
? existing[ i ]
|
||||||
|
: this._getDefaultValue( val_label )
|
||||||
|
);
|
||||||
|
|
||||||
|
indexes.forEach( ( _, i ) =>
|
||||||
|
group.setOptions( name, i, val_label, existing[ i ] )
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const update = this._populateWithMap(
|
||||||
|
dapi_manager, name, indexes, quote
|
||||||
|
);
|
||||||
|
|
||||||
|
update[ name ] = field_update;
|
||||||
|
|
||||||
|
quote.setData( update );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate bucket update with field expansion data
|
||||||
|
*
|
||||||
|
* If multiple indexes are provided, updates will be merged. If
|
||||||
|
* expansion data are missing, then the field will be ignored. If a
|
||||||
|
* destination field is populated such that auto-expanding would
|
||||||
|
* override that datum, then that field will be excluded from the
|
||||||
|
* expansion.
|
||||||
|
*
|
||||||
|
* @param {DataApiManager} dapi_manager manager responsible for fields
|
||||||
|
* @param {string} name field name
|
||||||
|
* @param {Array<number>} indexes field indexes
|
||||||
|
* @param {Quote} quote source quote
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _populateWithMap'( dapi_manager, name, indexes, quote )
|
||||||
|
{
|
||||||
|
const map = this._dapi_map[ name ];
|
||||||
|
|
||||||
|
// calculate field expansions for each index, which contains an
|
||||||
|
// object suitable as-is for use with Quote#setData
|
||||||
|
const expansions = indexes.map( ( _, i ) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return dapi_manager.getDataExpansion(
|
||||||
|
name, i, quote, map, false, {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch ( e )
|
||||||
|
{
|
||||||
|
if ( e instanceof MissingDataError )
|
||||||
|
{
|
||||||
|
// this value is ignored below
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// produce a final update that merges each of the expansions
|
||||||
|
return expansions.reduce( ( update, expansion, i ) =>
|
||||||
|
{
|
||||||
|
// it's important that we check here instead of using #filter on
|
||||||
|
// the array so that we maintain index association
|
||||||
|
if ( expansion === undefined )
|
||||||
|
{
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge each key individually
|
||||||
|
Object.keys( expansion ).forEach( key =>
|
||||||
|
{
|
||||||
|
const existing = ( quote.getDataByName( key ) || [] )[ i ];
|
||||||
|
|
||||||
|
// if set and non-empty, then it's already populated and we
|
||||||
|
// must leave the value alone (so as not to override
|
||||||
|
// something the user directly entered)
|
||||||
|
if ( existing !== undefined && existing !== "" )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update[ key ] = update[ key ] || [];
|
||||||
|
update[ key ][ i ] = expansion[ key ][ i ];
|
||||||
|
} );
|
||||||
|
|
||||||
|
return update;
|
||||||
|
}, {} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear field options
|
||||||
|
*
|
||||||
|
* @param {DataApiManager} dapi_manager DataAPI manager
|
||||||
|
* @param {string} name field name
|
||||||
|
* @param {number} index field index
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _clearFieldOptions'( dapi_manager, name, index )
|
||||||
|
{
|
||||||
|
const group = this._ui.getCurrentStep().getElementGroup( name );
|
||||||
|
|
||||||
|
// ignore unknown fields
|
||||||
|
if ( group === undefined )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
group.clearOptions( name, index );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear field failures
|
||||||
|
*
|
||||||
|
* @param {DataApiManager} dapi_manager DataAPI manager
|
||||||
|
* @param {string} name field name
|
||||||
|
* @param {number} index field index
|
||||||
|
*
|
||||||
|
* @return {undefined}
|
||||||
|
*/
|
||||||
|
'private _clearFieldFailures'( dapi_manager, name, index )
|
||||||
|
{
|
||||||
|
this._data_validator.clearFailures( {
|
||||||
|
[name]: [ index ],
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine default value for result set
|
||||||
|
*
|
||||||
|
* @param {Object} val_label value and label
|
||||||
|
*
|
||||||
|
* @return {string} default value for result set
|
||||||
|
*/
|
||||||
|
'private _getDefaultValue'( val_label )
|
||||||
|
{
|
||||||
|
// default to the empty string if no results were returned
|
||||||
|
if ( val_label.length === 0 )
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ( val_label[ 0 ] || {} ).value;
|
||||||
|
},
|
||||||
|
} );
|
|
@ -1,7 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Manages DataAPI requests and return data
|
* Manages DataAPI requests and return data
|
||||||
*
|
*
|
||||||
* Copyright (C) 2016 R-T Specialty, LLC.
|
* Copyright (C) 2016, 2018 R-T Specialty, LLC.
|
||||||
*
|
*
|
||||||
* This file is part of the Liza Data Collection Framework
|
* This file is part of the Liza Data Collection Framework
|
||||||
*
|
*
|
||||||
|
@ -19,8 +19,9 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Class = require( 'easejs' ).Class,
|
const { Class } = require( 'easejs' );
|
||||||
EventEmitter = require( 'events' ).EventEmitter;
|
const { EventEmitter } = require( 'events' );
|
||||||
|
const MissingDataError = require( './MissingDataError' );
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -613,14 +614,14 @@ module.exports = Class( 'DataApiManager' )
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var field_data = ( this._fieldData[ name ] || {} )[ index ],
|
var field_data = ( this._fieldData[ name ] || {} )[ index ],
|
||||||
data = {};
|
data = {},
|
||||||
field_value = ( diff[ name ] || bucket.getDataByName( name ) )[ index ];
|
field_value = ( diff[ name ] || bucket.getDataByName( name ) )[ index ];
|
||||||
|
|
||||||
// if it's undefined, then the change probably represents a delete
|
// if it's undefined, then the change probably represents a delete
|
||||||
if ( field_value === undefined )
|
if ( field_value === undefined )
|
||||||
{
|
{
|
||||||
( this._fieldDataEmitted[ name ] || [] )[ index ] = false;
|
( this._fieldDataEmitted[ name ] || [] )[ index ] = false;
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have no field data, try the "combined" index
|
// if we have no field data, try the "combined" index
|
||||||
|
@ -638,9 +639,9 @@ module.exports = Class( 'DataApiManager' )
|
||||||
if ( !predictive && !( data ) && ( field_value !== '' ) )
|
if ( !predictive && !( data ) && ( field_value !== '' ) )
|
||||||
{
|
{
|
||||||
// hmm..that's peculiar.
|
// hmm..that's peculiar.
|
||||||
this.emit( 'error', Error(
|
throw MissingDataError(
|
||||||
'Data missing for field ' + name + '[' + index + ']!'
|
'Data missing for field ' + name + '[' + index + ']!'
|
||||||
) );
|
);
|
||||||
}
|
}
|
||||||
else if ( !data )
|
else if ( !data )
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* MissingDataError
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* liza 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { Class } = require( 'easejs' );
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Class( 'MissingDataError' )
|
||||||
|
.extend( Error, {} );
|
|
@ -0,0 +1,525 @@
|
||||||
|
/**
|
||||||
|
* Tests DataApiMediator
|
||||||
|
*
|
||||||
|
* Copyright (C) 2018 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework
|
||||||
|
*
|
||||||
|
* Liza 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { expect } = require( 'chai' );
|
||||||
|
|
||||||
|
const {
|
||||||
|
client: { dapi: { DataApiMediator: Sut } },
|
||||||
|
dapi: { MissingDataError },
|
||||||
|
} = require( '../../../' );
|
||||||
|
|
||||||
|
|
||||||
|
describe( "DataApiMediator", () =>
|
||||||
|
{
|
||||||
|
it( "returns self on #monitor", () =>
|
||||||
|
{
|
||||||
|
const dapi_manager = createStubDapiManager();
|
||||||
|
const sut = Sut( {}, {}, {} );
|
||||||
|
|
||||||
|
expect( sut.monitor( dapi_manager ) ).to.equal( sut );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
describe( "updateFieldData event", () =>
|
||||||
|
{
|
||||||
|
it( "ignores unknown fields", () =>
|
||||||
|
{
|
||||||
|
const dapi_manager = createStubDapiManager();
|
||||||
|
|
||||||
|
const getQuote = () => ( {
|
||||||
|
getDataByName: () => {},
|
||||||
|
setDataByName: () => {},
|
||||||
|
} );
|
||||||
|
|
||||||
|
const ui = createStubUi( {} ); // no field groups
|
||||||
|
const sut = Sut( ui, {}, {}, getQuote ).monitor( dapi_manager );
|
||||||
|
|
||||||
|
dapi_manager.emit( 'updateFieldData', '', 0, {}, {} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: "keeps existing value if in result set (first index)",
|
||||||
|
name: 'foo',
|
||||||
|
index: 0,
|
||||||
|
value: { foo: [ "first", "second" ] },
|
||||||
|
expected: {
|
||||||
|
foo: [ "first" ],
|
||||||
|
dest1: [ "src1data" ],
|
||||||
|
dest2: [ "src2data" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [
|
||||||
|
{ value: "first result", label: "first" },
|
||||||
|
],
|
||||||
|
|
||||||
|
results: {
|
||||||
|
first: { src1: "src1data", src2: "src2data" },
|
||||||
|
second: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
expansion: [ {
|
||||||
|
dest1: [ "src1data" ],
|
||||||
|
dest2: [ "src2data" ],
|
||||||
|
} ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "keeps existing value if in result set (second index)",
|
||||||
|
name: 'bar',
|
||||||
|
index: 1,
|
||||||
|
value: { bar: [ "first", "second" ] },
|
||||||
|
expected: {
|
||||||
|
bar: [ , "second" ],
|
||||||
|
dest1: [ , "src1data_2" ],
|
||||||
|
dest2: [ , "src2data_2" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [
|
||||||
|
{ value: "first result", label: "first" },
|
||||||
|
{ value: "second result", label: "second" },
|
||||||
|
],
|
||||||
|
|
||||||
|
results: {
|
||||||
|
first: {},
|
||||||
|
second: { src1: "src1data_2", src2: "src2data_2" },
|
||||||
|
},
|
||||||
|
|
||||||
|
expansion: [ , {
|
||||||
|
dest1: [ , "src1data_2" ],
|
||||||
|
dest2: [ , "src2data_2" ],
|
||||||
|
} ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "keeps existing value if in result set (all indexes)",
|
||||||
|
name: 'bar',
|
||||||
|
index: -1,
|
||||||
|
value: { bar: [ "first", "second" ] },
|
||||||
|
expected: {
|
||||||
|
bar: [ "first", "second" ],
|
||||||
|
dest1: [ "src1data", "src1data_2" ],
|
||||||
|
dest2: [ "src2data", "src2data_2" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [
|
||||||
|
{ value: "first result", label: "first" },
|
||||||
|
{ value: "second result", label: "second" },
|
||||||
|
],
|
||||||
|
|
||||||
|
results: {
|
||||||
|
first: { src1: "src1data", src2: "src2data" },
|
||||||
|
second: { src1: "src1data_2", src2: "src2data_2" },
|
||||||
|
},
|
||||||
|
|
||||||
|
expansion: [
|
||||||
|
{
|
||||||
|
dest1: [ "src1data" ],
|
||||||
|
dest2: [ "src2data" ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dest1: [ , "src1data_2" ],
|
||||||
|
dest2: [ , "src2data_2" ],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "uses first value of result if existing not in result set (first index)",
|
||||||
|
name: 'foo',
|
||||||
|
index: 0,
|
||||||
|
value: { foo: [ "does not", "exist" ] },
|
||||||
|
expected: {
|
||||||
|
foo: [ "first result" ],
|
||||||
|
desta: [ "src1data" ],
|
||||||
|
destb: [ "src2data" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [
|
||||||
|
{ value: "first result", label: "first" },
|
||||||
|
{ value: "second result", label: "second" },
|
||||||
|
],
|
||||||
|
|
||||||
|
results: {
|
||||||
|
first: { src1: "src1data", src2: "src2data" },
|
||||||
|
second: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
expansion: [ {
|
||||||
|
desta: [ "src1data" ],
|
||||||
|
destb: [ "src2data" ],
|
||||||
|
} ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "uses first value of result if existing not in result set (second index)",
|
||||||
|
name: 'foo',
|
||||||
|
index: 1,
|
||||||
|
value: { foo: [ "does not", "exist" ] },
|
||||||
|
expected: {
|
||||||
|
foo: [ , "first result" ],
|
||||||
|
desta: [ , "src1data" ],
|
||||||
|
destb: [ , "src2data" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [
|
||||||
|
{ value: "first result", label: "first" },
|
||||||
|
{ value: "second result", label: "second" },
|
||||||
|
],
|
||||||
|
|
||||||
|
results: {
|
||||||
|
first: { src1: "src1data", src2: "src2data" },
|
||||||
|
second: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
expansion: [ , {
|
||||||
|
desta: [ , "src1data" ],
|
||||||
|
destb: [ , "src2data" ],
|
||||||
|
} ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "uses first value of result if existing not in result set (all indexes)",
|
||||||
|
name: 'foo',
|
||||||
|
index: -1,
|
||||||
|
value: { foo: [ "does not", "exist" ] },
|
||||||
|
expected: {
|
||||||
|
foo: [ "first result", "first result" ],
|
||||||
|
desta: [ "src1data", "src1data" ],
|
||||||
|
destb: [ "src1data", "src2data" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [
|
||||||
|
{ value: "first result", label: "first" },
|
||||||
|
{ value: "second result", label: "second" },
|
||||||
|
],
|
||||||
|
|
||||||
|
results: {
|
||||||
|
first: { src1: "src1data", src2: "src2data" },
|
||||||
|
second: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
expansion: [
|
||||||
|
{
|
||||||
|
desta: [ "src1data" ],
|
||||||
|
destb: [ "src1data" ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desta: [ , "src1data" ],
|
||||||
|
destb: [ , "src2data" ],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "uses empty string if empty result set (first index)",
|
||||||
|
name: 'foo',
|
||||||
|
index: 0,
|
||||||
|
value: { foo: [ "foo" ] },
|
||||||
|
expected: {
|
||||||
|
foo: [ "" ],
|
||||||
|
dest1: [ "" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [],
|
||||||
|
results: {},
|
||||||
|
expansion: [ {
|
||||||
|
dest1: [ "" ],
|
||||||
|
} ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "uses empty string if empty result set (second index)",
|
||||||
|
name: 'foo',
|
||||||
|
index: 1,
|
||||||
|
value: { foo: [ "foo", "bar" ] },
|
||||||
|
expected: {
|
||||||
|
foo: [ , "" ],
|
||||||
|
dest1: [ , "" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [],
|
||||||
|
results: {},
|
||||||
|
expansion: [ , {
|
||||||
|
dest1: [ , "" ],
|
||||||
|
} ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "uses empty string if empty result set (all indexes)",
|
||||||
|
name: 'foo',
|
||||||
|
index: -1,
|
||||||
|
value: { foo: [ "foo", "bar" ] },
|
||||||
|
expected: {
|
||||||
|
foo: [ "", "" ],
|
||||||
|
dest1: [ "", "" ],
|
||||||
|
dest2: [ "", "" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [],
|
||||||
|
results: {},
|
||||||
|
expansion: [
|
||||||
|
{
|
||||||
|
dest1: [ "" ],
|
||||||
|
dest2: [ "" ],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dest1: [ , "" ],
|
||||||
|
dest2: [ , "" ],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
label: "does not auto-expand into non-empty fields",
|
||||||
|
name: 'foo',
|
||||||
|
index: 0,
|
||||||
|
value: {
|
||||||
|
foo: [ "first", "second" ],
|
||||||
|
dest1: [ "leave alone" ],
|
||||||
|
dest2: [ "" ],
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
foo: [ "first" ],
|
||||||
|
// dest1 missing because it is already populated
|
||||||
|
dest2: [ "src2data" ],
|
||||||
|
},
|
||||||
|
|
||||||
|
val_label: [
|
||||||
|
{ value: "first result", label: "first" },
|
||||||
|
],
|
||||||
|
|
||||||
|
results: {
|
||||||
|
first: { src1: "src1data", src2: "src2data" },
|
||||||
|
second: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
expansion: [ {
|
||||||
|
dest1: [ "src1data" ],
|
||||||
|
dest2: [ "src2data" ],
|
||||||
|
} ],
|
||||||
|
}
|
||||||
|
].forEach( ( {
|
||||||
|
label, name, index, value, expected, val_label, results, expansion
|
||||||
|
} ) =>
|
||||||
|
{
|
||||||
|
it( label, done =>
|
||||||
|
{
|
||||||
|
let set_options = false;
|
||||||
|
|
||||||
|
const quote = {
|
||||||
|
getDataByName( given_name )
|
||||||
|
{
|
||||||
|
return value[ given_name ];
|
||||||
|
},
|
||||||
|
|
||||||
|
setData( given_data )
|
||||||
|
{
|
||||||
|
expect( given_data ).to.deep.equal( expected );
|
||||||
|
|
||||||
|
// should have called setOptions by now
|
||||||
|
expect( set_options ).to.be.true;
|
||||||
|
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQuote = () => quote;
|
||||||
|
|
||||||
|
const dapi_manager = createStubDapiManager( expansion );
|
||||||
|
|
||||||
|
// this isn't a valid map, but comparing the objects will
|
||||||
|
// ensure that the map is actually used
|
||||||
|
const dapimap = { foo: {}, bar: {} };
|
||||||
|
|
||||||
|
dapi_manager.getDataExpansion = (
|
||||||
|
given_name, given_index, given_quote, given_map,
|
||||||
|
predictive, diff
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
expect( given_name ).to.equal( name );
|
||||||
|
expect( given_quote ).to.equal( quote );
|
||||||
|
expect( given_map ).to.deep.equal( dapimap );
|
||||||
|
expect( predictive ).to.be.false;
|
||||||
|
expect( diff ).to.deep.equal( {} );
|
||||||
|
|
||||||
|
return expansion[ given_index ];
|
||||||
|
};
|
||||||
|
|
||||||
|
const field_groups = {
|
||||||
|
[name]: {
|
||||||
|
setOptions( given_name, given_index, given_data, given_cur )
|
||||||
|
{
|
||||||
|
// index is implicitly tested by the given_cur line
|
||||||
|
expect( given_name ).to.equal( name );
|
||||||
|
expect( given_data ).to.deep.equal( val_label );
|
||||||
|
expect( given_cur ).to.equal( value[ given_name ][ given_index ] );
|
||||||
|
|
||||||
|
set_options = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ui = createStubUi( field_groups );
|
||||||
|
|
||||||
|
const sut = Sut( ui, {}, { [name]: dapimap }, getQuote )
|
||||||
|
.monitor( dapi_manager );
|
||||||
|
|
||||||
|
dapi_manager.emit(
|
||||||
|
'updateFieldData', name, index, val_label, results
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
it( 'does not perform expansion if data are not available', done =>
|
||||||
|
{
|
||||||
|
const dapi_manager = createStubDapiManager();
|
||||||
|
|
||||||
|
dapi_manager.getDataExpansion = () =>
|
||||||
|
{
|
||||||
|
throw MissingDataError(
|
||||||
|
'this should happen, but should be caught'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const name = 'foo';
|
||||||
|
const value = 'bar';
|
||||||
|
|
||||||
|
const getQuote = () => ( {
|
||||||
|
getDataByName: () => [ value ],
|
||||||
|
setData( given_data )
|
||||||
|
{
|
||||||
|
// only the value should be set with no expansion data
|
||||||
|
expect( given_data ).to.deep.equal( {
|
||||||
|
[name]: [ value ],
|
||||||
|
} );
|
||||||
|
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
const field_groups = { [name]: { setOptions() {} } };
|
||||||
|
|
||||||
|
const ui = createStubUi( field_groups );
|
||||||
|
const sut = Sut( ui, {}, {}, getQuote ).monitor( dapi_manager );
|
||||||
|
|
||||||
|
const val_label = [
|
||||||
|
{ value: value, label: "bar" },
|
||||||
|
];
|
||||||
|
|
||||||
|
dapi_manager.emit( 'updateFieldData', name, 0, val_label, {} );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
describe( "on clearFieldData event", () =>
|
||||||
|
{
|
||||||
|
it( "ignores unknown fields", () =>
|
||||||
|
{
|
||||||
|
const dapi_manager = createStubDapiManager();
|
||||||
|
|
||||||
|
const field_groups = {}; // no groups
|
||||||
|
|
||||||
|
const ui = createStubUi( field_groups );
|
||||||
|
const sut = Sut( ui, {}, {} ).monitor( dapi_manager );
|
||||||
|
|
||||||
|
dapi_manager.emit( 'clearFieldData', 'unknown', 0 );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( "clears field", done =>
|
||||||
|
{
|
||||||
|
const dapi_manager = createStubDapiManager();
|
||||||
|
|
||||||
|
const name = 'foo';
|
||||||
|
const index = 3;
|
||||||
|
|
||||||
|
const field_groups = {
|
||||||
|
[name]: {
|
||||||
|
clearOptions( given_name, given_index )
|
||||||
|
{
|
||||||
|
expect( given_name ).to.equal( name );
|
||||||
|
expect( given_index ).to.equal( index );
|
||||||
|
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ui = createStubUi( field_groups );
|
||||||
|
const sut = Sut( ui, {}, {} ).monitor( dapi_manager );
|
||||||
|
|
||||||
|
dapi_manager.emit( 'clearFieldData', name, index );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
describe( "on fieldLoaded event", () =>
|
||||||
|
{
|
||||||
|
it( "clears failures for field", done =>
|
||||||
|
{
|
||||||
|
const dapi_manager = createStubDapiManager();
|
||||||
|
|
||||||
|
const name = 'bar';
|
||||||
|
const index = 2;
|
||||||
|
|
||||||
|
const data_validator = {
|
||||||
|
clearFailures( data )
|
||||||
|
{
|
||||||
|
expect( data[ name ] ).to.deep.equal( [ index ] );
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ui = {}; // unused by this event
|
||||||
|
const sut = Sut( ui, data_validator, {} ).monitor( dapi_manager );
|
||||||
|
|
||||||
|
dapi_manager.emit( 'fieldLoaded', name, index );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
|
||||||
|
function createStubDapiManager()
|
||||||
|
{
|
||||||
|
const callbacks = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
on( name, callback )
|
||||||
|
{
|
||||||
|
callbacks[ name ] = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
emit( name )
|
||||||
|
{
|
||||||
|
// we don't support rest yet in our version of node
|
||||||
|
const data = Array.prototype.slice.call( arguments, 1 );
|
||||||
|
|
||||||
|
callbacks[ name ].apply( null, data );
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createStubUi( field_groups )
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
getCurrentStep: () => ( {
|
||||||
|
getElementGroup: name => field_groups[ name ]
|
||||||
|
} )
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue