From 091551b5af4f0ed8886c261659669354d43828ff Mon Sep 17 00:00:00 2001 From: Moriel Schottlender Date: Wed, 7 Dec 2016 14:19:53 -0800 Subject: [PATCH] Add userExpLevel filter in the RCFilters UI Bug: T149435 Change-Id: I6a19d4b24c45068dbd9ddb8a36a0f8a87868b912 --- languages/i18n/en.json | 7 + languages/i18n/qqq.json | 7 + resources/Resources.php | 7 + .../dm/mw.rcfilters.dm.FiltersViewModel.js | 89 ++++- .../mediawiki.rcfilters/mw.rcfilters.init.js | 27 ++ .../dm.FiltersViewModel.test.js | 349 +++++++++++++++++- 6 files changed, 460 insertions(+), 26 deletions(-) diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 2d78a6bccf..60dfb48b00 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -1370,6 +1370,13 @@ "rcfilters-filter-editsbyself-description": "Edits by you.", "rcfilters-filter-editsbyother-label": "Edits by others", "rcfilters-filter-editsbyother-description": "Edits created by other users (not you.)", + "rcfilters-filtergroup-userExpLevel": "User experience level", + "rcfilters-filter-userExpLevel-newcomer-label": "Newcomers", + "rcfilters-filter-userExpLevel-newcomer-description": "Very new editors: fewer than 10 edits and 4 days of activity.", + "rcfilters-filter-userExpLevel-learner-label": "Learners", + "rcfilters-filter-userExpLevel-learner-description": "More days of activity and edits than 'Newcomers' but fewer than 'Experienced users.'", + "rcfilters-filter-userExpLevel-experienced-label": "Experienced users", + "rcfilters-filter-userExpLevel-experienced-description": "More than 30 days of activity and 500 edits.", "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since $3, $4 (up to $1 shown).", "rclistfrom": "Show new changes starting from $2, $3", "rcshowhideminor": "$1 minor edits", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 31f8b9b802..5184e5356e 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -1554,6 +1554,13 @@ "rcfilters-filter-editsbyself-description": "Description for the filter for showing edits made by the current user.", "rcfilters-filter-editsbyother-label": "Label for the filter for showing edits made by anyone other than the current user.", "rcfilters-filter-editsbyother-description": "Description for the filter for showing edits made by anyone other than the current user.", + "rcfilters-filtergroup-userExpLevel": "Title for the filter group for user experience levels.", + "rcfilters-filter-userExpLevel-newcomer-label": "Label for the filter for showing edits made by new editors.", + "rcfilters-filter-userExpLevel-newcomer-description": "Description for the filter for showing edits made by new editors.", + "rcfilters-filter-userExpLevel-learner-label": "Label for the filter for showing edits made by learning editors.", + "rcfilters-filter-userExpLevel-learner-description": "Description for the filter for showing edits made by learning editors.", + "rcfilters-filter-userExpLevel-experienced-label": "Label for the filter for showing edits made by experienced editors.", + "rcfilters-filter-userExpLevel-experienced-description": "Description for the filter for showing edits made by experienced editors.", "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL", "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.", "rcshowhideminor": "Option text in [[Special:RecentChanges]]. Parameters:\n* $1 - the \"show/hide\" command, with the text taken from either {{msg-mw|rcshowhideminor-show}} or {{msg-mw|rcshowhideminor-hide}}\n{{Identical|Minor edit}}", diff --git a/resources/Resources.php b/resources/Resources.php index c784f15d6e..1c16cc3b19 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1807,6 +1807,13 @@ return [ 'rcfilters-filter-editsbyself-description', 'rcfilters-filter-editsbyother-label', 'rcfilters-filter-editsbyother-description', + 'rcfilters-filtergroup-userExpLevel', + 'rcfilters-filter-userExpLevel-newcomer-label', + 'rcfilters-filter-userExpLevel-newcomer-description', + 'rcfilters-filter-userExpLevel-learner-label', + 'rcfilters-filter-userExpLevel-learner-description', + 'rcfilters-filter-userExpLevel-experienced-label', + 'rcfilters-filter-userExpLevel-experienced-description', ], 'dependencies' => [ 'oojs-ui', diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js index 1d0f45f9a8..3217d0d7d4 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js @@ -61,6 +61,7 @@ model.groups[ group ].title = data.title; model.groups[ group ].type = data.type; + model.groups[ group ].separator = data.separator || '|'; for ( i = 0; i < data.filters.length; i++ ) { filterItem = new mw.rcfilters.dm.FilterItem( data.filters[ i ].name, { @@ -129,14 +130,14 @@ * @return {Object} Parameter state object */ mw.rcfilters.dm.FiltersViewModel.prototype.getParametersFromFilters = function () { - var i, filterItems, anySelected, + var i, filterItems, anySelected, values, result = {}, groupItems = this.getFilterGroups(); $.each( groupItems, function ( group, data ) { - if ( data.type === 'send_unselected_if_any' ) { - filterItems = data.filters; + filterItems = data.filters; + if ( data.type === 'send_unselected_if_any' ) { // First, check if any of the items are selected at all. // If none is selected, we're treating it as if they are // all false @@ -149,6 +150,58 @@ result[ filterItems[ i ].getName() ] = anySelected ? Number( !filterItems[ i ].isSelected() ) : 0; } + } else if ( data.type === 'string_options' ) { + values = []; + for ( i = 0; i < filterItems.length; i++ ) { + if ( filterItems[ i ].isSelected() ) { + values.push( filterItems[ i ].getName() ); + } + } + + if ( values.length === 0 || values.length === filterItems.length ) { + result[ group ] = 'all'; + } else { + result[ group ] = values.join( data.separator ); + } + } + } ); + + return result; + }; + + /** + * Sanitize value group of a string_option groups type + * Remove duplicates and make sure to only use valid + * values. + * + * @param {string} groupName Group name + * @param {string[]} valueArray Array of values + * @return {string[]} Array of valid values + */ + mw.rcfilters.dm.FiltersViewModel.prototype.sanitizeStringOptionGroup = function( groupName, valueArray ) { + var result = [], + validNames = this.groups[ groupName ].filters.map( function ( filterItem ) { + return filterItem.getName(); + } ); + + if ( valueArray.indexOf( 'all' ) > -1 ) { + // If anywhere in the values there's 'all', we + // treat it as if only 'all' was selected. + // Example: param=valid1,valid2,all + // Result: param=all + return [ 'all' ]; + } + + // Get rid of any dupe and invalid parameter, only output + // valid ones + // Example: param=valid1,valid2,invalid1,valid1 + // Result: param=valid1,valid2 + valueArray.forEach( function ( value ) { + if ( + validNames.indexOf( value ) > -1 && + result.indexOf( value ) === -1 + ) { + result.push( value ); } } ); @@ -163,7 +216,7 @@ * @return {Object} Filter state object */ mw.rcfilters.dm.FiltersViewModel.prototype.getFiltersFromParameters = function ( params ) { - var i, filterItem, allItemsInGroup, + var i, filterItem, groupMap = {}, model = this, base = this.getParametersFromFilters(), @@ -175,7 +228,6 @@ $.each( params, function ( paramName, paramValue ) { // Find the filter item filterItem = model.getItemByName( paramName ); - // Ignore if no filter item exists if ( filterItem ) { groupMap[ filterItem.getGroup() ] = groupMap[ filterItem.getGroup() ] || {}; @@ -189,15 +241,20 @@ // Add the relevant filter into the group map groupMap[ filterItem.getGroup() ].filters = groupMap[ filterItem.getGroup() ].filters || []; groupMap[ filterItem.getGroup() ].filters.push( filterItem ); + } else if ( model.groups.hasOwnProperty( paramName ) ) { + // This parameter represents a group (values are the filters) + // this is equivalent to checking if the group is 'string_options' + groupMap[ paramName ] = { filters: model.groups[ paramName ].filters }; } } ); // Now that we know the groups' selection states, we need to go over // the filters in the groups and mark their selected states appropriately $.each( groupMap, function ( group, data ) { - if ( model.groups[ group ].type === 'send_unselected_if_any' ) { - allItemsInGroup = model.groups[ group ].filters; + var paramValues, filterItem, + allItemsInGroup = data.filters; + if ( model.groups[ group ].type === 'send_unselected_if_any' ) { for ( i = 0; i < allItemsInGroup.length; i++ ) { filterItem = allItemsInGroup[ i ]; @@ -210,6 +267,24 @@ // group, which means the state is false false; } + } else if ( model.groups[ group ].type === 'string_options' ) { + paramValues = model.sanitizeStringOptionGroup( group, params[ group ].split( model.groups[ group ].separator ) ); + + for ( i = 0; i < allItemsInGroup.length; i++ ) { + filterItem = allItemsInGroup[ i ]; + + result[ filterItem.getName() ] = ( + // If it is the word 'all' + paramValues.length === 1 && paramValues[ 0 ] === 'all' || + // All values are written + paramValues.length === model.groups[ group ].filters.length + ) ? + // All true (either because all values are written or the term 'all' is written) + // is the same as all filters set to false + false : + // Otherwise, the filter is selected only if it appears in the parameter values + paramValues.indexOf( filterItem.getName() ) > -1; + } } } ); return result; diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js index 8764e0ac84..e06ca051c1 100644 --- a/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js +++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.init.js @@ -37,6 +37,33 @@ description: mw.msg( 'rcfilters-filter-editsbyother-description' ) } ] + }, + userExpLevel: { + title: mw.msg( 'rcfilters-filtergroup-userExpLevel' ), + // Type 'string_options' means that the group is evaluated by + // string values separated by comma; for example, param=opt1,opt2 + // If all options are selected they are replaced by the term "all". + // The filters are the values for the parameter defined by the group. + // ** In this case, the parameter name is the group name. ** + type: 'string_options', + separator: ',', + filters: [ + { + name: 'newcomer', + label: mw.msg( 'rcfilters-filter-userExpLevel-newcomer-label' ), + description: mw.msg( 'rcfilters-filter-userExpLevel-newcomer-description' ) + }, + { + name: 'learner', + label: mw.msg( 'rcfilters-filter-userExpLevel-learner-label' ), + description: mw.msg( 'rcfilters-filter-userExpLevel-learner-description' ) + }, + { + name: 'experienced', + label: mw.msg( 'rcfilters-filter-userExpLevel-experienced-label' ), + description: mw.msg( 'rcfilters-filter-userExpLevel-experienced-description' ) + } + ] } } ); diff --git a/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js b/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js index aa490a6cb9..b2857d9bfe 100644 --- a/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js +++ b/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js @@ -34,6 +34,22 @@ description: 'Description of Filter 2 in Group 2' } ] + }, + group3: { + title: 'Group 3', + type: 'string_options', + filters: [ + { + name: 'group3filter1', + label: 'Group 3: Filter 1', + description: 'Description of Filter 1 in Group 3' + }, + { + name: 'group3filter2', + label: 'Group 3: Filter 2', + description: 'Description of Filter 2 in Group 3' + } + ] } }, model = new mw.rcfilters.dm.FiltersViewModel(); @@ -44,7 +60,9 @@ model.getItemByName( 'group1filter1' ) instanceof mw.rcfilters.dm.FilterItem && model.getItemByName( 'group1filter2' ) instanceof mw.rcfilters.dm.FilterItem && model.getItemByName( 'group2filter1' ) instanceof mw.rcfilters.dm.FilterItem && - model.getItemByName( 'group2filter2' ) instanceof mw.rcfilters.dm.FilterItem, + model.getItemByName( 'group2filter2' ) instanceof mw.rcfilters.dm.FilterItem && + model.getItemByName( 'group3filter1' ) instanceof mw.rcfilters.dm.FilterItem && + model.getItemByName( 'group3filter2' ) instanceof mw.rcfilters.dm.FilterItem, 'Filters instantiated and stored correctly' ); @@ -54,14 +72,17 @@ group1filter1: false, group1filter2: false, group2filter1: false, - group2filter2: false + group2filter2: false, + group3filter1: false, + group3filter2: false }, 'Initial state of filters' ); model.updateFilters( { group1filter1: true, - group2filter2: true + group2filter2: true, + group3filter1: true } ); assert.deepEqual( model.getState(), @@ -69,7 +90,9 @@ group1filter1: true, group1filter2: false, group2filter1: false, - group2filter2: true + group2filter2: true, + group3filter1: true, + group3filter2: false }, 'Updating filter states correctly' ); @@ -196,6 +219,28 @@ description: 'Description of Filter 3 in Group 2' } ] + }, + group3: { + title: 'Group 3', + type: 'string_options', + separator: ',', + filters: [ + { + name: 'filter7', + label: 'Group 3: Filter 1', + description: 'Description of Filter 1 in Group 3' + }, + { + name: 'filter8', + label: 'Group 3: Filter 2', + description: 'Description of Filter 2 in Group 3' + }, + { + name: 'filter9', + label: 'Group 3: Filter 3', + description: 'Description of Filter 3 in Group 3' + } + ] } }, model = new mw.rcfilters.dm.FiltersViewModel(); @@ -211,9 +256,10 @@ hidefilter3: 0, hidefilter4: 0, hidefilter5: 0, - hidefilter6: 0 + hidefilter6: 0, + group3: 'all', }, - 'Unselected filters return all parameters falsey.' + 'Unselected filters return all parameters falsey or \'all\'.' ); // Select 1 filter @@ -236,7 +282,8 @@ // Group 2 (nothing is selected, all false) hidefilter4: 0, hidefilter5: 0, - hidefilter6: 0 + hidefilter6: 0, + group3: 'all' }, 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.' ); @@ -261,7 +308,8 @@ // Group 2 (nothing is selected, all false) hidefilter4: 0, hidefilter5: 0, - hidefilter6: 0 + hidefilter6: 0, + group3: 'all' }, 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.' ); @@ -286,10 +334,81 @@ // Group 2 (nothing is selected, all false) hidefilter4: 0, hidefilter5: 0, - hidefilter6: 0 + hidefilter6: 0, + group3: 'all' }, 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.' ); + + // Select 1 filter from string_options + model.updateFilters( { + filter7: true, + filter8: false, + filter9: false + } ); + // All filters of the group are selected == this is the same as not selecting any + assert.deepEqual( + model.getParametersFromFilters(), + { + // Group 1 (all selected, all) + hidefilter1: 0, + hidefilter2: 0, + hidefilter3: 0, + // Group 2 (nothing is selected, all false) + hidefilter4: 0, + hidefilter5: 0, + hidefilter6: 0, + group3: 'filter7' + }, + 'One filter selected in "string_option" group returns that filter in the value.' + ); + + // Select 2 filters from string_options + model.updateFilters( { + filter7: true, + filter8: true, + filter9: false + } ); + // All filters of the group are selected == this is the same as not selecting any + assert.deepEqual( + model.getParametersFromFilters(), + { + // Group 1 (all selected, all) + hidefilter1: 0, + hidefilter2: 0, + hidefilter3: 0, + // Group 2 (nothing is selected, all false) + hidefilter4: 0, + hidefilter5: 0, + hidefilter6: 0, + group3: 'filter7,filter8' + }, + 'Two filters selected in "string_option" group returns those filters in the value.' + ); + + // Select 3 filters from string_options + model.updateFilters( { + filter7: true, + filter8: true, + filter9: true + } ); + // All filters of the group are selected == this is the same as not selecting any + assert.deepEqual( + model.getParametersFromFilters(), + { + // Group 1 (all selected, all) + hidefilter1: 0, + hidefilter2: 0, + hidefilter3: 0, + // Group 2 (nothing is selected, all false) + hidefilter4: 0, + hidefilter5: 0, + hidefilter6: 0, + group3: 'all' + }, + 'All filters selected in "string_option" group returns \'all\'.' + ); + } ); QUnit.test( 'getFiltersFromParameters', function ( assert ) { @@ -335,6 +454,28 @@ description: 'Description of Filter 3 in Group 2' } ] + }, + group3: { + title: 'Group 3', + type: 'string_options', + separator: ',', + filters: [ + { + name: 'filter7', + label: 'Group 3: Filter 1', + description: 'Description of Filter 1 in Group 3' + }, + { + name: 'filter8', + label: 'Group 3: Filter 2', + description: 'Description of Filter 2 in Group 3' + }, + { + name: 'filter9', + label: 'Group 3: Filter 3', + description: 'Description of Filter 3 in Group 3' + } + ] } }, model = new mw.rcfilters.dm.FiltersViewModel(); @@ -350,7 +491,10 @@ hidefilter3: false, // The text is "show filter 3" hidefilter4: false, // The text is "show filter 4" hidefilter5: false, // The text is "show filter 5" - hidefilter6: false // The text is "show filter 6" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false }, 'Empty parameter query results in filters in initial state' ); @@ -365,7 +509,10 @@ hidefilter3: true, // The text is "show filter 3" hidefilter4: false, // The text is "show filter 4" hidefilter5: false, // The text is "show filter 5" - hidefilter6: false // The text is "show filter 6" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false }, 'One falsey parameter in a group makes the rest of the filters in the group truthy (checked) in the interface' ); @@ -381,9 +528,12 @@ hidefilter3: true, // The text is "show filter 3" hidefilter4: false, // The text is "show filter 4" hidefilter5: false, // The text is "show filter 5" - hidefilter6: false // The text is "show filter 6" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false }, - 'Two falsey parameters in a group makes the rest of the filters in the group truthy (checked) in the interface' + 'Two falsey parameters in a \'send_unselected_if_any\' group makes the rest of the filters in the group truthy (checked) in the interface' ); assert.deepEqual( @@ -399,9 +549,12 @@ hidefilter3: false, // The text is "show filter 3" hidefilter4: false, // The text is "show filter 4" hidefilter5: false, // The text is "show filter 5" - hidefilter6: false // The text is "show filter 6" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false }, - 'All paremeters in the same group false is equivalent to none are truthy (checked) in the interface' + 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface' ); // The ones above don't update the model, so we have a clean state. @@ -430,9 +583,12 @@ hidefilter3: false, // The text is "show filter 3" hidefilter4: false, // The text is "show filter 4" hidefilter5: false, // The text is "show filter 5" - hidefilter6: false // The text is "show filter 6" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false }, - 'After unchecking 2 of 3 filters via separate updateFilters calls, only the remaining one is still checked.' + 'After unchecking 2 of 3 \'send_unselected_if_any\' filters via separate updateFilters calls, only the remaining one is still checked.' ); // Reset @@ -460,9 +616,164 @@ hidefilter3: false, // The text is "show filter 3" hidefilter4: false, // The text is "show filter 4" hidefilter5: false, // The text is "show filter 5" - hidefilter6: false // The text is "show filter 6" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false + }, + 'After unchecking then checking a \'send_unselected_if_any\' filter (without touching other filters in that group), all are checked' + ); + + model.updateFilters( + model.getFiltersFromParameters( { + group3: 'filter7' + } ) + ); + assert.deepEqual( + model.getState(), + { + hidefilter1: false, // The text is "show filter 1" + hidefilter2: false, // The text is "show filter 2" + hidefilter3: false, // The text is "show filter 3" + hidefilter4: false, // The text is "show filter 4" + hidefilter5: false, // The text is "show filter 5" + hidefilter6: false, // The text is "show filter 6" + filter7: true, + filter8: false, + filter9: false }, - 'After unchecking then checking a filter (without touching other filters in that group), all are checked' + 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked' + ); + + model.updateFilters( + model.getFiltersFromParameters( { + group3: 'filter7,filter8' + } ) + ); + assert.deepEqual( + model.getState(), + { + hidefilter1: false, // The text is "show filter 1" + hidefilter2: false, // The text is "show filter 2" + hidefilter3: false, // The text is "show filter 3" + hidefilter4: false, // The text is "show filter 4" + hidefilter5: false, // The text is "show filter 5" + hidefilter6: false, // The text is "show filter 6" + filter7: true, + filter8: true, + filter9: false + }, + 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked' + ); + + model.updateFilters( + model.getFiltersFromParameters( { + group3: 'filter7,filter8,filter9' + } ) + ); + assert.deepEqual( + model.getState(), + { + hidefilter1: false, // The text is "show filter 1" + hidefilter2: false, // The text is "show filter 2" + hidefilter3: false, // The text is "show filter 3" + hidefilter4: false, // The text is "show filter 4" + hidefilter5: false, // The text is "show filter 5" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false + }, + 'A \'string_options\' parameter containing all values, results in all filters of the group as unchecked.' + ); + + model.updateFilters( + model.getFiltersFromParameters( { + group3: 'filter7,filter8,filter9' + } ) + ); + assert.deepEqual( + model.getState(), + { + hidefilter1: false, // The text is "show filter 1" + hidefilter2: false, // The text is "show filter 2" + hidefilter3: false, // The text is "show filter 3" + hidefilter4: false, // The text is "show filter 4" + hidefilter5: false, // The text is "show filter 5" + hidefilter6: false, // The text is "show filter 6" + filter7: false, + filter8: false, + filter9: false + }, + 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as unchecked.' + ); + + model.updateFilters( + model.getFiltersFromParameters( { + group3: 'filter7,foo,filter9' + } ) + ); + assert.deepEqual( + model.getState(), + { + hidefilter1: false, // The text is "show filter 1" + hidefilter2: false, // The text is "show filter 2" + hidefilter3: false, // The text is "show filter 3" + hidefilter4: false, // The text is "show filter 4" + hidefilter5: false, // The text is "show filter 5" + hidefilter6: false, // The text is "show filter 6" + filter7: true, + filter8: false, + filter9: true + }, + 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.' + ); + } ); + + QUnit.test( 'sanitizeStringOptionGroup', function ( assert ) { + var definition = { + group1: { + title: 'Group 1', + type: 'string_options', + filters: [ + { + name: 'filter1', + label: 'Show filter 1', + description: 'Description of Filter 1 in Group 1' + }, + { + name: 'filter2', + label: 'Show filter 2', + description: 'Description of Filter 2 in Group 1' + }, + { + name: 'filter3', + label: 'Show filter 3', + description: 'Description of Filter 3 in Group 1' + } + ] + } + }, + model = new mw.rcfilters.dm.FiltersViewModel(); + + model.initializeFilters( definition ); + + assert.deepEqual( + model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ), + [ 'filter1', 'filter2' ], + 'Remove duplicate values' + ); + + assert.deepEqual( + model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ), + [ 'filter1', 'filter2' ], + 'Remove invalid values' + ); + + assert.deepEqual( + model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ), + [ 'all' ], + 'If any value is "all", the only value is "all".' ); } ); }( mediaWiki, jQuery ) ); -- 2.20.1