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'
16 QUnit
.test( 'Setting up filters', function ( assert
) {
20 type
: 'send_unselected_if_any',
24 label
: 'Group 1: Filter 1',
25 description
: 'Description of Filter 1 in Group 1'
29 label
: 'Group 1: Filter 2',
30 description
: 'Description of Filter 2 in Group 1'
36 type
: 'send_unselected_if_any',
40 label
: 'Group 2: Filter 1',
41 description
: 'Description of Filter 1 in Group 2'
45 label
: 'Group 2: Filter 2',
46 description
: 'Description of Filter 2 in Group 2'
52 type
: 'string_options',
56 label
: 'Group 3: Filter 1',
57 description
: 'Description of Filter 1 in Group 3'
61 label
: 'Group 3: Filter 2',
62 description
: 'Description of Filter 2 in Group 3'
66 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
68 model
.initializeFilters( definition
);
71 model
.getItemByName( 'group1__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
72 model
.getItemByName( 'group1__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
73 model
.getItemByName( 'group2__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
74 model
.getItemByName( 'group2__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
75 model
.getItemByName( 'group3__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
76 model
.getItemByName( 'group3__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
77 'Filters instantiated and stored correctly'
81 model
.getSelectedState(),
83 group1__filter1
: false,
84 group1__filter2
: false,
85 group2__filter1
: false,
86 group2__filter2
: false,
87 group3__filter1
: false,
88 group3__filter2
: false
90 'Initial state of filters'
93 model
.toggleFiltersSelected( {
94 group1__filter1
: true,
95 group2__filter2
: true,
99 model
.getSelectedState(),
101 group1__filter1
: true,
102 group1__filter2
: false,
103 group2__filter1
: false,
104 group2__filter2
: true,
105 group3__filter1
: true,
106 group3__filter2
: false
108 'Updating filter states correctly'
112 QUnit
.test( 'Default filters', function ( assert
) {
116 type
: 'send_unselected_if_any',
120 label
: 'Show filter 1',
121 description
: 'Description of Filter 1 in Group 1',
126 label
: 'Show filter 2',
127 description
: 'Description of Filter 2 in Group 1'
131 label
: 'Show filter 3',
132 description
: 'Description of Filter 3 in Group 1',
139 type
: 'send_unselected_if_any',
143 label
: 'Show filter 4',
144 description
: 'Description of Filter 1 in Group 2'
148 label
: 'Show filter 5',
149 description
: 'Description of Filter 2 in Group 2',
154 label
: 'Show filter 6',
155 description
: 'Description of Filter 3 in Group 2'
162 type
: 'string_options',
168 label
: 'Group 3: Filter 1',
169 description
: 'Description of Filter 1 in Group 3'
173 label
: 'Group 3: Filter 2',
174 description
: 'Description of Filter 2 in Group 3'
178 label
: 'Group 3: Filter 3',
179 description
: 'Description of Filter 3 in Group 3'
183 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
185 model
.initializeFilters( definition
);
187 // Empty query = only default values
189 model
.getDefaultParams(),
199 'Default parameters are stored properly per filter and group'
203 QUnit
.test( 'Finding matching filters', function ( assert
) {
207 title
: 'Group 1 title',
208 type
: 'send_unselected_if_any',
212 label
: 'group1filter1-label',
213 description
: 'group1filter1-desc'
217 label
: 'group1filter2-label',
218 description
: 'group1filter2-desc'
223 title
: 'Group 2 title',
224 type
: 'send_unselected_if_any',
228 label
: 'group2filter1-label',
229 description
: 'group2filter1-desc'
233 label
: 'group2filter2-label',
234 description
: 'group2filter2-desc'
242 group1
: [ 'group1__filter1', 'group1__filter2' ],
243 group2
: [ 'group2__filter1' ]
245 reason
: 'Finds filters starting with the query string'
248 query
: 'filter 2 in group',
250 group1
: [ 'group1__filter2' ],
251 group2
: [ 'group2__filter2' ]
253 reason
: 'Finds filters containing the query string in their description'
258 group1
: [ 'group1__filter1', 'group1__filter2' ],
259 group2
: [ 'group2__filter1', 'group2__filter2' ]
261 reason
: 'Finds filters containing the query string in their group title'
264 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
265 extractNames = function ( matches
) {
267 Object
.keys( matches
).forEach( function ( groupName
) {
268 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
269 return item
.getName();
275 model
.initializeFilters( definition
);
277 testCases
.forEach( function ( testCase
) {
278 matches
= model
.findMatches( testCase
.query
);
280 extractNames( matches
),
281 testCase
.expectedMatches
,
286 matches
= model
.findMatches( 'foo' );
288 $.isEmptyObject( matches
),
289 'findMatches returns an empty object when no results found'
293 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
297 type
: 'send_unselected_if_any',
301 label
: 'Group 1: Filter 1',
302 description
: 'Description of Filter 1 in Group 1'
306 label
: 'Group 1: Filter 2',
307 description
: 'Description of Filter 2 in Group 1'
311 label
: 'Group 1: Filter 3',
312 description
: 'Description of Filter 3 in Group 1'
318 type
: 'send_unselected_if_any',
322 label
: 'Group 2: Filter 1',
323 description
: 'Description of Filter 1 in Group 2'
327 label
: 'Group 2: Filter 2',
328 description
: 'Description of Filter 2 in Group 2'
332 label
: 'Group 2: Filter 3',
333 description
: 'Description of Filter 3 in Group 2'
339 type
: 'string_options',
344 label
: 'Group 3: Filter 1',
345 description
: 'Description of Filter 1 in Group 3'
349 label
: 'Group 3: Filter 2',
350 description
: 'Description of Filter 2 in Group 3'
354 label
: 'Group 3: Filter 3',
355 description
: 'Description of Filter 3 in Group 3'
359 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
361 model
.initializeFilters( definition
);
363 // Starting with all filters unselected
365 model
.getParametersFromFilters(),
375 'Unselected filters return all parameters falsey or \'\'.'
379 model
.toggleFiltersSelected( {
380 group1__hidefilter1
: true,
381 group1__hidefilter2
: false,
382 group1__hidefilter3
: false,
383 group2__hidefilter4
: false,
384 group2__hidefilter5
: false,
385 group2__hidefilter6
: false
387 // Only one filter in one group
389 model
.getParametersFromFilters(),
391 // Group 1 (one selected, the others are true)
395 // Group 2 (nothing is selected, all false)
401 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
405 model
.toggleFiltersSelected( {
406 group1__hidefilter1
: true,
407 group1__hidefilter2
: true,
408 group1__hidefilter3
: false,
409 group2__hidefilter4
: false,
410 group2__hidefilter5
: false,
411 group2__hidefilter6
: false
413 // Two selected filters in one group
415 model
.getParametersFromFilters(),
417 // Group 1 (two selected, the others are true)
421 // Group 2 (nothing is selected, all false)
427 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
431 model
.toggleFiltersSelected( {
432 group1__hidefilter1
: true,
433 group1__hidefilter2
: true,
434 group1__hidefilter3
: true,
435 group2__hidefilter4
: false,
436 group2__hidefilter5
: false,
437 group2__hidefilter6
: false
439 // All filters of the group are selected == this is the same as not selecting any
441 model
.getParametersFromFilters(),
443 // Group 1 (all selected, all false)
447 // Group 2 (nothing is selected, all false)
453 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
456 // Select 1 filter from string_options
457 model
.toggleFiltersSelected( {
458 group3__filter7
: true,
459 group3__filter8
: false,
460 group3__filter9
: false
462 // All filters of the group are selected == this is the same as not selecting any
464 model
.getParametersFromFilters(),
466 // Group 1 (all selected, all)
470 // Group 2 (nothing is selected, all false)
476 'One filter selected in "string_option" group returns that filter in the value.'
479 // Select 2 filters from string_options
480 model
.toggleFiltersSelected( {
481 group3__filter7
: true,
482 group3__filter8
: true,
483 group3__filter9
: false
485 // All filters of the group are selected == this is the same as not selecting any
487 model
.getParametersFromFilters(),
489 // Group 1 (all selected, all)
493 // Group 2 (nothing is selected, all false)
497 group3
: 'filter7,filter8'
499 'Two filters selected in "string_option" group returns those filters in the value.'
502 // Select 3 filters from string_options
503 model
.toggleFiltersSelected( {
504 group3__filter7
: true,
505 group3__filter8
: true,
506 group3__filter9
: true
508 // All filters of the group are selected == this is the same as not selecting any
510 model
.getParametersFromFilters(),
512 // Group 1 (all selected, all)
516 // Group 2 (nothing is selected, all false)
522 'All filters selected in "string_option" group returns \'all\'.'
527 QUnit
.test( 'getParametersFromFilters (custom object)', function ( assert
) {
529 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
533 type
: 'send_unselected_if_any',
535 { name
: 'hidefilter1', label
: 'Hide filter 1', description
: '' },
536 { name
: 'hidefilter2', label
: 'Hide filter 2', description
: '' },
537 { name
: 'hidefilter3', label
: 'Hide filter 3', description
: '' }
542 type
: 'send_unselected_if_any',
544 { name
: 'hidefilter4', label
: 'Hide filter 4', description
: '' },
545 { name
: 'hidefilter5', label
: 'Hide filter 5', description
: '' },
546 { name
: 'hidefilter6', label
: 'Hide filter 6', description
: '' }
551 type
: 'string_options',
554 { name
: 'filter7', label
: 'Hide filter 7', description
: '' },
555 { name
: 'filter8', label
: 'Hide filter 8', description
: '' },
556 { name
: 'filter9', label
: 'Hide filter 9', description
: '' }
561 // This is mocking the cases above, both
562 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
563 // - 'Two filters selected in "string_option" group returns those filters in the value.'
565 group1__hidefilter1
: true,
566 group1__hidefilter2
: true,
567 group1__hidefilter3
: false,
568 group2__hidefilter4
: false,
569 group2__hidefilter5
: false,
570 group2__hidefilter6
: false,
571 group3__filter7
: true,
572 group3__filter8
: true,
573 group3__filter9
: false
576 // Group 1 (two selected, the others are true)
580 // Group 2 (nothing is selected, all false)
584 group3
: 'filter7,filter8'
586 msg
: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
589 // This is mocking case above
590 // - 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
592 group1__hidefilter1
: 1
595 // Group 1 (one selected, the others are true)
599 // Group 2 (nothing is selected, all false)
605 msg
: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
609 model
.initializeFilters( definition
);
610 // Store original state
611 originalState
= model
.getSelectedState();
614 cases
.forEach( function ( test
) {
616 model
.getParametersFromFilters( test
.input
),
622 // After doing the above tests, make sure the actual state
623 // of the filter stayed the same
625 model
.getSelectedState(),
627 'Running the method with external definition to parse does not actually change the state of the model'
631 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
635 type
: 'send_unselected_if_any',
639 label
: 'Show filter 1',
640 description
: 'Description of Filter 1 in Group 1',
645 label
: 'Show filter 2',
646 description
: 'Description of Filter 2 in Group 1'
650 label
: 'Show filter 3',
651 description
: 'Description of Filter 3 in Group 1',
658 type
: 'send_unselected_if_any',
662 label
: 'Show filter 4',
663 description
: 'Description of Filter 1 in Group 2'
667 label
: 'Show filter 5',
668 description
: 'Description of Filter 2 in Group 2',
673 label
: 'Show filter 6',
674 description
: 'Description of Filter 3 in Group 2'
681 type
: 'string_options',
687 label
: 'Group 3: Filter 1',
688 description
: 'Description of Filter 1 in Group 3'
692 label
: 'Group 3: Filter 2',
693 description
: 'Description of Filter 2 in Group 3'
697 label
: 'Group 3: Filter 3',
698 description
: 'Description of Filter 3 in Group 3'
702 baseFilterRepresentation
= {
703 group1__hidefilter1
: false,
704 group1__hidefilter2
: false,
705 group1__hidefilter3
: false,
706 group2__hidefilter4
: false,
707 group2__hidefilter5
: false,
708 group2__hidefilter6
: false,
709 group3__filter7
: false,
710 group3__filter8
: false,
711 group3__filter9
: false
713 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
715 model
.initializeFilters( definition
);
717 // Empty query = only default values
719 model
.getFiltersFromParameters( {} ),
720 baseFilterRepresentation
,
721 'Empty parameter query results in an object representing all filters set to false'
725 model
.getFiltersFromParameters( {
728 $.extend( {}, baseFilterRepresentation
, {
729 group1__hidefilter1
: true, // The text is "show filter 1"
730 group1__hidefilter2
: false, // The text is "show filter 2"
731 group1__hidefilter3
: true // The text is "show filter 3"
733 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
737 model
.getFiltersFromParameters( {
742 $.extend( {}, baseFilterRepresentation
, {
743 group1__hidefilter1
: false, // The text is "show filter 1"
744 group1__hidefilter2
: false, // The text is "show filter 2"
745 group1__hidefilter3
: false // The text is "show filter 3"
747 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
750 // The ones above don't update the model, so we have a clean state.
751 // getFiltersFromParameters is stateless; any change is unaffected by the current state
752 // This test is demonstrating wrong usage of the method;
753 // We should be aware that getFiltersFromParameters is stateless,
754 // so each call gives us a filter state that only reflects the query given.
755 // This means that the two calls to toggleFiltersSelected() below collide.
756 // The result of the first is overridden by the result of the second,
757 // since both get a full state object from getFiltersFromParameters that **only** relates
758 // to the input it receives.
759 model
.toggleFiltersSelected(
760 model
.getFiltersFromParameters( {
765 model
.toggleFiltersSelected(
766 model
.getFiltersFromParameters( {
771 // The result here is ignoring the first toggleFiltersSelected call
773 model
.getSelectedState(),
774 $.extend( {}, baseFilterRepresentation
, {
775 group2__hidefilter4
: true,
776 group2__hidefilter5
: true,
777 group2__hidefilter6
: false
779 'getFiltersFromParameters does not care about previous or existing state.'
783 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
784 model
.initializeFilters( definition
);
786 model
.toggleFiltersSelected(
787 model
.getFiltersFromParameters( {
792 model
.getSelectedState(),
793 $.extend( {}, baseFilterRepresentation
, {
794 group3__filter7
: true,
795 group3__filter8
: false,
796 group3__filter9
: false
798 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
801 model
.toggleFiltersSelected(
802 model
.getFiltersFromParameters( {
803 group3
: 'filter7,filter8'
807 model
.getSelectedState(),
808 $.extend( {}, baseFilterRepresentation
, {
809 group3__filter7
: true,
810 group3__filter8
: true,
811 group3__filter9
: false
813 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
816 model
.toggleFiltersSelected(
817 model
.getFiltersFromParameters( {
818 group3
: 'filter7,filter8,filter9'
822 model
.getSelectedState(),
823 $.extend( {}, baseFilterRepresentation
, {
824 group3__filter7
: true,
825 group3__filter8
: true,
826 group3__filter9
: true
828 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
831 model
.toggleFiltersSelected(
832 model
.getFiltersFromParameters( {
833 group3
: 'filter7,all,filter9'
837 model
.getSelectedState(),
838 $.extend( {}, baseFilterRepresentation
, {
839 group3__filter7
: true,
840 group3__filter8
: true,
841 group3__filter9
: true
843 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
846 model
.toggleFiltersSelected(
847 model
.getFiltersFromParameters( {
848 group3
: 'filter7,foo,filter9'
852 model
.getSelectedState(),
853 $.extend( {}, baseFilterRepresentation
, {
854 group3__filter7
: true,
855 group3__filter8
: false,
856 group3__filter9
: true
858 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
862 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
866 type
: 'string_options',
870 label
: 'Show filter 1',
871 description
: 'Description of Filter 1 in Group 1'
875 label
: 'Show filter 2',
876 description
: 'Description of Filter 2 in Group 1'
880 label
: 'Show filter 3',
881 description
: 'Description of Filter 3 in Group 1'
885 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
887 model
.initializeFilters( definition
);
890 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
891 [ 'filter1', 'filter2' ],
892 'Remove duplicate values'
896 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
897 [ 'filter1', 'filter2' ],
898 'Remove invalid values'
902 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
904 'If any value is "all", the only value is "all".'
908 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
912 type
: 'string_options',
916 label
: 'Show filter 1',
917 description
: 'Description of Filter 1 in Group 1',
931 label
: 'Show filter 2',
932 description
: 'Description of Filter 2 in Group 1',
942 label
: 'Show filter 3',
943 description
: 'Description of Filter 3 in Group 1'
948 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
949 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
950 group1__filter3
: { selected
: false, conflicted
: false, included
: false }
952 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
954 model
.initializeFilters( definition
);
955 // Select a filter that has subset with another filter
956 model
.toggleFiltersSelected( {
957 group1__filter1
: true
960 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
962 model
.getFullState(),
963 $.extend( true, {}, baseFullState
, {
964 group1__filter1
: { selected
: true },
965 group1__filter2
: { included
: true },
966 group1__filter3
: { included
: true }
968 'Filters with subsets are represented in the model.'
971 // Select another filter that has a subset with the same previous filter
972 model
.toggleFiltersSelected( {
973 group1__filter2
: true
975 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
977 model
.getFullState(),
978 $.extend( true, {}, baseFullState
, {
979 group1__filter1
: { selected
: true },
980 group1__filter2
: { selected
: true, included
: true },
981 group1__filter3
: { included
: true }
983 'Filters that have multiple subsets are represented.'
986 // Remove one filter (but leave the other) that affects filter3
987 model
.toggleFiltersSelected( {
988 group1__filter1
: false
990 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
992 model
.getFullState(),
993 $.extend( true, {}, baseFullState
, {
994 group1__filter2
: { selected
: true, included
: false },
995 group1__filter3
: { included
: true }
997 'Removing a filter only un-includes its subset if there is no other filter affecting.'
1000 model
.toggleFiltersSelected( {
1001 group1__filter2
: false
1003 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1005 model
.getFullState(),
1007 'Removing all supersets also un-includes the subsets.'
1011 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
1012 var definition
= [ {
1015 type
: 'string_options',
1016 fullCoverage
: false,
1018 { name
: 'filter1', label
: '1', description
: '1' },
1019 { name
: 'filter2', label
: '2', description
: '2' },
1020 { name
: 'filter3', label
: '3', description
: '3' }
1025 type
: 'send_unselected_if_any',
1028 { name
: 'filter4', label
: '4', description
: '4' },
1029 { name
: 'filter5', label
: '5', description
: '5' },
1030 { name
: 'filter6', label
: '6', description
: '6' }
1033 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
1034 isCapsuleItemMuted = function ( filterName
) {
1035 var itemModel
= model
.getItemByName( filterName
),
1036 groupModel
= itemModel
.getGroupModel();
1038 // This is the logic inside the capsule widget
1040 // The capsule item widget only appears if the item is selected
1041 itemModel
.isSelected() &&
1042 // Muted state is only valid if group is full coverage and all items are selected
1043 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
1046 getCurrentItemsMutedState = function () {
1048 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
1049 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
1050 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
1051 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
1052 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
1053 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
1057 group1__filter1
: false,
1058 group1__filter2
: false,
1059 group1__filter3
: false,
1060 group2__filter4
: false,
1061 group2__filter5
: false,
1062 group2__filter6
: false
1065 model
.initializeFilters( definition
);
1067 // Starting state, no selection, all items are non-muted
1069 getCurrentItemsMutedState(),
1071 'No selection - all items are non-muted'
1074 // Select most (but not all) items in each group
1075 model
.toggleFiltersSelected( {
1076 group1__filter1
: true,
1077 group1__filter2
: true,
1078 group2__filter4
: true,
1079 group2__filter5
: true
1082 // Both groups have multiple (but not all) items selected, all items are non-muted
1084 getCurrentItemsMutedState(),
1086 'Not all items in the group selected - all items are non-muted'
1089 // Select all items in 'fullCoverage' group (group2)
1090 model
.toggleFiltersSelected( {
1091 group2__filter6
: true
1094 // Group2 (full coverage) has all items selected, all its items are muted
1096 getCurrentItemsMutedState(),
1097 $.extend( {}, baseMuteState
, {
1098 group2__filter4
: true,
1099 group2__filter5
: true,
1100 group2__filter6
: true
1102 'All items in \'full coverage\' group are selected - all items in the group are muted'
1105 // Select all items in non 'fullCoverage' group (group1)
1106 model
.toggleFiltersSelected( {
1107 group1__filter3
: true
1110 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1112 getCurrentItemsMutedState(),
1113 $.extend( {}, baseMuteState
, {
1114 group2__filter4
: true,
1115 group2__filter5
: true,
1116 group2__filter6
: true
1118 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1121 // Uncheck an item from each group
1122 model
.toggleFiltersSelected( {
1123 group1__filter3
: false,
1124 group2__filter5
: false
1127 getCurrentItemsMutedState(),
1129 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1133 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
1134 var definition
= [ {
1137 type
: 'string_options',
1143 conflicts
: [ { group
: 'group2' } ]
1149 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ]
1160 type
: 'send_unselected_if_any',
1161 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
1177 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
1182 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1183 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1184 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
1185 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
1186 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
1187 group2__filter6
: { selected
: false, conflicted
: false, included
: false }
1189 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1191 model
.initializeFilters( definition
);
1194 model
.getFullState(),
1196 'Initial state: no conflicts because no selections.'
1199 // Select a filter that has a conflict with an entire group
1200 model
.toggleFiltersSelected( {
1201 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1204 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1207 model
.getFullState(),
1208 $.extend( true, {}, baseFullState
, {
1209 group1__filter1
: { selected
: true },
1210 group2__filter4
: { conflicted
: true },
1211 group2__filter5
: { conflicted
: true },
1212 group2__filter6
: { conflicted
: true }
1214 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1217 // Select one of the conflicts (both filters are now conflicted and selected)
1218 model
.toggleFiltersSelected( {
1219 group2__filter4
: true // conflicts: filter 1
1221 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
1224 model
.getFullState(),
1225 $.extend( true, {}, baseFullState
, {
1226 group1__filter1
: { selected
: true, conflicted
: true },
1227 group2__filter4
: { selected
: true, conflicted
: true },
1228 group2__filter5
: { conflicted
: true },
1229 group2__filter6
: { conflicted
: true }
1231 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1235 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1236 model
.initializeFilters( definition
);
1238 // Select a filter that has a conflict with a specific filter
1239 model
.toggleFiltersSelected( {
1240 group1__filter2
: true // conflicts: filter6
1242 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1245 model
.getFullState(),
1246 $.extend( true, {}, baseFullState
, {
1247 group1__filter2
: { selected
: true },
1248 group2__filter6
: { conflicted
: true }
1250 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1253 // Select the conflicting filter
1254 model
.toggleFiltersSelected( {
1255 group2__filter6
: true // conflicts: filter2
1258 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1261 model
.getFullState(),
1262 $.extend( true, {}, baseFullState
, {
1263 group1__filter2
: { selected
: true, conflicted
: true },
1264 group2__filter6
: { selected
: true, conflicted
: true },
1265 // This is added to the conflicts because filter6 is part of group2,
1266 // who is in conflict with filter1; note that filter2 also conflicts
1267 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1268 // and also because its **own sibling** (filter2) is **also** in conflict with the
1269 // selected items in group2 (filter6)
1270 group1__filter1
: { conflicted
: true }
1272 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1275 // Now choose a non-conflicting filter from the group
1276 model
.toggleFiltersSelected( {
1277 group2__filter5
: true
1280 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1283 model
.getFullState(),
1284 $.extend( true, {}, baseFullState
, {
1285 group1__filter2
: { selected
: true },
1286 group2__filter6
: { selected
: true },
1287 group2__filter5
: { selected
: true }
1288 // Filter6 and filter1 are no longer in conflict because
1289 // filter5, while it is in conflict with filter1, it is
1290 // not in conflict with filter2 - and since filter2 is
1291 // selected, it removes the conflict bidirectionally
1293 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1296 // Followup on the previous test, unselect filter2 so filter1
1297 // is now the only one selected in its own group, and since
1298 // it is in conflict with the entire of group2, it means
1299 // filter1 is once again conflicted
1300 model
.toggleFiltersSelected( {
1301 group1__filter2
: false
1304 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1307 model
.getFullState(),
1308 $.extend( true, {}, baseFullState
, {
1309 group1__filter1
: { conflicted
: true },
1310 group2__filter6
: { selected
: true },
1311 group2__filter5
: { selected
: true }
1313 'Unselecting an item that did not conflict returns the conflict state.'
1316 // Followup #2: Now actually select filter1, and make everything conflicted
1317 model
.toggleFiltersSelected( {
1318 group1__filter1
: true
1321 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1324 model
.getFullState(),
1325 $.extend( true, {}, baseFullState
, {
1326 group1__filter1
: { selected
: true, conflicted
: true },
1327 group2__filter6
: { selected
: true, conflicted
: true },
1328 group2__filter5
: { selected
: true, conflicted
: true },
1329 group2__filter4
: { conflicted
: true } // Not selected but conflicted because it's in group2
1331 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1336 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1337 model
.initializeFilters( definition
);
1339 // Select a filter that has a conflict with a specific filter
1340 model
.toggleFiltersSelected( {
1341 group1__filter2
: true // conflicts: filter6
1344 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1347 model
.getFullState(),
1348 $.extend( true, {}, baseFullState
, {
1349 group1__filter2
: { selected
: true },
1350 group2__filter6
: { conflicted
: true }
1352 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1355 model
.toggleFiltersSelected( {
1356 group1__filter3
: true // conflicts: filter6
1359 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1362 model
.getFullState(),
1363 $.extend( true, {}, baseFullState
, {
1364 group1__filter2
: { selected
: true },
1365 group1__filter3
: { selected
: true }
1367 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1372 QUnit
.test( 'Filter highlights', function ( assert
) {
1373 var definition
= [ {
1376 type
: 'string_options',
1378 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1379 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1380 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1381 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1382 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1383 { name
: 'filter6', label
: '6', description
: '6' }
1386 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1388 model
.initializeFilters( definition
);
1391 !model
.isHighlightEnabled(),
1392 'Initially, highlight is disabled.'
1395 model
.toggleHighlight( true );
1397 model
.isHighlightEnabled(),
1398 'Highlight is enabled on toggle.'
1401 model
.setHighlightColor( 'group1__filter1', 'color1' );
1402 model
.setHighlightColor( 'group1__filter2', 'color2' );
1405 model
.getHighlightedItems().map( function ( item
) {
1406 return item
.getName();
1412 'Highlighted items are highlighted.'
1416 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1418 'Item highlight color is set.'
1421 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1423 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1425 'Item highlight color is changed on setHighlightColor.'
1428 model
.clearHighlightColor( 'group1__filter1' );
1430 model
.getHighlightedItems().map( function ( item
) {
1431 return item
.getName();
1436 'Clear highlight from an item results in the item no longer being highlighted.'
1440 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1441 model
.initializeFilters( definition
);
1443 model
.setHighlightColor( 'group1__filter1', 'color1' );
1444 model
.setHighlightColor( 'group1__filter2', 'color2' );
1445 model
.setHighlightColor( 'group1__filter3', 'color3' );
1448 model
.getHighlightedItems().map( function ( item
) {
1449 return item
.getName();
1456 'Even if highlights are not enabled, the items remember their highlight state'
1457 // NOTE: When actually displaying the highlights, the UI checks whether
1458 // highlighting is generally active and then goes over the highlighted
1459 // items. The item models, however, and the view model in general, still
1460 // retains the knowledge about which filters have different colors, so we
1461 // can seamlessly return to the colors the user previously chose if they
1462 // reapply highlights.
1466 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1467 model
.initializeFilters( definition
);
1469 model
.setHighlightColor( 'group1__filter1', 'color1' );
1470 model
.setHighlightColor( 'group1__filter6', 'color6' );
1473 model
.getHighlightedItems().map( function ( item
) {
1474 return item
.getName();
1479 'Items without a specified class identifier are not highlighted.'
1482 }( mediaWiki
, jQuery
) );