1 /* eslint-disable camelcase */
3 QUnit
.module( 'mediawiki.rcfilters - FiltersViewModel', QUnit
.newMwEnvironment( {
5 'group1filter1-label': 'Group 1: Filter 1',
6 'group1filter1-desc': 'Description of Filter 1 in Group 1',
7 'group1filter2-label': 'Group 1: Filter 2',
8 'group1filter2-desc': 'Description of Filter 2 in Group 1',
9 'group2filter1-label': 'Group 2: Filter 1',
10 'group2filter1-desc': 'Description of Filter 1 in Group 2',
11 'group2filter2-label': 'xGroup 2: Filter 2',
12 'group2filter2-desc': 'Description of Filter 2 in Group 2'
15 wgStructuredChangeFiltersEnableExperimentalViews
: true
19 QUnit
.test( 'Setting up filters', function ( assert
) {
23 type
: 'send_unselected_if_any',
27 label
: 'Group 1: Filter 1',
28 description
: 'Description of Filter 1 in Group 1'
32 label
: 'Group 1: Filter 2',
33 description
: 'Description of Filter 2 in Group 1'
39 type
: 'send_unselected_if_any',
43 label
: 'Group 2: Filter 1',
44 description
: 'Description of Filter 1 in Group 2'
48 label
: 'Group 2: Filter 2',
49 description
: 'Description of Filter 2 in Group 2'
55 type
: 'string_options',
59 label
: 'Group 3: Filter 1',
60 description
: 'Description of Filter 1 in Group 3'
64 label
: 'Group 3: Filter 2',
65 description
: 'Description of Filter 2 in Group 3'
78 { name
: 0, label
: 'Main' },
79 { name
: 1, label
: 'Talk' },
80 { name
: 2, label
: 'User' },
81 { name
: 3, label
: 'User talk' }
86 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
88 model
.initializeFilters( definition
, views
);
91 model
.getItemByName( 'group1__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
92 model
.getItemByName( 'group1__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
93 model
.getItemByName( 'group2__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
94 model
.getItemByName( 'group2__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
95 model
.getItemByName( 'group3__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
96 model
.getItemByName( 'group3__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
97 model
.getItemByName( 'namespace__0' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
98 model
.getItemByName( 'namespace__1' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
99 model
.getItemByName( 'namespace__2' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
100 model
.getItemByName( 'namespace__3' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
101 'Filters instantiated and stored correctly'
105 model
.getSelectedState(),
107 group1__filter1
: false,
108 group1__filter2
: false,
109 group2__filter1
: false,
110 group2__filter2
: false,
111 group3__filter1
: false,
112 group3__filter2
: false,
118 'Initial state of filters'
121 model
.toggleFiltersSelected( {
122 group1__filter1
: true,
123 group2__filter2
: true,
124 group3__filter1
: true
127 model
.getSelectedState(),
129 group1__filter1
: true,
130 group1__filter2
: false,
131 group2__filter1
: false,
132 group2__filter2
: true,
133 group3__filter1
: true,
134 group3__filter2
: false,
140 'Updating filter states correctly'
144 QUnit
.test( 'Default filters', function ( assert
) {
148 type
: 'send_unselected_if_any',
152 label
: 'Show filter 1',
153 description
: 'Description of Filter 1 in Group 1',
158 label
: 'Show filter 2',
159 description
: 'Description of Filter 2 in Group 1'
163 label
: 'Show filter 3',
164 description
: 'Description of Filter 3 in Group 1',
171 type
: 'send_unselected_if_any',
175 label
: 'Show filter 4',
176 description
: 'Description of Filter 1 in Group 2'
180 label
: 'Show filter 5',
181 description
: 'Description of Filter 2 in Group 2',
186 label
: 'Show filter 6',
187 description
: 'Description of Filter 3 in Group 2'
194 type
: 'string_options',
200 label
: 'Group 3: Filter 1',
201 description
: 'Description of Filter 1 in Group 3'
205 label
: 'Group 3: Filter 2',
206 description
: 'Description of Filter 2 in Group 3'
210 label
: 'Group 3: Filter 3',
211 description
: 'Description of Filter 3 in Group 3'
215 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
217 model
.initializeFilters( definition
);
219 // Empty query = only default values
221 model
.getDefaultParams(),
231 'Default parameters are stored properly per filter and group'
235 QUnit
.test( 'Finding matching filters', function ( assert
) {
239 title
: 'Group 1 title',
240 type
: 'send_unselected_if_any',
244 label
: 'group1filter1-label',
245 description
: 'group1filter1-desc'
249 label
: 'group1filter2-label',
250 description
: 'group1filter2-desc'
255 title
: 'Group 2 title',
256 type
: 'send_unselected_if_any',
260 label
: 'group2filter1-label',
261 description
: 'group2filter1-desc'
265 label
: 'group2filter2-label',
266 description
: 'group2filter2-desc'
279 { name
: 0, label
: 'Main' },
280 { name
: 1, label
: 'Talk' },
281 { name
: 2, label
: 'User' },
282 { name
: 3, label
: 'User talk' }
291 group1
: [ 'group1__filter1', 'group1__filter2' ],
292 group2
: [ 'group2__filter1' ]
294 reason
: 'Finds filters starting with the query string'
297 query
: 'filter 2 in group',
299 group1
: [ 'group1__filter2' ],
300 group2
: [ 'group2__filter2' ]
302 reason
: 'Finds filters containing the query string in their description'
307 group1
: [ 'group1__filter1', 'group1__filter2' ],
308 group2
: [ 'group2__filter1', 'group2__filter2' ]
310 reason
: 'Finds filters containing the query string in their group title'
315 namespace: [ 'namespace__0' ]
317 reason
: 'Finds namespaces when using : prefix'
322 reason
: 'Finds no results if using namespaces prefix (:) to search for filter title'
325 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
326 extractNames = function ( matches
) {
328 Object
.keys( matches
).forEach( function ( groupName
) {
329 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
330 return item
.getName();
336 model
.initializeFilters( definition
, views
);
338 testCases
.forEach( function ( testCase
) {
339 matches
= model
.findMatches( testCase
.query
);
341 extractNames( matches
),
342 testCase
.expectedMatches
,
347 matches
= model
.findMatches( 'foo' );
349 $.isEmptyObject( matches
),
350 'findMatches returns an empty object when no results found'
354 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
358 type
: 'send_unselected_if_any',
362 label
: 'Group 1: Filter 1',
363 description
: 'Description of Filter 1 in Group 1'
367 label
: 'Group 1: Filter 2',
368 description
: 'Description of Filter 2 in Group 1'
372 label
: 'Group 1: Filter 3',
373 description
: 'Description of Filter 3 in Group 1'
379 type
: 'send_unselected_if_any',
383 label
: 'Group 2: Filter 1',
384 description
: 'Description of Filter 1 in Group 2'
388 label
: 'Group 2: Filter 2',
389 description
: 'Description of Filter 2 in Group 2'
393 label
: 'Group 2: Filter 3',
394 description
: 'Description of Filter 3 in Group 2'
400 type
: 'string_options',
405 label
: 'Group 3: Filter 1',
406 description
: 'Description of Filter 1 in Group 3'
410 label
: 'Group 3: Filter 2',
411 description
: 'Description of Filter 2 in Group 3'
415 label
: 'Group 3: Filter 3',
416 description
: 'Description of Filter 3 in Group 3'
420 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
422 model
.initializeFilters( definition
);
424 // Starting with all filters unselected
426 model
.getParametersFromFilters(),
436 'Unselected filters return all parameters falsey or \'\'.'
440 model
.toggleFiltersSelected( {
441 group1__hidefilter1
: true,
442 group1__hidefilter2
: false,
443 group1__hidefilter3
: false,
444 group2__hidefilter4
: false,
445 group2__hidefilter5
: false,
446 group2__hidefilter6
: false
448 // Only one filter in one group
450 model
.getParametersFromFilters(),
452 // Group 1 (one selected, the others are true)
456 // Group 2 (nothing is selected, all false)
462 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
466 model
.toggleFiltersSelected( {
467 group1__hidefilter1
: true,
468 group1__hidefilter2
: true,
469 group1__hidefilter3
: false,
470 group2__hidefilter4
: false,
471 group2__hidefilter5
: false,
472 group2__hidefilter6
: false
474 // Two selected filters in one group
476 model
.getParametersFromFilters(),
478 // Group 1 (two selected, the others are true)
482 // Group 2 (nothing is selected, all false)
488 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
492 model
.toggleFiltersSelected( {
493 group1__hidefilter1
: true,
494 group1__hidefilter2
: true,
495 group1__hidefilter3
: true,
496 group2__hidefilter4
: false,
497 group2__hidefilter5
: false,
498 group2__hidefilter6
: false
500 // All filters of the group are selected == this is the same as not selecting any
502 model
.getParametersFromFilters(),
504 // Group 1 (all selected, all false)
508 // Group 2 (nothing is selected, all false)
514 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
517 // Select 1 filter from string_options
518 model
.toggleFiltersSelected( {
519 group3__filter7
: true,
520 group3__filter8
: false,
521 group3__filter9
: false
523 // All filters of the group are selected == this is the same as not selecting any
525 model
.getParametersFromFilters(),
527 // Group 1 (all selected, all)
531 // Group 2 (nothing is selected, all false)
537 'One filter selected in "string_option" group returns that filter in the value.'
540 // Select 2 filters from string_options
541 model
.toggleFiltersSelected( {
542 group3__filter7
: true,
543 group3__filter8
: true,
544 group3__filter9
: false
546 // All filters of the group are selected == this is the same as not selecting any
548 model
.getParametersFromFilters(),
550 // Group 1 (all selected, all)
554 // Group 2 (nothing is selected, all false)
558 group3
: 'filter7,filter8'
560 'Two filters selected in "string_option" group returns those filters in the value.'
563 // Select 3 filters from string_options
564 model
.toggleFiltersSelected( {
565 group3__filter7
: true,
566 group3__filter8
: true,
567 group3__filter9
: true
569 // All filters of the group are selected == this is the same as not selecting any
571 model
.getParametersFromFilters(),
573 // Group 1 (all selected, all)
577 // Group 2 (nothing is selected, all false)
583 'All filters selected in "string_option" group returns \'all\'.'
588 QUnit
.test( 'getParametersFromFilters (custom object)', function ( assert
) {
590 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
594 type
: 'send_unselected_if_any',
596 { name
: 'hidefilter1', label
: 'Hide filter 1', description
: '' },
597 { name
: 'hidefilter2', label
: 'Hide filter 2', description
: '' },
598 { name
: 'hidefilter3', label
: 'Hide filter 3', description
: '' }
603 type
: 'send_unselected_if_any',
605 { name
: 'hidefilter4', label
: 'Hide filter 4', description
: '' },
606 { name
: 'hidefilter5', label
: 'Hide filter 5', description
: '' },
607 { name
: 'hidefilter6', label
: 'Hide filter 6', description
: '' }
612 type
: 'string_options',
615 { name
: 'filter7', label
: 'Hide filter 7', description
: '' },
616 { name
: 'filter8', label
: 'Hide filter 8', description
: '' },
617 { name
: 'filter9', label
: 'Hide filter 9', description
: '' }
622 // This is mocking the cases above, both
623 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
624 // - 'Two filters selected in "string_option" group returns those filters in the value.'
626 group1__hidefilter1
: true,
627 group1__hidefilter2
: true,
628 group1__hidefilter3
: false,
629 group2__hidefilter4
: false,
630 group2__hidefilter5
: false,
631 group2__hidefilter6
: false,
632 group3__filter7
: true,
633 group3__filter8
: true,
634 group3__filter9
: false
637 // Group 1 (two selected, the others are true)
641 // Group 2 (nothing is selected, all false)
645 group3
: 'filter7,filter8'
647 msg
: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
650 // This is mocking case above
651 // - 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
653 group1__hidefilter1
: 1
656 // Group 1 (one selected, the others are true)
660 // Group 2 (nothing is selected, all false)
666 msg
: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
679 msg
: 'Given an explicit empty object, the result is all filters set to their falsey unselected value.'
683 model
.initializeFilters( definition
);
684 // Store original state
685 originalState
= model
.getSelectedState();
688 cases
.forEach( function ( test
) {
690 model
.getParametersFromFilters( test
.input
),
696 // After doing the above tests, make sure the actual state
697 // of the filter stayed the same
699 model
.getSelectedState(),
701 'Running the method with external definition to parse does not actually change the state of the model'
705 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
709 type
: 'send_unselected_if_any',
713 label
: 'Show filter 1',
714 description
: 'Description of Filter 1 in Group 1',
719 label
: 'Show filter 2',
720 description
: 'Description of Filter 2 in Group 1'
724 label
: 'Show filter 3',
725 description
: 'Description of Filter 3 in Group 1',
732 type
: 'send_unselected_if_any',
736 label
: 'Show filter 4',
737 description
: 'Description of Filter 1 in Group 2'
741 label
: 'Show filter 5',
742 description
: 'Description of Filter 2 in Group 2',
747 label
: 'Show filter 6',
748 description
: 'Description of Filter 3 in Group 2'
755 type
: 'string_options',
761 label
: 'Group 3: Filter 1',
762 description
: 'Description of Filter 1 in Group 3'
766 label
: 'Group 3: Filter 2',
767 description
: 'Description of Filter 2 in Group 3'
771 label
: 'Group 3: Filter 3',
772 description
: 'Description of Filter 3 in Group 3'
776 baseFilterRepresentation
= {
777 group1__hidefilter1
: false,
778 group1__hidefilter2
: false,
779 group1__hidefilter3
: false,
780 group2__hidefilter4
: false,
781 group2__hidefilter5
: false,
782 group2__hidefilter6
: false,
783 group3__filter7
: false,
784 group3__filter8
: false,
785 group3__filter9
: false
787 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
789 model
.initializeFilters( definition
);
791 // Empty query = only default values
793 model
.getFiltersFromParameters( {} ),
794 baseFilterRepresentation
,
795 'Empty parameter query results in an object representing all filters set to false'
799 model
.getFiltersFromParameters( {
802 $.extend( {}, baseFilterRepresentation
, {
803 group1__hidefilter1
: true, // The text is "show filter 1"
804 group1__hidefilter2
: false, // The text is "show filter 2"
805 group1__hidefilter3
: true // The text is "show filter 3"
807 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
811 model
.getFiltersFromParameters( {
816 $.extend( {}, baseFilterRepresentation
, {
817 group1__hidefilter1
: false, // The text is "show filter 1"
818 group1__hidefilter2
: false, // The text is "show filter 2"
819 group1__hidefilter3
: false // The text is "show filter 3"
821 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
824 // The ones above don't update the model, so we have a clean state.
825 // getFiltersFromParameters is stateless; any change is unaffected by the current state
826 // This test is demonstrating wrong usage of the method;
827 // We should be aware that getFiltersFromParameters is stateless,
828 // so each call gives us a filter state that only reflects the query given.
829 // This means that the two calls to toggleFiltersSelected() below collide.
830 // The result of the first is overridden by the result of the second,
831 // since both get a full state object from getFiltersFromParameters that **only** relates
832 // to the input it receives.
833 model
.toggleFiltersSelected(
834 model
.getFiltersFromParameters( {
839 model
.toggleFiltersSelected(
840 model
.getFiltersFromParameters( {
845 // The result here is ignoring the first toggleFiltersSelected call
847 model
.getSelectedState(),
848 $.extend( {}, baseFilterRepresentation
, {
849 group2__hidefilter4
: true,
850 group2__hidefilter5
: true,
851 group2__hidefilter6
: false
853 'getFiltersFromParameters does not care about previous or existing state.'
857 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
858 model
.initializeFilters( definition
);
860 model
.toggleFiltersSelected(
861 model
.getFiltersFromParameters( {
866 model
.getSelectedState(),
867 $.extend( {}, baseFilterRepresentation
, {
868 group3__filter7
: true,
869 group3__filter8
: false,
870 group3__filter9
: false
872 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
875 model
.toggleFiltersSelected(
876 model
.getFiltersFromParameters( {
877 group3
: 'filter7,filter8'
881 model
.getSelectedState(),
882 $.extend( {}, baseFilterRepresentation
, {
883 group3__filter7
: true,
884 group3__filter8
: true,
885 group3__filter9
: false
887 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
890 model
.toggleFiltersSelected(
891 model
.getFiltersFromParameters( {
892 group3
: 'filter7,filter8,filter9'
896 model
.getSelectedState(),
897 $.extend( {}, baseFilterRepresentation
, {
898 group3__filter7
: true,
899 group3__filter8
: true,
900 group3__filter9
: true
902 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
905 model
.toggleFiltersSelected(
906 model
.getFiltersFromParameters( {
907 group3
: 'filter7,all,filter9'
911 model
.getSelectedState(),
912 $.extend( {}, baseFilterRepresentation
, {
913 group3__filter7
: true,
914 group3__filter8
: true,
915 group3__filter9
: true
917 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
920 model
.toggleFiltersSelected(
921 model
.getFiltersFromParameters( {
922 group3
: 'filter7,foo,filter9'
926 model
.getSelectedState(),
927 $.extend( {}, baseFilterRepresentation
, {
928 group3__filter7
: true,
929 group3__filter8
: false,
930 group3__filter9
: true
932 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
936 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
940 type
: 'string_options',
944 label
: 'Show filter 1',
945 description
: 'Description of Filter 1 in Group 1'
949 label
: 'Show filter 2',
950 description
: 'Description of Filter 2 in Group 1'
954 label
: 'Show filter 3',
955 description
: 'Description of Filter 3 in Group 1'
959 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
961 model
.initializeFilters( definition
);
964 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
965 [ 'filter1', 'filter2' ],
966 'Remove duplicate values'
970 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
971 [ 'filter1', 'filter2' ],
972 'Remove invalid values'
976 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
978 'If any value is "all", the only value is "all".'
982 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
986 type
: 'string_options',
990 label
: 'Show filter 1',
991 description
: 'Description of Filter 1 in Group 1',
1005 label
: 'Show filter 2',
1006 description
: 'Description of Filter 2 in Group 1',
1016 label
: 'Show filter 3',
1017 description
: 'Description of Filter 3 in Group 1'
1022 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1023 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1024 group1__filter3
: { selected
: false, conflicted
: false, included
: false }
1026 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1028 model
.initializeFilters( definition
);
1029 // Select a filter that has subset with another filter
1030 model
.toggleFiltersSelected( {
1031 group1__filter1
: true
1034 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1036 model
.getFullState(),
1037 $.extend( true, {}, baseFullState
, {
1038 group1__filter1
: { selected
: true },
1039 group1__filter2
: { included
: true },
1040 group1__filter3
: { included
: true }
1042 'Filters with subsets are represented in the model.'
1045 // Select another filter that has a subset with the same previous filter
1046 model
.toggleFiltersSelected( {
1047 group1__filter2
: true
1049 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
1051 model
.getFullState(),
1052 $.extend( true, {}, baseFullState
, {
1053 group1__filter1
: { selected
: true },
1054 group1__filter2
: { selected
: true, included
: true },
1055 group1__filter3
: { included
: true }
1057 'Filters that have multiple subsets are represented.'
1060 // Remove one filter (but leave the other) that affects filter3
1061 model
.toggleFiltersSelected( {
1062 group1__filter1
: false
1064 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1066 model
.getFullState(),
1067 $.extend( true, {}, baseFullState
, {
1068 group1__filter2
: { selected
: true, included
: false },
1069 group1__filter3
: { included
: true }
1071 'Removing a filter only un-includes its subset if there is no other filter affecting.'
1074 model
.toggleFiltersSelected( {
1075 group1__filter2
: false
1077 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1079 model
.getFullState(),
1081 'Removing all supersets also un-includes the subsets.'
1085 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
1086 var definition
= [ {
1089 type
: 'string_options',
1090 fullCoverage
: false,
1092 { name
: 'filter1', label
: '1', description
: '1' },
1093 { name
: 'filter2', label
: '2', description
: '2' },
1094 { name
: 'filter3', label
: '3', description
: '3' }
1099 type
: 'send_unselected_if_any',
1102 { name
: 'filter4', label
: '4', description
: '4' },
1103 { name
: 'filter5', label
: '5', description
: '5' },
1104 { name
: 'filter6', label
: '6', description
: '6' }
1107 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
1108 isCapsuleItemMuted = function ( filterName
) {
1109 var itemModel
= model
.getItemByName( filterName
),
1110 groupModel
= itemModel
.getGroupModel();
1112 // This is the logic inside the capsule widget
1114 // The capsule item widget only appears if the item is selected
1115 itemModel
.isSelected() &&
1116 // Muted state is only valid if group is full coverage and all items are selected
1117 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
1120 getCurrentItemsMutedState = function () {
1122 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
1123 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
1124 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
1125 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
1126 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
1127 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
1131 group1__filter1
: false,
1132 group1__filter2
: false,
1133 group1__filter3
: false,
1134 group2__filter4
: false,
1135 group2__filter5
: false,
1136 group2__filter6
: false
1139 model
.initializeFilters( definition
);
1141 // Starting state, no selection, all items are non-muted
1143 getCurrentItemsMutedState(),
1145 'No selection - all items are non-muted'
1148 // Select most (but not all) items in each group
1149 model
.toggleFiltersSelected( {
1150 group1__filter1
: true,
1151 group1__filter2
: true,
1152 group2__filter4
: true,
1153 group2__filter5
: true
1156 // Both groups have multiple (but not all) items selected, all items are non-muted
1158 getCurrentItemsMutedState(),
1160 'Not all items in the group selected - all items are non-muted'
1163 // Select all items in 'fullCoverage' group (group2)
1164 model
.toggleFiltersSelected( {
1165 group2__filter6
: true
1168 // Group2 (full coverage) has all items selected, all its items are muted
1170 getCurrentItemsMutedState(),
1171 $.extend( {}, baseMuteState
, {
1172 group2__filter4
: true,
1173 group2__filter5
: true,
1174 group2__filter6
: true
1176 'All items in \'full coverage\' group are selected - all items in the group are muted'
1179 // Select all items in non 'fullCoverage' group (group1)
1180 model
.toggleFiltersSelected( {
1181 group1__filter3
: true
1184 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1186 getCurrentItemsMutedState(),
1187 $.extend( {}, baseMuteState
, {
1188 group2__filter4
: true,
1189 group2__filter5
: true,
1190 group2__filter6
: true
1192 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1195 // Uncheck an item from each group
1196 model
.toggleFiltersSelected( {
1197 group1__filter3
: false,
1198 group2__filter5
: false
1201 getCurrentItemsMutedState(),
1203 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1207 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
1208 var definition
= [ {
1211 type
: 'string_options',
1217 conflicts
: [ { group
: 'group2' } ]
1223 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ]
1234 type
: 'send_unselected_if_any',
1235 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
1251 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
1256 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1257 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1258 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
1259 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
1260 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
1261 group2__filter6
: { selected
: false, conflicted
: false, included
: false }
1263 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1265 model
.initializeFilters( definition
);
1268 model
.getFullState(),
1270 'Initial state: no conflicts because no selections.'
1273 // Select a filter that has a conflict with an entire group
1274 model
.toggleFiltersSelected( {
1275 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1278 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1281 model
.getFullState(),
1282 $.extend( true, {}, baseFullState
, {
1283 group1__filter1
: { selected
: true },
1284 group2__filter4
: { conflicted
: true },
1285 group2__filter5
: { conflicted
: true },
1286 group2__filter6
: { conflicted
: true }
1288 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1291 // Select one of the conflicts (both filters are now conflicted and selected)
1292 model
.toggleFiltersSelected( {
1293 group2__filter4
: true // conflicts: filter 1
1295 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
1298 model
.getFullState(),
1299 $.extend( true, {}, baseFullState
, {
1300 group1__filter1
: { selected
: true, conflicted
: true },
1301 group2__filter4
: { selected
: true, conflicted
: true },
1302 group2__filter5
: { conflicted
: true },
1303 group2__filter6
: { conflicted
: true }
1305 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1309 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1310 model
.initializeFilters( definition
);
1312 // Select a filter that has a conflict with a specific filter
1313 model
.toggleFiltersSelected( {
1314 group1__filter2
: true // conflicts: filter6
1316 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1319 model
.getFullState(),
1320 $.extend( true, {}, baseFullState
, {
1321 group1__filter2
: { selected
: true },
1322 group2__filter6
: { conflicted
: true }
1324 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1327 // Select the conflicting filter
1328 model
.toggleFiltersSelected( {
1329 group2__filter6
: true // conflicts: filter2
1332 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1335 model
.getFullState(),
1336 $.extend( true, {}, baseFullState
, {
1337 group1__filter2
: { selected
: true, conflicted
: true },
1338 group2__filter6
: { selected
: true, conflicted
: true },
1339 // This is added to the conflicts because filter6 is part of group2,
1340 // who is in conflict with filter1; note that filter2 also conflicts
1341 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1342 // and also because its **own sibling** (filter2) is **also** in conflict with the
1343 // selected items in group2 (filter6)
1344 group1__filter1
: { conflicted
: true }
1346 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1349 // Now choose a non-conflicting filter from the group
1350 model
.toggleFiltersSelected( {
1351 group2__filter5
: true
1354 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1357 model
.getFullState(),
1358 $.extend( true, {}, baseFullState
, {
1359 group1__filter2
: { selected
: true },
1360 group2__filter6
: { selected
: true },
1361 group2__filter5
: { selected
: true }
1362 // Filter6 and filter1 are no longer in conflict because
1363 // filter5, while it is in conflict with filter1, it is
1364 // not in conflict with filter2 - and since filter2 is
1365 // selected, it removes the conflict bidirectionally
1367 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1370 // Followup on the previous test, unselect filter2 so filter1
1371 // is now the only one selected in its own group, and since
1372 // it is in conflict with the entire of group2, it means
1373 // filter1 is once again conflicted
1374 model
.toggleFiltersSelected( {
1375 group1__filter2
: false
1378 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1381 model
.getFullState(),
1382 $.extend( true, {}, baseFullState
, {
1383 group1__filter1
: { conflicted
: true },
1384 group2__filter6
: { selected
: true },
1385 group2__filter5
: { selected
: true }
1387 'Unselecting an item that did not conflict returns the conflict state.'
1390 // Followup #2: Now actually select filter1, and make everything conflicted
1391 model
.toggleFiltersSelected( {
1392 group1__filter1
: true
1395 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1398 model
.getFullState(),
1399 $.extend( true, {}, baseFullState
, {
1400 group1__filter1
: { selected
: true, conflicted
: true },
1401 group2__filter6
: { selected
: true, conflicted
: true },
1402 group2__filter5
: { selected
: true, conflicted
: true },
1403 group2__filter4
: { conflicted
: true } // Not selected but conflicted because it's in group2
1405 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1410 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1411 model
.initializeFilters( definition
);
1413 // Select a filter that has a conflict with a specific filter
1414 model
.toggleFiltersSelected( {
1415 group1__filter2
: true // conflicts: filter6
1418 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1421 model
.getFullState(),
1422 $.extend( true, {}, baseFullState
, {
1423 group1__filter2
: { selected
: true },
1424 group2__filter6
: { conflicted
: true }
1426 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1429 model
.toggleFiltersSelected( {
1430 group1__filter3
: true // conflicts: filter6
1433 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1436 model
.getFullState(),
1437 $.extend( true, {}, baseFullState
, {
1438 group1__filter2
: { selected
: true },
1439 group1__filter3
: { selected
: true }
1441 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1446 QUnit
.test( 'Filter highlights', function ( assert
) {
1447 var definition
= [ {
1450 type
: 'string_options',
1452 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1453 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1454 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1455 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1456 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1457 { name
: 'filter6', label
: '6', description
: '6' }
1460 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1462 model
.initializeFilters( definition
);
1465 !model
.isHighlightEnabled(),
1466 'Initially, highlight is disabled.'
1469 model
.toggleHighlight( true );
1471 model
.isHighlightEnabled(),
1472 'Highlight is enabled on toggle.'
1475 model
.setHighlightColor( 'group1__filter1', 'color1' );
1476 model
.setHighlightColor( 'group1__filter2', 'color2' );
1479 model
.getHighlightedItems().map( function ( item
) {
1480 return item
.getName();
1486 'Highlighted items are highlighted.'
1490 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1492 'Item highlight color is set.'
1495 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1497 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1499 'Item highlight color is changed on setHighlightColor.'
1502 model
.clearHighlightColor( 'group1__filter1' );
1504 model
.getHighlightedItems().map( function ( item
) {
1505 return item
.getName();
1510 'Clear highlight from an item results in the item no longer being highlighted.'
1514 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1515 model
.initializeFilters( definition
);
1517 model
.setHighlightColor( 'group1__filter1', 'color1' );
1518 model
.setHighlightColor( 'group1__filter2', 'color2' );
1519 model
.setHighlightColor( 'group1__filter3', 'color3' );
1522 model
.getHighlightedItems().map( function ( item
) {
1523 return item
.getName();
1530 'Even if highlights are not enabled, the items remember their highlight state'
1531 // NOTE: When actually displaying the highlights, the UI checks whether
1532 // highlighting is generally active and then goes over the highlighted
1533 // items. The item models, however, and the view model in general, still
1534 // retains the knowledge about which filters have different colors, so we
1535 // can seamlessly return to the colors the user previously chose if they
1536 // reapply highlights.
1540 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1541 model
.initializeFilters( definition
);
1543 model
.setHighlightColor( 'group1__filter1', 'color1' );
1544 model
.setHighlightColor( 'group1__filter6', 'color6' );
1547 model
.getHighlightedItems().map( function ( item
) {
1548 return item
.getName();
1553 'Items without a specified class identifier are not highlighted.'
1556 }( mediaWiki
, jQuery
) );