From 6d0abc04de38a1c03848bc8dd997fc17c5c836ee Mon Sep 17 00:00:00 2001 From: Moriel Schottlender Date: Fri, 10 Mar 2017 18:10:26 -0800 Subject: [PATCH] RCFilters UI: Separate name from paramName in filters Represent filters, internally, with a unique name comprised of their group and param name, and leave a reference to the parameter (or value) they represent so the state can be rebuilt. Change-Id: I08b69a09463afa1cf08e1cdbf7aaec8dfc16fd2e --- .../dm/mw.rcfilters.dm.FilterGroup.js | 45 ++ .../dm/mw.rcfilters.dm.FilterItem.js | 25 +- .../dm/mw.rcfilters.dm.FiltersViewModel.js | 145 +++--- .../mediawiki.rcfilters/dm.FilterItem.test.js | 17 +- .../dm.FiltersViewModel.test.js | 450 +++++++++--------- 5 files changed, 381 insertions(+), 301 deletions(-) diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js index 22323e8498..1070b982b4 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterGroup.js @@ -207,6 +207,42 @@ ); }; + /** + * Get the parameter representation from this group + * + * @return {Object} Parameter representation + */ + mw.rcfilters.dm.FilterGroup.prototype.getParamRepresentation = function () { + var i, values, + result = {}, + filterItems = this.getItems(); + + if ( this.getType() === '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 + + // Go over the items and define the correct values + for ( i = 0; i < filterItems.length; i++ ) { + result[ filterItems[ i ].getParamName() ] = this.areAnySelected() ? + Number( !filterItems[ i ].isSelected() ) : 0; + } + + } else if ( this.getType() === 'string_options' ) { + values = []; + for ( i = 0; i < filterItems.length; i++ ) { + if ( filterItems[ i ].isSelected() ) { + values.push( filterItems[ i ].getParamName() ); + } + } + + result[ this.getName() ] = ( values.length === filterItems.length ) ? + 'all' : values.join( this.getSeparator() ); + } + + return result; + }; + /** * Get group type * @@ -216,6 +252,15 @@ return this.type; }; + /** + * Get the prefix used for the filter names inside this group + * + * @return {string} Group prefix + */ + mw.rcfilters.dm.FilterGroup.prototype.getNamePrefix = function () { + return this.getName() + '__'; + }; + /** * Get group's title * diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js index f162528587..852b810c43 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FilterItem.js @@ -5,7 +5,7 @@ * @mixins OO.EventEmitter * * @constructor - * @param {string} name Filter name + * @param {string} param Filter param name * @param {mw.rcfilters.dm.FilterGroup} groupModel Filter group model * @param {Object} config Configuration object * @cfg {string} [group] The group this item belongs to @@ -19,14 +19,15 @@ * @cfg {Object} [conflicts] Defines the conflicts for this filter * @cfg {string} [cssClass] The class identifying the results that match this filter */ - mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( name, groupModel, config ) { + mw.rcfilters.dm.FilterItem = function MwRcfiltersDmFilterItem( param, groupModel, config ) { config = config || {}; // Mixin constructor OO.EventEmitter.call( this ); - this.name = name; + this.param = param; this.groupModel = groupModel; + this.name = this.groupModel.getNamePrefix() + param; this.label = config.label || this.name; this.description = config.description; @@ -86,6 +87,15 @@ return this.name; }; + /** + * Get the param name or value of this filter + * + * @return {string} Filter param name + */ + mw.rcfilters.dm.FilterItem.prototype.getParamName = function () { + return this.param; + }; + /** * Get the model of the group this filter belongs to * @@ -230,6 +240,15 @@ this.superset = superset || []; }; + /** + * Set filter subset + * + * @param {string[]} subset Filter subset + */ + mw.rcfilters.dm.FilterItem.prototype.setSubset = function ( subset ) { + this.subset = subset || []; + }; + /** * Check whether a filter exists in the subset list for this filter * 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 36fc4a71be..cf51424fe4 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js @@ -16,6 +16,7 @@ this.defaultParams = {}; this.defaultFiltersEmpty = null; this.highlightEnabled = false; + this.parameterMap = {}; // Events this.aggregate( { update: 'filterItemUpdate' } ); @@ -162,7 +163,7 @@ * @param {Array} filters Filter group definition */ mw.rcfilters.dm.FiltersViewModel.prototype.initializeFilters = function ( filters ) { - var i, filterItem, selectedFilterNames, filterConflictResult, groupConflictResult, + var i, filterItem, selectedFilterNames, filterConflictResult, groupConflictResult, subsetNames, model = this, items = [], supersetMap = {}, @@ -182,11 +183,20 @@ expandConflictDefinitions = function ( obj ) { var result = {}; - $.each( obj, function ( group, conflicts ) { - var adjustedConflicts = {}; + $.each( obj, function ( key, conflicts ) { + var filterName, + adjustedConflicts = {}; + conflicts.forEach( function ( conflict ) { if ( conflict.filter ) { - adjustedConflicts[ conflict.filter ] = conflict; + filterName = model.groups[ conflict.group ].getNamePrefix() + conflict.filter; + + // Rename + adjustedConflicts[ filterName ] = $.extend( + {}, + conflict, + { filter: filterName } + ); } else { // This conflict is for an entire group. Split it up to // represent each filter @@ -194,12 +204,16 @@ // Get the relevant group items model.groups[ conflict.group ].getItems().forEach( function ( groupItem ) { // Rebuild the conflict - adjustedConflicts[ groupItem.getName() ] = $.extend( {}, conflict, { filter: groupItem.getName() } ); + adjustedConflicts[ groupItem.getName() ] = $.extend( + {}, + conflict, + { filter: groupItem.getName() } + ); } ); } } ); - result[ group ] = adjustedConflicts; + result[ key ] = adjustedConflicts; } ); return result; @@ -236,27 +250,36 @@ group: group, label: mw.msg( data.filters[ i ].label ), description: mw.msg( data.filters[ i ].description ), - subset: data.filters[ i ].subset, cssClass: data.filters[ i ].cssClass } ); - // For convenience, we should store each filter's "supersets" -- these are - // the filters that have that item in their subset list. This will just - // make it easier to go through whether the item has any other items - // that affect it (and are selected) at any given time if ( data.filters[ i ].subset ) { + subsetNames = []; data.filters[ i ].subset.forEach( function ( subsetFilterName ) { // eslint-disable-line no-loop-func - supersetMap[ subsetFilterName ] = supersetMap[ subsetFilterName ] || []; + var subsetName = model.groups[ group ].getNamePrefix() + subsetFilterName; + // For convenience, we should store each filter's "supersets" -- these are + // the filters that have that item in their subset list. This will just + // make it easier to go through whether the item has any other items + // that affect it (and are selected) at any given time + supersetMap[ subsetName ] = supersetMap[ subsetName ] || []; addArrayElementsUnique( - supersetMap[ subsetFilterName ], + supersetMap[ subsetName ], filterItem.getName() ); + + // Translate subset param name to add the group name, so we + // get consistent naming. We know that subsets are only within + // the same group + subsetNames.push( subsetName ); } ); + + // Set translated subset + filterItem.setSubset( subsetNames ); } // Store conflicts if ( data.filters[ i ].conflicts ) { - filterConflictMap[ data.filters[ i ].name ] = data.filters[ i ].conflicts; + filterConflictMap[ filterItem.getName() ] = data.filters[ i ].conflicts; } if ( data.type === 'send_unselected_if_any' ) { @@ -301,6 +324,19 @@ } } ); + // Create a map between known parameters and their models + $.each( this.groups, function ( group, groupModel ) { + if ( groupModel.getType() === 'send_unselected_if_any' ) { + // Individual filters + groupModel.getItems().forEach( function ( filterItem ) { + model.parameterMap[ filterItem.getParamName() ] = filterItem; + } ); + } else if ( groupModel.getType() === 'string_options' ) { + // Group + model.parameterMap[ groupModel.getName() ] = groupModel; + } + } ); + // Add items to the model this.addItems( items ); @@ -401,40 +437,11 @@ * @return {Object} Parameter state object */ mw.rcfilters.dm.FiltersViewModel.prototype.getParametersFromFilters = function ( filterGroups ) { - var i, filterItems, anySelected, values, - result = {}, + var result = {}, groupItems = filterGroups || this.getFilterGroups(); $.each( groupItems, function ( group, model ) { - filterItems = model.getItems(); - - if ( model.getType() === '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 - anySelected = filterItems.some( function ( filterItem ) { - return filterItem.isSelected(); - } ); - - // Go over the items and define the correct values - for ( i = 0; i < filterItems.length; i++ ) { - result[ filterItems[ i ].getName() ] = anySelected ? - Number( !filterItems[ i ].isSelected() ) : 0; - } - } else if ( model.getType() === 'string_options' ) { - values = []; - for ( i = 0; i < filterItems.length; i++ ) { - if ( filterItems[ i ].isSelected() ) { - values.push( filterItems[ i ].getName() ); - } - } - - if ( values.length === filterItems.length ) { - result[ group ] = 'all'; - } else { - result[ group ] = values.join( model.getSeparator() ); - } - } + $.extend( result, model.getParamRepresentation() ); } ); return result; @@ -468,7 +475,7 @@ mw.rcfilters.dm.FiltersViewModel.prototype.sanitizeStringOptionGroup = function( groupName, valueArray ) { var result = [], validNames = this.getGroupFilters( groupName ).map( function ( filterItem ) { - return filterItem.getName(); + return filterItem.getParamName(); } ); if ( valueArray.indexOf( 'all' ) > -1 ) { @@ -536,7 +543,7 @@ * @return {Object} Filter state object */ mw.rcfilters.dm.FiltersViewModel.prototype.getFiltersFromParameters = function ( params ) { - var i, filterItem, + var i, groupMap = {}, model = this, base = this.getDefaultParams(), @@ -544,26 +551,26 @@ params = $.extend( {}, base, params ); + // Go over the given parameters $.each( params, function ( paramName, paramValue ) { - // Find the filter item - filterItem = model.getItemByName( paramName ); - // Ignore if no filter item exists - if ( filterItem ) { - groupMap[ filterItem.getGroupName() ] = groupMap[ filterItem.getGroupName() ] || {}; + var itemOrGroup = model.parameterMap[ paramName ]; + if ( itemOrGroup instanceof mw.rcfilters.dm.FilterItem ) { // Mark the group if it has any items that are selected - groupMap[ filterItem.getGroupName() ].hasSelected = ( - groupMap[ filterItem.getGroupName() ].hasSelected || + groupMap[ itemOrGroup.getGroupName() ] = groupMap[ itemOrGroup.getGroupName() ] || {}; + groupMap[ itemOrGroup.getGroupName() ].hasSelected = ( + groupMap[ itemOrGroup.getGroupName() ].hasSelected || !!Number( paramValue ) ); - // Add the relevant filter into the group map - groupMap[ filterItem.getGroupName() ].filters = groupMap[ filterItem.getGroupName() ].filters || []; - groupMap[ filterItem.getGroupName() ].filters.push( filterItem ); - } else if ( model.groups.hasOwnProperty( paramName ) ) { + // Add filters + groupMap[ itemOrGroup.getGroupName() ].filters = groupMap[ itemOrGroup.getGroupName() ].filters || []; + groupMap[ itemOrGroup.getGroupName() ].filters.push( itemOrGroup ); + } else if ( itemOrGroup instanceof mw.rcfilters.dm.FilterGroup ) { + groupMap[ itemOrGroup.getName() ] = groupMap[ itemOrGroup.getName() ] || {}; // 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 ].getItems() }; + groupMap[ itemOrGroup.getName() ].filters = itemOrGroup.getItems(); } } ); @@ -577,17 +584,22 @@ for ( i = 0; i < allItemsInGroup.length; i++ ) { filterItem = allItemsInGroup[ i ]; - result[ filterItem.getName() ] = data.hasSelected ? + result[ filterItem.getName() ] = groupMap[ filterItem.getGroupName() ].hasSelected ? // Flip the definition between the parameter // state and the filter state // This is what the 'toggleSelected' value of the filter is - !Number( params[ filterItem.getName() ] ) : + !Number( params[ filterItem.getParamName() ] ) : // Otherwise, there are no selected items in the // group, which means the state is false false; } } else if ( model.groups[ group ].getType() === 'string_options' ) { - paramValues = model.sanitizeStringOptionGroup( group, params[ group ].split( model.groups[ group ].getSeparator() ) ); + paramValues = model.sanitizeStringOptionGroup( + group, + params[ group ].split( + model.groups[ group ].getSeparator() + ) + ); for ( i = 0; i < allItemsInGroup.length; i++ ) { filterItem = allItemsInGroup[ i ]; @@ -602,10 +614,11 @@ // 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; + paramValues.indexOf( filterItem.getParamName() ) > -1; } } } ); + return result; }; @@ -638,7 +651,11 @@ * @param {boolean} [isSelected] Filter selected state */ mw.rcfilters.dm.FiltersViewModel.prototype.toggleFilterSelected = function ( name, isSelected ) { - this.getItemByName( name ).toggleSelected( isSelected ); + var item = this.getItemByName( name ); + + if ( item ) { + item.toggleSelected( isSelected ); + } }; /** diff --git a/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js b/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js index 3232d08a68..271648f57a 100644 --- a/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js +++ b/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FilterItem.test.js @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ ( function ( mw ) { QUnit.module( 'mediawiki.rcfilters - FilterItem' ); @@ -9,7 +10,7 @@ item = new mw.rcfilters.dm.FilterItem( 'filter1', group1 ); assert.equal( item.getName(), - 'filter1', + 'group1__filter1', 'Filter name is retained.' ); assert.equal( @@ -98,24 +99,22 @@ group1, { conflicts: { - conflict1: { group: 'group2', filter: 'conflict1' }, - conflict2: { group: 'group2', filter: 'conflict2' }, - conflict3: { group: 'group2', filter: 'conflict3' } + group2__conflict1: { group: 'group2', filter: 'group2__conflict1' }, + group2__conflict2: { group: 'group2', filter: 'group2__conflict2' }, + group2__conflict3: { group: 'group2', filter: 'group2__conflict3' } } } ); assert.deepEqual( item.getConflicts(), { - conflict1: { group: 'group2', filter: 'conflict1' }, - conflict2: { group: 'group2', filter: 'conflict2' }, - conflict3: { group: 'group2', filter: 'conflict3' } + group2__conflict1: { group: 'group2', filter: 'group2__conflict1' }, + group2__conflict2: { group: 'group2', filter: 'group2__conflict2' }, + group2__conflict3: { group: 'group2', filter: 'group2__conflict3' } }, 'Conflict information is retained.' ); assert.equal( - // TODO: Consider allowing for either a FilterItem or a filter name - // in this method, so it is consistent with the subset one item.existsInConflicts( new mw.rcfilters.dm.FilterItem( 'conflict1', group2 ) ), true, 'Specific item exists in conflicts.' 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 60a32c3c4c..405fdcf1de 100644 --- a/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js +++ b/tests/qunit/suites/resources/mediawiki.rcfilters/dm.FiltersViewModel.test.js @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ ( function ( mw, $ ) { QUnit.module( 'mediawiki.rcfilters - FiltersViewModel', QUnit.newMwEnvironment( { messages: { @@ -19,12 +20,12 @@ type: 'send_unselected_if_any', filters: [ { - name: 'group1filter1', + name: 'filter1', label: 'Group 1: Filter 1', description: 'Description of Filter 1 in Group 1' }, { - name: 'group1filter2', + name: 'filter2', label: 'Group 1: Filter 2', description: 'Description of Filter 2 in Group 1' } @@ -35,12 +36,12 @@ type: 'send_unselected_if_any', filters: [ { - name: 'group2filter1', + name: 'filter1', label: 'Group 2: Filter 1', description: 'Description of Filter 1 in Group 2' }, { - name: 'group2filter2', + name: 'filter2', label: 'Group 2: Filter 2', description: 'Description of Filter 2 in Group 2' } @@ -51,12 +52,12 @@ type: 'string_options', filters: [ { - name: 'group3filter1', + name: 'filter1', label: 'Group 3: Filter 1', description: 'Description of Filter 1 in Group 3' }, { - name: 'group3filter2', + name: 'filter2', label: 'Group 3: Filter 2', description: 'Description of Filter 2 in Group 3' } @@ -67,42 +68,42 @@ model.initializeFilters( definition ); assert.ok( - 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( 'group3filter1' ) instanceof mw.rcfilters.dm.FilterItem && - model.getItemByName( 'group3filter2' ) instanceof mw.rcfilters.dm.FilterItem, + model.getItemByName( 'group1__filter1' ) instanceof mw.rcfilters.dm.FilterItem && + model.getItemByName( 'group1__filter2' ) instanceof mw.rcfilters.dm.FilterItem && + model.getItemByName( 'group2__filter1' ) instanceof mw.rcfilters.dm.FilterItem && + model.getItemByName( 'group2__filter2' ) instanceof mw.rcfilters.dm.FilterItem && + model.getItemByName( 'group3__filter1' ) instanceof mw.rcfilters.dm.FilterItem && + model.getItemByName( 'group3__filter2' ) instanceof mw.rcfilters.dm.FilterItem, 'Filters instantiated and stored correctly' ); assert.deepEqual( model.getSelectedState(), { - group1filter1: false, - group1filter2: false, - group2filter1: false, - group2filter2: false, - group3filter1: false, - group3filter2: false + group1__filter1: false, + group1__filter2: false, + group2__filter1: false, + group2__filter2: false, + group3__filter1: false, + group3__filter2: false }, 'Initial state of filters' ); model.toggleFiltersSelected( { - group1filter1: true, - group2filter2: true, - group3filter1: true + group1__filter1: true, + group2__filter2: true, + group3__filter1: true } ); assert.deepEqual( model.getSelectedState(), { - group1filter1: true, - group1filter2: false, - group2filter1: false, - group2filter2: true, - group3filter1: true, - group3filter2: false + group1__filter1: true, + group1__filter2: false, + group2__filter1: false, + group2__filter2: true, + group3__filter1: true, + group3__filter2: false }, 'Updating filter states correctly' ); @@ -116,12 +117,12 @@ type: 'send_unselected_if_any', filters: [ { - name: 'group1filter1', + name: 'filter1', label: 'group1filter1-label', description: 'group1filter1-desc' }, { - name: 'group1filter2', + name: 'filter2', label: 'group1filter2-label', description: 'group1filter2-desc' } @@ -132,12 +133,12 @@ type: 'send_unselected_if_any', filters: [ { - name: 'group2filter1', + name: 'filter1', label: 'group2filter1-label', description: 'group2filter1-desc' }, { - name: 'group2filter2', + name: 'filter2', label: 'group2filter2-label', description: 'group2filter2-desc' } @@ -147,24 +148,24 @@ { query: 'group', expectedMatches: { - group1: [ 'group1filter1', 'group1filter2' ], - group2: [ 'group2filter1' ] + group1: [ 'group1__filter1', 'group1__filter2' ], + group2: [ 'group2__filter1' ] }, reason: 'Finds filters starting with the query string' }, { query: 'filter 2 in group', expectedMatches: { - group1: [ 'group1filter2' ], - group2: [ 'group2filter2' ] + group1: [ 'group1__filter2' ], + group2: [ 'group2__filter2' ] }, reason: 'Finds filters containing the query string in their description' }, { query: 'title', expectedMatches: { - group1: [ 'group1filter1', 'group1filter2' ], - group2: [ 'group2filter1', 'group2filter2' ] + group1: [ 'group1__filter1', 'group1__filter2' ], + group2: [ 'group2__filter1', 'group2__filter2' ] }, reason: 'Finds filters containing the query string in their group title' } @@ -285,12 +286,12 @@ // Select 1 filter model.toggleFiltersSelected( { - hidefilter1: true, - hidefilter2: false, - hidefilter3: false, - hidefilter4: false, - hidefilter5: false, - hidefilter6: false + group1__hidefilter1: true, + group1__hidefilter2: false, + group1__hidefilter3: false, + group2__hidefilter4: false, + group2__hidefilter5: false, + group2__hidefilter6: false } ); // Only one filter in one group assert.deepEqual( @@ -311,12 +312,12 @@ // Select 2 filters model.toggleFiltersSelected( { - hidefilter1: true, - hidefilter2: true, - hidefilter3: false, - hidefilter4: false, - hidefilter5: false, - hidefilter6: false + group1__hidefilter1: true, + group1__hidefilter2: true, + group1__hidefilter3: false, + group2__hidefilter4: false, + group2__hidefilter5: false, + group2__hidefilter6: false } ); // Two selected filters in one group assert.deepEqual( @@ -337,12 +338,12 @@ // Select 3 filters model.toggleFiltersSelected( { - hidefilter1: true, - hidefilter2: true, - hidefilter3: true, - hidefilter4: false, - hidefilter5: false, - hidefilter6: false + group1__hidefilter1: true, + group1__hidefilter2: true, + group1__hidefilter3: true, + group2__hidefilter4: false, + group2__hidefilter5: false, + group2__hidefilter6: false } ); // All filters of the group are selected == this is the same as not selecting any assert.deepEqual( @@ -363,9 +364,9 @@ // Select 1 filter from string_options model.toggleFiltersSelected( { - filter7: true, - filter8: false, - filter9: false + group3__filter7: true, + group3__filter8: false, + group3__filter9: false } ); // All filters of the group are selected == this is the same as not selecting any assert.deepEqual( @@ -386,9 +387,9 @@ // Select 2 filters from string_options model.toggleFiltersSelected( { - filter7: true, - filter8: true, - filter9: false + group3__filter7: true, + group3__filter8: true, + group3__filter9: false } ); // All filters of the group are selected == this is the same as not selecting any assert.deepEqual( @@ -409,9 +410,9 @@ // Select 3 filters from string_options model.toggleFiltersSelected( { - filter7: true, - filter8: true, - filter9: true + group3__filter7: true, + group3__filter8: true, + group3__filter9: true } ); // All filters of the group are selected == this is the same as not selecting any assert.deepEqual( @@ -505,16 +506,16 @@ } ], defaultFilterRepresentation = { // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters - hidefilter1: false, - hidefilter2: true, - hidefilter3: false, - hidefilter4: true, - hidefilter5: false, - hidefilter6: true, + group1__hidefilter1: false, + group1__hidefilter2: true, + group1__hidefilter3: false, + group2__hidefilter4: true, + group2__hidefilter5: false, + group2__hidefilter6: true, // Group 3, "string_options", default values correspond to parameters and filters - filter7: false, - filter8: true, - filter9: false + group3__filter7: false, + group3__filter8: true, + group3__filter9: false }, model = new mw.rcfilters.dm.FiltersViewModel(); @@ -532,9 +533,9 @@ hidefilter2: '1' } ), $.extend( {}, defaultFilterRepresentation, { - hidefilter1: false, // The text is "show filter 1" - hidefilter2: false, // The text is "show filter 2" - hidefilter3: false // The text is "show filter 3" + group1__hidefilter1: false, // The text is "show filter 1" + group1__hidefilter2: false, // The text is "show filter 2" + group1__hidefilter3: false // The text is "show filter 3" } ), 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)' ); @@ -546,9 +547,9 @@ hidefilter3: '1' } ), $.extend( {}, defaultFilterRepresentation, { - hidefilter1: false, // The text is "show filter 1" - hidefilter2: false, // The text is "show filter 2" - hidefilter3: false // The text is "show filter 3" + group1__hidefilter1: false, // The text is "show filter 1" + group1__hidefilter2: false, // The text is "show filter 2" + group1__hidefilter3: false // The text is "show filter 3" } ), 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface' ); @@ -579,8 +580,8 @@ assert.deepEqual( model.getSelectedState(), $.extend( {}, defaultFilterRepresentation, { - hidefilter5: false, - hidefilter6: false + group2__hidefilter5: false, + group2__hidefilter6: false } ), 'getFiltersFromParameters does not care about previous or existing state.' ); @@ -616,9 +617,9 @@ assert.deepEqual( model.getSelectedState(), $.extend( {}, defaultFilterRepresentation, { - filter7: true, - filter8: false, - filter9: false + group3__filter7: true, + group3__filter8: false, + group3__filter9: false } ), 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked' ); @@ -631,9 +632,9 @@ assert.deepEqual( model.getSelectedState(), $.extend( {}, defaultFilterRepresentation, { - filter7: true, - filter8: true, - filter9: false + group3__filter7: true, + group3__filter8: true, + group3__filter9: false } ), 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked' ); @@ -646,9 +647,9 @@ assert.deepEqual( model.getSelectedState(), $.extend( {}, defaultFilterRepresentation, { - filter7: false, - filter8: false, - filter9: false + group3__filter7: false, + group3__filter8: false, + group3__filter9: false } ), 'A \'string_options\' parameter containing all values, results in all filters of the group as unchecked.' ); @@ -661,9 +662,9 @@ assert.deepEqual( model.getSelectedState(), $.extend( {}, defaultFilterRepresentation, { - filter7: false, - filter8: false, - filter9: false + group3__filter7: false, + group3__filter8: false, + group3__filter9: false } ), 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as unchecked.' ); @@ -676,9 +677,9 @@ assert.deepEqual( model.getSelectedState(), $.extend( {}, defaultFilterRepresentation, { - filter7: true, - filter8: false, - filter9: true + group3__filter7: true, + group3__filter8: false, + group3__filter9: true } ), 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.' ); @@ -779,12 +780,12 @@ } ], defaultFilterRepresentation = { // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters - hidefilter1: false, - hidefilter2: true, - hidefilter3: false, - hidefilter4: true, - hidefilter5: false, - hidefilter6: true + group1__hidefilter1: false, + group1__hidefilter2: true, + group1__hidefilter3: false, + group2__hidefilter4: true, + group2__hidefilter5: false, + group2__hidefilter6: true }, model = new mw.rcfilters.dm.FiltersViewModel(); @@ -793,19 +794,19 @@ assert.deepEqual( model.getSelectedState(), { - hidefilter1: false, - hidefilter2: false, - hidefilter3: false, - hidefilter4: false, - hidefilter5: false, - hidefilter6: false + group1__hidefilter1: false, + group1__hidefilter2: false, + group1__hidefilter3: false, + group2__hidefilter4: false, + group2__hidefilter5: false, + group2__hidefilter6: false }, 'Initial state: default filters are not selected (controller selects defaults explicitly).' ); model.toggleFiltersSelected( { - hidefilter1: false, - hidefilter3: false + group1__hidefilter1: false, + group1__hidefilter3: false } ); model.setFiltersToDefaults(); @@ -857,62 +858,62 @@ ] } ], baseFullState = { - filter1: { selected: false, conflicted: false, included: false }, - filter2: { selected: false, conflicted: false, included: false }, - filter3: { selected: false, conflicted: false, included: false } + group1__filter1: { selected: false, conflicted: false, included: false }, + group1__filter2: { selected: false, conflicted: false, included: false }, + group1__filter3: { selected: false, conflicted: false, included: false } }, model = new mw.rcfilters.dm.FiltersViewModel(); model.initializeFilters( definition ); // Select a filter that has subset with another filter model.toggleFiltersSelected( { - filter1: true + group1__filter1: true } ); - model.reassessFilterInteractions( model.getItemByName( 'filter1' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter1: { selected: true }, - filter2: { included: true }, - filter3: { included: true } + group1__filter1: { selected: true }, + group1__filter2: { included: true }, + group1__filter3: { included: true } } ), 'Filters with subsets are represented in the model.' ); // Select another filter that has a subset with the same previous filter model.toggleFiltersSelected( { - filter2: true + group1__filter2: true } ); model.reassessFilterInteractions( model.getItemByName( 'filter2' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter1: { selected: true }, - filter2: { selected: true, included: true }, - filter3: { included: true } + group1__filter1: { selected: true }, + group1__filter2: { selected: true, included: true }, + group1__filter3: { included: true } } ), 'Filters that have multiple subsets are represented.' ); - // Remove one filter (but leave the other) that affects filter2 + // Remove one filter (but leave the other) that affects filter3 model.toggleFiltersSelected( { - filter1: false + group1__filter1: false } ); - model.reassessFilterInteractions( model.getItemByName( 'filter1' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter2: { selected: true, included: false }, - filter3: { included: true } + group1__filter2: { selected: true, included: false }, + group1__filter3: { included: true } } ), 'Removing a filter only un-includes its subset if there is no other filter affecting.' ); model.toggleFiltersSelected( { - filter2: false + group1__filter2: false } ); - model.reassessFilterInteractions( model.getItemByName( 'filter2' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) ); assert.deepEqual( model.getFullState(), baseFullState, @@ -957,21 +958,21 @@ }, getCurrentItemsMutedState = function () { return { - filter1: isCapsuleItemMuted( 'filter1' ), - filter2: isCapsuleItemMuted( 'filter2' ), - filter3: isCapsuleItemMuted( 'filter3' ), - filter4: isCapsuleItemMuted( 'filter4' ), - filter5: isCapsuleItemMuted( 'filter5' ), - filter6: isCapsuleItemMuted( 'filter6' ) + group1__filter1: isCapsuleItemMuted( 'group1__filter1' ), + group1__filter2: isCapsuleItemMuted( 'group1__filter2' ), + group1__filter3: isCapsuleItemMuted( 'group1__filter3' ), + group2__filter4: isCapsuleItemMuted( 'group2__filter4' ), + group2__filter5: isCapsuleItemMuted( 'group2__filter5' ), + group2__filter6: isCapsuleItemMuted( 'group2__filter6' ) }; }, baseMuteState = { - filter1: false, - filter2: false, - filter3: false, - filter4: false, - filter5: false, - filter6: false + group1__filter1: false, + group1__filter2: false, + group1__filter3: false, + group2__filter4: false, + group2__filter5: false, + group2__filter6: false }; model.initializeFilters( definition ); @@ -985,10 +986,10 @@ // Select most (but not all) items in each group model.toggleFiltersSelected( { - filter1: true, - filter2: true, - filter4: true, - filter5: true + group1__filter1: true, + group1__filter2: true, + group2__filter4: true, + group2__filter5: true } ); // Both groups have multiple (but not all) items selected, all items are non-muted @@ -1000,40 +1001,40 @@ // Select all items in 'fullCoverage' group (group2) model.toggleFiltersSelected( { - filter6: true + group2__filter6: true } ); // Group2 (full coverage) has all items selected, all its items are muted assert.deepEqual( getCurrentItemsMutedState(), $.extend( {}, baseMuteState, { - filter4: true, - filter5: true, - filter6: true + group2__filter4: true, + group2__filter5: true, + group2__filter6: true } ), 'All items in \'full coverage\' group are selected - all items in the group are muted' ); // Select all items in non 'fullCoverage' group (group1) model.toggleFiltersSelected( { - filter3: true + group1__filter3: true } ); // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage) assert.deepEqual( getCurrentItemsMutedState(), $.extend( {}, baseMuteState, { - filter4: true, - filter5: true, - filter6: true + group2__filter4: true, + group2__filter5: true, + group2__filter6: true } ), 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted' ); // Uncheck an item from each group model.toggleFiltersSelected( { - filter3: false, - filter5: false + group1__filter3: false, + group2__filter5: false } ); assert.deepEqual( getCurrentItemsMutedState(), @@ -1091,12 +1092,12 @@ ] } ], baseFullState = { - filter1: { selected: false, conflicted: false, included: false }, - filter2: { selected: false, conflicted: false, included: false }, - filter3: { selected: false, conflicted: false, included: false }, - filter4: { selected: false, conflicted: false, included: false }, - filter5: { selected: false, conflicted: false, included: false }, - filter6: { selected: false, conflicted: false, included: false } + group1__filter1: { selected: false, conflicted: false, included: false }, + group1__filter2: { selected: false, conflicted: false, included: false }, + group1__filter3: { selected: false, conflicted: false, included: false }, + group2__filter4: { selected: false, conflicted: false, included: false }, + group2__filter5: { selected: false, conflicted: false, included: false }, + group2__filter6: { selected: false, conflicted: false, included: false } }, model = new mw.rcfilters.dm.FiltersViewModel(); @@ -1110,35 +1111,35 @@ // Select a filter that has a conflict with an entire group model.toggleFiltersSelected( { - filter1: true // conflicts: entire of group 2 ( filter4, filter5, filter6) + group1__filter1: true // conflicts: entire of group 2 ( filter4, filter5, filter6) } ); - model.reassessFilterInteractions( model.getItemByName( 'filter1' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter1: { selected: true }, - filter4: { conflicted: true }, - filter5: { conflicted: true }, - filter6: { conflicted: true } + group1__filter1: { selected: true }, + group2__filter4: { conflicted: true }, + group2__filter5: { conflicted: true }, + group2__filter6: { conflicted: true } } ), 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".' ); // Select one of the conflicts (both filters are now conflicted and selected) model.toggleFiltersSelected( { - filter4: true // conflicts: filter 1 + group2__filter4: true // conflicts: filter 1 } ); - model.reassessFilterInteractions( model.getItemByName( 'filter4' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group2__filter4' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter1: { selected: true, conflicted: true }, - filter4: { selected: true, conflicted: true }, - filter5: { conflicted: true }, - filter6: { conflicted: true } + group1__filter1: { selected: true, conflicted: true }, + group2__filter4: { selected: true, conflicted: true }, + group2__filter5: { conflicted: true }, + group2__filter6: { conflicted: true } } ), 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.' ); @@ -1149,55 +1150,54 @@ // Select a filter that has a conflict with a specific filter model.toggleFiltersSelected( { - filter2: true // conflicts: filter6 + group1__filter2: true // conflicts: filter6 } ); - - model.reassessFilterInteractions( model.getItemByName( 'filter2' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter2: { selected: true }, - filter6: { conflicted: true } + group1__filter2: { selected: true }, + group2__filter6: { conflicted: true } } ), 'Selecting a filter that conflicts with another filter sets the other as "conflicted".' ); // Select the conflicting filter model.toggleFiltersSelected( { - filter6: true // conflicts: filter2 + group2__filter6: true // conflicts: filter2 } ); - model.reassessFilterInteractions( model.getItemByName( 'filter6' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group2__filter6' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter2: { selected: true, conflicted: true }, - filter6: { selected: true, conflicted: true }, + group1__filter2: { selected: true, conflicted: true }, + group2__filter6: { selected: true, conflicted: true }, // This is added to the conflicts because filter6 is part of group2, // who is in conflict with filter1; note that filter2 also conflicts // with filter6 which means that filter1 conflicts with filter6 (because it's in group2) // and also because its **own sibling** (filter2) is **also** in conflict with the // selected items in group2 (filter6) - filter1: { conflicted: true } + group1__filter1: { conflicted: true } } ), 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.' ); // Now choose a non-conflicting filter from the group model.toggleFiltersSelected( { - filter5: true + group2__filter5: true } ); - model.reassessFilterInteractions( model.getItemByName( 'filter5' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group2__filter5' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter2: { selected: true }, - filter6: { selected: true }, - filter5: { selected: true } + group1__filter2: { selected: true }, + group2__filter6: { selected: true }, + group2__filter5: { selected: true } // Filter6 and filter1 are no longer in conflict because // filter5, while it is in conflict with filter1, it is // not in conflict with filter2 - and since filter2 is @@ -1211,35 +1211,35 @@ // it is in conflict with the entire of group2, it means // filter1 is once again conflicted model.toggleFiltersSelected( { - filter2: false + group1__filter2: false } ); - model.reassessFilterInteractions( model.getItemByName( 'filter2' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter1: { conflicted: true }, - filter6: { selected: true }, - filter5: { selected: true } + group1__filter1: { conflicted: true }, + group2__filter6: { selected: true }, + group2__filter5: { selected: true } } ), 'Unselecting an item that did not conflict returns the conflict state.' ); // Followup #2: Now actually select filter1, and make everything conflicted model.toggleFiltersSelected( { - filter1: true + group1__filter1: true } ); - model.reassessFilterInteractions( model.getItemByName( 'filter1' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter1: { selected: true, conflicted: true }, - filter6: { selected: true, conflicted: true }, - filter5: { selected: true, conflicted: true }, - filter4: { conflicted: true } // Not selected but conflicted because it's in group2 + group1__filter1: { selected: true, conflicted: true }, + group2__filter6: { selected: true, conflicted: true }, + group2__filter5: { selected: true, conflicted: true }, + group2__filter4: { conflicted: true } // Not selected but conflicted because it's in group2 } ), 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.' ); @@ -1251,31 +1251,31 @@ // Select a filter that has a conflict with a specific filter model.toggleFiltersSelected( { - filter2: true // conflicts: filter6 + group1__filter2: true // conflicts: filter6 } ); - model.reassessFilterInteractions( model.getItemByName( 'filter2' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter2: { selected: true }, - filter6: { conflicted: true } + group1__filter2: { selected: true }, + group2__filter6: { conflicted: true } } ), 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".' ); model.toggleFiltersSelected( { - filter3: true // conflicts: filter6 + group1__filter3: true // conflicts: filter6 } ); - model.reassessFilterInteractions( model.getItemByName( 'filter3' ) ); + model.reassessFilterInteractions( model.getItemByName( 'group1__filter3' ) ); assert.deepEqual( model.getFullState(), $.extend( true, {}, baseFullState, { - filter2: { selected: true }, - filter3: { selected: true } + group1__filter2: { selected: true }, + group1__filter3: { selected: true } } ), 'Simple case: Selecting a filter that is not in conflict removes the conflict.' ); @@ -1311,40 +1311,40 @@ 'Highlight is enabled on toggle.' ); - model.setHighlightColor( 'filter1', 'color1' ); - model.setHighlightColor( 'filter2', 'color2' ); + model.setHighlightColor( 'group1__filter1', 'color1' ); + model.setHighlightColor( 'group1__filter2', 'color2' ); assert.deepEqual( model.getHighlightedItems().map( function ( item ) { return item.getName(); } ), [ - 'filter1', - 'filter2' + 'group1__filter1', + 'group1__filter2' ], 'Highlighted items are highlighted.' ); assert.equal( - model.getItemByName( 'filter1' ).getHighlightColor(), + model.getItemByName( 'group1__filter1' ).getHighlightColor(), 'color1', 'Item highlight color is set.' ); - model.setHighlightColor( 'filter1', 'color1changed' ); + model.setHighlightColor( 'group1__filter1', 'color1changed' ); assert.equal( - model.getItemByName( 'filter1' ).getHighlightColor(), + model.getItemByName( 'group1__filter1' ).getHighlightColor(), 'color1changed', 'Item highlight color is changed on setHighlightColor.' ); - model.clearHighlightColor( 'filter1' ); + model.clearHighlightColor( 'group1__filter1' ); assert.deepEqual( model.getHighlightedItems().map( function ( item ) { return item.getName(); } ), [ - 'filter2' + 'group1__filter2' ], 'Clear highlight from an item results in the item no longer being highlighted.' ); @@ -1353,18 +1353,18 @@ model = new mw.rcfilters.dm.FiltersViewModel(); model.initializeFilters( definition ); - model.setHighlightColor( 'filter1', 'color1' ); - model.setHighlightColor( 'filter2', 'color2' ); - model.setHighlightColor( 'filter3', 'color3' ); + model.setHighlightColor( 'group1__filter1', 'color1' ); + model.setHighlightColor( 'group1__filter2', 'color2' ); + model.setHighlightColor( 'group1__filter3', 'color3' ); assert.deepEqual( model.getHighlightedItems().map( function ( item ) { return item.getName(); } ), [ - 'filter1', - 'filter2', - 'filter3' + 'group1__filter1', + 'group1__filter2', + 'group1__filter3' ], 'Even if highlights are not enabled, the items remember their highlight state' // NOTE: When actually displaying the highlights, the UI checks whether @@ -1379,15 +1379,15 @@ model = new mw.rcfilters.dm.FiltersViewModel(); model.initializeFilters( definition ); - model.setHighlightColor( 'filter1', 'color1' ); - model.setHighlightColor( 'filter6', 'color6' ); + model.setHighlightColor( 'group1__filter1', 'color1' ); + model.setHighlightColor( 'group1__filter6', 'color6' ); assert.deepEqual( model.getHighlightedItems().map( function ( item ) { return item.getName(); } ), [ - 'filter1' + 'group1__filter1' ], 'Items without a specified class identifier are not highlighted.' ); -- 2.20.1