'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.SavedQueriesModel.js',
'resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js',
'resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js',
+ 'resources/src/mediawiki.rcfilters/mw.rcfilters.UriProcessor.js',
],
'dependencies' => [
'oojs',
var subsetNames = [],
filterItem = new mw.rcfilters.dm.FilterItem( filter.name, model, {
group: model.getName(),
- label: mw.msg( filter.label ),
- description: mw.msg( filter.description ),
+ label: filter.label ? mw.msg( filter.label ) : filter.name,
+ description: filter.description ? mw.msg( filter.description ) : '',
cssClass: filter.cssClass
} );
this.savedQueriesModel = savedQueriesModel;
this.requestCounter = 0;
this.baseFilterState = {};
- this.emptyParameterState = {};
+ this.uriProcessor = null;
this.initializing = false;
};
* @param {Array} filterStructure Filter definition and structure for the model
*/
mw.rcfilters.Controller.prototype.initialize = function ( filterStructure ) {
- var parsedSavedQueries, validParameterNames,
+ var parsedSavedQueries,
uri = new mw.Uri(),
$changesList = $( '.mw-changeslist' ).first().contents();
this.filtersModel.initializeFilters( filterStructure );
this._buildBaseFilterState();
- this._buildEmptyParameterState();
- validParameterNames = Object.keys( this._getEmptyParameterState() )
- .filter( function ( param ) {
- // Remove 'highlight' parameter from this check;
- // if it's the only parameter in the URL we still
- // want to consider the URL 'empty' for defaults to load
- return param !== 'highlight';
- } );
+ this.uriProcessor = new mw.rcfilters.UriProcessor(
+ this.filtersModel
+ );
try {
parsedSavedQueries = JSON.parse( mw.user.options.get( 'rcfilters-saved-queries' ) || '{}' );
// the user loads the base-page and we load defaults.
// Defaults should only be applied on load (if necessary)
// or on request
+ this.initializing = true;
if (
- Object.keys( uri.query ).some( function ( parameter ) {
- return validParameterNames.indexOf( parameter ) > -1;
- } )
+ this.savedQueriesModel.getDefault() &&
+ !this.uriProcessor.doesQueryContainRecognizedParams( uri.query )
) {
- // There are parameters in the url, update model state
- this.updateStateBasedOnUrl();
+ // We have defaults from a saved query.
+ // We will load them straight-forward (as if
+ // they were clicked in the menu) so we trigger
+ // a full ajax request and change of URL
+ this.applySavedQuery( this.savedQueriesModel.getDefault() );
} else {
- this.initializing = true;
- // No valid parameters are given, load defaults
- this._updateModelState(
- $.extend(
- true,
- // We've ignored the highlight parameter for the sake
- // of seeing whether we need to apply defaults - but
- // while we do load the defaults, we still want to retain
- // the actual value given in the URL for it on top of the
- // defaults
- { highlight: String( Number( uri.query.highlight ) ) },
- this._getDefaultParams()
- )
+ // There are either recognized parameters in the URL
+ // or there are none, but there is also no default
+ // saved query (so defaults are from the backend)
+ // We want to update the state but not fetch results
+ // again
+ this.updateStateFromUrl( false );
+
+ // Update the changes list with the existing data
+ // so it gets processed
+ this.changesListModel.update(
+ $changesList.length ? $changesList : 'NO_RESULTS',
+ $( 'fieldset.rcoptions' ).first()
);
- this.updateChangesList();
- this.initializing = false;
}
-
- // Update the changes list with the existing data
- // so it gets processed
- this.changesListModel.update(
- $changesList.length ? $changesList : 'NO_RESULTS',
- $( 'fieldset.rcoptions' ).first()
- );
+ this.initializing = false;
};
/**
* Reset to default filters
*/
mw.rcfilters.Controller.prototype.resetToDefaults = function () {
- this._updateModelState( $.extend( true, { highlight: '0' }, this._getDefaultParams() ) );
+ this.uriProcessor.updateModelBasedOnQuery( this._getDefaultParams() );
this.updateChangesList();
};
};
};
- /**
- * Build an empty representation of the parameters, where all parameters
- * are either set to '0' or '' depending on their type.
- * This must run during initialization, before highlights are set.
- */
- mw.rcfilters.Controller.prototype._buildEmptyParameterState = function () {
- var emptyParams = this.filtersModel.getParametersFromFilters( {} ),
- emptyHighlights = this.filtersModel.getHighlightParameters();
-
- this.emptyParameterState = $.extend(
- true,
- {},
- emptyParams,
- emptyHighlights,
- { highlight: '0' }
- );
- };
-
/**
* Get an object representing the base filter state of both
* filters and highlights. The structure is similar to what we use
return this.baseFilterState;
};
- /**
- * Get an object representing the base state of parameters
- * and highlights. The structure is similar to what we use
- * to store each query in the saved queries object:
- * {
- * param1: "value",
- * param2: "value1|value2"
- * }
- *
- * @return {Object} Object representing the base state of
- * parameters and highlights
- */
- mw.rcfilters.Controller.prototype._getEmptyParameterState = function () {
- return this.emptyParameterState;
- };
-
/**
* Get an object that holds only the parameters and highlights that have
* values different than the base default value.
* without adding an history entry.
*/
mw.rcfilters.Controller.prototype.replaceUrl = function () {
- window.history.replaceState(
- { tag: 'rcfilters' },
- document.title,
- this._getUpdatedUri().toString()
- );
+ mw.rcfilters.UriProcessor.static.replaceState( this._getUpdatedUri() );
};
/**
* Update filter state (selection and highlighting) based
* on current URL values.
+ *
+ * @param {boolean} [fetchChangesList=true] Fetch new results into the changes
+ * list based on the updated model.
*/
- mw.rcfilters.Controller.prototype.updateStateBasedOnUrl = function () {
- var uri = new mw.Uri();
+ mw.rcfilters.Controller.prototype.updateStateFromUrl = function ( fetchChangesList ) {
+ fetchChangesList = fetchChangesList === undefined ? true : !!fetchChangesList;
- this._updateModelState( uri.query );
- this.updateChangesList();
+ this.uriProcessor.updateModelBasedOnQuery( new mw.Uri().query );
+
+ // Only update and fetch new results if it is requested
+ if ( fetchChangesList ) {
+ this.updateChangesList();
+ }
};
/**
};
/**
- * Update the model state from given the given parameters.
- *
- * This is an internal method, and should only be used from inside
- * the controller.
+ * Get an object representing the default parameter state, whether
+ * it is from the model defaults or from the saved queries.
*
- * @param {Object} parameters Object representing the parameters for
- * filters and highlights
+ * @return {Object} Default parameters
*/
- mw.rcfilters.Controller.prototype._updateModelState = function ( parameters ) {
- // Update filter states
- this.filtersModel.toggleFiltersSelected(
- this.filtersModel.getFiltersFromParameters(
- parameters
- )
- );
+ mw.rcfilters.Controller.prototype._getDefaultParams = function () {
+ var data, queryHighlights,
+ savedParams = {},
+ savedHighlights = {},
+ defaultSavedQueryItem = this.savedQueriesModel.getItemByID( this.savedQueriesModel.getDefault() );
- // Update highlight state
- this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
- this.filtersModel.getItems().forEach( function ( filterItem ) {
- var color = parameters[ filterItem.getName() + '_color' ];
- if ( color ) {
- filterItem.setHighlightColor( color );
- } else {
- filterItem.clearHighlightColor();
- }
- } );
+ if ( mw.config.get( 'wgStructuredChangeFiltersEnableSaving' ) &&
+ defaultSavedQueryItem ) {
- // Check all filter interactions
- this.filtersModel.reassessFilterInteractions();
+ data = defaultSavedQueryItem.getData();
+
+ queryHighlights = data.highlights || {};
+ savedParams = this.filtersModel.getParametersFromFilters( data.filters || {} );
+
+ // Translate highlights to parameters
+ savedHighlights.highlight = String( Number( queryHighlights.highlight ) );
+ $.each( queryHighlights, function ( filterName, color ) {
+ if ( filterName !== 'highlights' ) {
+ savedHighlights[ filterName + '_color' ] = color;
+ }
+ } );
+
+ return $.extend( true, {}, savedParams, savedHighlights );
+ }
+
+ return $.extend(
+ { highlight: '0' },
+ this.filtersModel.getDefaultParams()
+ );
};
/**
* @param {Object} [params] Extra parameters to add to the API call
*/
mw.rcfilters.Controller.prototype._updateURL = function ( params ) {
- var currentFilterState, updatedFilterState, updatedUri,
- uri = new mw.Uri(),
- notEquivalent = function ( obj1, obj2 ) {
- var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
- return keys.some( function ( key ) {
- return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
- } );
- };
-
- params = params || {};
-
- updatedUri = this._getUpdatedUri();
- updatedUri.extend( params );
-
- // Compare states instead of parameters
- // This will allow us to always have a proper check of whether
- // the requested new url is one to change or not, regardless of
- // actual parameter visibility/representation in the URL
- currentFilterState = this.filtersModel.getFiltersFromParameters( uri.query );
- updatedFilterState = this.filtersModel.getFiltersFromParameters( updatedUri.query );
- // HACK: Re-merge extra parameters in
- // This is a hack and a quickfix; a better, more sustainable
- // fix is being worked on with a UriProcessor, but for now
- // we need to make sure the **comparison** of whether currentFilterState
- // and updatedFilterState differ **includes** the extra parameters in the URL
- currentFilterState = $.extend( true, {}, uri.query, currentFilterState );
- updatedFilterState = $.extend( true, {}, updatedUri.query, updatedFilterState );
-
- // Include highlight states
- $.extend( true,
- currentFilterState,
- this.filtersModel.extractHighlightValues( uri.query ),
- { highlight: !!Number( uri.query.highlight ) }
- );
- $.extend( true,
- updatedFilterState,
- this.filtersModel.extractHighlightValues( updatedUri.query ),
- { highlight: !!Number( updatedUri.query.highlight ) }
- );
+ var currentUri = new mw.Uri(),
+ updatedUri = this._getUpdatedUri();
- if ( notEquivalent( currentFilterState, updatedFilterState ) ) {
+ updatedUri.extend( params || {} );
+
+ if (
+ this.uriProcessor.getVersion( currentUri.query ) !== 2 ||
+ this.uriProcessor.isNewState( currentUri.query, updatedUri.query )
+ ) {
if ( this.initializing ) {
// Initially, when we just build the first page load
// out of defaults, we want to replace the history
- window.history.replaceState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+ mw.rcfilters.UriProcessor.static.replaceState( updatedUri );
} else {
- window.history.pushState( { tag: 'rcfilters' }, document.title, updatedUri.toString() );
+ mw.rcfilters.UriProcessor.static.pushState( updatedUri );
}
}
};
* @return {mw.Uri} Updated Uri
*/
mw.rcfilters.Controller.prototype._getUpdatedUri = function () {
- var uri = new mw.Uri(),
- highlightParams = this.filtersModel.getHighlightParameters(),
- modelParameters = this.filtersModel.getParametersFromFilters(),
- baseParams = this._getEmptyParameterState();
-
- // Minimize values of the model parameters; show only the values that
- // are non-zero. We assume that all parameters that are not literally
- // showing in the URL are set to zero or empty
- $.each( modelParameters, function ( paramName, value ) {
- if ( baseParams[ paramName ] !== value ) {
- uri.query[ paramName ] = value;
- } else {
- // We need to remove this value from the url
- delete uri.query[ paramName ];
- }
- } );
+ var uri = new mw.Uri();
- // highlight params
- if ( this.filtersModel.isHighlightEnabled() ) {
- uri.query.highlight = Number( this.filtersModel.isHighlightEnabled() );
- } else {
- delete uri.query.highlight;
- }
- $.each( highlightParams, function ( paramName, value ) {
- // Only output if it is different than the base parameters
- if ( baseParams[ paramName ] !== value ) {
- uri.query[ paramName ] = value;
- } else {
- delete uri.query[ paramName ];
- }
- } );
+ // Minimize url
+ uri.query = this.uriProcessor.minimizeQuery(
+ $.extend(
+ true,
+ {},
+ // We want to retain unrecognized params
+ // The uri params from model will override
+ // any recognized value in the current uri
+ // query, retain unrecognized params, and
+ // the result will then be minimized
+ uri.query,
+ this.uriProcessor.getUriParametersFromModel(),
+ { urlversion: '2' }
+ )
+ );
return uri;
};
--- /dev/null
+( function ( mw, $ ) {
+ /* eslint no-underscore-dangle: "off" */
+ /**
+ * URI Processor for RCFilters
+ *
+ * @param {mw.rcfilters.dm.FiltersViewModel} filtersModel Filters view model
+ */
+ mw.rcfilters.UriProcessor = function MwRcfiltersController( filtersModel ) {
+ this.emptyParameterState = {};
+ this.filtersModel = filtersModel;
+
+ // Initialize
+ this._buildEmptyParameterState();
+ };
+
+ /* Initialization */
+ OO.initClass( mw.rcfilters.UriProcessor );
+
+ /* Static methods */
+
+ /**
+ * Replace the url history through replaceState
+ *
+ * @param {mw.Uri} newUri New URI to replace
+ */
+ mw.rcfilters.UriProcessor.static.replaceState = function ( newUri ) {
+ window.history.replaceState(
+ { tag: 'rcfilters' },
+ document.title,
+ newUri.toString()
+ );
+ };
+
+ /**
+ * Push the url to history through pushState
+ *
+ * @param {mw.Uri} newUri New URI to push
+ */
+ mw.rcfilters.UriProcessor.static.pushState = function ( newUri ) {
+ window.history.pushState(
+ { tag: 'rcfilters' },
+ document.title,
+ newUri.toString()
+ );
+ };
+
+ /* Methods */
+
+ /**
+ * Get the version that this URL query is tagged with.
+ *
+ * @param {Object} [uriQuery] URI query
+ * @return {number} URL version
+ */
+ mw.rcfilters.UriProcessor.prototype.getVersion = function ( uriQuery ) {
+ uriQuery = uriQuery || new mw.Uri().query;
+
+ return Number( uriQuery.urlversion || 1 );
+ };
+
+ /**
+ * Update the filters model based on the URI query
+ * This happens on initialization, and from this moment on,
+ * we consider the system synchronized, and the model serves
+ * as the source of truth for the URL.
+ *
+ * This methods should only be called once on initialiation.
+ * After initialization, the model updates the URL, not the
+ * other way around.
+ *
+ * @param {Object} [uriQuery] URI query
+ */
+ mw.rcfilters.UriProcessor.prototype.updateModelBasedOnQuery = function ( uriQuery ) {
+ var parameters = this._getNormalizedQueryParams( uriQuery || new mw.Uri().query );
+
+ // Update filter states
+ this.filtersModel.toggleFiltersSelected(
+ this.filtersModel.getFiltersFromParameters(
+ parameters
+ )
+ );
+
+ // Update highlight state
+ this.filtersModel.toggleHighlight( !!Number( parameters.highlight ) );
+ this.filtersModel.getItems().forEach( function ( filterItem ) {
+ var color = parameters[ filterItem.getName() + '_color' ];
+ if ( color ) {
+ filterItem.setHighlightColor( color );
+ } else {
+ filterItem.clearHighlightColor();
+ }
+ } );
+
+ // Check all filter interactions
+ this.filtersModel.reassessFilterInteractions();
+ };
+
+ /**
+ * Get parameters representing the current state of the model
+ *
+ * @return {Object} Uri query parameters
+ */
+ mw.rcfilters.UriProcessor.prototype.getUriParametersFromModel = function () {
+ return $.extend(
+ true,
+ {},
+ this.filtersModel.getParametersFromFilters(),
+ this.filtersModel.getHighlightParameters(),
+ { highlight: String( Number( this.filtersModel.isHighlightEnabled() ) ) }
+ );
+ };
+
+ /**
+ * Build the full parameter representation based on given query parameters
+ *
+ * @private
+ * @param {Object} uriQuery Given URI query
+ * @return {Object} Full parameter state representing the URI query
+ */
+ mw.rcfilters.UriProcessor.prototype._expandModelParameters = function ( uriQuery ) {
+ var filterRepresentation = this.filtersModel.getFiltersFromParameters( uriQuery );
+
+ return $.extend( true,
+ {},
+ uriQuery,
+ this.filtersModel.getParametersFromFilters( filterRepresentation ),
+ this.filtersModel.extractHighlightValues( uriQuery ),
+ { highlight: String( Number( uriQuery.highlight ) ) }
+ );
+ };
+
+ /**
+ * Compare two URI queries to decide whether they are different
+ * enough to represent a new state.
+ *
+ * @param {Object} currentUriQuery Current Uri query
+ * @param {Object} updatedUriQuery Updated Uri query
+ * @return {boolean} This is a new state
+ */
+ mw.rcfilters.UriProcessor.prototype.isNewState = function ( currentUriQuery, updatedUriQuery ) {
+ var currentParamState, updatedParamState,
+ notEquivalent = function ( obj1, obj2 ) {
+ var keys = Object.keys( obj1 ).concat( Object.keys( obj2 ) );
+ return keys.some( function ( key ) {
+ return obj1[ key ] != obj2[ key ]; // eslint-disable-line eqeqeq
+ } );
+ };
+
+ // Compare states instead of parameters
+ // This will allow us to always have a proper check of whether
+ // the requested new url is one to change or not, regardless of
+ // actual parameter visibility/representation in the URL
+ currentParamState = this._expandModelParameters( currentUriQuery );
+ updatedParamState = this._expandModelParameters( updatedUriQuery );
+
+ return notEquivalent( currentParamState, updatedParamState );
+ };
+
+ /**
+ * Check whether the given query has parameters that are
+ * recognized as parameters we should load the system with
+ *
+ * @param {mw.Uri} [uriQuery] Given URI query
+ * @return {boolean} Query contains valid recognized parameters
+ */
+ mw.rcfilters.UriProcessor.prototype.doesQueryContainRecognizedParams = function ( uriQuery ) {
+ var anyValidInUrl,
+ validParameterNames = Object.keys( this._getEmptyParameterState() )
+ .filter( function ( param ) {
+ // Remove 'highlight' parameter from this check;
+ // if it's the only parameter in the URL we still
+ // want to consider the URL 'empty' for defaults to load
+ return param !== 'highlight';
+ } );
+
+ uriQuery = uriQuery || new mw.Uri().query;
+
+ anyValidInUrl = Object.keys( uriQuery ).some( function ( parameter ) {
+ return validParameterNames.indexOf( parameter ) > -1;
+ } );
+
+ // URL version 2 is allowed to be empty or within nonrecognized params
+ return anyValidInUrl || this.getVersion( uriQuery ) === 2;
+ };
+
+ /**
+ * Remove all parameters that have the same value as the base state
+ * This method expects uri queries of the urlversion=2 format
+ *
+ * @private
+ * @param {Object} uriQuery Current uri query
+ * @return {Object} Minimized query
+ */
+ mw.rcfilters.UriProcessor.prototype.minimizeQuery = function ( uriQuery ) {
+ var baseParams = this._getEmptyParameterState(),
+ uriResult = $.extend( true, {}, uriQuery );
+
+ $.each( uriResult, function ( paramName, paramValue ) {
+ if (
+ baseParams[ paramName ] !== undefined &&
+ baseParams[ paramName ] === paramValue
+ ) {
+ // Remove parameter from query
+ delete uriResult[ paramName ];
+ }
+ } );
+
+ return uriResult;
+ };
+
+ /**
+ * Get the adjusted URI params based on the url version
+ * If the urlversion is not 2, the parameters are merged with
+ * the model's defaults.
+ *
+ * @private
+ * @param {Object} uriQuery Current URI query
+ * @return {Object} Normalized parameters
+ */
+ mw.rcfilters.UriProcessor.prototype._getNormalizedQueryParams = function ( uriQuery ) {
+ // Check whether we are dealing with urlversion=2
+ // If we are, we do not merge the initial request with
+ // defaults. Not having urlversion=2 means we need to
+ // reproduce the server-side request and merge the
+ // requested parameters (or starting state) with the
+ // wiki default.
+ // Any subsequent change of the URL through the RCFilters
+ // system will receive 'urlversion=2'
+ var base = this.getVersion( uriQuery ) === 2 ?
+ {} :
+ this.filtersModel.getDefaultParams();
+
+ return this.minimizeQuery(
+ $.extend( true, {}, base, uriQuery, { urlversion: '2' } )
+ );
+ };
+
+ /**
+ * Get the representation of an empty parameter state
+ *
+ * @private
+ * @return {Object} Empty parameter state
+ */
+ mw.rcfilters.UriProcessor.prototype._getEmptyParameterState = function () {
+ return this.emptyParameterState;
+ };
+
+ /**
+ * Build an empty representation of the parameters, where all parameters
+ * are either set to '0' or '' depending on their type.
+ * This must run during initialization, before highlights are set.
+ *
+ * @private
+ */
+ mw.rcfilters.UriProcessor.prototype._buildEmptyParameterState = function () {
+ var emptyParams = this.filtersModel.getParametersFromFilters( {} ),
+ emptyHighlights = this.filtersModel.getHighlightParameters();
+
+ this.emptyParameterState = $.extend(
+ true,
+ {},
+ emptyParams,
+ emptyHighlights,
+ { highlight: '0' }
+ );
+ };
+}( mediaWiki, jQuery ) );
$( '.rcfilters-head' ).addClass( 'mw-rcfilters-ui-ready' );
window.addEventListener( 'popstate', function () {
- controller.updateStateBasedOnUrl();
- controller.updateChangesList();
+ // Update the state of the model from the URL
+ // and re-fetch results into the changes list
+ controller.updateStateFromUrl();
} );
$( 'a.mw-helplink' ).attr(
'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js',
'tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js',
'tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js',
+ 'tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js',
--- /dev/null
+/* eslint-disable camelcase */
+/* eslint no-underscore-dangle: "off" */
+( function ( mw, $ ) {
+ var mockFilterStructure = [ {
+ name: 'group1',
+ title: 'Group 1',
+ type: 'send_unselected_if_any',
+ filters: [
+ { name: 'filter1', default: true },
+ { name: 'filter2' }
+ ]
+ }, {
+ name: 'group2',
+ title: 'Group 2',
+ type: 'send_unselected_if_any',
+ filters: [
+ { name: 'filter3' },
+ { name: 'filter4', default: true }
+ ]
+ }, {
+ name: 'group3',
+ title: 'Group 3',
+ type: 'string_options',
+ filters: [
+ { name: 'filter5' },
+ { name: 'filter6' }
+ ]
+ } ],
+ minimalDefaultParams = {
+ filter1: '1',
+ filter4: '1'
+ };
+
+ QUnit.module( 'mediawiki.rcfilters - UriProcessor' );
+
+ QUnit.test( 'getVersion', function ( assert ) {
+ var uriProcessor = new mw.rcfilters.UriProcessor( new mw.rcfilters.dm.FiltersViewModel() );
+
+ assert.ok(
+ uriProcessor.getVersion( { param1: 'foo', urlversion: '2' } ),
+ 2,
+ 'Retrieving the version from the URI query'
+ );
+
+ assert.ok(
+ uriProcessor.getVersion( { param1: 'foo' } ),
+ 1,
+ 'Getting version 1 if no version is specified'
+ );
+ } );
+
+ QUnit.test( 'updateModelBasedOnQuery & getUriParametersFromModel', function ( assert ) {
+ var uriProcessor,
+ filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ baseParams = {
+ filter1: '0',
+ filter2: '0',
+ filter3: '0',
+ filter4: '0',
+ group3: '',
+ highlight: '0',
+ group1__filter1_color: null,
+ group1__filter2_color: null,
+ group2__filter3_color: null,
+ group2__filter4_color: null,
+ group3__filter5_color: null,
+ group3__filter6_color: null
+ };
+
+ filtersModel.initializeFilters( mockFilterStructure );
+ uriProcessor = new mw.rcfilters.UriProcessor( filtersModel );
+
+ uriProcessor.updateModelBasedOnQuery( {} );
+ assert.deepEqual(
+ uriProcessor.getUriParametersFromModel(),
+ $.extend( true, {}, baseParams, minimalDefaultParams ),
+ 'Version 1: Empty url query sets model to defaults'
+ );
+
+ uriProcessor.updateModelBasedOnQuery( { urlversion: '2' } );
+ assert.deepEqual(
+ uriProcessor.getUriParametersFromModel(),
+ baseParams,
+ 'Version 2: Empty url query sets model to all-false'
+ );
+
+ uriProcessor.updateModelBasedOnQuery( { filter1: '1', urlversion: '2' } );
+ assert.deepEqual(
+ uriProcessor.getUriParametersFromModel(),
+ $.extend( true, {}, baseParams, { filter1: '1' } ),
+ 'Parameters in Uri query set parameter value in the model'
+ );
+
+ uriProcessor.updateModelBasedOnQuery( { highlight: '1', group1__filter1_color: 'c1', urlversion: '2' } );
+ assert.deepEqual(
+ uriProcessor.getUriParametersFromModel(),
+ $.extend( true, {}, baseParams, {
+ highlight: '1',
+ group1__filter1_color: 'c1'
+ } ),
+ 'Highlight parameters in Uri query set highlight state in the model'
+ );
+ } );
+
+ QUnit.test( 'isNewState', function ( assert ) {
+ var uriProcessor,
+ filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ cases = [
+ {
+ states: {
+ curr: {},
+ new: {}
+ },
+ result: false,
+ message: 'Empty objects are not new state.'
+ },
+ {
+ states: {
+ curr: { filter1: '1' },
+ new: { filter1: '0' }
+ },
+ result: true,
+ message: 'Nulified parameter is a new state'
+ },
+ {
+ states: {
+ curr: { filter1: '1' },
+ new: { filter1: '1', filter2: '1' }
+ },
+ result: true,
+ message: 'Added parameters are a new state'
+ },
+ {
+ states: {
+ curr: { filter1: '1' },
+ new: { filter1: '1', filter2: '0' }
+ },
+ result: false,
+ message: 'Added null parameters are not a new state (normalizing equals old state)'
+ },
+ {
+ states: {
+ curr: { filter1: '1' },
+ new: { filter1: '1', foo: 'bar' }
+ },
+ result: true,
+ message: 'Added unrecognized parameters are a new state'
+ },
+ {
+ states: {
+ curr: { filter1: '1', foo: 'bar' },
+ new: { filter1: '1', foo: 'baz' }
+ },
+ result: true,
+ message: 'Changed unrecognized parameters are a new state'
+ }
+ ];
+
+ filtersModel.initializeFilters( mockFilterStructure );
+ uriProcessor = new mw.rcfilters.UriProcessor( filtersModel );
+
+ cases.forEach( function ( testCase ) {
+ assert.equal(
+ uriProcessor.isNewState( testCase.states.curr, testCase.states.new ),
+ testCase.result,
+ testCase.message
+ );
+ } );
+ } );
+
+ QUnit.test( 'doesQueryContainRecognizedParams', function ( assert ) {
+ var uriProcessor,
+ filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ cases = [
+ {
+ query: {},
+ result: false,
+ message: 'Empty query is not valid for load.'
+ },
+ {
+ query: { highlight: '1' },
+ result: false,
+ message: 'Highlight state alone is not valid for load'
+ },
+ {
+ query: { urlversion: '2' },
+ result: true,
+ message: 'urlversion=2 state alone is valid for load as an empty state'
+ },
+ {
+ query: { filter1: '1', foo: 'bar' },
+ result: true,
+ message: 'Existence of recognized parameters makes the query valid for load'
+ },
+ {
+ query: { foo: 'bar', debug: true },
+ result: false,
+ message: 'Only unrecognized parameters makes the query invalid for load'
+ }
+ ];
+
+ filtersModel.initializeFilters( mockFilterStructure );
+ uriProcessor = new mw.rcfilters.UriProcessor( filtersModel );
+
+ cases.forEach( function ( testCase ) {
+ assert.equal(
+ uriProcessor.doesQueryContainRecognizedParams( testCase.query ),
+ testCase.result,
+ testCase.message
+ );
+ } );
+ } );
+
+ QUnit.test( '_getNormalizedQueryParams', function ( assert ) {
+ var uriProcessor,
+ filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
+ cases = [
+ {
+ query: {},
+ result: $.extend( true, { urlversion: '2' }, minimalDefaultParams ),
+ message: 'Empty query returns defaults (urlversion 1).'
+ },
+ {
+ query: { urlversion: '2' },
+ result: { urlversion: '2' },
+ message: 'Empty query returns empty (urlversion 2)'
+ },
+ {
+ query: { filter1: '0' },
+ result: { urlversion: '2', filter4: '1' },
+ message: 'urlversion 1 returns query that overrides defaults'
+ },
+ {
+ query: { filter3: '1' },
+ result: { urlversion: '2', filter1: '1', filter4: '1', filter3: '1' },
+ message: 'urlversion 1 with an extra param value returns query that is joined with defaults'
+ }
+ ];
+
+ filtersModel.initializeFilters( mockFilterStructure );
+ uriProcessor = new mw.rcfilters.UriProcessor( filtersModel );
+
+ cases.forEach( function ( testCase ) {
+ assert.deepEqual(
+ uriProcessor._getNormalizedQueryParams( testCase.query ),
+ testCase.result,
+ testCase.message
+ );
+ } );
+ } );
+
+}( mediaWiki, jQuery ) );