Merge "RCFilters UI: Adjust popup positioning again"
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki.rcfilters / dm.FiltersViewModel.test.js
index 998817d..52ba360 100644 (file)
@@ -1,57 +1,67 @@
 ( function ( mw, $ ) {
-       QUnit.module( 'mediawiki.rcfilters - FiltersViewModel' );
+       QUnit.module( 'mediawiki.rcfilters - FiltersViewModel', QUnit.newMwEnvironment( {
+               messages: {
+                       'group1filter1-label': 'Group 1: Filter 1',
+                       'group1filter1-desc': 'Description of Filter 1 in Group 1',
+                       'group1filter2-label': 'Group 1: Filter 2',
+                       'group1filter2-desc': 'Description of Filter 2 in Group 1',
+                       'group2filter1-label': 'Group 2: Filter 1',
+                       'group2filter1-desc': 'Description of Filter 1 in Group 2',
+                       'group2filter2-label': 'xGroup 2: Filter 2',
+                       'group2filter2-desc': 'Description of Filter 2 in Group 2'
+               }
+       } ) );
 
        QUnit.test( 'Setting up filters', function ( assert ) {
-               var definition = {
-                               group1: {
-                                       title: 'Group 1',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'group1filter1',
-                                                       label: 'Group 1: Filter 1',
-                                                       description: 'Description of Filter 1 in Group 1'
-                                               },
-                                               {
-                                                       name: 'group1filter2',
-                                                       label: 'Group 1: Filter 2',
-                                                       description: 'Description of Filter 2 in Group 1'
-                                               }
-                                       ]
-                               },
-                               group2: {
-                                       title: 'Group 2',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'group2filter1',
-                                                       label: 'Group 2: Filter 1',
-                                                       description: 'Description of Filter 1 in Group 2'
-                                               },
-                                               {
-                                                       name: 'group2filter2',
-                                                       label: 'Group 2: Filter 2',
-                                                       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'
-                                               }
-                                       ]
-                               }
-                       },
+               var definition = [ {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'group1filter1',
+                                               label: 'Group 1: Filter 1',
+                                               description: 'Description of Filter 1 in Group 1'
+                                       },
+                                       {
+                                               name: 'group1filter2',
+                                               label: 'Group 1: Filter 2',
+                                               description: 'Description of Filter 2 in Group 1'
+                                       }
+                               ]
+                       }, {
+                               name: 'group2',
+                               title: 'Group 2',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'group2filter1',
+                                               label: 'Group 2: Filter 1',
+                                               description: 'Description of Filter 1 in Group 2'
+                                       },
+                                       {
+                                               name: 'group2filter2',
+                                               label: 'Group 2: Filter 2',
+                                               description: 'Description of Filter 2 in Group 2'
+                                       }
+                               ]
+                       }, {
+                               name: '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();
 
                model.initializeFilters( definition );
@@ -79,7 +89,7 @@
                        'Initial state of filters'
                );
 
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        group1filter1: true,
                        group2filter2: true,
                        group3filter1: true
 
        QUnit.test( 'Finding matching filters', function ( assert ) {
                var matches,
-                       definition = {
-                               group1: {
-                                       title: 'Group 1 title',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'group1filter1',
-                                                       label: 'Group 1: Filter 1',
-                                                       description: 'Description of Filter 1 in Group 1'
-                                               },
-                                               {
-                                                       name: 'group1filter2',
-                                                       label: 'Group 1: Filter 2',
-                                                       description: 'Description of Filter 2 in Group 1'
-                                               }
-                                       ]
-                               },
-                               group2: {
-                                       title: 'Group 2 title',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'group2filter1',
-                                                       label: 'Group 2: Filter 1',
-                                                       description: 'Description of Filter 1 in Group 2'
-                                               },
-                                               {
-                                                       name: 'group2filter2',
-                                                       label: 'xGroup 2: Filter 2',
-                                                       description: 'Description of Filter 2 in Group 2'
-                                               }
-                                       ]
-                               }
-                       },
+                       definition = [ {
+                               name: 'group1',
+                               title: 'Group 1 title',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'group1filter1',
+                                               label: 'group1filter1-label',
+                                               description: 'group1filter1-desc'
+                                       },
+                                       {
+                                               name: 'group1filter2',
+                                               label: 'group1filter2-label',
+                                               description: 'group1filter2-desc'
+                                       }
+                               ]
+                       }, {
+                               name: 'group2',
+                               title: 'Group 2 title',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'group2filter1',
+                                               label: 'group2filter1-label',
+                                               description: 'group2filter1-desc'
+                                       },
+                                       {
+                                               name: 'group2filter2',
+                                               label: 'group2filter2-label',
+                                               description: 'group2filter2-desc'
+                                       }
+                               ]
+                       } ],
                        testCases = [
                                {
                                        query: 'group',
        } );
 
        QUnit.test( 'getParametersFromFilters', function ( assert ) {
-               var definition = {
-                               group1: {
-                                       title: 'Group 1',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'hidefilter1',
-                                                       label: 'Group 1: Filter 1',
-                                                       description: 'Description of Filter 1 in Group 1'
-                                               },
-                                               {
-                                                       name: 'hidefilter2',
-                                                       label: 'Group 1: Filter 2',
-                                                       description: 'Description of Filter 2 in Group 1'
-                                               },
-                                               {
-                                                       name: 'hidefilter3',
-                                                       label: 'Group 1: Filter 3',
-                                                       description: 'Description of Filter 3 in Group 1'
-                                               }
-                                       ]
-                               },
-                               group2: {
-                                       title: 'Group 2',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'hidefilter4',
-                                                       label: 'Group 2: Filter 1',
-                                                       description: 'Description of Filter 1 in Group 2'
-                                               },
-                                               {
-                                                       name: 'hidefilter5',
-                                                       label: 'Group 2: Filter 2',
-                                                       description: 'Description of Filter 2 in Group 2'
-                                               },
-                                               {
-                                                       name: 'hidefilter6',
-                                                       label: 'Group 2: Filter 3',
-                                                       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'
-                                               }
-                                       ]
-                               }
-                       },
+               var definition = [ {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'hidefilter1',
+                                               label: 'Group 1: Filter 1',
+                                               description: 'Description of Filter 1 in Group 1'
+                                       },
+                                       {
+                                               name: 'hidefilter2',
+                                               label: 'Group 1: Filter 2',
+                                               description: 'Description of Filter 2 in Group 1'
+                                       },
+                                       {
+                                               name: 'hidefilter3',
+                                               label: 'Group 1: Filter 3',
+                                               description: 'Description of Filter 3 in Group 1'
+                                       }
+                               ]
+                       }, {
+                               name: 'group2',
+                               title: 'Group 2',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'hidefilter4',
+                                               label: 'Group 2: Filter 1',
+                                               description: 'Description of Filter 1 in Group 2'
+                                       },
+                                       {
+                                               name: 'hidefilter5',
+                                               label: 'Group 2: Filter 2',
+                                               description: 'Description of Filter 2 in Group 2'
+                                       },
+                                       {
+                                               name: 'hidefilter6',
+                                               label: 'Group 2: Filter 3',
+                                               description: 'Description of Filter 3 in Group 2'
+                                       }
+                               ]
+                       }, {
+                               name: '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();
 
                model.initializeFilters( definition );
                                hidefilter4: 0,
                                hidefilter5: 0,
                                hidefilter6: 0,
-                               group3: 'all',
+                               group3: ''
                        },
-                       'Unselected filters return all parameters falsey or \'all\'.'
+                       'Unselected filters return all parameters falsey or \'\'.'
                );
 
                // Select 1 filter
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        hidefilter1: true,
                        hidefilter2: false,
                        hidefilter3: false,
                                hidefilter4: 0,
                                hidefilter5: 0,
                                hidefilter6: 0,
-                               group3: 'all'
+                               group3: ''
                        },
                        'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
                );
 
                // Select 2 filters
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        hidefilter1: true,
                        hidefilter2: true,
                        hidefilter3: false,
                                hidefilter4: 0,
                                hidefilter5: 0,
                                hidefilter6: 0,
-                               group3: 'all'
+                               group3: ''
                        },
                        'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
                );
 
                // Select 3 filters
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        hidefilter1: true,
                        hidefilter2: true,
                        hidefilter3: true,
                                hidefilter4: 0,
                                hidefilter5: 0,
                                hidefilter6: 0,
-                               group3: 'all'
+                               group3: ''
                        },
                        'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
                );
 
                // Select 1 filter from string_options
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        filter7: true,
                        filter8: false,
                        filter9: false
                );
 
                // Select 2 filters from string_options
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        filter7: true,
                        filter8: true,
                        filter9: false
                );
 
                // Select 3 filters from string_options
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        filter7: true,
                        filter8: true,
                        filter9: true
        } );
 
        QUnit.test( 'getFiltersFromParameters', function ( assert ) {
-               var definition = {
-                               group1: {
-                                       title: 'Group 1',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'hidefilter1',
-                                                       label: 'Show filter 1',
-                                                       description: 'Description of Filter 1 in Group 1',
-                                                       default: true
-                                               },
-                                               {
-                                                       name: 'hidefilter2',
-                                                       label: 'Show filter 2',
-                                                       description: 'Description of Filter 2 in Group 1'
-                                               },
-                                               {
-                                                       name: 'hidefilter3',
-                                                       label: 'Show filter 3',
-                                                       description: 'Description of Filter 3 in Group 1',
-                                                       default: true
-                                               }
-                                       ]
-                               },
-                               group2: {
-                                       title: 'Group 2',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'hidefilter4',
-                                                       label: 'Show filter 4',
-                                                       description: 'Description of Filter 1 in Group 2'
-                                               },
-                                               {
-                                                       name: 'hidefilter5',
-                                                       label: 'Show filter 5',
-                                                       description: 'Description of Filter 2 in Group 2',
-                                                       default: true
-                                               },
-                                               {
-                                                       name: 'hidefilter6',
-                                                       label: 'Show filter 6',
-                                                       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',
-                                                       default: true
-                                               },
-                                               {
-                                                       name: 'filter9',
-                                                       label: 'Group 3: Filter 3',
-                                                       description: 'Description of Filter 3 in Group 3'
-                                               }
-                                       ]
-                               }
-                       },
+               var definition = {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'hidefilter1',
+                                               label: 'Show filter 1',
+                                               description: 'Description of Filter 1 in Group 1',
+                                               default: true
+                                       },
+                                       {
+                                               name: 'hidefilter2',
+                                               label: 'Show filter 2',
+                                               description: 'Description of Filter 2 in Group 1'
+                                       },
+                                       {
+                                               name: 'hidefilter3',
+                                               label: 'Show filter 3',
+                                               description: 'Description of Filter 3 in Group 1',
+                                               default: true
+                                       }
+                               ]
+                       }, {
+                               name: 'group2',
+                               title: 'Group 2',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'hidefilter4',
+                                               label: 'Show filter 4',
+                                               description: 'Description of Filter 1 in Group 2'
+                                       },
+                                       {
+                                               name: 'hidefilter5',
+                                               label: 'Show filter 5',
+                                               description: 'Description of Filter 2 in Group 2',
+                                               default: true
+                                       },
+                                       {
+                                               name: 'hidefilter6',
+                                               label: 'Show filter 6',
+                                               description: 'Description of Filter 3 in Group 2'
+                                       }
+                               ]
+                       }, {
+
+                               name: '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',
+                                               default: true
+                                       },
+                                       {
+                                               name: 'filter9',
+                                               label: 'Group 3: Filter 3',
+                                               description: 'Description of Filter 3 in Group 3'
+                                       }
+                               ]
+                       } ],
                        defaultFilterRepresentation = {
                                // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
                                hidefilter1: false,
                // This test is demonstrating wrong usage of the method;
                // We should be aware that getFiltersFromParameters is stateless,
                // so each call gives us a filter state that only reflects the query given.
-               // This means that the two calls to updateFilters() below collide.
+               // This means that the two calls to toggleFiltersSelected() below collide.
                // The result of the first is overridden by the result of the second,
                // since both get a full state object from getFiltersFromParameters that **only** relates
                // to the input it receives.
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                hidefilter1: '1'
                        } )
                );
 
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                hidefilter6: '1'
                        } )
                );
 
-               // The result here is ignoring the first updateFilters call
+               // The result here is ignoring the first toggleFiltersSelected call
                // We should receive default values + hidefilter6 as false
                assert.deepEqual(
                        model.getSelectedState(),
                model = new mw.rcfilters.dm.FiltersViewModel();
                model.initializeFilters( definition );
 
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                hidefilter1: '0'
                        } )
                );
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                hidefilter1: '1'
                        } )
                        'After checking and then unchecking a \'send_unselected_if_any\' filter (without touching other filters in that group), results are default'
                );
 
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                group3: 'filter7'
                        } )
                        'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
                );
 
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                group3: 'filter7,filter8'
                        } )
                        'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
                );
 
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                group3: 'filter7,filter8,filter9'
                        } )
                        'A \'string_options\' parameter containing all values, results in all filters of the group as unchecked.'
                );
 
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                group3: 'filter7,all,filter9'
                        } )
                        'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as unchecked.'
                );
 
-               model.updateFilters(
+               model.toggleFiltersSelected(
                        model.getFiltersFromParameters( {
                                group3: 'filter7,foo,filter9'
                        } )
        } );
 
        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'
-                                               }
-                                       ]
-                               }
-                       },
+               var definition = [ {
+                               name: '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 );
        } );
 
        QUnit.test( 'setFiltersToDefaults', function ( assert ) {
-               var definition = {
-                               group1: {
-                                       title: 'Group 1',
-                                       type: 'send_unselected_if_any',
-                                       exclusionType: 'default',
-                                       filters: [
-                                               {
-                                                       name: 'hidefilter1',
-                                                       label: 'Show filter 1',
-                                                       description: 'Description of Filter 1 in Group 1',
-                                                       default: true
-                                               },
-                                               {
-                                                       name: 'hidefilter2',
-                                                       label: 'Show filter 2',
-                                                       description: 'Description of Filter 2 in Group 1'
-                                               },
-                                               {
-                                                       name: 'hidefilter3',
-                                                       label: 'Show filter 3',
-                                                       description: 'Description of Filter 3 in Group 1',
-                                                       default: true
-                                               }
-                                       ]
-                               },
-                               group2: {
-                                       title: 'Group 2',
-                                       type: 'send_unselected_if_any',
-                                       filters: [
-                                               {
-                                                       name: 'hidefilter4',
-                                                       label: 'Show filter 4',
-                                                       description: 'Description of Filter 1 in Group 2'
-                                               },
-                                               {
-                                                       name: 'hidefilter5',
-                                                       label: 'Show filter 5',
-                                                       description: 'Description of Filter 2 in Group 2',
-                                                       default: true
-                                               },
-                                               {
-                                                       name: 'hidefilter6',
-                                                       label: 'Show filter 6',
-                                                       description: 'Description of Filter 3 in Group 2'
-                                               }
-                                       ]
-                               }
+               var definition = [ {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'hidefilter1',
+                                               label: 'Show filter 1',
+                                               description: 'Description of Filter 1 in Group 1',
+                                               default: true
+                                       },
+                                       {
+                                               name: 'hidefilter2',
+                                               label: 'Show filter 2',
+                                               description: 'Description of Filter 2 in Group 1'
+                                       },
+                                       {
+                                               name: 'hidefilter3',
+                                               label: 'Show filter 3',
+                                               description: 'Description of Filter 3 in Group 1',
+                                               default: true
+                                       }
+                               ]
+                       }, {
+                               name: 'group2',
+                               title: 'Group 2',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'hidefilter4',
+                                               label: 'Show filter 4',
+                                               description: 'Description of Filter 1 in Group 2'
+                                       },
+                                       {
+                                               name: 'hidefilter5',
+                                               label: 'Show filter 5',
+                                               description: 'Description of Filter 2 in Group 2',
+                                               default: true
+                                       },
+                                       {
+                                               name: 'hidefilter6',
+                                               label: 'Show filter 6',
+                                               description: 'Description of Filter 3 in Group 2'
+                                       }
+                               ]
+                       } ],
+                       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
                        },
                        model = new mw.rcfilters.dm.FiltersViewModel();
 
                model.initializeFilters( definition );
 
                assert.deepEqual(
-                       model.getFullState(),
+                       model.getSelectedState(),
                        {
-                               // Group 1
-                               hidefilter1: { selected: true, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: true, active: true },
-                               // Group 2
-                               hidefilter4: { selected: false, active: true },
-                               hidefilter5: { selected: true, active: true },
-                               hidefilter6: { selected: false, active: true },
+                               hidefilter1: false,
+                               hidefilter2: false,
+                               hidefilter3: false,
+                               hidefilter4: false,
+                               hidefilter5: false,
+                               hidefilter6: false
                        },
-                       'Initial state: all filters are active, and select states are default.'
+                       'Initial state: default filters are not selected (controller selects defaults explicitly).'
                );
 
-               // Default behavior for 'exclusion' type with only 1 item selected, means that:
-               // - The items in the same group that are *not* selected are *not* active
-               // - Items in other groups are unaffected (all active)
-               model.updateFilters( {
+               model.toggleFiltersSelected( {
                        hidefilter1: false,
-                       hidefilter2: false,
-                       hidefilter3: false,
-                       hidefilter4: false,
-                       hidefilter5: false,
-                       hidefilter6: true
+                       hidefilter3: false
                } );
+
+               model.setFiltersToDefaults();
+
                assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               // Group 1: not affected
-                               hidefilter1: { selected: false, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: false, active: true },
-                               // Group 2: affected
-                               hidefilter4: { selected: false, active: false },
-                               hidefilter5: { selected: false, active: false },
-                               hidefilter6: { selected: true, active: true },
+                       model.getSelectedState(),
+                       defaultFilterRepresentation,
+                       'Changing values of filters and then returning to defaults still results in default filters being selected.'
+               );
+       } );
+
+       QUnit.test( 'Filter interaction: subsets', function ( assert ) {
+               var definition = [ {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'string_options',
+                               filters: [
+                                       {
+                                               name: 'filter1',
+                                               label: 'Show filter 1',
+                                               description: 'Description of Filter 1 in Group 1',
+                                               subset: [
+                                                       {
+                                                               group: 'group1',
+                                                               filter: 'filter2'
+                                                       },
+                                                       {
+                                                               group: 'group1',
+                                                               filter: 'filter3'
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               name: 'filter2',
+                                               label: 'Show filter 2',
+                                               description: 'Description of Filter 2 in Group 1',
+                                               subset: [
+                                                       {
+                                                               group: 'group1',
+                                                               filter: 'filter3'
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               name: 'filter3',
+                                               label: 'Show filter 3',
+                                               description: 'Description of Filter 3 in Group 1'
+                                       }
+                               ]
+                       } ],
+                       baseFullState = {
+                               filter1: { selected: false, conflicted: false, included: false },
+                               filter2: { selected: false, conflicted: false, included: false },
+                               filter3: { selected: false, conflicted: false, included: false }
                        },
-                       'Default exclusion behavior with 1 item selected in the group.'
+                       model = new mw.rcfilters.dm.FiltersViewModel();
+
+               model.initializeFilters( definition );
+               // Select a filter that has subset with another filter
+               model.toggleFiltersSelected( {
+                       filter1: true
+               } );
+
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
+               assert.deepEqual(
+                       model.getFullState(),
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { included: true },
+                               filter3: { included: true }
+                       } ),
+                       'Filters with subsets are represented in the model.'
                );
 
-               // Default behavior for 'exclusion' type with multiple items selected, but not all, means that:
-               // - The items in the same group that are *not* selected are *not* active
-               // - Items in other groups are unaffected (all active)
-               model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       hidefilter1: false,
-                       hidefilter2: false,
-                       hidefilter3: false,
-                       hidefilter4: false,
-                       hidefilter5: true,
-                       hidefilter6: true
+               // Select another filter that has a subset with the same previous filter
+               model.toggleFiltersSelected( {
+                       filter2: true
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter2' ) );
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               // Group 1: not affected
-                               hidefilter1: { selected: false, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: false, active: true },
-                               // Group 2: affected
-                               hidefilter4: { selected: false, active: false },
-                               hidefilter5: { selected: true, active: true },
-                               hidefilter6: { selected: true, active: true },
-                       },
-                       'Default exclusion behavior with multiple items (but not all) selected in the group.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { selected: true, included: true },
+                               filter3: { included: true }
+                       } ),
+                       'Filters that have multiple subsets are represented.'
                );
 
-               // Default behavior for 'exclusion' type with all items in the group selected, means that:
-               // - All items in the group are NOT active
-               // - Items in other groups are unaffected (all active)
-               model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       hidefilter1: false,
-                       hidefilter2: false,
-                       hidefilter3: false,
-                       hidefilter4: true,
-                       hidefilter5: true,
-                       hidefilter6: true
+               // Remove one filter (but leave the other) that affects filter2
+               model.toggleFiltersSelected( {
+                       filter1: false
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               // Group 1: not affected
-                               hidefilter1: { selected: false, active: true },
-                               hidefilter2: { selected: false, active: true },
-                               hidefilter3: { selected: false, active: true },
-                               // Group 2: affected
-                               hidefilter4: { selected: true, active: false },
-                               hidefilter5: { selected: true, active: false },
-                               hidefilter6: { selected: true, active: false },
-                       },
-                       'Default exclusion behavior with all items in the group.'
+                       $.extend( true, {}, baseFullState, {
+                               filter2: { selected: true, included: false },
+                               filter3: { included: true }
+                       } ),
+                       'Removing a filter only un-includes its subset if there is no other filter affecting.'
+               );
+
+               model.toggleFiltersSelected( {
+                       filter2: false
+               } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter2' ) );
+               assert.deepEqual(
+                       model.getFullState(),
+                       baseFullState,
+                       'Removing all supersets also un-includes the subsets.'
                );
        } );
 
-       QUnit.test( 'reapplyActiveFilters - "explicit" exclusion rules', function ( assert ) {
-               var definition = {
-                               group1: {
-                                       title: 'Group 1',
-                                       type: 'send_unselected_if_any',
-                                       exclusionType: 'explicit',
-                                       filters: [
-                                               {
-                                                       name: 'filter1',
-                                                       excludes: [ 'filter2', 'filter3' ],
-                                                       label: 'Show filter 1',
-                                                       description: 'Description of Filter 1 in Group 1'
-                                               },
-                                               {
-                                                       name: 'filter2',
-                                                       excludes: [ 'filter3' ],
-                                                       label: 'Show filter 2',
-                                                       description: 'Description of Filter 2 in Group 1'
-                                               },
-                                               {
-                                                       name: 'filter3',
-                                                       label: 'Show filter 3',
-                                                       excludes: [ 'filter1' ],
-                                                       description: 'Description of Filter 3 in Group 1'
-                                               },
-                                               {
-                                                       name: 'filter4',
-                                                       label: 'Show filter 4',
-                                                       description: 'Description of Filter 4 in Group 1'
-                                               }
-                                       ]
-                               }
+       QUnit.test( 'Filter interaction: full coverage', function ( assert ) {
+               var definition = [ {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'string_options',
+                               fullCoverage: false,
+                               filters: [
+                                       { name: 'filter1', label: '1', description: '1' },
+                                       { name: 'filter2', label: '2', description: '2' },
+                                       { name: 'filter3', label: '3', description: '3' }
+                               ]
+                       }, {
+                               name: 'group2',
+                               title: 'Group 2',
+                               type: 'send_unselected_if_any',
+                               fullCoverage: true,
+                               filters: [
+                                       { name: 'filter4', label: '4', description: '4' },
+                                       { name: 'filter5', label: '5', description: '5' },
+                                       { name: 'filter6', label: '6', description: '6' }
+                               ]
+                       } ],
+                       model = new mw.rcfilters.dm.FiltersViewModel(),
+                       isCapsuleItemMuted = function ( filterName ) {
+                               var itemModel = model.getItemByName( filterName ),
+                                       groupModel = itemModel.getGroupModel();
+
+                               // This is the logic inside the capsule widget
+                               return (
+                                       // The capsule item widget only appears if the item is selected
+                                       itemModel.isSelected() &&
+                                       // Muted state is only valid if group is full coverage and all items are selected
+                                       groupModel.isFullCoverage() && groupModel.areAllSelected()
+                               );
                        },
-                       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,
-                               // Group 3, "string_options", default values correspond to parameters and filters
-                               filter7: false,
-                               filter8: true,
-                               filter9: false
+                       getCurrentItemsMutedState = function () {
+                               return {
+                                       filter1: isCapsuleItemMuted( 'filter1' ),
+                                       filter2: isCapsuleItemMuted( 'filter2' ),
+                                       filter3: isCapsuleItemMuted( 'filter3' ),
+                                       filter4: isCapsuleItemMuted( 'filter4' ),
+                                       filter5: isCapsuleItemMuted( 'filter5' ),
+                                       filter6: isCapsuleItemMuted( 'filter6' )
+                               };
                        },
-                       model = new mw.rcfilters.dm.FiltersViewModel();
+                       baseMuteState = {
+                               filter1: false,
+                               filter2: false,
+                               filter3: false,
+                               filter4: false,
+                               filter5: false,
+                               filter6: false
+                       };
 
                model.initializeFilters( definition );
 
+               // Starting state, no selection, all items are non-muted
                assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               filter1: { selected: false, active: true },
-                               filter2: { selected: false, active: true },
-                               filter3: { selected: false, active: true },
-                               filter4: { selected: false, active: true }
-                       },
-                       'Initial state: all filters are active.'
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'No selection - all items are non-muted'
                );
 
-               // "Explicit" behavior for 'exclusion' with one item checked:
-               // - Items in the 'excluded' list of the selected filter are inactive
-               model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       filter1: true, // Excludes 'hidefilter2', 'hidefilter3'
-                       filter2: false, // Excludes 'hidefilter3'
-                       filter3: false, // Excludes 'hidefilter1'
-                       filter4: false // No exclusion list
+               // Select most (but not all) items in each group
+               model.toggleFiltersSelected( {
+                       filter1: true,
+                       filter2: true,
+                       filter4: true,
+                       filter5: true
                } );
+
+               // Both groups have multiple (but not all) items selected, all items are non-muted
                assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               filter1: { selected: true, active: true },
-                               filter2: { selected: false, active: false },
-                               filter3: { selected: false, active: false },
-                               filter4: { selected: false, active: true }
-                       },
-                       '"Explicit" exclusion behavior with one item selected that has an exclusion list.'
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'Not all items in the group selected - all items are non-muted'
                );
 
-               // "Explicit" behavior for 'exclusion' with two item checked:
-               // - Items in the 'excluded' list of each of the selected filter are inactive
-               model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       filter1: true, // Excludes 'hidefilter2', 'hidefilter3'
-                       filter2: false, // Excludes 'hidefilter3'
-                       filter3: true, // Excludes 'hidefilter1'
-                       filter4: false // No exclusion list
+               // Select all items in 'fullCoverage' group (group2)
+               model.toggleFiltersSelected( {
+                       filter6: true
                } );
+
+               // Group2 (full coverage) has all items selected, all its items are muted
                assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               filter1: { selected: true, active: false },
-                               filter2: { selected: false, active: false },
-                               filter3: { selected: true, active: false },
-                               filter4: { selected: false, active: true }
-                       },
-                       '"Explicit" exclusion behavior with two selected items that both have an exclusion list.'
+                       getCurrentItemsMutedState(),
+                       $.extend( {}, baseMuteState, {
+                               filter4: true,
+                               filter5: true,
+                               filter6: true
+                       } ),
+                       'All items in \'full coverage\' group are selected - all items in the group are muted'
                );
 
-               // "Explicit behavior" with two filters that exclude the same item
+               // Select all items in non 'fullCoverage' group (group1)
+               model.toggleFiltersSelected( {
+                       filter3: true
+               } );
 
-               // Two filters selected, both exclude 'hidefilter3'
-               model.updateFilters( {
-                       // Literally updating filters to create a clean state
-                       filter1: true, // Excludes 'hidefilter2', 'hidefilter3'
-                       filter2: true, // Excludes 'hidefilter3'
-                       filter3: false, // Excludes 'hidefilter1'
-                       filter4: false // No exclusion list
+               // 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
+                       } ),
+                       '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
                } );
                assert.deepEqual(
-                       model.getFullState(),
-                       {
-                               filter1: { selected: true, active: true },
-                               filter2: { selected: true, active: false }, // Excluded by filter1
-                               filter3: { selected: false, active: false }, // Excluded by both filter1 and filter2
-                               filter4: { selected: false, active: true }
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'Not all items in the group are checked - all items are non-muted regardless of group coverage'
+               );
+       } );
+
+       QUnit.test( 'Filter interaction: conflicts', function ( assert ) {
+               var definition = [ {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'string_options',
+                               filters: [
+                                       {
+                                               name: 'filter1',
+                                               label: '1',
+                                               description: '1',
+                                               conflicts: [ 'filter2', 'filter4' ]
+                                       },
+                                       {
+                                               name: 'filter2',
+                                               label: '2',
+                                               description: '2',
+                                               conflicts: [ 'filter6' ]
+                                       },
+                                       {
+                                               name: 'filter3',
+                                               label: '3',
+                                               description: '3'
+                                       }
+                               ]
+                       }, {
+                               name: 'group2',
+                               title: 'Group 2',
+                               type: 'send_unselected_if_any',
+                               filters: [
+                                       {
+                                               name: 'filter4',
+                                               label: '1',
+                                               description: '1'
+                                       },
+                                       {
+                                               name: 'filter5',
+                                               label: '5',
+                                               description: '5',
+                                               conflicts: [ 'filter3' ]
+                                       },
+                                       {
+                                               name: 'filter6',
+                                               label: '6',
+                                               description: '6'
+                                       }
+                               ]
+                       } ],
+                       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 }
                        },
-                       '"Explicit" exclusion behavior with two selected items that both exclude another item.'
+                       model = new mw.rcfilters.dm.FiltersViewModel();
+
+               model.initializeFilters( definition );
+
+               assert.deepEqual(
+                       model.getFullState(),
+                       baseFullState,
+                       'Initial state: no conflicts because no selections.'
                );
 
-               // Unselect filter2: filter3 should still be excluded, because filter1 excludes it and is selected
-               model.updateFilters( {
-                       filter2: false, // Excludes 'hidefilter3'
+               // Select a filter that has a conflict with another
+               model.toggleFiltersSelected( {
+                       filter1: true // conflicts: filter2, filter4
                } );
+
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
+
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: true, active: true },
-                               filter2: { selected: false, active: false }, // Excluded by filter1
-                               filter3: { selected: false, active: false }, // Still excluded by filter1
-                               filter4: { selected: false, active: true }
-                       },
-                       '"Explicit" exclusion behavior unselecting one item that excludes another item, that is being excluded by a third active item.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { conflicted: true },
+                               filter4: { conflicted: true }
+                       } ),
+                       'Selecting a filter set its conflicts list as "conflicted".'
                );
 
-               // Unselect filter1: filter3 should now be active, since both filters that exclude it are unselected
-               model.updateFilters( {
-                       filter1: false, // Excludes 'hidefilter3' and 'hidefilter2'
+               // Select one of the conflicts (both filters are now conflicted and selected)
+               model.toggleFiltersSelected( {
+                       filter4: true // conflicts: filter 1
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
+
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: false, active: true },
-                               filter2: { selected: false, active: true }, // No longer excluded by filter1
-                               filter3: { selected: false, active: true }, // No longer excluded by either filter1 nor filter2
-                               filter4: { selected: false, active: true }
-                       },
-                       '"Explicit" exclusion behavior unselecting both items that excluded the same third item.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true, conflicted: true },
+                               filter2: { conflicted: true },
+                               filter4: { selected: true, conflicted: true }
+                       } ),
+                       'Selecting a conflicting filter sets both sides to conflicted and selected.'
                );
 
+               // Select another filter from filter4 group, meaning:
+               // now filter1 no longer conflicts with filter4
+               model.toggleFiltersSelected( {
+                       filter6: true // conflicts: filter2
+               } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter6' ) );
+
+               assert.deepEqual(
+                       model.getFullState(),
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true, conflicted: false }, // No longer conflicts (filter4 is not the only in the group)
+                               filter2: { conflicted: true }, // While not selected, still in conflict with filter1, which is selected
+                               filter4: { selected: true, conflicted: false }, // No longer conflicts with filter1
+                               filter6: { selected: true, conflicted: false }
+                       } ),
+                       'Selecting a non-conflicting filter from a conflicting group removes the conflict'
+               );
+       } );
+
+       QUnit.test( 'Filter highlights', function ( assert ) {
+               var definition = [ {
+                               name: 'group1',
+                               title: 'Group 1',
+                               type: 'string_options',
+                               filters: [
+                                       { name: 'filter1', cssClass: 'class1', label: '1', description: '1' },
+                                       { name: 'filter2', cssClass: 'class2', label: '2', description: '2' },
+                                       { name: 'filter3', cssClass: 'class3', label: '3', description: '3' },
+                                       { name: 'filter4', cssClass: 'class4', label: '4', description: '4' },
+                                       { name: 'filter5', cssClass: 'class5', label: '5', description: '5' },
+                                       { name: 'filter6', label: '6', description: '6' }
+                               ]
+                       } ],
+                       model = new mw.rcfilters.dm.FiltersViewModel();
+
+               model.initializeFilters( definition );
+
+               assert.ok(
+                       !model.isHighlightEnabled(),
+                       'Initially, highlight is disabled.'
+               );
+
+               model.toggleHighlight( true );
+               assert.ok(
+                       model.isHighlightEnabled(),
+                       'Highlight is enabled on toggle.'
+               );
+
+               model.setHighlightColor( 'filter1', 'color1' );
+               model.setHighlightColor( 'filter2', 'color2' );
+
+               assert.deepEqual(
+                       model.getHighlightedItems().map( function ( item ) {
+                               return item.getName();
+                       } ),
+                       [
+                               'filter1',
+                               'filter2'
+                       ],
+                       'Highlighted items are highlighted.'
+               );
+
+               assert.equal(
+                       model.getItemByName( 'filter1' ).getHighlightColor(),
+                       'color1',
+                       'Item highlight color is set.'
+               );
+
+               model.setHighlightColor( 'filter1', 'color1changed' );
+               assert.equal(
+                       model.getItemByName( 'filter1' ).getHighlightColor(),
+                       'color1changed',
+                       'Item highlight color is changed on setHighlightColor.'
+               );
+
+               model.clearHighlightColor( 'filter1' );
+               assert.deepEqual(
+                       model.getHighlightedItems().map( function ( item ) {
+                               return item.getName();
+                       } ),
+                       [
+                               'filter2'
+                       ],
+                       'Clear highlight from an item results in the item no longer being highlighted.'
+               );
+
+               // Reset
+               model = new mw.rcfilters.dm.FiltersViewModel();
+               model.initializeFilters( definition );
+
+               model.setHighlightColor( 'filter1', 'color1' );
+               model.setHighlightColor( 'filter2', 'color2' );
+               model.setHighlightColor( 'filter3', 'color3' );
+
+               assert.deepEqual(
+                       model.getHighlightedItems().map( function ( item ) {
+                               return item.getName();
+                       } ),
+                       [
+                               'filter1',
+                               'filter2',
+                               'filter3'
+                       ],
+                       'Even if highlights are not enabled, the items remember their highlight state'
+                       // NOTE: When actually displaying the highlights, the UI checks whether
+                       // highlighting is generally active and then goes over the highlighted
+                       // items. The item models, however, and the view model in general, still
+                       // retains the knowledge about which filters have different colors, so we
+                       // can seamlessly return to the colors the user previously chose if they
+                       // reapply highlights.
+               );
+
+               // Reset
+               model = new mw.rcfilters.dm.FiltersViewModel();
+               model.initializeFilters( definition );
+
+               model.setHighlightColor( 'filter1', 'color1' );
+               model.setHighlightColor( 'filter6', 'color6' );
+
+               assert.deepEqual(
+                       model.getHighlightedItems().map( function ( item ) {
+                               return item.getName();
+                       } ),
+                       [
+                               'filter1'
+                       ],
+                       'Items without a specified class identifier are not highlighted.'
+               );
        } );
 }( mediaWiki, jQuery ) );