RCFilters: Create a sticky preference for days/limit groups
authorMoriel Schottlender <moriel@gmail.com>
Thu, 27 Jul 2017 22:32:32 +0000 (15:32 -0700)
committerMoriel Schottlender <moriel@gmail.com>
Fri, 28 Jul 2017 23:06:30 +0000 (16:06 -0700)
- Add sticky preference for groups and the operation behind
  it.
- Allow normalization from the UriProcessor
- Backwards-compatibility for saved queries
- Allow saved queries to load regardless of sticky params
  and to be compared correctly without the sticky params.
- Add days/limit preferences and update those on change
- Update the preference even if we received a new value
  from the URL.

Bug: T171514
Bug: T171368
Change-Id: I5232f3372f0e5c981332d152faf0ab47cc470b56

includes/Preferences.php
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ItemModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.SavedQueriesModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.UriProcessor.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesLimitButtonWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.DateButtonWidget.js
tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js

index 7efbef1..de6d681 100644 (file)
@@ -918,6 +918,9 @@ class Preferences {
                $defaultPreferences['rcfilters-saved-queries'] = [
                        'type' => 'api',
                ];
+               $defaultPreferences['rcfilters-rclimit'] = [
+                       'type' => 'api',
+               ];
 
                if ( $config->get( 'RCWatchCategoryMembership' ) ) {
                        $defaultPreferences['hidecategorization'] = [
index 7849cc2..5cca5d8 100644 (file)
@@ -11,6 +11,8 @@
         * @cfg {string} [type='send_unselected_if_any'] Group type
         * @cfg {string} [view='default'] Name of the display group this group
         *  is a part of.
+        * @cfg {boolean} [isSticky] This group is using a 'sticky' default; meaning
+        *  that every time a value is changed, it becomes the new default
         * @cfg {string} [title] Group title
         * @cfg {boolean} [hidden] This group is hidden from the regular menu views
         * @cfg {boolean} [allowArbitrary] Allows for an arbitrary value to be added to the
@@ -38,6 +40,7 @@
                this.name = name;
                this.type = config.type || 'send_unselected_if_any';
                this.view = config.view || 'default';
+               this.sticky = !!config.isSticky;
                this.title = config.title || name;
                this.hidden = !!config.hidden;
                this.allowArbitrary = !!config.allowArbitrary;
@@ -90,7 +93,6 @@
                        var subsetNames = [],
                                filterItem = new mw.rcfilters.dm.FilterItem( filter.name, model, {
                                        group: model.getName(),
-                                       useDefaultAsBaseValue: !!filter.useDefaultAsBaseValue,
                                        label: filter.label || filter.name,
                                        description: filter.description || '',
                                        labelPrefixKey: model.labelPrefixKey,
                        // For this group, the parameter is the group name,
                        // and a single item can be selected: default or first item
                        this.defaultParams[ this.getName() ] = defaultParam;
-
-                       // Single option means there must be a single option
-                       // selected, so we have to either select the default
-                       // or select the first option
-                       this.selectItemByParamName( defaultParam );
                }
 
                // Store default filter state based on default params
                this.defaultFilters = this.getFilterRepresentation( this.getDefaultParams() );
 
                // Check for filters that should be initially selected by their default value
-               this.getItems().forEach( function ( item ) {
-                       if (
-                               item.isUsingDefaultAsBaseValue() &&
-                               (
-                                       // This setting can only be applied to these groups
-                                       // the other groups are way too complex for that
-                                       model.getType() === 'single_option' ||
-                                       model.getType() === 'boolean'
-                               )
-                       ) {
-                               // Apply selection
-                               item.toggleSelected( !!model.defaultFilters[ item.getName() ] );
-                       }
-               } );
+               if ( this.isSticky() ) {
+                       $.each( this.defaultFilters, function ( filterName, filterValue ) {
+                               model.getItemByName( filterName ).toggleSelected( filterValue );
+                       } );
+               }
+
+               // Verify that single_option group has at least one item selected
+               if (
+                       this.getType() === 'single_option' &&
+                       this.getSelectedItems().length === 0
+               ) {
+                       defaultParam = groupDefault !== undefined ?
+                               groupDefault : this.getItems()[ 0 ].getParamName();
+
+                       // Single option means there must be a single option
+                       // selected, so we have to either select the default
+                       // or select the first option
+                       this.selectItemByParamName( defaultParam );
+               }
        };
 
        /**
                        // Single option means there must be a single option
                        // selected, so we have to either select the default
                        // or select the first option
-                       this.currSelected = this.getItemByParamName( this.defaultParams[ this.getName() ] );
+                       this.currSelected = this.getItemByParamName( this.defaultParams[ this.getName() ] ) ||
+                               this.getItems()[ 0 ];
                        this.currSelected.toggleSelected( true );
                        changed = true;
                }
                        this.active !== active ||
                        this.currSelected !== item
                ) {
+                       if ( this.isSticky() ) {
+                               // If this group is sticky, then change the default according to the
+                               // current selection.
+                               this.defaultParams = this.getParamRepresentation( this.getSelectedState() );
+                       }
+
                        this.active = active;
                        this.currSelected = item;
 
                                // This means we have not been given a filter representation
                                // so we are building one based on current state
                                filterRepresentation[ item.getName() ] = item.isSelected();
-                       } else if ( !filterRepresentation[ item.getName() ] ) {
+                       } else if ( filterRepresentation[ item.getName() ] === undefined ) {
                                // We are given a filter representation, but we have to make
                                // sure that we fill in the missing filters if there are any
-                               // we will assume they are all falsey, unless they have
-                               // isUsingDefaultAsBaseValue, in which case they get their
-                               // default state
-                               if (
-                                       item.isUsingDefaultAsBaseValue() &&
-                                       (
-                                               // This setting can only be applied to these groups
-                                               // the other groups are way too complex for that
-                                               model.getType() === 'single_option' ||
-                                               model.getType() === 'boolean'
-                                       )
-                               ) {
+                               // we will assume they are all falsey
+                               if ( model.isSticky() ) {
                                        filterRepresentation[ item.getName() ] = !!defaultFilters[ item.getName() ];
                                } else {
                                        filterRepresentation[ item.getName() ] = false;
         * @return {Object} Filter representation
         */
        mw.rcfilters.dm.FilterGroup.prototype.getFilterRepresentation = function ( paramRepresentation ) {
-               var areAnySelected, paramValues, defaultValue, item, currentValue,
+               var areAnySelected, paramValues, item, currentValue,
                        oneWasSelected = false,
                        defaultParams = this.getDefaultParams(),
-                       defaultFilters = this.getDefaultFilters(),
                        expandedParams = $.extend( true, {}, paramRepresentation ),
                        model = this,
                        paramToFilterMap = {},
                        result = {};
 
+               if ( this.isSticky() ) {
+                       // If the group is sticky, check if all parameters are represented
+                       // and for those that aren't represented, add them with their default
+                       // values
+                       paramRepresentation = $.extend( true, {}, this.getDefaultParams(), paramRepresentation );
+               }
+
                paramRepresentation = paramRepresentation || {};
                if (
                        this.getType() === 'send_unselected_if_any' ||
                        } );
 
                        $.each( expandedParams, function ( paramName, paramValue ) {
-                               var value = paramValue,
-                                       filterItem = paramToFilterMap[ paramName ];
+                               var filterItem = paramToFilterMap[ paramName ];
 
                                if ( model.getType() === 'send_unselected_if_any' ) {
                                        // Flip the definition between the parameter
                                                false;
                                } else if ( model.getType() === 'boolean' ) {
                                        // Straight-forward definition of state
-                                       if (
-                                               filterItem.isUsingDefaultAsBaseValue() &&
-                                               paramRepresentation[ filterItem.getParamName() ] === undefined
-                                       ) {
-                                               value = defaultParams[ filterItem.getParamName() ];
-                                       }
-                                       result[ filterItem.getName() ] = !!Number( value );
+                                       result[ filterItem.getName() ] = !!Number( paramRepresentation[ filterItem.getParamName() ] );
                                }
                        } );
                } else if ( this.getType() === 'string_options' ) {
                } else if ( this.getType() === 'single_option' ) {
                        // There is parameter that fits a single filter and if not, get the default
                        this.getItems().forEach( function ( filterItem ) {
-                               var selected = false;
+                               var selected = filterItem.getParamName() === paramRepresentation[ model.getName() ];
 
-                               if (
-                                       filterItem.isUsingDefaultAsBaseValue() &&
-                                       paramRepresentation[ model.getName() ] === undefined
-                               ) {
-                                       selected = !!Number( paramRepresentation[ model.getName() ] );
-                               } else {
-                                       selected = filterItem.getParamName() === paramRepresentation[ model.getName() ];
-                               }
                                result[ filterItem.getName() ] = selected;
                                oneWasSelected = oneWasSelected || selected;
                        } );
                // Go over result and make sure all filters are represented.
                // If any filters are missing, they will get a falsey value
                this.getItems().forEach( function ( filterItem ) {
-                       if (
-                               (
-                                       // This setting can only be applied to these groups
-                                       // the other groups are way too complex for that
-                                       model.getType() === 'single_option' ||
-                                       model.getType() === 'boolean'
-                               ) &&
-                               result[ filterItem.getName() ] === undefined &&
-                               filterItem.isUsingDefaultAsBaseValue()
-                       ) {
-                               result[ filterItem.getName() ] = !!defaultFilters[ filterItem.getName() ];
+                       if ( result[ filterItem.getName() ] === undefined ) {
+                               result[ filterItem.getName() ] = false;
                        }
-                       oneWasSelected = oneWasSelected || !!result[ filterItem.getName() ];
                } );
 
                // Make sure that at least one option is selected in
                        this.getType() === 'single_option' &&
                        !oneWasSelected
                ) {
-                       defaultValue = this.getDefaultParams();
-                       item = this.getItemByParamName( defaultValue[ this.getName() ] );
+                       if ( defaultParams[ this.getName() ] ) {
+                               item = this.getItemByParamName( defaultParams[ this.getName() ] );
+                       } else {
+                               item = this.getItems()[ 0 ];
+                       }
                        result[ item.getName() ] = true;
                }
 
                return result;
        };
 
+       /**
+        * Get current selected state of all filter items in this group
+        *
+        * @return {Object} Selected state
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.getSelectedState = function () {
+               var state = {};
+
+               this.getItems().forEach( function ( filterItem ) {
+                       state[ filterItem.getName() ] = filterItem.isSelected();
+               } );
+
+               return state;
+       };
+
        /**
         * Get item by its filter name
         *
        mw.rcfilters.dm.FilterGroup.prototype.isFullCoverage = function () {
                return this.fullCoverage;
        };
+
+       /**
+        * Check whether the group is defined as sticky default
+        *
+        * @return {boolean} Group is sticky default
+        */
+       mw.rcfilters.dm.FilterGroup.prototype.isSticky = function () {
+               return this.sticky;
+       };
 }( mediaWiki ) );
index 57e618c..a6a22ef 100644 (file)
                return result;
        };
 
+       /**
+        * Get a parameter representation of all sticky parameters
+        *
+        * @return {Object} Sticky parameter values
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.getStickyParams = function () {
+               var result = {};
+
+               $.each( this.groups, function ( name, model ) {
+                       if ( model.isSticky() ) {
+                               $.extend( true, result, model.getDefaultParams() );
+                       }
+               } );
+
+               return result;
+       };
+
+       /**
+        * Get a filter representation of all sticky parameters
+        *
+        * @return {Object} Sticky filters values
+        */
+       mw.rcfilters.dm.FiltersViewModel.prototype.getStickyFiltersState = function () {
+               var result = {};
+
+               $.each( this.groups, function ( name, model ) {
+                       if ( model.isSticky() ) {
+                               $.extend( true, result, model.getSelectedState() );
+                       }
+               } );
+
+               return result;
+       };
+
        /**
         * Analyze the groups and their filters and output an object representing
         * the state of the parameters they represent.
index 54a4dbe..aa82e21 100644 (file)
@@ -32,7 +32,6 @@
                this.namePrefix = config.namePrefix || 'item_';
                this.name = this.namePrefix + param;
 
-               this.useDefaultAsBaseValue = !!config.useDefaultAsBaseValue;
                this.label = config.label || this.name;
                this.labelPrefixKey = config.labelPrefixKey;
                this.description = config.description || '';
                return this.identifiers;
        };
 
-       /**
-        * Check whether the item uses its default state as a base value
-        *
-        * @return {boolean} Use default as base value
-        */
-       mw.rcfilters.dm.ItemModel.prototype.isUsingDefaultAsBaseValue = function () {
-               return this.useDefaultAsBaseValue;
-       };
-
        /**
         * Toggle the highlight feature on and off for this filter.
         * It only works if highlight is supported for this filter.
index d6dda1e..a7f3d23 100644 (file)
         *  the above structure.
         * @param {Object} [baseState] An object representing the base state
         *  so we can normalize the data
+        * @param {string[]} [ignoreFilters] Filters to ignore and remove from
+        *  the data
         * @fires initialize
         */
-       mw.rcfilters.dm.SavedQueriesModel.prototype.initialize = function ( savedQueries, baseState ) {
+       mw.rcfilters.dm.SavedQueriesModel.prototype.initialize = function ( savedQueries, baseState, ignoreFilters ) {
                var items = [],
                        defaultItem = null;
 
                savedQueries = savedQueries || {};
+               ignoreFilters = ignoreFilters || {};
 
                this.baseState = baseState;
 
                        // for existing users, who are only betalabs users at the moment.
                        normalizedData.highlights.highlight = !!Number( normalizedData.highlights.highlight );
 
+                       // Backwards-compat fix: Remove sticky parameters from the 'ignoreFilters' list
+                       ignoreFilters.forEach( function ( name ) {
+                               delete normalizedData.filters[ name ];
+                       } );
+
                        item = new mw.rcfilters.dm.SavedQueryItemModel(
                                id,
                                obj.label,
index 8cee0a8..73ff165 100644 (file)
@@ -32,7 +32,7 @@
         * @param {Object} [tagList] Tag definition
         */
        mw.rcfilters.Controller.prototype.initialize = function ( filterStructure, namespaceStructure, tagList ) {
-               var parsedSavedQueries,
+               var parsedSavedQueries, limitDefault,
                        controller = this,
                        views = {},
                        items = [],
                        };
                }
 
+               // Convert the default from the old preference
+               // since the limit preference actually affects more
+               // than just the RecentChanges page
+               limitDefault = Number( mw.user.options.get( 'rcfilters-rclimit', mw.user.options.get( 'rclimit', '50' ) ) );
+
                // Add parameter range operations
                views.range = {
                        groups: [
                                        allowArbitrary: true,
                                        validate: $.isNumeric,
                                        sortFunc: function ( a, b ) { return Number( a.name ) - Number( b.name ); },
-                                       'default': mw.user.options.get( 'rclimit' ),
+                                       'default': String( limitDefault ),
+                                       isSticky: true,
                                        filters: [ 50, 100, 250, 500 ].map( function ( num ) {
                                                return controller._createFilterDataFromNumber( num, num );
                                        } )
                                                        ( Number( i ) * 24 ).toFixed( 2 ) :
                                                        Number( i );
                                        },
-                                       'default': mw.user.options.get( 'rcdays' ),
+                                       'default': mw.user.options.get( 'rcdays', '30' ),
+                                       isSticky: true,
                                        filters: [
                                                // Hours (1, 2, 6, 12)
                                                0.04166, 0.0833, 0.25, 0.5,
                // can normalize them per each query item
                this.savedQueriesModel.initialize(
                        parsedSavedQueries,
-                       this._getBaseFilterState()
+                       this._getBaseFilterState(),
+                       // This is for backwards compatibility - delete all sticky filter states
+                       Object.keys( this.filtersModel.getStickyFiltersState() )
                );
 
                // Check whether we need to load defaults.
         */
        mw.rcfilters.Controller.prototype.resetToDefaults = function () {
                this.uriProcessor.updateModelBasedOnQuery( this._getDefaultParams() );
+
                this.updateChangesList();
        };
 
        mw.rcfilters.Controller.prototype.saveCurrentQuery = function ( label, setAsDefault ) {
                var queryID,
                        highlightedItems = {},
-                       highlightEnabled = this.filtersModel.isHighlightEnabled();
+                       highlightEnabled = this.filtersModel.isHighlightEnabled(),
+                       selectedState = this.filtersModel.getSelectedState();
 
                // Prepare highlights
                this.filtersModel.getHighlightedItems().forEach( function ( item ) {
                // These are filter states; highlight is stored as boolean
                highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
 
+               // Delete all sticky filters
+               this._deleteStickyValuesFromFilterState( selectedState );
+
                // Add item
                queryID = this.savedQueriesModel.addNewQuery(
                        label || mw.msg( 'rcfilters-savedqueries-defaultlabel' ),
                        {
-                               filters: this.filtersModel.getSelectedState(),
+                               filters: selectedState,
                                highlights: highlightedItems,
                                invert: this.filtersModel.areNamespacesInverted()
                        }
                        highlights.highlight = highlights.highlights || highlights.highlight;
 
                        // Update model state from filters
-                       this.filtersModel.toggleFiltersSelected( data.filters );
+                       this.filtersModel.toggleFiltersSelected(
+                               // Merge filters with sticky values
+                               $.extend( true, {}, data.filters, this.filtersModel.getStickyFiltersState() )
+                       );
 
                        // Update namespace inverted property
                        this.filtersModel.toggleInvertedNamespaces( !!Number( data.invert ) );
         * @return {boolean} Query exists
         */
        mw.rcfilters.Controller.prototype.findQueryMatchingCurrentState = function () {
-               var highlightedItems = {};
+               var highlightedItems = {},
+                       selectedState = this.filtersModel.getSelectedState();
 
                // Prepare highlights of the current query
                this.filtersModel.getItemsSupportingHighlights().forEach( function ( item ) {
                } );
                highlightedItems.highlight = this.filtersModel.isHighlightEnabled();
 
+               // Remove sticky filters
+               this._deleteStickyValuesFromFilterState( selectedState );
+
                return this.savedQueriesModel.findMatchingQuery(
                        {
-                               filters: this.filtersModel.getSelectedState(),
+                               filters: selectedState,
                                highlights: highlightedItems,
                                invert: this.filtersModel.areNamespacesInverted()
                        }
                );
        };
 
+       /**
+        * Delete sticky filters from given object
+        *
+        * @param {Object} filterState Filter state
+        */
+       mw.rcfilters.Controller.prototype._deleteStickyValuesFromFilterState = function ( filterState ) {
+               // Remove sticky filters
+               $.each( this.filtersModel.getStickyFiltersState(), function ( filterName ) {
+                       delete filterState[ filterName ];
+               } );
+       };
+
        /**
         * Get an object representing the base state of parameters
         * and highlights.
                mw.user.options.set( 'rcfilters-saved-queries', stringified );
        };
 
+       /**
+        * Update sticky preferences with current model state
+        */
+       mw.rcfilters.Controller.prototype.updateStickyPreferences = function () {
+               // Update default sticky values with selected, whether they came from
+               // the initial defaults or from the URL value that is being normalized
+               this.updateDaysDefault( this.filtersModel.getGroup( 'days' ).getSelectedItems()[ 0 ].getParamName() );
+               this.updateLimitDefault( this.filtersModel.getGroup( 'limit' ).getSelectedItems()[ 0 ].getParamName() );
+       };
+
+       /**
+        * Update the limit default value
+        *
+        * @param {number} newValue New value
+        */
+       mw.rcfilters.Controller.prototype.updateLimitDefault = function ( newValue ) {
+               if ( !$.isNumeric( newValue ) ) {
+                       return;
+               }
+
+               newValue = Number( newValue );
+
+               if ( mw.user.options.get( 'rcfilters-rclimit' ) !== newValue ) {
+                       // Save the preference
+                       new mw.Api().saveOption( 'rcfilters-rclimit', newValue );
+                       // Update the preference for this session
+                       mw.user.options.set( 'rcfilters-rclimit', newValue );
+               }
+       };
+
+       /**
+        * Update the days default value
+        *
+        * @param {number} newValue New value
+        */
+       mw.rcfilters.Controller.prototype.updateDaysDefault = function ( newValue ) {
+               if ( !$.isNumeric( newValue ) ) {
+                       return;
+               }
+
+               newValue = Number( newValue );
+
+               if ( mw.user.options.get( 'rcdays' ) !== newValue ) {
+                       // Save the preference
+                       new mw.Api().saveOption( 'rcdays', newValue );
+                       // Update the preference for this session
+                       mw.user.options.set( 'rcdays', newValue );
+               }
+       };
+
        /**
         * Synchronize the URL with the current state of the filters
         * without adding an history entry.
 
                this.uriProcessor.updateModelBasedOnQuery( new mw.Uri().query );
 
+               // Update the sticky preferences, in case we received a value
+               // from the URL
+               this.updateStickyPreferences();
+
                // Only update and fetch new results if it is requested
                if ( fetchChangesList ) {
                        this.updateChangesList();
                        data = defaultSavedQueryItem.getData();
 
                        queryHighlights = data.highlights || {};
-                       savedParams = this.filtersModel.getParametersFromFilters( data.filters || {} );
+                       savedParams = this.filtersModel.getParametersFromFilters(
+                               // Merge filters with sticky values
+                               $.extend( true, {}, data.filters, this.filtersModel.getStickyFiltersState() )
+                       );
 
                        // Translate highlights to parameters
                        savedHighlights.highlight = String( Number( queryHighlights.highlight ) );
         */
        mw.rcfilters.Controller.prototype._fetchChangesList = function () {
                var uri = this._getUpdatedUri(),
+                       stickyParams = this.filtersModel.getStickyParams(),
                        requestId = ++this.requestCounter,
                        latestRequest = function () {
                                return requestId === this.requestCounter;
                        }.bind( this );
 
+               // Sticky parameters override the URL params
+               // this is to make sure that whether we represent
+               // the sticky params in the URL or not (they may
+               // be normalized out) the sticky parameters are
+               // always being sent to the server with their
+               // current/default values
+               uri.extend( stickyParams );
+
                return $.ajax( uri.toString(), { contentType: 'html' } )
                        .then(
                                // Success
index b4ea8af..ba61ba9 100644 (file)
         * @return {Object} Empty parameter state
         */
        mw.rcfilters.UriProcessor.prototype._getEmptyParameterState = function () {
-               return this.emptyParameterState;
+               // Override empty parameter state with the sticky parameter values
+               return $.extend( true, {}, this.emptyParameterState, this.filtersModel.getStickyParams() );
        };
 
        /**
index b85a89f..959d4ad 100644 (file)
         * @param {string} filterName Chosen filter name
         */
        mw.rcfilters.ui.ChangesLimitButtonWidget.prototype.onPopupLimit = function ( filterName ) {
+               var item = this.limitGroupModel.getItemByName( filterName );
+
                this.controller.toggleFilterSelect( filterName, true );
+               this.controller.updateLimitDefault( item.getParamName() );
                this.button.popup.toggle( false );
        };
 
index 647e42d..84dbe39 100644 (file)
         * @param {string} filterName Chosen filter name
         */
        mw.rcfilters.ui.DateButtonWidget.prototype.onPopupDays = function ( filterName ) {
+               var item = this.daysGroupModel.getItemByName( filterName );
+
                this.controller.toggleFilterSelect( filterName, true );
+               this.controller.updateDaysDefault( item.getParamName() );
                this.button.popup.toggle( false );
        };
 
index da7bafd..58e4d29 100644 (file)
                }, {
                        name: 'group6',
                        type: 'boolean',
+                       isSticky: true,
                        filters: [
-                               { name: 'group6option1', label: 'group6option1-label', description: 'group5option1-desc' },
-                               { name: 'group6option2', label: 'group6option2-label', description: 'group5option2-desc', default: true, useDefaultAsBaseValue: true },
-                               { name: 'group6option3', label: 'group6option3-label', description: 'group5option3-desc', default: true }
+                               { name: 'group6option1', label: 'group6option1-label', description: 'group6option1-desc' },
+                               { name: 'group6option2', label: 'group6option2-label', description: 'group6option2-desc', default: true },
+                               { name: 'group6option3', label: 'group6option3-label', description: 'group6option3-desc', default: true }
+                       ]
+               }, {
+                       name: 'group7',
+                       type: 'single_option',
+                       isSticky: true,
+                       default: 'group7option2',
+                       filters: [
+                               { name: 'group7option1', label: 'group7option1-label', description: 'group7option1-desc' },
+                               { name: 'group7option2', label: 'group7option2-label', description: 'group7option2-desc' },
+                               { name: 'group7option3', label: 'group7option3-label', description: 'group7option3-desc' }
                        ]
                } ],
                viewsDefinition = {
                        group6option1: '0',
                        group6option2: '1',
                        group6option3: '1',
+                       group7: 'group7option2',
                        namespace: ''
                },
                baseParamRepresentation = {
                        group5: 'option1',
                        group6option1: '0',
                        group6option2: '1',
-                       group6option3: '0',
+                       group6option3: '1',
+                       group7: 'group7option2',
                        namespace: ''
                },
                baseFilterRepresentation = {
                        group5__option3: false,
                        group6__group6option1: false,
                        group6__group6option2: true,
-                       group6__group6option3: false,
+                       group6__group6option3: true,
+                       group7__group7option1: false,
+                       group7__group7option2: true,
+                       group7__group7option3: false,
                        namespace__0: false,
                        namespace__1: false,
                        namespace__2: false,
                        group5__option3: { selected: false, conflicted: false, included: false },
                        group6__group6option1: { selected: false, conflicted: false, included: false },
                        group6__group6option2: { selected: true, conflicted: false, included: false },
-                       group6__group6option3: { selected: false, conflicted: false, included: false },
+                       group6__group6option3: { selected: true, conflicted: false, included: false },
+                       group7__group7option1: { selected: false, conflicted: false, included: false },
+                       group7__group7option2: { selected: true, conflicted: false, included: false },
+                       group7__group7option3: { selected: false, conflicted: false, included: false },
                        namespace__0: { selected: false, conflicted: false, included: false },
                        namespace__1: { selected: false, conflicted: false, included: false },
                        namespace__2: { selected: false, conflicted: false, included: false },
                        defaultParameters,
                        'Default parameters are stored properly per filter and group'
                );
+
+               // Change sticky filter
+               model.toggleFiltersSelected( {
+                       group7__group7option1: true
+               } );
+
+               // Make sure defaults have changed
+               assert.deepEqual(
+                       model.getDefaultParams(),
+                       $.extend( true, {}, defaultParameters, {
+                               group7: 'group7option1'
+                       } ),
+                       'Default parameters are stored properly per filter and group'
+               );
        } );
 
        QUnit.test( 'Finding matching filters', function ( assert ) {