1 /* eslint-disable camelcase */
3 var filterDefinition
= [ {
5 type
: 'send_unselected_if_any',
8 name
: 'filter1', label
: 'group1filter1-label', description
: 'group1filter1-desc',
10 conflicts
: [ { group
: 'group2' } ],
23 name
: 'filter2', label
: 'group1filter2-label', description
: 'group1filter2-desc',
24 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ],
32 { name
: 'filter3', label
: 'group1filter3-label', description
: 'group1filter3-desc', default: true }
36 type
: 'send_unselected_if_any',
38 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
40 { name
: 'filter4', label
: 'group2filter4-label', description
: 'group2filter4-desc' },
41 { name
: 'filter5', label
: 'group2filter5-label', description
: 'group2filter5-desc', default: true },
43 name
: 'filter6', label
: 'group2filter6-label', description
: 'group2filter6-desc',
44 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
49 type
: 'string_options',
53 { name
: 'filter7', label
: 'group3filter7-label', description
: 'group3filter7-desc' },
54 { name
: 'filter8', label
: 'group3filter8-label', description
: 'group3filter8-desc' },
55 { name
: 'filter9', label
: 'group3filter9-label', description
: 'group3filter9-desc' }
59 type
: 'single_option',
62 { name
: 'option1', label
: 'group4option1-label', description
: 'group4option1-desc' },
63 { name
: 'option2', label
: 'group4option2-label', description
: 'group4option2-desc' },
64 { name
: 'option3', label
: 'group4option3-label', description
: 'group4option3-desc' }
74 type
: 'string_options',
77 { name
: 0, label
: 'Main' },
78 { name
: 1, label
: 'Talk' },
79 { name
: 2, label
: 'User' },
80 { name
: 3, label
: 'User talk' }
96 baseParamRepresentation
= {
107 baseFilterRepresentation
= {
108 group1__filter1
: false,
109 group1__filter2
: false,
110 group1__filter3
: false,
111 group2__filter4
: false,
112 group2__filter5
: false,
113 group2__filter6
: false,
114 group3__filter7
: false,
115 group3__filter8
: false,
116 group3__filter9
: false,
117 group4__option1
: false,
118 group4__option2
: false,
119 group4__option3
: false,
125 baseFullFilterState
= {
126 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
127 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
128 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
129 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
130 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
131 group2__filter6
: { selected
: false, conflicted
: false, included
: false },
132 group3__filter7
: { selected
: false, conflicted
: false, included
: false },
133 group3__filter8
: { selected
: false, conflicted
: false, included
: false },
134 group3__filter9
: { selected
: false, conflicted
: false, included
: false },
135 group4__option1
: { selected
: false, conflicted
: false, included
: false },
136 group4__option2
: { selected
: false, conflicted
: false, included
: false },
137 group4__option3
: { selected
: false, conflicted
: false, included
: false },
138 namespace__0
: { selected
: false, conflicted
: false, included
: false },
139 namespace__1
: { selected
: false, conflicted
: false, included
: false },
140 namespace__2
: { selected
: false, conflicted
: false, included
: false },
141 namespace__3
: { selected
: false, conflicted
: false, included
: false }
144 QUnit
.module( 'mediawiki.rcfilters - FiltersViewModel', QUnit
.newMwEnvironment( {
146 'group1filter1-label': 'Group 1: Filter 1 title',
147 'group1filter1-desc': 'Description of Filter 1 in Group 1',
148 'group1filter2-label': 'Group 1: Filter 2 title',
149 'group1filter2-desc': 'Description of Filter 2 in Group 1',
150 'group1filter3-label': 'Group 1: Filter 3',
151 'group1filter3-desc': 'Description of Filter 3 in Group 1',
153 'group2filter4-label': 'Group 2: Filter 4 title',
154 'group2filter4-desc': 'Description of Filter 4 in Group 2',
155 'group2filter5-label': 'Group 2: Filter 5',
156 'group2filter5-desc': 'Description of Filter 5 in Group 2',
157 'group2filter6-label': 'xGroup 2: Filter 6',
158 'group2filter6-desc': 'Description of Filter 6 in Group 2'
161 wgStructuredChangeFiltersEnableExperimentalViews
: true
165 QUnit
.test( 'Setting up filters', function ( assert
) {
166 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
168 model
.initializeFilters( filterDefinition
, viewsDefinition
);
170 // Test that all items were created
172 Object
.keys( baseFilterRepresentation
).every( function ( filterName
) {
173 return model
.getItemByName( filterName
) instanceof mw
.rcfilters
.dm
.FilterItem
;
175 'Filters instantiated and stored correctly'
179 model
.getSelectedState(),
180 baseFilterRepresentation
,
181 'Initial state of filters'
184 model
.toggleFiltersSelected( {
185 group1__filter1
: true,
186 group2__filter5
: true,
187 group3__filter7
: true
190 model
.getSelectedState(),
191 $.extend( true, {}, baseFilterRepresentation
, {
192 group1__filter1
: true,
193 group2__filter5
: true,
194 group3__filter7
: true
196 'Updating filter states correctly'
200 QUnit
.test( 'Default filters', function ( assert
) {
201 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
203 model
.initializeFilters( filterDefinition
, viewsDefinition
);
205 // Empty query = only default values
207 model
.getDefaultParams(),
209 'Default parameters are stored properly per filter and group'
213 QUnit
.test( 'Finding matching filters', function ( assert
) {
219 group1
: [ 'group1__filter1', 'group1__filter2', 'group1__filter3' ],
220 group2
: [ 'group2__filter4', 'group2__filter5' ]
222 reason
: 'Finds filters starting with the query string'
227 group2
: [ 'group2__filter4', 'group2__filter5', 'group2__filter6' ]
229 reason
: 'Finds filters containing the query string in their description'
234 group1
: [ 'group1__filter1', 'group1__filter2' ],
235 group2
: [ 'group2__filter4' ]
237 reason
: 'Finds filters containing the query string in their group title'
242 namespace: [ 'namespace__0' ]
244 reason
: 'Finds item in view when a prefix is used'
249 reason
: 'Finds no results if using namespaces prefix (:) to search for filter title'
252 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
253 extractNames = function ( matches
) {
255 Object
.keys( matches
).forEach( function ( groupName
) {
256 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
257 return item
.getName();
263 model
.initializeFilters( filterDefinition
, viewsDefinition
);
265 testCases
.forEach( function ( testCase
) {
266 matches
= model
.findMatches( testCase
.query
);
268 extractNames( matches
),
269 testCase
.expectedMatches
,
274 matches
= model
.findMatches( 'foo' );
276 $.isEmptyObject( matches
),
277 'findMatches returns an empty object when no results found'
281 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
282 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
284 model
.initializeFilters( filterDefinition
, viewsDefinition
);
286 // Starting with all filters unselected
288 model
.getParametersFromFilters(),
289 baseParamRepresentation
,
290 'Unselected filters return all parameters falsey or \'\'.'
294 model
.toggleFiltersSelected( {
295 group1__filter1
: true
297 // Only one filter in one group
299 model
.getParametersFromFilters(),
300 $.extend( true, {}, baseParamRepresentation
, {
301 // Group 1 (one selected, the others are true)
305 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
309 model
.toggleFiltersSelected( {
310 group1__filter1
: true,
311 group1__filter2
: true
313 // Two selected filters in one group
315 model
.getParametersFromFilters(),
316 $.extend( true, {}, baseParamRepresentation
, {
317 // Group 1 (two selected, the other is true)
320 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
324 model
.toggleFiltersSelected( {
325 group1__filter1
: true,
326 group1__filter2
: true,
327 group1__filter3
: true
329 // All filters of the group are selected == this is the same as not selecting any
331 model
.getParametersFromFilters(),
332 baseParamRepresentation
,
333 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
336 // Select 1 filter from string_options
337 model
.toggleFiltersSelected( {
338 group3__filter7
: true,
339 group3__filter8
: false,
340 group3__filter9
: false
342 // All filters of the group are selected == this is the same as not selecting any
344 model
.getParametersFromFilters(),
345 $.extend( true, {}, baseParamRepresentation
, {
348 'One filter selected in "string_option" group returns that filter in the value.'
351 // Select 2 filters from string_options
352 model
.toggleFiltersSelected( {
353 group3__filter7
: true,
354 group3__filter8
: true,
355 group3__filter9
: false
357 // All filters of the group are selected == this is the same as not selecting any
359 model
.getParametersFromFilters(),
360 $.extend( true, {}, baseParamRepresentation
, {
361 group3
: 'filter7,filter8'
363 'Two filters selected in "string_option" group returns those filters in the value.'
366 // Select 3 filters from string_options
367 model
.toggleFiltersSelected( {
368 group3__filter7
: true,
369 group3__filter8
: true,
370 group3__filter9
: true
372 // All filters of the group are selected == this is the same as not selecting any
374 model
.getParametersFromFilters(),
375 $.extend( true, {}, baseParamRepresentation
, {
378 'All filters selected in "string_option" group returns \'all\'.'
382 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
383 model
.initializeFilters( filterDefinition
, viewsDefinition
);
385 // Select an option from single_option group
386 model
.toggleFiltersSelected( {
387 group4__option2
: true
389 // All filters of the group are selected == this is the same as not selecting any
391 model
.getParametersFromFilters(),
392 $.extend( true, {}, baseParamRepresentation
, {
395 'Selecting an option from "single_option" group returns that option as a value.'
398 // Select a different option from single_option group
399 model
.toggleFiltersSelected( {
400 group4__option3
: true
402 // All filters of the group are selected == this is the same as not selecting any
404 model
.getParametersFromFilters(),
405 $.extend( true, {}, baseParamRepresentation
, {
408 'Selecting a different option from "single_option" group changes the selection.'
412 QUnit
.test( 'getParametersFromFilters (custom object)', function ( assert
) {
413 // This entire test uses different base definition than the global one
414 // on purpose, to verify that the values inserted as a custom object
415 // are the ones we expect in return
417 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
421 type
: 'send_unselected_if_any',
423 { name
: 'hidefilter1', label
: 'Hide filter 1', description
: '' },
424 { name
: 'hidefilter2', label
: 'Hide filter 2', description
: '' },
425 { name
: 'hidefilter3', label
: 'Hide filter 3', description
: '' }
430 type
: 'send_unselected_if_any',
432 { name
: 'hidefilter4', label
: 'Hide filter 4', description
: '' },
433 { name
: 'hidefilter5', label
: 'Hide filter 5', description
: '' },
434 { name
: 'hidefilter6', label
: 'Hide filter 6', description
: '' }
439 type
: 'string_options',
442 { name
: 'filter7', label
: 'Hide filter 7', description
: '' },
443 { name
: 'filter8', label
: 'Hide filter 8', description
: '' },
444 { name
: 'filter9', label
: 'Hide filter 9', description
: '' }
449 type
: 'single_option',
451 { name
: 'filter10', label
: 'Hide filter 10', description
: '' },
452 { name
: 'filter11', label
: 'Hide filter 11', description
: '' },
453 { name
: 'filter12', label
: 'Hide filter 12', description
: '' }
468 // This is mocking the cases above, both
469 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
470 // - 'Two filters selected in "string_option" group returns those filters in the value.'
472 group1__hidefilter1
: true,
473 group1__hidefilter2
: true,
474 group1__hidefilter3
: false,
475 group2__hidefilter4
: false,
476 group2__hidefilter5
: false,
477 group2__hidefilter6
: false,
478 group3__filter7
: true,
479 group3__filter8
: true,
480 group3__filter9
: false
482 expected
: $.extend( true, {}, baseResult
, {
483 // Group 1 (two selected, the others are true)
485 // Group 3 (two selected)
486 group3
: 'filter7,filter8'
488 msg
: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
491 // This is mocking case above
492 // - 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
494 group1__hidefilter1
: 1
496 expected
: $.extend( true, {}, baseResult
, {
497 // Group 1 (one selected, the others are true)
501 msg
: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
505 group4__filter10
: true
507 expected
: $.extend( true, {}, baseResult
, {
510 msg
: 'Given a single value for "single_option" that option is represented in the result.'
514 group4__filter10
: true,
515 group4__filter11
: true
517 expected
: $.extend( true, {}, baseResult
, {
520 msg
: 'Given more than one true value for "single_option" (which should not happen!) only the first value counts, and the second is ignored.'
524 expected
: baseResult
,
525 msg
: 'Given an explicit empty object, the result is all filters set to their falsey unselected value.'
529 model
.initializeFilters( definition
);
530 // Store original state
531 originalState
= model
.getSelectedState();
534 cases
.forEach( function ( test
) {
536 model
.getParametersFromFilters( test
.input
),
542 // After doing the above tests, make sure the actual state
543 // of the filter stayed the same
545 model
.getSelectedState(),
547 'Running the method with external definition to parse does not actually change the state of the model'
551 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
552 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
554 model
.initializeFilters( filterDefinition
, viewsDefinition
);
556 // Empty query = only default values
558 model
.getFiltersFromParameters( {} ),
559 baseFilterRepresentation
,
560 'Empty parameter query results in an object representing all filters set to false'
564 model
.getFiltersFromParameters( {
567 $.extend( {}, baseFilterRepresentation
, {
568 group1__filter1
: true, // The text is "show filter 1"
569 group1__filter2
: false, // The text is "show filter 2"
570 group1__filter3
: true // The text is "show filter 3"
572 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
576 model
.getFiltersFromParameters( {
581 $.extend( {}, baseFilterRepresentation
, {
582 group1__filter1
: false, // The text is "show filter 1"
583 group1__filter2
: false, // The text is "show filter 2"
584 group1__filter3
: false // The text is "show filter 3"
586 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
589 // The ones above don't update the model, so we have a clean state.
590 // getFiltersFromParameters is stateless; any change is unaffected by the current state
591 // This test is demonstrating wrong usage of the method;
592 // We should be aware that getFiltersFromParameters is stateless,
593 // so each call gives us a filter state that only reflects the query given.
594 // This means that the two calls to toggleFiltersSelected() below collide.
595 // The result of the first is overridden by the result of the second,
596 // since both get a full state object from getFiltersFromParameters that **only** relates
597 // to the input it receives.
598 model
.toggleFiltersSelected(
599 model
.getFiltersFromParameters( {
604 model
.toggleFiltersSelected(
605 model
.getFiltersFromParameters( {
610 // The result here is ignoring the first toggleFiltersSelected call
612 model
.getSelectedState(),
613 $.extend( {}, baseFilterRepresentation
, {
614 group2__filter4
: true,
615 group2__filter5
: true,
616 group2__filter6
: false
618 'getFiltersFromParameters does not care about previous or existing state.'
622 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
623 model
.initializeFilters( filterDefinition
, viewsDefinition
);
625 model
.toggleFiltersSelected(
626 model
.getFiltersFromParameters( {
631 model
.getSelectedState(),
632 $.extend( {}, baseFilterRepresentation
, {
633 group3__filter7
: true,
634 group3__filter8
: false,
635 group3__filter9
: false
637 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
640 model
.toggleFiltersSelected(
641 model
.getFiltersFromParameters( {
642 group3
: 'filter7,filter8'
646 model
.getSelectedState(),
647 $.extend( {}, baseFilterRepresentation
, {
648 group3__filter7
: true,
649 group3__filter8
: true,
650 group3__filter9
: false
652 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
655 model
.toggleFiltersSelected(
656 model
.getFiltersFromParameters( {
657 group3
: 'filter7,filter8,filter9'
661 model
.getSelectedState(),
662 $.extend( {}, baseFilterRepresentation
, {
663 group3__filter7
: true,
664 group3__filter8
: true,
665 group3__filter9
: true
667 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
670 model
.toggleFiltersSelected(
671 model
.getFiltersFromParameters( {
672 group3
: 'filter7,all,filter9'
676 model
.getSelectedState(),
677 $.extend( {}, baseFilterRepresentation
, {
678 group3__filter7
: true,
679 group3__filter8
: true,
680 group3__filter9
: true
682 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
685 model
.toggleFiltersSelected(
686 model
.getFiltersFromParameters( {
687 group3
: 'filter7,foo,filter9'
691 model
.getSelectedState(),
692 $.extend( {}, baseFilterRepresentation
, {
693 group3__filter7
: true,
694 group3__filter8
: false,
695 group3__filter9
: true
697 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
700 model
.toggleFiltersSelected(
701 model
.getFiltersFromParameters( {
706 model
.getSelectedState(),
707 $.extend( {}, baseFilterRepresentation
, {
708 group4__option1
: true
710 'A \'single_option\' parameter reflects a single selected value.'
714 model
.getFiltersFromParameters( {
715 group4
: 'option1,option2'
717 baseFilterRepresentation
,
718 'An invalid \'single_option\' parameter is ignored.'
721 // Change to one value
722 model
.toggleFiltersSelected(
723 model
.getFiltersFromParameters( {
727 // Change again to another value
728 model
.toggleFiltersSelected(
729 model
.getFiltersFromParameters( {
734 model
.getSelectedState(),
735 $.extend( {}, baseFilterRepresentation
, {
736 group4__option2
: true
738 'A \'single_option\' parameter always reflects the latest selected value.'
742 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
743 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
745 model
.initializeFilters( filterDefinition
, viewsDefinition
);
748 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
749 [ 'filter1', 'filter2' ],
750 'Remove duplicate values'
754 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
755 [ 'filter1', 'filter2' ],
756 'Remove invalid values'
760 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
762 'If any value is "all", the only value is "all".'
766 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
767 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
769 model
.initializeFilters( filterDefinition
, viewsDefinition
);
771 // Select a filter that has subset with another filter
772 model
.toggleFiltersSelected( {
773 group1__filter1
: true
776 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
778 model
.getFullState(),
779 $.extend( true, {}, baseFullFilterState
, {
780 group1__filter1
: { selected
: true },
781 group1__filter2
: { included
: true },
782 group1__filter3
: { included
: true },
783 // Conflicts are affected
784 group2__filter4
: { conflicted
: true },
785 group2__filter5
: { conflicted
: true },
786 group2__filter6
: { conflicted
: true }
788 'Filters with subsets are represented in the model.'
791 // Select another filter that has a subset with the same previous filter
792 model
.toggleFiltersSelected( {
793 group1__filter2
: true
795 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
797 model
.getFullState(),
798 $.extend( true, {}, baseFullFilterState
, {
799 group1__filter1
: { selected
: true },
800 group1__filter2
: { selected
: true, included
: true },
801 group1__filter3
: { included
: true },
802 // Conflicts are affected
803 group2__filter6
: { conflicted
: true }
805 'Filters that have multiple subsets are represented.'
808 // Remove one filter (but leave the other) that affects filter3
809 model
.toggleFiltersSelected( {
810 group1__filter1
: false
812 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
814 model
.getFullState(),
815 $.extend( true, {}, baseFullFilterState
, {
816 group1__filter2
: { selected
: true, included
: false },
817 group1__filter3
: { included
: true },
818 // Conflicts are affected
819 group2__filter6
: { conflicted
: true }
821 'Removing a filter only un-includes its subset if there is no other filter affecting.'
824 model
.toggleFiltersSelected( {
825 group1__filter2
: false
827 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
829 model
.getFullState(),
831 'Removing all supersets also un-includes the subsets.'
835 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
836 var model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
837 isCapsuleItemMuted = function ( filterName
) {
838 var itemModel
= model
.getItemByName( filterName
),
839 groupModel
= itemModel
.getGroupModel();
841 // This is the logic inside the capsule widget
843 // The capsule item widget only appears if the item is selected
844 itemModel
.isSelected() &&
845 // Muted state is only valid if group is full coverage and all items are selected
846 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
849 getCurrentItemsMutedState = function () {
851 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
852 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
853 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
854 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
855 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
856 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
860 group1__filter1
: false,
861 group1__filter2
: false,
862 group1__filter3
: false,
863 group2__filter4
: false,
864 group2__filter5
: false,
865 group2__filter6
: false
868 model
.initializeFilters( filterDefinition
, viewsDefinition
);
870 // Starting state, no selection, all items are non-muted
872 getCurrentItemsMutedState(),
874 'No selection - all items are non-muted'
877 // Select most (but not all) items in each group
878 model
.toggleFiltersSelected( {
879 group1__filter1
: true,
880 group1__filter2
: true,
881 group2__filter4
: true,
882 group2__filter5
: true
885 // Both groups have multiple (but not all) items selected, all items are non-muted
887 getCurrentItemsMutedState(),
889 'Not all items in the group selected - all items are non-muted'
892 // Select all items in 'fullCoverage' group (group2)
893 model
.toggleFiltersSelected( {
894 group2__filter6
: true
897 // Group2 (full coverage) has all items selected, all its items are muted
899 getCurrentItemsMutedState(),
900 $.extend( {}, baseMuteState
, {
901 group2__filter4
: true,
902 group2__filter5
: true,
903 group2__filter6
: true
905 'All items in \'full coverage\' group are selected - all items in the group are muted'
908 // Select all items in non 'fullCoverage' group (group1)
909 model
.toggleFiltersSelected( {
910 group1__filter3
: true
913 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
915 getCurrentItemsMutedState(),
916 $.extend( {}, baseMuteState
, {
917 group2__filter4
: true,
918 group2__filter5
: true,
919 group2__filter6
: true
921 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
924 // Uncheck an item from each group
925 model
.toggleFiltersSelected( {
926 group1__filter3
: false,
927 group2__filter5
: false
930 getCurrentItemsMutedState(),
932 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
936 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
937 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
939 model
.initializeFilters( filterDefinition
, viewsDefinition
);
942 model
.getFullState(),
944 'Initial state: no conflicts because no selections.'
947 // Select a filter that has a conflict with an entire group
948 model
.toggleFiltersSelected( {
949 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
952 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
955 model
.getFullState(),
956 $.extend( true, {}, baseFullFilterState
, {
957 group1__filter1
: { selected
: true },
958 group2__filter4
: { conflicted
: true },
959 group2__filter5
: { conflicted
: true },
960 group2__filter6
: { conflicted
: true },
961 // Subsets are affected by the selection
962 group1__filter2
: { included
: true },
963 group1__filter3
: { included
: true }
965 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
968 // Select one of the conflicts (both filters are now conflicted and selected)
969 model
.toggleFiltersSelected( {
970 group2__filter4
: true // conflicts: filter 1
972 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
975 model
.getFullState(),
976 $.extend( true, {}, baseFullFilterState
, {
977 group1__filter1
: { selected
: true, conflicted
: true },
978 group2__filter4
: { selected
: true, conflicted
: true },
979 group2__filter5
: { conflicted
: true },
980 group2__filter6
: { conflicted
: true },
981 // Subsets are affected by the selection
982 group1__filter2
: { included
: true },
983 group1__filter3
: { included
: true }
985 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
989 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
990 model
.initializeFilters( filterDefinition
, viewsDefinition
);
992 // Select a filter that has a conflict with a specific filter
993 model
.toggleFiltersSelected( {
994 group1__filter2
: true // conflicts: filter6
996 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
999 model
.getFullState(),
1000 $.extend( true, {}, baseFullFilterState
, {
1001 group1__filter2
: { selected
: true },
1002 group2__filter6
: { conflicted
: true },
1003 // Subsets are affected by the selection
1004 group1__filter3
: { included
: true }
1006 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1009 // Select the conflicting filter
1010 model
.toggleFiltersSelected( {
1011 group2__filter6
: true // conflicts: filter2
1014 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1017 model
.getFullState(),
1018 $.extend( true, {}, baseFullFilterState
, {
1019 group1__filter2
: { selected
: true, conflicted
: true },
1020 group2__filter6
: { selected
: true, conflicted
: true },
1021 // This is added to the conflicts because filter6 is part of group2,
1022 // who is in conflict with filter1; note that filter2 also conflicts
1023 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1024 // and also because its **own sibling** (filter2) is **also** in conflict with the
1025 // selected items in group2 (filter6)
1026 group1__filter1
: { conflicted
: true },
1028 // Subsets are affected by the selection
1029 group1__filter3
: { included
: true }
1031 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1034 // Now choose a non-conflicting filter from the group
1035 model
.toggleFiltersSelected( {
1036 group2__filter5
: true
1039 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1042 model
.getFullState(),
1043 $.extend( true, {}, baseFullFilterState
, {
1044 group1__filter2
: { selected
: true },
1045 group2__filter6
: { selected
: true },
1046 group2__filter5
: { selected
: true },
1047 // Filter6 and filter1 are no longer in conflict because
1048 // filter5, while it is in conflict with filter1, it is
1049 // not in conflict with filter2 - and since filter2 is
1050 // selected, it removes the conflict bidirectionally
1052 // Subsets are affected by the selection
1053 group1__filter3
: { included
: true }
1055 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1058 // Followup on the previous test, unselect filter2 so filter1
1059 // is now the only one selected in its own group, and since
1060 // it is in conflict with the entire of group2, it means
1061 // filter1 is once again conflicted
1062 model
.toggleFiltersSelected( {
1063 group1__filter2
: false
1066 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1069 model
.getFullState(),
1070 $.extend( true, {}, baseFullFilterState
, {
1071 group1__filter1
: { conflicted
: true },
1072 group2__filter6
: { selected
: true },
1073 group2__filter5
: { selected
: true }
1075 'Unselecting an item that did not conflict returns the conflict state.'
1078 // Followup #2: Now actually select filter1, and make everything conflicted
1079 model
.toggleFiltersSelected( {
1080 group1__filter1
: true
1083 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1086 model
.getFullState(),
1087 $.extend( true, {}, baseFullFilterState
, {
1088 group1__filter1
: { selected
: true, conflicted
: true },
1089 group2__filter6
: { selected
: true, conflicted
: true },
1090 group2__filter5
: { selected
: true, conflicted
: true },
1091 group2__filter4
: { conflicted
: true }, // Not selected but conflicted because it's in group2
1092 // Subsets are affected by the selection
1093 group1__filter2
: { included
: true },
1094 group1__filter3
: { included
: true }
1096 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1101 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1102 model
.initializeFilters( filterDefinition
, viewsDefinition
);
1104 // Select a filter that has a conflict with a specific filter
1105 model
.toggleFiltersSelected( {
1106 group1__filter2
: true // conflicts: filter6
1109 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1112 model
.getFullState(),
1113 $.extend( true, {}, baseFullFilterState
, {
1114 group1__filter2
: { selected
: true },
1115 group2__filter6
: { conflicted
: true },
1116 // Subsets are affected by the selection
1117 group1__filter3
: { included
: true }
1119 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1122 model
.toggleFiltersSelected( {
1123 group1__filter3
: true // conflicts: filter6
1126 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1129 model
.getFullState(),
1130 $.extend( true, {}, baseFullFilterState
, {
1131 group1__filter2
: { selected
: true },
1132 // Subsets are affected by the selection
1133 group1__filter3
: { selected
: true, included
: true }
1135 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1139 QUnit
.test( 'Filter highlights', function ( assert
) {
1140 // We are using a different (smaller) definition here than the global one
1141 var definition
= [ {
1144 type
: 'string_options',
1146 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1147 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1148 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1149 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1150 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1151 { name
: 'filter6', label
: '6', description
: '6' }
1154 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1156 model
.initializeFilters( definition
);
1159 !model
.isHighlightEnabled(),
1160 'Initially, highlight is disabled.'
1163 model
.toggleHighlight( true );
1165 model
.isHighlightEnabled(),
1166 'Highlight is enabled on toggle.'
1169 model
.setHighlightColor( 'group1__filter1', 'color1' );
1170 model
.setHighlightColor( 'group1__filter2', 'color2' );
1173 model
.getHighlightedItems().map( function ( item
) {
1174 return item
.getName();
1180 'Highlighted items are highlighted.'
1184 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1186 'Item highlight color is set.'
1189 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1191 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1193 'Item highlight color is changed on setHighlightColor.'
1196 model
.clearHighlightColor( 'group1__filter1' );
1198 model
.getHighlightedItems().map( function ( item
) {
1199 return item
.getName();
1204 'Clear highlight from an item results in the item no longer being highlighted.'
1208 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1209 model
.initializeFilters( definition
);
1211 model
.setHighlightColor( 'group1__filter1', 'color1' );
1212 model
.setHighlightColor( 'group1__filter2', 'color2' );
1213 model
.setHighlightColor( 'group1__filter3', 'color3' );
1216 model
.getHighlightedItems().map( function ( item
) {
1217 return item
.getName();
1224 'Even if highlights are not enabled, the items remember their highlight state'
1225 // NOTE: When actually displaying the highlights, the UI checks whether
1226 // highlighting is generally active and then goes over the highlighted
1227 // items. The item models, however, and the view model in general, still
1228 // retains the knowledge about which filters have different colors, so we
1229 // can seamlessly return to the colors the user previously chose if they
1230 // reapply highlights.
1234 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1235 model
.initializeFilters( definition
);
1237 model
.setHighlightColor( 'group1__filter1', 'color1' );
1238 model
.setHighlightColor( 'group1__filter6', 'color6' );
1241 model
.getHighlightedItems().map( function ( item
) {
1242 return item
.getName();
1247 'Items without a specified class identifier are not highlighted.'
1250 }( mediaWiki
, jQuery
) );