Add userExpLevel filter in the RCFilters UI
authorMoriel Schottlender <moriel@gmail.com>
Wed, 7 Dec 2016 22:19:53 +0000 (14:19 -0800)
committerRoan Kattouw <roan.kattouw@gmail.com>
Thu, 5 Jan 2017 20:13:04 +0000 (12:13 -0800)
Bug: T149435
Change-Id: I6a19d4b24c45068dbd9ddb8a36a0f8a87868b912

languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js

index 2d78a6b..60dfb48 100644 (file)
        "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 <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfrom": "Show new changes starting from $2, $3",
        "rcshowhideminor": "$1 minor edits",
index 31f8b9b..5184e53 100644 (file)
        "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}}",
index c784f15..1c16cc3 100644 (file)
@@ -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',
index 1d0f45f..3217d0d 100644 (file)
@@ -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, {
         * @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
                                        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 );
                        }
                } );
 
         * @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(),
                $.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() ] || {};
                                // 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 ];
 
                                                // 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;
index 8764e0a..e06ca05 100644 (file)
                                                        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' )
+                                               }
+                                       ]
                                }
                        } );
 
index aa490a6..b2857d9 100644 (file)
                                                        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'
                );
 
                                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'
                );
                                                        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();
                                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
                                // 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.'
                );
                                // 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.'
                );
                                // 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 ) {
                                                        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();
                                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'
                );
                                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'
                );
                                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(
                                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.
                                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
                                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 ) );