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'
75 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
77 model
.initializeFilters( definition
, namespaces
);
80 model
.getItemByName( 'group1__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
81 model
.getItemByName( 'group1__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
82 model
.getItemByName( 'group2__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
83 model
.getItemByName( 'group2__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
84 model
.getItemByName( 'group3__filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
85 model
.getItemByName( 'group3__filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
86 model
.getItemByName( 'namespace__0' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
87 model
.getItemByName( 'namespace__1' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
88 model
.getItemByName( 'namespace__2' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
89 model
.getItemByName( 'namespace__3' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
90 'Filters instantiated and stored correctly'
94 model
.getSelectedState(),
96 group1__filter1
: false,
97 group1__filter2
: false,
98 group2__filter1
: false,
99 group2__filter2
: false,
100 group3__filter1
: false,
101 group3__filter2
: false,
107 'Initial state of filters'
110 model
.toggleFiltersSelected( {
111 group1__filter1
: true,
112 group2__filter2
: true,
113 group3__filter1
: true
116 model
.getSelectedState(),
118 group1__filter1
: true,
119 group1__filter2
: false,
120 group2__filter1
: false,
121 group2__filter2
: true,
122 group3__filter1
: true,
123 group3__filter2
: false,
129 'Updating filter states correctly'
133 QUnit
.test( 'Default filters', function ( assert
) {
137 type
: 'send_unselected_if_any',
141 label
: 'Show filter 1',
142 description
: 'Description of Filter 1 in Group 1',
147 label
: 'Show filter 2',
148 description
: 'Description of Filter 2 in Group 1'
152 label
: 'Show filter 3',
153 description
: 'Description of Filter 3 in Group 1',
160 type
: 'send_unselected_if_any',
164 label
: 'Show filter 4',
165 description
: 'Description of Filter 1 in Group 2'
169 label
: 'Show filter 5',
170 description
: 'Description of Filter 2 in Group 2',
175 label
: 'Show filter 6',
176 description
: 'Description of Filter 3 in Group 2'
183 type
: 'string_options',
189 label
: 'Group 3: Filter 1',
190 description
: 'Description of Filter 1 in Group 3'
194 label
: 'Group 3: Filter 2',
195 description
: 'Description of Filter 2 in Group 3'
199 label
: 'Group 3: Filter 3',
200 description
: 'Description of Filter 3 in Group 3'
204 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
206 model
.initializeFilters( definition
);
208 // Empty query = only default values
210 model
.getDefaultParams(),
220 'Default parameters are stored properly per filter and group'
224 QUnit
.test( 'Finding matching filters', function ( assert
) {
228 title
: 'Group 1 title',
229 type
: 'send_unselected_if_any',
233 label
: 'group1filter1-label',
234 description
: 'group1filter1-desc'
238 label
: 'group1filter2-label',
239 description
: 'group1filter2-desc'
244 title
: 'Group 2 title',
245 type
: 'send_unselected_if_any',
249 label
: 'group2filter1-label',
250 description
: 'group2filter1-desc'
254 label
: 'group2filter2-label',
255 description
: 'group2filter2-desc'
269 group1
: [ 'group1__filter1', 'group1__filter2' ],
270 group2
: [ 'group2__filter1' ]
272 reason
: 'Finds filters starting with the query string'
275 query
: 'filter 2 in group',
277 group1
: [ 'group1__filter2' ],
278 group2
: [ 'group2__filter2' ]
280 reason
: 'Finds filters containing the query string in their description'
285 group1
: [ 'group1__filter1', 'group1__filter2' ],
286 group2
: [ 'group2__filter1', 'group2__filter2' ]
288 reason
: 'Finds filters containing the query string in their group title'
293 namespace: [ 'namespace__0' ]
295 reason
: 'Finds namespaces when using : prefix'
300 reason
: 'Finds no results if using namespaces prefix (:) to search for filter title'
303 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
304 extractNames = function ( matches
) {
306 Object
.keys( matches
).forEach( function ( groupName
) {
307 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
308 return item
.getName();
314 model
.initializeFilters( definition
, namespaces
);
316 testCases
.forEach( function ( testCase
) {
317 matches
= model
.findMatches( testCase
.query
);
319 extractNames( matches
),
320 testCase
.expectedMatches
,
325 matches
= model
.findMatches( 'foo' );
327 $.isEmptyObject( matches
),
328 'findMatches returns an empty object when no results found'
332 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
336 type
: 'send_unselected_if_any',
340 label
: 'Group 1: Filter 1',
341 description
: 'Description of Filter 1 in Group 1'
345 label
: 'Group 1: Filter 2',
346 description
: 'Description of Filter 2 in Group 1'
350 label
: 'Group 1: Filter 3',
351 description
: 'Description of Filter 3 in Group 1'
357 type
: 'send_unselected_if_any',
361 label
: 'Group 2: Filter 1',
362 description
: 'Description of Filter 1 in Group 2'
366 label
: 'Group 2: Filter 2',
367 description
: 'Description of Filter 2 in Group 2'
371 label
: 'Group 2: Filter 3',
372 description
: 'Description of Filter 3 in Group 2'
378 type
: 'string_options',
383 label
: 'Group 3: Filter 1',
384 description
: 'Description of Filter 1 in Group 3'
388 label
: 'Group 3: Filter 2',
389 description
: 'Description of Filter 2 in Group 3'
393 label
: 'Group 3: Filter 3',
394 description
: 'Description of Filter 3 in Group 3'
398 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
400 model
.initializeFilters( definition
);
402 // Starting with all filters unselected
404 model
.getParametersFromFilters(),
414 'Unselected filters return all parameters falsey or \'\'.'
418 model
.toggleFiltersSelected( {
419 group1__hidefilter1
: true,
420 group1__hidefilter2
: false,
421 group1__hidefilter3
: false,
422 group2__hidefilter4
: false,
423 group2__hidefilter5
: false,
424 group2__hidefilter6
: false
426 // Only one filter in one group
428 model
.getParametersFromFilters(),
430 // Group 1 (one selected, the others are true)
434 // Group 2 (nothing is selected, all false)
440 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
444 model
.toggleFiltersSelected( {
445 group1__hidefilter1
: true,
446 group1__hidefilter2
: true,
447 group1__hidefilter3
: false,
448 group2__hidefilter4
: false,
449 group2__hidefilter5
: false,
450 group2__hidefilter6
: false
452 // Two selected filters in one group
454 model
.getParametersFromFilters(),
456 // Group 1 (two selected, the others are true)
460 // Group 2 (nothing is selected, all false)
466 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
470 model
.toggleFiltersSelected( {
471 group1__hidefilter1
: true,
472 group1__hidefilter2
: true,
473 group1__hidefilter3
: true,
474 group2__hidefilter4
: false,
475 group2__hidefilter5
: false,
476 group2__hidefilter6
: false
478 // All filters of the group are selected == this is the same as not selecting any
480 model
.getParametersFromFilters(),
482 // Group 1 (all selected, all false)
486 // Group 2 (nothing is selected, all false)
492 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
495 // Select 1 filter from string_options
496 model
.toggleFiltersSelected( {
497 group3__filter7
: true,
498 group3__filter8
: false,
499 group3__filter9
: false
501 // All filters of the group are selected == this is the same as not selecting any
503 model
.getParametersFromFilters(),
505 // Group 1 (all selected, all)
509 // Group 2 (nothing is selected, all false)
515 'One filter selected in "string_option" group returns that filter in the value.'
518 // Select 2 filters from string_options
519 model
.toggleFiltersSelected( {
520 group3__filter7
: true,
521 group3__filter8
: true,
522 group3__filter9
: false
524 // All filters of the group are selected == this is the same as not selecting any
526 model
.getParametersFromFilters(),
528 // Group 1 (all selected, all)
532 // Group 2 (nothing is selected, all false)
536 group3
: 'filter7,filter8'
538 'Two filters selected in "string_option" group returns those filters in the value.'
541 // Select 3 filters from string_options
542 model
.toggleFiltersSelected( {
543 group3__filter7
: true,
544 group3__filter8
: true,
545 group3__filter9
: true
547 // All filters of the group are selected == this is the same as not selecting any
549 model
.getParametersFromFilters(),
551 // Group 1 (all selected, all)
555 // Group 2 (nothing is selected, all false)
561 'All filters selected in "string_option" group returns \'all\'.'
566 QUnit
.test( 'getParametersFromFilters (custom object)', function ( assert
) {
568 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
572 type
: 'send_unselected_if_any',
574 { name
: 'hidefilter1', label
: 'Hide filter 1', description
: '' },
575 { name
: 'hidefilter2', label
: 'Hide filter 2', description
: '' },
576 { name
: 'hidefilter3', label
: 'Hide filter 3', description
: '' }
581 type
: 'send_unselected_if_any',
583 { name
: 'hidefilter4', label
: 'Hide filter 4', description
: '' },
584 { name
: 'hidefilter5', label
: 'Hide filter 5', description
: '' },
585 { name
: 'hidefilter6', label
: 'Hide filter 6', description
: '' }
590 type
: 'string_options',
593 { name
: 'filter7', label
: 'Hide filter 7', description
: '' },
594 { name
: 'filter8', label
: 'Hide filter 8', description
: '' },
595 { name
: 'filter9', label
: 'Hide filter 9', description
: '' }
600 // This is mocking the cases above, both
601 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
602 // - 'Two filters selected in "string_option" group returns those filters in the value.'
604 group1__hidefilter1
: true,
605 group1__hidefilter2
: true,
606 group1__hidefilter3
: false,
607 group2__hidefilter4
: false,
608 group2__hidefilter5
: false,
609 group2__hidefilter6
: false,
610 group3__filter7
: true,
611 group3__filter8
: true,
612 group3__filter9
: false
615 // Group 1 (two selected, the others are true)
619 // Group 2 (nothing is selected, all false)
623 group3
: 'filter7,filter8'
625 msg
: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
628 // This is mocking case above
629 // - 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
631 group1__hidefilter1
: 1
634 // Group 1 (one selected, the others are true)
638 // Group 2 (nothing is selected, all false)
644 msg
: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
657 msg
: 'Given an explicit empty object, the result is all filters set to their falsey unselected value.'
661 model
.initializeFilters( definition
);
662 // Store original state
663 originalState
= model
.getSelectedState();
666 cases
.forEach( function ( test
) {
668 model
.getParametersFromFilters( test
.input
),
674 // After doing the above tests, make sure the actual state
675 // of the filter stayed the same
677 model
.getSelectedState(),
679 'Running the method with external definition to parse does not actually change the state of the model'
683 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
687 type
: 'send_unselected_if_any',
691 label
: 'Show filter 1',
692 description
: 'Description of Filter 1 in Group 1',
697 label
: 'Show filter 2',
698 description
: 'Description of Filter 2 in Group 1'
702 label
: 'Show filter 3',
703 description
: 'Description of Filter 3 in Group 1',
710 type
: 'send_unselected_if_any',
714 label
: 'Show filter 4',
715 description
: 'Description of Filter 1 in Group 2'
719 label
: 'Show filter 5',
720 description
: 'Description of Filter 2 in Group 2',
725 label
: 'Show filter 6',
726 description
: 'Description of Filter 3 in Group 2'
733 type
: 'string_options',
739 label
: 'Group 3: Filter 1',
740 description
: 'Description of Filter 1 in Group 3'
744 label
: 'Group 3: Filter 2',
745 description
: 'Description of Filter 2 in Group 3'
749 label
: 'Group 3: Filter 3',
750 description
: 'Description of Filter 3 in Group 3'
754 baseFilterRepresentation
= {
755 group1__hidefilter1
: false,
756 group1__hidefilter2
: false,
757 group1__hidefilter3
: false,
758 group2__hidefilter4
: false,
759 group2__hidefilter5
: false,
760 group2__hidefilter6
: false,
761 group3__filter7
: false,
762 group3__filter8
: false,
763 group3__filter9
: false
765 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
767 model
.initializeFilters( definition
);
769 // Empty query = only default values
771 model
.getFiltersFromParameters( {} ),
772 baseFilterRepresentation
,
773 'Empty parameter query results in an object representing all filters set to false'
777 model
.getFiltersFromParameters( {
780 $.extend( {}, baseFilterRepresentation
, {
781 group1__hidefilter1
: true, // The text is "show filter 1"
782 group1__hidefilter2
: false, // The text is "show filter 2"
783 group1__hidefilter3
: true // The text is "show filter 3"
785 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
789 model
.getFiltersFromParameters( {
794 $.extend( {}, baseFilterRepresentation
, {
795 group1__hidefilter1
: false, // The text is "show filter 1"
796 group1__hidefilter2
: false, // The text is "show filter 2"
797 group1__hidefilter3
: false // The text is "show filter 3"
799 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
802 // The ones above don't update the model, so we have a clean state.
803 // getFiltersFromParameters is stateless; any change is unaffected by the current state
804 // This test is demonstrating wrong usage of the method;
805 // We should be aware that getFiltersFromParameters is stateless,
806 // so each call gives us a filter state that only reflects the query given.
807 // This means that the two calls to toggleFiltersSelected() below collide.
808 // The result of the first is overridden by the result of the second,
809 // since both get a full state object from getFiltersFromParameters that **only** relates
810 // to the input it receives.
811 model
.toggleFiltersSelected(
812 model
.getFiltersFromParameters( {
817 model
.toggleFiltersSelected(
818 model
.getFiltersFromParameters( {
823 // The result here is ignoring the first toggleFiltersSelected call
825 model
.getSelectedState(),
826 $.extend( {}, baseFilterRepresentation
, {
827 group2__hidefilter4
: true,
828 group2__hidefilter5
: true,
829 group2__hidefilter6
: false
831 'getFiltersFromParameters does not care about previous or existing state.'
835 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
836 model
.initializeFilters( definition
);
838 model
.toggleFiltersSelected(
839 model
.getFiltersFromParameters( {
844 model
.getSelectedState(),
845 $.extend( {}, baseFilterRepresentation
, {
846 group3__filter7
: true,
847 group3__filter8
: false,
848 group3__filter9
: false
850 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
853 model
.toggleFiltersSelected(
854 model
.getFiltersFromParameters( {
855 group3
: 'filter7,filter8'
859 model
.getSelectedState(),
860 $.extend( {}, baseFilterRepresentation
, {
861 group3__filter7
: true,
862 group3__filter8
: true,
863 group3__filter9
: false
865 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
868 model
.toggleFiltersSelected(
869 model
.getFiltersFromParameters( {
870 group3
: 'filter7,filter8,filter9'
874 model
.getSelectedState(),
875 $.extend( {}, baseFilterRepresentation
, {
876 group3__filter7
: true,
877 group3__filter8
: true,
878 group3__filter9
: true
880 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
883 model
.toggleFiltersSelected(
884 model
.getFiltersFromParameters( {
885 group3
: 'filter7,all,filter9'
889 model
.getSelectedState(),
890 $.extend( {}, baseFilterRepresentation
, {
891 group3__filter7
: true,
892 group3__filter8
: true,
893 group3__filter9
: true
895 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
898 model
.toggleFiltersSelected(
899 model
.getFiltersFromParameters( {
900 group3
: 'filter7,foo,filter9'
904 model
.getSelectedState(),
905 $.extend( {}, baseFilterRepresentation
, {
906 group3__filter7
: true,
907 group3__filter8
: false,
908 group3__filter9
: true
910 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
914 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
918 type
: 'string_options',
922 label
: 'Show filter 1',
923 description
: 'Description of Filter 1 in Group 1'
927 label
: 'Show filter 2',
928 description
: 'Description of Filter 2 in Group 1'
932 label
: 'Show filter 3',
933 description
: 'Description of Filter 3 in Group 1'
937 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
939 model
.initializeFilters( definition
);
942 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
943 [ 'filter1', 'filter2' ],
944 'Remove duplicate values'
948 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
949 [ 'filter1', 'filter2' ],
950 'Remove invalid values'
954 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
956 'If any value is "all", the only value is "all".'
960 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
964 type
: 'string_options',
968 label
: 'Show filter 1',
969 description
: 'Description of Filter 1 in Group 1',
983 label
: 'Show filter 2',
984 description
: 'Description of Filter 2 in Group 1',
994 label
: 'Show filter 3',
995 description
: 'Description of Filter 3 in Group 1'
1000 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1001 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1002 group1__filter3
: { selected
: false, conflicted
: false, included
: false }
1004 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1006 model
.initializeFilters( definition
);
1007 // Select a filter that has subset with another filter
1008 model
.toggleFiltersSelected( {
1009 group1__filter1
: true
1012 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1014 model
.getFullState(),
1015 $.extend( true, {}, baseFullState
, {
1016 group1__filter1
: { selected
: true },
1017 group1__filter2
: { included
: true },
1018 group1__filter3
: { included
: true }
1020 'Filters with subsets are represented in the model.'
1023 // Select another filter that has a subset with the same previous filter
1024 model
.toggleFiltersSelected( {
1025 group1__filter2
: true
1027 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
1029 model
.getFullState(),
1030 $.extend( true, {}, baseFullState
, {
1031 group1__filter1
: { selected
: true },
1032 group1__filter2
: { selected
: true, included
: true },
1033 group1__filter3
: { included
: true }
1035 'Filters that have multiple subsets are represented.'
1038 // Remove one filter (but leave the other) that affects filter3
1039 model
.toggleFiltersSelected( {
1040 group1__filter1
: false
1042 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1044 model
.getFullState(),
1045 $.extend( true, {}, baseFullState
, {
1046 group1__filter2
: { selected
: true, included
: false },
1047 group1__filter3
: { included
: true }
1049 'Removing a filter only un-includes its subset if there is no other filter affecting.'
1052 model
.toggleFiltersSelected( {
1053 group1__filter2
: false
1055 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1057 model
.getFullState(),
1059 'Removing all supersets also un-includes the subsets.'
1063 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
1064 var definition
= [ {
1067 type
: 'string_options',
1068 fullCoverage
: false,
1070 { name
: 'filter1', label
: '1', description
: '1' },
1071 { name
: 'filter2', label
: '2', description
: '2' },
1072 { name
: 'filter3', label
: '3', description
: '3' }
1077 type
: 'send_unselected_if_any',
1080 { name
: 'filter4', label
: '4', description
: '4' },
1081 { name
: 'filter5', label
: '5', description
: '5' },
1082 { name
: 'filter6', label
: '6', description
: '6' }
1085 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
1086 isCapsuleItemMuted = function ( filterName
) {
1087 var itemModel
= model
.getItemByName( filterName
),
1088 groupModel
= itemModel
.getGroupModel();
1090 // This is the logic inside the capsule widget
1092 // The capsule item widget only appears if the item is selected
1093 itemModel
.isSelected() &&
1094 // Muted state is only valid if group is full coverage and all items are selected
1095 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
1098 getCurrentItemsMutedState = function () {
1100 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
1101 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
1102 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
1103 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
1104 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
1105 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
1109 group1__filter1
: false,
1110 group1__filter2
: false,
1111 group1__filter3
: false,
1112 group2__filter4
: false,
1113 group2__filter5
: false,
1114 group2__filter6
: false
1117 model
.initializeFilters( definition
);
1119 // Starting state, no selection, all items are non-muted
1121 getCurrentItemsMutedState(),
1123 'No selection - all items are non-muted'
1126 // Select most (but not all) items in each group
1127 model
.toggleFiltersSelected( {
1128 group1__filter1
: true,
1129 group1__filter2
: true,
1130 group2__filter4
: true,
1131 group2__filter5
: true
1134 // Both groups have multiple (but not all) items selected, all items are non-muted
1136 getCurrentItemsMutedState(),
1138 'Not all items in the group selected - all items are non-muted'
1141 // Select all items in 'fullCoverage' group (group2)
1142 model
.toggleFiltersSelected( {
1143 group2__filter6
: true
1146 // Group2 (full coverage) has all items selected, all its items are muted
1148 getCurrentItemsMutedState(),
1149 $.extend( {}, baseMuteState
, {
1150 group2__filter4
: true,
1151 group2__filter5
: true,
1152 group2__filter6
: true
1154 'All items in \'full coverage\' group are selected - all items in the group are muted'
1157 // Select all items in non 'fullCoverage' group (group1)
1158 model
.toggleFiltersSelected( {
1159 group1__filter3
: true
1162 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1164 getCurrentItemsMutedState(),
1165 $.extend( {}, baseMuteState
, {
1166 group2__filter4
: true,
1167 group2__filter5
: true,
1168 group2__filter6
: true
1170 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1173 // Uncheck an item from each group
1174 model
.toggleFiltersSelected( {
1175 group1__filter3
: false,
1176 group2__filter5
: false
1179 getCurrentItemsMutedState(),
1181 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1185 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
1186 var definition
= [ {
1189 type
: 'string_options',
1195 conflicts
: [ { group
: 'group2' } ]
1201 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ]
1212 type
: 'send_unselected_if_any',
1213 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
1229 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
1234 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1235 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1236 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
1237 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
1238 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
1239 group2__filter6
: { selected
: false, conflicted
: false, included
: false }
1241 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1243 model
.initializeFilters( definition
);
1246 model
.getFullState(),
1248 'Initial state: no conflicts because no selections.'
1251 // Select a filter that has a conflict with an entire group
1252 model
.toggleFiltersSelected( {
1253 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1256 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1259 model
.getFullState(),
1260 $.extend( true, {}, baseFullState
, {
1261 group1__filter1
: { selected
: true },
1262 group2__filter4
: { conflicted
: true },
1263 group2__filter5
: { conflicted
: true },
1264 group2__filter6
: { conflicted
: true }
1266 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1269 // Select one of the conflicts (both filters are now conflicted and selected)
1270 model
.toggleFiltersSelected( {
1271 group2__filter4
: true // conflicts: filter 1
1273 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
1276 model
.getFullState(),
1277 $.extend( true, {}, baseFullState
, {
1278 group1__filter1
: { selected
: true, conflicted
: true },
1279 group2__filter4
: { selected
: true, conflicted
: true },
1280 group2__filter5
: { conflicted
: true },
1281 group2__filter6
: { conflicted
: true }
1283 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1287 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1288 model
.initializeFilters( definition
);
1290 // Select a filter that has a conflict with a specific filter
1291 model
.toggleFiltersSelected( {
1292 group1__filter2
: true // conflicts: filter6
1294 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1297 model
.getFullState(),
1298 $.extend( true, {}, baseFullState
, {
1299 group1__filter2
: { selected
: true },
1300 group2__filter6
: { conflicted
: true }
1302 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1305 // Select the conflicting filter
1306 model
.toggleFiltersSelected( {
1307 group2__filter6
: true // conflicts: filter2
1310 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1313 model
.getFullState(),
1314 $.extend( true, {}, baseFullState
, {
1315 group1__filter2
: { selected
: true, conflicted
: true },
1316 group2__filter6
: { selected
: true, conflicted
: true },
1317 // This is added to the conflicts because filter6 is part of group2,
1318 // who is in conflict with filter1; note that filter2 also conflicts
1319 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1320 // and also because its **own sibling** (filter2) is **also** in conflict with the
1321 // selected items in group2 (filter6)
1322 group1__filter1
: { conflicted
: true }
1324 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1327 // Now choose a non-conflicting filter from the group
1328 model
.toggleFiltersSelected( {
1329 group2__filter5
: true
1332 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1335 model
.getFullState(),
1336 $.extend( true, {}, baseFullState
, {
1337 group1__filter2
: { selected
: true },
1338 group2__filter6
: { selected
: true },
1339 group2__filter5
: { selected
: true }
1340 // Filter6 and filter1 are no longer in conflict because
1341 // filter5, while it is in conflict with filter1, it is
1342 // not in conflict with filter2 - and since filter2 is
1343 // selected, it removes the conflict bidirectionally
1345 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1348 // Followup on the previous test, unselect filter2 so filter1
1349 // is now the only one selected in its own group, and since
1350 // it is in conflict with the entire of group2, it means
1351 // filter1 is once again conflicted
1352 model
.toggleFiltersSelected( {
1353 group1__filter2
: false
1356 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1359 model
.getFullState(),
1360 $.extend( true, {}, baseFullState
, {
1361 group1__filter1
: { conflicted
: true },
1362 group2__filter6
: { selected
: true },
1363 group2__filter5
: { selected
: true }
1365 'Unselecting an item that did not conflict returns the conflict state.'
1368 // Followup #2: Now actually select filter1, and make everything conflicted
1369 model
.toggleFiltersSelected( {
1370 group1__filter1
: true
1373 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1376 model
.getFullState(),
1377 $.extend( true, {}, baseFullState
, {
1378 group1__filter1
: { selected
: true, conflicted
: true },
1379 group2__filter6
: { selected
: true, conflicted
: true },
1380 group2__filter5
: { selected
: true, conflicted
: true },
1381 group2__filter4
: { conflicted
: true } // Not selected but conflicted because it's in group2
1383 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1388 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1389 model
.initializeFilters( definition
);
1391 // Select a filter that has a conflict with a specific filter
1392 model
.toggleFiltersSelected( {
1393 group1__filter2
: true // conflicts: filter6
1396 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1399 model
.getFullState(),
1400 $.extend( true, {}, baseFullState
, {
1401 group1__filter2
: { selected
: true },
1402 group2__filter6
: { conflicted
: true }
1404 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1407 model
.toggleFiltersSelected( {
1408 group1__filter3
: true // conflicts: filter6
1411 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1414 model
.getFullState(),
1415 $.extend( true, {}, baseFullState
, {
1416 group1__filter2
: { selected
: true },
1417 group1__filter3
: { selected
: true }
1419 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1424 QUnit
.test( 'Filter highlights', function ( assert
) {
1425 var definition
= [ {
1428 type
: 'string_options',
1430 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1431 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1432 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1433 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1434 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1435 { name
: 'filter6', label
: '6', description
: '6' }
1438 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1440 model
.initializeFilters( definition
);
1443 !model
.isHighlightEnabled(),
1444 'Initially, highlight is disabled.'
1447 model
.toggleHighlight( true );
1449 model
.isHighlightEnabled(),
1450 'Highlight is enabled on toggle.'
1453 model
.setHighlightColor( 'group1__filter1', 'color1' );
1454 model
.setHighlightColor( 'group1__filter2', 'color2' );
1457 model
.getHighlightedItems().map( function ( item
) {
1458 return item
.getName();
1464 'Highlighted items are highlighted.'
1468 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1470 'Item highlight color is set.'
1473 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1475 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1477 'Item highlight color is changed on setHighlightColor.'
1480 model
.clearHighlightColor( 'group1__filter1' );
1482 model
.getHighlightedItems().map( function ( item
) {
1483 return item
.getName();
1488 'Clear highlight from an item results in the item no longer being highlighted.'
1492 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1493 model
.initializeFilters( definition
);
1495 model
.setHighlightColor( 'group1__filter1', 'color1' );
1496 model
.setHighlightColor( 'group1__filter2', 'color2' );
1497 model
.setHighlightColor( 'group1__filter3', 'color3' );
1500 model
.getHighlightedItems().map( function ( item
) {
1501 return item
.getName();
1508 'Even if highlights are not enabled, the items remember their highlight state'
1509 // NOTE: When actually displaying the highlights, the UI checks whether
1510 // highlighting is generally active and then goes over the highlighted
1511 // items. The item models, however, and the view model in general, still
1512 // retains the knowledge about which filters have different colors, so we
1513 // can seamlessly return to the colors the user previously chose if they
1514 // reapply highlights.
1518 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1519 model
.initializeFilters( definition
);
1521 model
.setHighlightColor( 'group1__filter1', 'color1' );
1522 model
.setHighlightColor( 'group1__filter6', 'color6' );
1525 model
.getHighlightedItems().map( function ( item
) {
1526 return item
.getName();
1531 'Items without a specified class identifier are not highlighted.'
1534 }( mediaWiki
, jQuery
) );