qunit: Make eslint config pass on qunit test files
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki.rcfilters / dm.FiltersViewModel.test.js
index 62d247a..a5b12c9 100644 (file)
                var matches,
                        definition = {
                                group1: {
-                                       title: 'Group 1',
+                                       title: 'Group 1 title',
                                        type: 'send_unselected_if_any',
                                        filters: [
                                                {
                                        ]
                                },
                                group2: {
-                                       title: 'Group 2',
+                                       title: 'Group 2 title',
                                        type: 'send_unselected_if_any',
                                        filters: [
                                                {
                                                },
                                                {
                                                        name: 'group2filter2',
-                                                       label: 'Group 2: Filter 2',
+                                                       label: 'xGroup 2: Filter 2',
                                                        description: 'Description of Filter 2 in Group 2'
                                                }
                                        ]
                                }
                        },
-                       model = new mw.rcfilters.dm.FiltersViewModel();
+                       testCases = [
+                               {
+                                       query: 'group',
+                                       expectedMatches: {
+                                               group1: [ 'group1filter1', 'group1filter2' ],
+                                               group2: [ 'group2filter1' ]
+                                       },
+                                       reason: 'Finds filters starting with the query string'
+                               },
+                               {
+                                       query: 'filter 2 in group',
+                                       expectedMatches: {
+                                               group1: [ 'group1filter2' ],
+                                               group2: [ 'group2filter2' ]
+                                       },
+                                       reason: 'Finds filters containing the query string in their description'
+                               },
+                               {
+                                       query: 'title',
+                                       expectedMatches: {
+                                               group1: [ 'group1filter1', 'group1filter2' ],
+                                               group2: [ 'group2filter1', 'group2filter2' ]
+                                       },
+                                       reason: 'Finds filters containing the query string in their group title'
+                               }
+                       ],
+                       model = new mw.rcfilters.dm.FiltersViewModel(),
+                       extractNames = function ( matches ) {
+                               var result = {};
+                               Object.keys( matches ).forEach( function ( groupName ) {
+                                       result[ groupName ] = matches[ groupName ].map( function ( item ) {
+                                               return item.getName();
+                                       } );
+                               } );
+                               return result;
+                       };
 
                model.initializeFilters( definition );
 
-               matches = model.findMatches( 'group 1' );
-               assert.equal(
-                       matches.group1.length,
-                       2,
-                       'findMatches finds correct group with correct number of results'
-               );
-
-               assert.deepEqual(
-                       matches.group1.map( function ( item ) { return item.getName(); } ),
-                       [ 'group1filter1', 'group1filter2' ],
-                       'findMatches finds the correct items within a single group'
-               );
-
-               matches = model.findMatches( 'filter 1' );
-               assert.ok(
-                       matches.group1.length === 1 && matches.group2.length === 1,
-                       'findMatches finds correct number of results in multiple groups'
-               );
-
-               assert.deepEqual(
-                       [
-                               matches.group1.map( function ( item ) { return item.getName(); } ),
-                               matches.group2.map( function ( item ) { return item.getName(); } )
-                       ],
-                       [
-                               [ 'group1filter1' ],
-                               [ 'group2filter1' ]
-                       ],
-                       'findMatches finds the correct items within multiple groups'
-               );
+               testCases.forEach( function ( testCase ) {
+                       matches = model.findMatches( testCase.query );
+                       assert.deepEqual(
+                               extractNames( matches ),
+                               testCase.expectedMatches,
+                               testCase.reason
+                       );
+               } );
 
                matches = model.findMatches( 'foo' );
                assert.ok(
                                hidefilter4: 0,
                                hidefilter5: 0,
                                hidefilter6: 0,
-                               group3: 'all',
+                               group3: 'all'
                        },
                        'Unselected filters return all parameters falsey or \'all\'.'
                );
                                group1: {
                                        title: 'Group 1',
                                        type: 'send_unselected_if_any',
-                                       exclusionType: 'default',
                                        filters: [
                                                {
                                                        name: 'hidefilter1',
                                        ]
                                }
                        },
+                       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( {
                        hidefilter1: false,
-                       hidefilter2: false,
-                       hidefilter3: false,
-                       hidefilter4: false,
-                       hidefilter5: false,
-                       hidefilter6: true
+                       hidefilter3: false
                } );
-               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 },
-                       },
-                       'Default exclusion behavior with 1 item selected in the group.'
-               );
 
-               // 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
-               } );
-               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.'
-               );
+               model.setFiltersToDefaults();
 
-               // 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
-               } );
                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.'
+                       model.getSelectedState(),
+                       defaultFilterRepresentation,
+                       'Changing values of filters and then returning to defaults still results in default filters being selected.'
                );
        } );
 
-       QUnit.test( 'reapplyActiveFilters - "explicit" exclusion rules', function ( assert ) {
+       QUnit.test( 'Filter interaction: subsets', function ( assert ) {
                var definition = {
                                group1: {
                                        title: 'Group 1',
-                                       type: 'send_unselected_if_any',
-                                       exclusionType: 'explicit',
+                                       type: 'string_options',
                                        filters: [
                                                {
                                                        name: 'filter1',
-                                                       excludes: [ 'filter2', 'filter3' ],
                                                        label: 'Show filter 1',
-                                                       description: 'Description of Filter 1 in Group 1'
+                                                       description: 'Description of Filter 1 in Group 1',
+                                                       subset: [ 'filter2', 'filter5' ]
                                                },
                                                {
                                                        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'
-                                               },
+                                               }
+                                       ]
+                               },
+                               group2: {
+                                       title: 'Group 2',
+                                       type: 'send_unselected_if_any',
+                                       filters: [
                                                {
                                                        name: 'filter4',
                                                        label: 'Show filter 4',
-                                                       description: 'Description of Filter 4 in Group 1'
+                                                       description: 'Description of Filter 1 in Group 2',
+                                                       subset: [ 'filter3', 'filter5' ]
+                                               },
+                                               {
+                                                       name: 'filter5',
+                                                       label: 'Show filter 5',
+                                                       description: 'Description of Filter 2 in Group 2'
+                                               },
+                                               {
+                                                       name: 'filter6',
+                                                       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,
-                               // Group 3, "string_options", default values correspond to parameters and filters
-                               filter7: false,
-                               filter8: true,
-                               filter9: false
+                       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 }
                        },
                        model = new mw.rcfilters.dm.FiltersViewModel();
 
                model.initializeFilters( definition );
+               // Select a filter that has subset with another filter
+               model.updateFilters( {
+                       filter1: true
+               } );
 
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
                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.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { included: true },
+                               filter5: { included: true }
+                       } ),
+                       'Filters with subsets are represented in the model.'
                );
 
-               // "Explicit" behavior for 'exclusion' with one item checked:
-               // - Items in the 'excluded' list of the selected filter are inactive
+               // Select another filter that has a subset with the same previous filter
                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
+                       filter4: true
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
                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.'
+                       $.extend( true, {}, baseFullState, {
+                               filter1: { selected: true },
+                               filter2: { included: true },
+                               filter3: { included: true },
+                               filter4: { selected: true },
+                               filter5: { included: true }
+                       } ),
+                       'Filters that have multiple subsets are represented.'
                );
 
-               // "Explicit" behavior for 'exclusion' with two item checked:
-               // - Items in the 'excluded' list of each of the selected filter are inactive
+               // Remove one filter (but leave the other) that affects filter2
                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
+                       filter1: false
                } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
                assert.deepEqual(
                        model.getFullState(),
-                       {
-                               filter1: { selected: true, active: false },
-                               filter2: { selected: false, active: false },
-                               filter3: { selected: true, active: false },
-                               filter4: { selected: false, active: true }
+                       $.extend( true, {}, baseFullState, {
+                               filter2: { included: false },
+                               filter3: { included: true },
+                               filter4: { selected: true },
+                               filter5: { included: true }
+                       } ),
+                       'Removing a filter only un-includes its subset if there is no other filter affecting.'
+               );
+
+               model.updateFilters( {
+                       filter4: false
+               } );
+               model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
+               assert.deepEqual(
+                       model.getFullState(),
+                       baseFullState,
+                       'Removing all supersets also un-includes the subsets.'
+               );
+       } );
+
+       QUnit.test( 'Filter interaction: full coverage', function ( assert ) {
+               var definition = {
+                               group1: {
+                                       title: 'Group 1',
+                                       type: 'string_options',
+                                       fullCoverage: false,
+                                       filters: [
+                                               { name: 'filter1' },
+                                               { name: 'filter2' },
+                                               { name: 'filter3' }
+                                       ]
+                               },
+                               group2: {
+                                       title: 'Group 2',
+                                       type: 'send_unselected_if_any',
+                                       fullCoverage: true,
+                                       filters: [
+                                               { name: 'filter4' },
+                                               { name: 'filter5' },
+                                               { name: 'filter6' }
+                                       ]
+                               }
+                       },
+                       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()
+                               );
+                       },
+                       getCurrentItemsMutedState = function () {
+                               return {
+                                       filter1: isCapsuleItemMuted( 'filter1' ),
+                                       filter2: isCapsuleItemMuted( 'filter2' ),
+                                       filter3: isCapsuleItemMuted( 'filter3' ),
+                                       filter4: isCapsuleItemMuted( 'filter4' ),
+                                       filter5: isCapsuleItemMuted( 'filter5' ),
+                                       filter6: isCapsuleItemMuted( 'filter6' )
+                               };
                        },
-                       '"Explicit" exclusion behavior with two selected items that both have an exclusion list.'
+                       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(
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'No selection - all items are non-muted'
                );
 
-               // "Explicit behavior" with two filters that exclude the same item
+               // Select most (but not all) items in each group
+               model.updateFilters( {
+                       filter1: true,
+                       filter2: true,
+                       filter4: true,
+                       filter5: true
+               } );
 
-               // Two filters selected, both exclude 'hidefilter3'
+               // Both groups have multiple (but not all) items selected, all items are non-muted
+               assert.deepEqual(
+                       getCurrentItemsMutedState(),
+                       baseMuteState,
+                       'Not all items in the group selected - all items are non-muted'
+               );
+
+               // Select all items in 'fullCoverage' group (group2)
                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
+                       filter6: true
                } );
+
+               // Group2 (full coverage) has all items selected, all its items are muted
                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(),
+                       $.extend( {}, baseMuteState, {
+                               filter4: true,
+                               filter5: true,
+                               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.updateFilters( {
+                       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
+                       } ),
+                       '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.updateFilters( {
+                       filter3: false,
+                       filter5: false
+               } );
+               assert.deepEqual(
+                       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 = {
+                               group1: {
+                                       title: 'Group 1',
+                                       type: 'string_options',
+                                       filters: [
+                                               {
+                                                       name: 'filter1',
+                                                       conflicts: [ 'filter2', 'filter4' ]
+                                               },
+                                               {
+                                                       name: 'filter2',
+                                                       conflicts: [ 'filter6' ]
+                                               },
+                                               {
+                                                       name: 'filter3'
+                                               }
+                                       ]
+                               },
+                               group2: {
+                                       title: 'Group 2',
+                                       type: 'send_unselected_if_any',
+                                       filters: [
+                                               {
+                                                       name: 'filter4'
+                                               },
+                                               {
+                                                       name: 'filter5',
+                                                       conflicts: [ 'filter3' ]
+                                               },
+                                               {
+                                                       name: 'filter6'
+                                               }
+                                       ]
+                               }
+                       },
+                       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
+               // Select a filter that has a conflict with another
                model.updateFilters( {
-                       filter2: false, // Excludes 'hidefilter3'
+                       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
+               // Select one of the conflicts (both filters are now conflicted and selected)
                model.updateFilters( {
-                       filter1: false, // Excludes 'hidefilter3' and 'hidefilter2'
+                       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.updateFilters( {
+                       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'
+               );
        } );
 }( mediaWiki, jQuery ) );