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' }
65 type
: 'string_options',
68 { name
: 0, label
: 'Main' },
69 { name
: 1, label
: 'Talk' },
70 { name
: 2, label
: 'User' },
71 { name
: 3, label
: 'User talk' }
86 baseParamRepresentation
= {
96 baseFilterRepresentation
= {
97 group1__filter1
: false,
98 group1__filter2
: false,
99 group1__filter3
: false,
100 group2__filter4
: false,
101 group2__filter5
: false,
102 group2__filter6
: false,
103 group3__filter7
: false,
104 group3__filter8
: false,
105 group3__filter9
: false,
111 baseFullFilterState
= {
112 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
113 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
114 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
115 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
116 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
117 group2__filter6
: { selected
: false, conflicted
: false, included
: false },
118 group3__filter7
: { selected
: false, conflicted
: false, included
: false },
119 group3__filter8
: { selected
: false, conflicted
: false, included
: false },
120 group3__filter9
: { selected
: false, conflicted
: false, included
: false },
121 namespace__0
: { selected
: false, conflicted
: false, included
: false },
122 namespace__1
: { selected
: false, conflicted
: false, included
: false },
123 namespace__2
: { selected
: false, conflicted
: false, included
: false },
124 namespace__3
: { selected
: false, conflicted
: false, included
: false }
127 QUnit
.module( 'mediawiki.rcfilters - FiltersViewModel', QUnit
.newMwEnvironment( {
129 'group1filter1-label': 'Group 1: Filter 1 title',
130 'group1filter1-desc': 'Description of Filter 1 in Group 1',
131 'group1filter2-label': 'Group 1: Filter 2 title',
132 'group1filter2-desc': 'Description of Filter 2 in Group 1',
133 'group1filter3-label': 'Group 1: Filter 3',
134 'group1filter3-desc': 'Description of Filter 3 in Group 1',
136 'group2filter4-label': 'Group 2: Filter 4 title',
137 'group2filter4-desc': 'Description of Filter 4 in Group 2',
138 'group2filter5-label': 'Group 2: Filter 5',
139 'group2filter5-desc': 'Description of Filter 5 in Group 2',
140 'group2filter6-label': 'xGroup 2: Filter 6',
141 'group2filter6-desc': 'Description of Filter 6 in Group 2'
144 wgStructuredChangeFiltersEnableExperimentalViews
: true
148 QUnit
.test( 'Setting up filters', function ( assert
) {
149 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
151 model
.initializeFilters( filterDefinition
, viewsDefinition
);
153 // Test that all items were created
155 Object
.keys( baseFilterRepresentation
).every( function ( filterName
) {
156 return model
.getItemByName( filterName
) instanceof mw
.rcfilters
.dm
.FilterItem
;
158 'Filters instantiated and stored correctly'
162 model
.getSelectedState(),
163 baseFilterRepresentation
,
164 'Initial state of filters'
167 model
.toggleFiltersSelected( {
168 group1__filter1
: true,
169 group2__filter5
: true,
170 group3__filter7
: true
173 model
.getSelectedState(),
174 $.extend( true, {}, baseFilterRepresentation
, {
175 group1__filter1
: true,
176 group2__filter5
: true,
177 group3__filter7
: true
179 'Updating filter states correctly'
183 QUnit
.test( 'Default filters', function ( assert
) {
184 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
186 model
.initializeFilters( filterDefinition
, viewsDefinition
);
188 // Empty query = only default values
190 model
.getDefaultParams(),
192 'Default parameters are stored properly per filter and group'
196 QUnit
.test( 'Finding matching filters', function ( assert
) {
202 group1
: [ 'group1__filter1', 'group1__filter2', 'group1__filter3' ],
203 group2
: [ 'group2__filter4', 'group2__filter5' ]
205 reason
: 'Finds filters starting with the query string'
210 group2
: [ 'group2__filter4', 'group2__filter5', 'group2__filter6' ]
212 reason
: 'Finds filters containing the query string in their description'
217 group1
: [ 'group1__filter1', 'group1__filter2' ],
218 group2
: [ 'group2__filter4' ]
220 reason
: 'Finds filters containing the query string in their group title'
225 namespace: [ 'namespace__0' ]
227 reason
: 'Finds item in view when a prefix is used'
232 reason
: 'Finds no results if using namespaces prefix (:) to search for filter title'
235 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
236 extractNames = function ( matches
) {
238 Object
.keys( matches
).forEach( function ( groupName
) {
239 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
240 return item
.getName();
246 model
.initializeFilters( filterDefinition
, viewsDefinition
);
248 testCases
.forEach( function ( testCase
) {
249 matches
= model
.findMatches( testCase
.query
);
251 extractNames( matches
),
252 testCase
.expectedMatches
,
257 matches
= model
.findMatches( 'foo' );
259 $.isEmptyObject( matches
),
260 'findMatches returns an empty object when no results found'
264 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
265 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
267 model
.initializeFilters( filterDefinition
, viewsDefinition
);
269 // Starting with all filters unselected
271 model
.getParametersFromFilters(),
272 baseParamRepresentation
,
273 'Unselected filters return all parameters falsey or \'\'.'
277 model
.toggleFiltersSelected( {
278 group1__filter1
: true
280 // Only one filter in one group
282 model
.getParametersFromFilters(),
283 $.extend( true, {}, baseParamRepresentation
, {
284 // Group 1 (one selected, the others are true)
288 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
292 model
.toggleFiltersSelected( {
293 group1__filter1
: true,
294 group1__filter2
: true
296 // Two selected filters in one group
298 model
.getParametersFromFilters(),
299 $.extend( true, {}, baseParamRepresentation
, {
300 // Group 1 (two selected, the other is true)
303 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
307 model
.toggleFiltersSelected( {
308 group1__filter1
: true,
309 group1__filter2
: true,
310 group1__filter3
: true
312 // All filters of the group are selected == this is the same as not selecting any
314 model
.getParametersFromFilters(),
315 baseParamRepresentation
,
316 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
319 // Select 1 filter from string_options
320 model
.toggleFiltersSelected( {
321 group3__filter7
: true,
322 group3__filter8
: false,
323 group3__filter9
: false
325 // All filters of the group are selected == this is the same as not selecting any
327 model
.getParametersFromFilters(),
328 $.extend( true, {}, baseParamRepresentation
, {
331 'One filter selected in "string_option" group returns that filter in the value.'
334 // Select 2 filters from string_options
335 model
.toggleFiltersSelected( {
336 group3__filter7
: true,
337 group3__filter8
: true,
338 group3__filter9
: false
340 // All filters of the group are selected == this is the same as not selecting any
342 model
.getParametersFromFilters(),
343 $.extend( true, {}, baseParamRepresentation
, {
344 group3
: 'filter7,filter8'
346 'Two filters selected in "string_option" group returns those filters in the value.'
349 // Select 3 filters from string_options
350 model
.toggleFiltersSelected( {
351 group3__filter7
: true,
352 group3__filter8
: true,
353 group3__filter9
: true
355 // All filters of the group are selected == this is the same as not selecting any
357 model
.getParametersFromFilters(),
358 $.extend( true, {}, baseParamRepresentation
, {
361 'All filters selected in "string_option" group returns \'all\'.'
365 QUnit
.test( 'getParametersFromFilters (custom object)', function ( assert
) {
366 // This entire test uses different base definition than the global one
367 // on purpose, to verify that the values inserted as a custom object
368 // are the ones we expect in return
370 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
374 type
: 'send_unselected_if_any',
376 { name
: 'hidefilter1', label
: 'Hide filter 1', description
: '' },
377 { name
: 'hidefilter2', label
: 'Hide filter 2', description
: '' },
378 { name
: 'hidefilter3', label
: 'Hide filter 3', description
: '' }
383 type
: 'send_unselected_if_any',
385 { name
: 'hidefilter4', label
: 'Hide filter 4', description
: '' },
386 { name
: 'hidefilter5', label
: 'Hide filter 5', description
: '' },
387 { name
: 'hidefilter6', label
: 'Hide filter 6', description
: '' }
392 type
: 'string_options',
395 { name
: 'filter7', label
: 'Hide filter 7', description
: '' },
396 { name
: 'filter8', label
: 'Hide filter 8', description
: '' },
397 { name
: 'filter9', label
: 'Hide filter 9', description
: '' }
402 // This is mocking the cases above, both
403 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
404 // - 'Two filters selected in "string_option" group returns those filters in the value.'
406 group1__hidefilter1
: true,
407 group1__hidefilter2
: true,
408 group1__hidefilter3
: false,
409 group2__hidefilter4
: false,
410 group2__hidefilter5
: false,
411 group2__hidefilter6
: false,
412 group3__filter7
: true,
413 group3__filter8
: true,
414 group3__filter9
: false
417 // Group 1 (two selected, the others are true)
421 // Group 2 (nothing is selected, all false)
425 group3
: 'filter7,filter8'
427 msg
: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
430 // This is mocking case above
431 // - 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
433 group1__hidefilter1
: 1
436 // Group 1 (one selected, the others are true)
440 // Group 2 (nothing is selected, all false)
446 msg
: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
459 msg
: 'Given an explicit empty object, the result is all filters set to their falsey unselected value.'
463 model
.initializeFilters( definition
);
464 // Store original state
465 originalState
= model
.getSelectedState();
468 cases
.forEach( function ( test
) {
470 model
.getParametersFromFilters( test
.input
),
476 // After doing the above tests, make sure the actual state
477 // of the filter stayed the same
479 model
.getSelectedState(),
481 'Running the method with external definition to parse does not actually change the state of the model'
485 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
486 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
488 model
.initializeFilters( filterDefinition
, viewsDefinition
);
490 // Empty query = only default values
492 model
.getFiltersFromParameters( {} ),
493 baseFilterRepresentation
,
494 'Empty parameter query results in an object representing all filters set to false'
498 model
.getFiltersFromParameters( {
501 $.extend( {}, baseFilterRepresentation
, {
502 group1__filter1
: true, // The text is "show filter 1"
503 group1__filter2
: false, // The text is "show filter 2"
504 group1__filter3
: true // The text is "show filter 3"
506 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
510 model
.getFiltersFromParameters( {
515 $.extend( {}, baseFilterRepresentation
, {
516 group1__filter1
: false, // The text is "show filter 1"
517 group1__filter2
: false, // The text is "show filter 2"
518 group1__filter3
: false // The text is "show filter 3"
520 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
523 // The ones above don't update the model, so we have a clean state.
524 // getFiltersFromParameters is stateless; any change is unaffected by the current state
525 // This test is demonstrating wrong usage of the method;
526 // We should be aware that getFiltersFromParameters is stateless,
527 // so each call gives us a filter state that only reflects the query given.
528 // This means that the two calls to toggleFiltersSelected() below collide.
529 // The result of the first is overridden by the result of the second,
530 // since both get a full state object from getFiltersFromParameters that **only** relates
531 // to the input it receives.
532 model
.toggleFiltersSelected(
533 model
.getFiltersFromParameters( {
538 model
.toggleFiltersSelected(
539 model
.getFiltersFromParameters( {
544 // The result here is ignoring the first toggleFiltersSelected call
546 model
.getSelectedState(),
547 $.extend( {}, baseFilterRepresentation
, {
548 group2__filter4
: true,
549 group2__filter5
: true,
550 group2__filter6
: false
552 'getFiltersFromParameters does not care about previous or existing state.'
556 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
557 model
.initializeFilters( filterDefinition
, viewsDefinition
);
559 model
.toggleFiltersSelected(
560 model
.getFiltersFromParameters( {
565 model
.getSelectedState(),
566 $.extend( {}, baseFilterRepresentation
, {
567 group3__filter7
: true,
568 group3__filter8
: false,
569 group3__filter9
: false
571 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
574 model
.toggleFiltersSelected(
575 model
.getFiltersFromParameters( {
576 group3
: 'filter7,filter8'
580 model
.getSelectedState(),
581 $.extend( {}, baseFilterRepresentation
, {
582 group3__filter7
: true,
583 group3__filter8
: true,
584 group3__filter9
: false
586 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
589 model
.toggleFiltersSelected(
590 model
.getFiltersFromParameters( {
591 group3
: 'filter7,filter8,filter9'
595 model
.getSelectedState(),
596 $.extend( {}, baseFilterRepresentation
, {
597 group3__filter7
: true,
598 group3__filter8
: true,
599 group3__filter9
: true
601 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
604 model
.toggleFiltersSelected(
605 model
.getFiltersFromParameters( {
606 group3
: 'filter7,all,filter9'
610 model
.getSelectedState(),
611 $.extend( {}, baseFilterRepresentation
, {
612 group3__filter7
: true,
613 group3__filter8
: true,
614 group3__filter9
: true
616 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
619 model
.toggleFiltersSelected(
620 model
.getFiltersFromParameters( {
621 group3
: 'filter7,foo,filter9'
625 model
.getSelectedState(),
626 $.extend( {}, baseFilterRepresentation
, {
627 group3__filter7
: true,
628 group3__filter8
: false,
629 group3__filter9
: true
631 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
635 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
636 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
638 model
.initializeFilters( filterDefinition
, viewsDefinition
);
641 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
642 [ 'filter1', 'filter2' ],
643 'Remove duplicate values'
647 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
648 [ 'filter1', 'filter2' ],
649 'Remove invalid values'
653 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
655 'If any value is "all", the only value is "all".'
659 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
660 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
662 model
.initializeFilters( filterDefinition
, viewsDefinition
);
664 // Select a filter that has subset with another filter
665 model
.toggleFiltersSelected( {
666 group1__filter1
: true
669 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
671 model
.getFullState(),
672 $.extend( true, {}, baseFullFilterState
, {
673 group1__filter1
: { selected
: true },
674 group1__filter2
: { included
: true },
675 group1__filter3
: { included
: true },
676 // Conflicts are affected
677 group2__filter4
: { conflicted
: true },
678 group2__filter5
: { conflicted
: true },
679 group2__filter6
: { conflicted
: true }
681 'Filters with subsets are represented in the model.'
684 // Select another filter that has a subset with the same previous filter
685 model
.toggleFiltersSelected( {
686 group1__filter2
: true
688 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
690 model
.getFullState(),
691 $.extend( true, {}, baseFullFilterState
, {
692 group1__filter1
: { selected
: true },
693 group1__filter2
: { selected
: true, included
: true },
694 group1__filter3
: { included
: true },
695 // Conflicts are affected
696 group2__filter6
: { conflicted
: true }
698 'Filters that have multiple subsets are represented.'
701 // Remove one filter (but leave the other) that affects filter3
702 model
.toggleFiltersSelected( {
703 group1__filter1
: false
705 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
707 model
.getFullState(),
708 $.extend( true, {}, baseFullFilterState
, {
709 group1__filter2
: { selected
: true, included
: false },
710 group1__filter3
: { included
: true },
711 // Conflicts are affected
712 group2__filter6
: { conflicted
: true }
714 'Removing a filter only un-includes its subset if there is no other filter affecting.'
717 model
.toggleFiltersSelected( {
718 group1__filter2
: false
720 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
722 model
.getFullState(),
724 'Removing all supersets also un-includes the subsets.'
728 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
729 var model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
730 isCapsuleItemMuted = function ( filterName
) {
731 var itemModel
= model
.getItemByName( filterName
),
732 groupModel
= itemModel
.getGroupModel();
734 // This is the logic inside the capsule widget
736 // The capsule item widget only appears if the item is selected
737 itemModel
.isSelected() &&
738 // Muted state is only valid if group is full coverage and all items are selected
739 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
742 getCurrentItemsMutedState = function () {
744 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
745 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
746 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
747 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
748 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
749 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
753 group1__filter1
: false,
754 group1__filter2
: false,
755 group1__filter3
: false,
756 group2__filter4
: false,
757 group2__filter5
: false,
758 group2__filter6
: false
761 model
.initializeFilters( filterDefinition
, viewsDefinition
);
763 // Starting state, no selection, all items are non-muted
765 getCurrentItemsMutedState(),
767 'No selection - all items are non-muted'
770 // Select most (but not all) items in each group
771 model
.toggleFiltersSelected( {
772 group1__filter1
: true,
773 group1__filter2
: true,
774 group2__filter4
: true,
775 group2__filter5
: true
778 // Both groups have multiple (but not all) items selected, all items are non-muted
780 getCurrentItemsMutedState(),
782 'Not all items in the group selected - all items are non-muted'
785 // Select all items in 'fullCoverage' group (group2)
786 model
.toggleFiltersSelected( {
787 group2__filter6
: true
790 // Group2 (full coverage) has all items selected, all its items are muted
792 getCurrentItemsMutedState(),
793 $.extend( {}, baseMuteState
, {
794 group2__filter4
: true,
795 group2__filter5
: true,
796 group2__filter6
: true
798 'All items in \'full coverage\' group are selected - all items in the group are muted'
801 // Select all items in non 'fullCoverage' group (group1)
802 model
.toggleFiltersSelected( {
803 group1__filter3
: true
806 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
808 getCurrentItemsMutedState(),
809 $.extend( {}, baseMuteState
, {
810 group2__filter4
: true,
811 group2__filter5
: true,
812 group2__filter6
: true
814 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
817 // Uncheck an item from each group
818 model
.toggleFiltersSelected( {
819 group1__filter3
: false,
820 group2__filter5
: false
823 getCurrentItemsMutedState(),
825 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
829 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
830 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
832 model
.initializeFilters( filterDefinition
, viewsDefinition
);
835 model
.getFullState(),
837 'Initial state: no conflicts because no selections.'
840 // Select a filter that has a conflict with an entire group
841 model
.toggleFiltersSelected( {
842 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
845 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
848 model
.getFullState(),
849 $.extend( true, {}, baseFullFilterState
, {
850 group1__filter1
: { selected
: true },
851 group2__filter4
: { conflicted
: true },
852 group2__filter5
: { conflicted
: true },
853 group2__filter6
: { conflicted
: true },
854 // Subsets are affected by the selection
855 group1__filter2
: { included
: true },
856 group1__filter3
: { included
: true }
858 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
861 // Select one of the conflicts (both filters are now conflicted and selected)
862 model
.toggleFiltersSelected( {
863 group2__filter4
: true // conflicts: filter 1
865 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
868 model
.getFullState(),
869 $.extend( true, {}, baseFullFilterState
, {
870 group1__filter1
: { selected
: true, conflicted
: true },
871 group2__filter4
: { selected
: true, conflicted
: true },
872 group2__filter5
: { conflicted
: true },
873 group2__filter6
: { conflicted
: true },
874 // Subsets are affected by the selection
875 group1__filter2
: { included
: true },
876 group1__filter3
: { included
: true }
878 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
882 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
883 model
.initializeFilters( filterDefinition
, viewsDefinition
);
885 // Select a filter that has a conflict with a specific filter
886 model
.toggleFiltersSelected( {
887 group1__filter2
: true // conflicts: filter6
889 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
892 model
.getFullState(),
893 $.extend( true, {}, baseFullFilterState
, {
894 group1__filter2
: { selected
: true },
895 group2__filter6
: { conflicted
: true },
896 // Subsets are affected by the selection
897 group1__filter3
: { included
: true }
899 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
902 // Select the conflicting filter
903 model
.toggleFiltersSelected( {
904 group2__filter6
: true // conflicts: filter2
907 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
910 model
.getFullState(),
911 $.extend( true, {}, baseFullFilterState
, {
912 group1__filter2
: { selected
: true, conflicted
: true },
913 group2__filter6
: { selected
: true, conflicted
: true },
914 // This is added to the conflicts because filter6 is part of group2,
915 // who is in conflict with filter1; note that filter2 also conflicts
916 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
917 // and also because its **own sibling** (filter2) is **also** in conflict with the
918 // selected items in group2 (filter6)
919 group1__filter1
: { conflicted
: true },
921 // Subsets are affected by the selection
922 group1__filter3
: { included
: true }
924 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
927 // Now choose a non-conflicting filter from the group
928 model
.toggleFiltersSelected( {
929 group2__filter5
: true
932 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
935 model
.getFullState(),
936 $.extend( true, {}, baseFullFilterState
, {
937 group1__filter2
: { selected
: true },
938 group2__filter6
: { selected
: true },
939 group2__filter5
: { selected
: true },
940 // Filter6 and filter1 are no longer in conflict because
941 // filter5, while it is in conflict with filter1, it is
942 // not in conflict with filter2 - and since filter2 is
943 // selected, it removes the conflict bidirectionally
945 // Subsets are affected by the selection
946 group1__filter3
: { included
: true }
948 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
951 // Followup on the previous test, unselect filter2 so filter1
952 // is now the only one selected in its own group, and since
953 // it is in conflict with the entire of group2, it means
954 // filter1 is once again conflicted
955 model
.toggleFiltersSelected( {
956 group1__filter2
: false
959 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
962 model
.getFullState(),
963 $.extend( true, {}, baseFullFilterState
, {
964 group1__filter1
: { conflicted
: true },
965 group2__filter6
: { selected
: true },
966 group2__filter5
: { selected
: true }
968 'Unselecting an item that did not conflict returns the conflict state.'
971 // Followup #2: Now actually select filter1, and make everything conflicted
972 model
.toggleFiltersSelected( {
973 group1__filter1
: true
976 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
979 model
.getFullState(),
980 $.extend( true, {}, baseFullFilterState
, {
981 group1__filter1
: { selected
: true, conflicted
: true },
982 group2__filter6
: { selected
: true, conflicted
: true },
983 group2__filter5
: { selected
: true, conflicted
: true },
984 group2__filter4
: { conflicted
: true }, // Not selected but conflicted because it's in group2
985 // Subsets are affected by the selection
986 group1__filter2
: { included
: true },
987 group1__filter3
: { included
: true }
989 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
994 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
995 model
.initializeFilters( filterDefinition
, viewsDefinition
);
997 // Select a filter that has a conflict with a specific filter
998 model
.toggleFiltersSelected( {
999 group1__filter2
: true // conflicts: filter6
1002 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1005 model
.getFullState(),
1006 $.extend( true, {}, baseFullFilterState
, {
1007 group1__filter2
: { selected
: true },
1008 group2__filter6
: { conflicted
: true },
1009 // Subsets are affected by the selection
1010 group1__filter3
: { included
: true }
1012 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1015 model
.toggleFiltersSelected( {
1016 group1__filter3
: true // conflicts: filter6
1019 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1022 model
.getFullState(),
1023 $.extend( true, {}, baseFullFilterState
, {
1024 group1__filter2
: { selected
: true },
1025 // Subsets are affected by the selection
1026 group1__filter3
: { selected
: true, included
: true }
1028 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1032 QUnit
.test( 'Filter highlights', function ( assert
) {
1033 // We are using a different (smaller) definition here than the global one
1034 var definition
= [ {
1037 type
: 'string_options',
1039 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1040 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1041 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1042 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1043 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1044 { name
: 'filter6', label
: '6', description
: '6' }
1047 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1049 model
.initializeFilters( definition
);
1052 !model
.isHighlightEnabled(),
1053 'Initially, highlight is disabled.'
1056 model
.toggleHighlight( true );
1058 model
.isHighlightEnabled(),
1059 'Highlight is enabled on toggle.'
1062 model
.setHighlightColor( 'group1__filter1', 'color1' );
1063 model
.setHighlightColor( 'group1__filter2', 'color2' );
1066 model
.getHighlightedItems().map( function ( item
) {
1067 return item
.getName();
1073 'Highlighted items are highlighted.'
1077 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1079 'Item highlight color is set.'
1082 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1084 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1086 'Item highlight color is changed on setHighlightColor.'
1089 model
.clearHighlightColor( 'group1__filter1' );
1091 model
.getHighlightedItems().map( function ( item
) {
1092 return item
.getName();
1097 'Clear highlight from an item results in the item no longer being highlighted.'
1101 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1102 model
.initializeFilters( definition
);
1104 model
.setHighlightColor( 'group1__filter1', 'color1' );
1105 model
.setHighlightColor( 'group1__filter2', 'color2' );
1106 model
.setHighlightColor( 'group1__filter3', 'color3' );
1109 model
.getHighlightedItems().map( function ( item
) {
1110 return item
.getName();
1117 'Even if highlights are not enabled, the items remember their highlight state'
1118 // NOTE: When actually displaying the highlights, the UI checks whether
1119 // highlighting is generally active and then goes over the highlighted
1120 // items. The item models, however, and the view model in general, still
1121 // retains the knowledge about which filters have different colors, so we
1122 // can seamlessly return to the colors the user previously chose if they
1123 // reapply highlights.
1127 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1128 model
.initializeFilters( definition
);
1130 model
.setHighlightColor( 'group1__filter1', 'color1' );
1131 model
.setHighlightColor( 'group1__filter6', 'color6' );
1134 model
.getHighlightedItems().map( function ( item
) {
1135 return item
.getName();
1140 'Items without a specified class identifier are not highlighted.'
1143 }( mediaWiki
, jQuery
) );