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( 'Finding matching filters', function ( assert
) {
116 title
: 'Group 1 title',
117 type
: 'send_unselected_if_any',
121 label
: 'group1filter1-label',
122 description
: 'group1filter1-desc'
126 label
: 'group1filter2-label',
127 description
: 'group1filter2-desc'
132 title
: 'Group 2 title',
133 type
: 'send_unselected_if_any',
137 label
: 'group2filter1-label',
138 description
: 'group2filter1-desc'
142 label
: 'group2filter2-label',
143 description
: 'group2filter2-desc'
151 group1
: [ 'group1__filter1', 'group1__filter2' ],
152 group2
: [ 'group2__filter1' ]
154 reason
: 'Finds filters starting with the query string'
157 query
: 'filter 2 in group',
159 group1
: [ 'group1__filter2' ],
160 group2
: [ 'group2__filter2' ]
162 reason
: 'Finds filters containing the query string in their description'
167 group1
: [ 'group1__filter1', 'group1__filter2' ],
168 group2
: [ 'group2__filter1', 'group2__filter2' ]
170 reason
: 'Finds filters containing the query string in their group title'
173 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
174 extractNames = function ( matches
) {
176 Object
.keys( matches
).forEach( function ( groupName
) {
177 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
178 return item
.getName();
184 model
.initializeFilters( definition
);
186 testCases
.forEach( function ( testCase
) {
187 matches
= model
.findMatches( testCase
.query
);
189 extractNames( matches
),
190 testCase
.expectedMatches
,
195 matches
= model
.findMatches( 'foo' );
197 $.isEmptyObject( matches
),
198 'findMatches returns an empty object when no results found'
202 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
206 type
: 'send_unselected_if_any',
210 label
: 'Group 1: Filter 1',
211 description
: 'Description of Filter 1 in Group 1'
215 label
: 'Group 1: Filter 2',
216 description
: 'Description of Filter 2 in Group 1'
220 label
: 'Group 1: Filter 3',
221 description
: 'Description of Filter 3 in Group 1'
227 type
: 'send_unselected_if_any',
231 label
: 'Group 2: Filter 1',
232 description
: 'Description of Filter 1 in Group 2'
236 label
: 'Group 2: Filter 2',
237 description
: 'Description of Filter 2 in Group 2'
241 label
: 'Group 2: Filter 3',
242 description
: 'Description of Filter 3 in Group 2'
248 type
: 'string_options',
253 label
: 'Group 3: Filter 1',
254 description
: 'Description of Filter 1 in Group 3'
258 label
: 'Group 3: Filter 2',
259 description
: 'Description of Filter 2 in Group 3'
263 label
: 'Group 3: Filter 3',
264 description
: 'Description of Filter 3 in Group 3'
268 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
270 model
.initializeFilters( definition
);
272 // Starting with all filters unselected
274 model
.getParametersFromFilters(),
284 'Unselected filters return all parameters falsey or \'\'.'
288 model
.toggleFiltersSelected( {
289 group1__hidefilter1
: true,
290 group1__hidefilter2
: false,
291 group1__hidefilter3
: false,
292 group2__hidefilter4
: false,
293 group2__hidefilter5
: false,
294 group2__hidefilter6
: false
296 // Only one filter in one group
298 model
.getParametersFromFilters(),
300 // Group 1 (one selected, the others are true)
304 // Group 2 (nothing is selected, all false)
310 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
314 model
.toggleFiltersSelected( {
315 group1__hidefilter1
: true,
316 group1__hidefilter2
: true,
317 group1__hidefilter3
: false,
318 group2__hidefilter4
: false,
319 group2__hidefilter5
: false,
320 group2__hidefilter6
: false
322 // Two selected filters in one group
324 model
.getParametersFromFilters(),
326 // Group 1 (two selected, the others are true)
330 // Group 2 (nothing is selected, all false)
336 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
340 model
.toggleFiltersSelected( {
341 group1__hidefilter1
: true,
342 group1__hidefilter2
: true,
343 group1__hidefilter3
: true,
344 group2__hidefilter4
: false,
345 group2__hidefilter5
: false,
346 group2__hidefilter6
: false
348 // All filters of the group are selected == this is the same as not selecting any
350 model
.getParametersFromFilters(),
352 // Group 1 (all selected, all false)
356 // Group 2 (nothing is selected, all false)
362 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
365 // Select 1 filter from string_options
366 model
.toggleFiltersSelected( {
367 group3__filter7
: true,
368 group3__filter8
: false,
369 group3__filter9
: false
371 // All filters of the group are selected == this is the same as not selecting any
373 model
.getParametersFromFilters(),
375 // Group 1 (all selected, all)
379 // Group 2 (nothing is selected, all false)
385 'One filter selected in "string_option" group returns that filter in the value.'
388 // Select 2 filters from string_options
389 model
.toggleFiltersSelected( {
390 group3__filter7
: true,
391 group3__filter8
: true,
392 group3__filter9
: false
394 // All filters of the group are selected == this is the same as not selecting any
396 model
.getParametersFromFilters(),
398 // Group 1 (all selected, all)
402 // Group 2 (nothing is selected, all false)
406 group3
: 'filter7,filter8'
408 'Two filters selected in "string_option" group returns those filters in the value.'
411 // Select 3 filters from string_options
412 model
.toggleFiltersSelected( {
413 group3__filter7
: true,
414 group3__filter8
: true,
415 group3__filter9
: true
417 // All filters of the group are selected == this is the same as not selecting any
419 model
.getParametersFromFilters(),
421 // Group 1 (all selected, all)
425 // Group 2 (nothing is selected, all false)
431 'All filters selected in "string_option" group returns \'all\'.'
436 QUnit
.test( 'getParametersFromFilters (custom object)', function ( assert
) {
438 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
442 type
: 'send_unselected_if_any',
444 { name
: 'hidefilter1', label
: 'Hide filter 1', description
: '' },
445 { name
: 'hidefilter2', label
: 'Hide filter 2', description
: '' },
446 { name
: 'hidefilter3', label
: 'Hide filter 3', description
: '' }
451 type
: 'send_unselected_if_any',
453 { name
: 'hidefilter4', label
: 'Hide filter 4', description
: '' },
454 { name
: 'hidefilter5', label
: 'Hide filter 5', description
: '' },
455 { name
: 'hidefilter6', label
: 'Hide filter 6', description
: '' }
460 type
: 'string_options',
463 { name
: 'filter7', label
: 'Hide filter 7', description
: '' },
464 { name
: 'filter8', label
: 'Hide filter 8', description
: '' },
465 { name
: 'filter9', label
: 'Hide filter 9', description
: '' }
470 // This is mocking the cases above, both
471 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
472 // - 'Two filters selected in "string_option" group returns those filters in the value.'
474 group1__hidefilter1
: true,
475 group1__hidefilter2
: true,
476 group1__hidefilter3
: false,
477 group2__hidefilter4
: false,
478 group2__hidefilter5
: false,
479 group2__hidefilter6
: false,
480 group3__filter7
: true,
481 group3__filter8
: true,
482 group3__filter9
: false
485 // Group 1 (two selected, the others are true)
489 // Group 2 (nothing is selected, all false)
493 group3
: 'filter7,filter8'
495 msg
: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
498 // This is mocking case above
499 // - 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
501 group1__hidefilter1
: 1
504 // Group 1 (one selected, the others are true)
508 // Group 2 (nothing is selected, all false)
514 msg
: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
518 model
.initializeFilters( definition
);
519 // Store original state
520 originalState
= model
.getSelectedState();
523 cases
.forEach( function ( test
) {
525 model
.getParametersFromFilters( test
.input
),
531 // After doing the above tests, make sure the actual state
532 // of the filter stayed the same
534 model
.getSelectedState(),
536 'Running the method with external definition to parse does not actually change the state of the model'
540 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
544 type
: 'send_unselected_if_any',
548 label
: 'Show filter 1',
549 description
: 'Description of Filter 1 in Group 1',
554 label
: 'Show filter 2',
555 description
: 'Description of Filter 2 in Group 1'
559 label
: 'Show filter 3',
560 description
: 'Description of Filter 3 in Group 1',
567 type
: 'send_unselected_if_any',
571 label
: 'Show filter 4',
572 description
: 'Description of Filter 1 in Group 2'
576 label
: 'Show filter 5',
577 description
: 'Description of Filter 2 in Group 2',
582 label
: 'Show filter 6',
583 description
: 'Description of Filter 3 in Group 2'
590 type
: 'string_options',
596 label
: 'Group 3: Filter 1',
597 description
: 'Description of Filter 1 in Group 3'
601 label
: 'Group 3: Filter 2',
602 description
: 'Description of Filter 2 in Group 3'
606 label
: 'Group 3: Filter 3',
607 description
: 'Description of Filter 3 in Group 3'
611 defaultFilterRepresentation
= {
612 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
613 group1__hidefilter1
: false,
614 group1__hidefilter2
: true,
615 group1__hidefilter3
: false,
616 group2__hidefilter4
: true,
617 group2__hidefilter5
: false,
618 group2__hidefilter6
: true,
619 // Group 3, "string_options", default values correspond to parameters and filters
620 group3__filter7
: false,
621 group3__filter8
: true,
622 group3__filter9
: false
624 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
626 model
.initializeFilters( definition
);
628 // Empty query = only default values
630 model
.getFiltersFromParameters( {} ),
631 defaultFilterRepresentation
,
632 'Empty parameter query results in filters in initial default state'
636 model
.getFiltersFromParameters( {
639 $.extend( {}, defaultFilterRepresentation
, {
640 group1__hidefilter1
: false, // The text is "show filter 1"
641 group1__hidefilter2
: false, // The text is "show filter 2"
642 group1__hidefilter3
: false // The text is "show filter 3"
644 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
648 model
.getFiltersFromParameters( {
653 $.extend( {}, defaultFilterRepresentation
, {
654 group1__hidefilter1
: false, // The text is "show filter 1"
655 group1__hidefilter2
: false, // The text is "show filter 2"
656 group1__hidefilter3
: false // The text is "show filter 3"
658 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
661 // The ones above don't update the model, so we have a clean state.
662 // getFiltersFromParameters is stateless; any change is unaffected by the current state
663 // This test is demonstrating wrong usage of the method;
664 // We should be aware that getFiltersFromParameters is stateless,
665 // so each call gives us a filter state that only reflects the query given.
666 // This means that the two calls to toggleFiltersSelected() below collide.
667 // The result of the first is overridden by the result of the second,
668 // since both get a full state object from getFiltersFromParameters that **only** relates
669 // to the input it receives.
670 model
.toggleFiltersSelected(
671 model
.getFiltersFromParameters( {
676 model
.toggleFiltersSelected(
677 model
.getFiltersFromParameters( {
682 // The result here is ignoring the first toggleFiltersSelected call
683 // We should receive default values + hidefilter6 as false
685 model
.getSelectedState(),
686 $.extend( {}, defaultFilterRepresentation
, {
687 group2__hidefilter5
: false,
688 group2__hidefilter6
: false
690 'getFiltersFromParameters does not care about previous or existing state.'
694 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
695 model
.initializeFilters( definition
);
697 model
.toggleFiltersSelected(
698 model
.getFiltersFromParameters( {
702 model
.toggleFiltersSelected(
703 model
.getFiltersFromParameters( {
708 // Simulates minor edits being hidden in preferences, then unhidden via URL
711 model
.getSelectedState(),
712 defaultFilterRepresentation
,
713 'After checking and then unchecking a \'send_unselected_if_any\' filter (without touching other filters in that group), results are default'
716 model
.toggleFiltersSelected(
717 model
.getFiltersFromParameters( {
722 model
.getSelectedState(),
723 $.extend( {}, defaultFilterRepresentation
, {
724 group3__filter7
: true,
725 group3__filter8
: false,
726 group3__filter9
: false
728 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
731 model
.toggleFiltersSelected(
732 model
.getFiltersFromParameters( {
733 group3
: 'filter7,filter8'
737 model
.getSelectedState(),
738 $.extend( {}, defaultFilterRepresentation
, {
739 group3__filter7
: true,
740 group3__filter8
: true,
741 group3__filter9
: false
743 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
746 model
.toggleFiltersSelected(
747 model
.getFiltersFromParameters( {
748 group3
: 'filter7,filter8,filter9'
752 model
.getSelectedState(),
753 $.extend( {}, defaultFilterRepresentation
, {
754 group3__filter7
: true,
755 group3__filter8
: true,
756 group3__filter9
: true
758 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
761 model
.toggleFiltersSelected(
762 model
.getFiltersFromParameters( {
763 group3
: 'filter7,all,filter9'
767 model
.getSelectedState(),
768 $.extend( {}, defaultFilterRepresentation
, {
769 group3__filter7
: true,
770 group3__filter8
: true,
771 group3__filter9
: true
773 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
776 model
.toggleFiltersSelected(
777 model
.getFiltersFromParameters( {
778 group3
: 'filter7,foo,filter9'
782 model
.getSelectedState(),
783 $.extend( {}, defaultFilterRepresentation
, {
784 group3__filter7
: true,
785 group3__filter8
: false,
786 group3__filter9
: true
788 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
792 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
796 type
: 'string_options',
800 label
: 'Show filter 1',
801 description
: 'Description of Filter 1 in Group 1'
805 label
: 'Show filter 2',
806 description
: 'Description of Filter 2 in Group 1'
810 label
: 'Show filter 3',
811 description
: 'Description of Filter 3 in Group 1'
815 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
817 model
.initializeFilters( definition
);
820 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
821 [ 'filter1', 'filter2' ],
822 'Remove duplicate values'
826 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
827 [ 'filter1', 'filter2' ],
828 'Remove invalid values'
832 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
834 'If any value is "all", the only value is "all".'
838 QUnit
.test( 'setFiltersToDefaults', function ( assert
) {
842 type
: 'send_unselected_if_any',
846 label
: 'Show filter 1',
847 description
: 'Description of Filter 1 in Group 1',
852 label
: 'Show filter 2',
853 description
: 'Description of Filter 2 in Group 1'
857 label
: 'Show filter 3',
858 description
: 'Description of Filter 3 in Group 1',
865 type
: 'send_unselected_if_any',
869 label
: 'Show filter 4',
870 description
: 'Description of Filter 1 in Group 2'
874 label
: 'Show filter 5',
875 description
: 'Description of Filter 2 in Group 2',
880 label
: 'Show filter 6',
881 description
: 'Description of Filter 3 in Group 2'
885 defaultFilterRepresentation
= {
886 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
887 group1__hidefilter1
: false,
888 group1__hidefilter2
: true,
889 group1__hidefilter3
: false,
890 group2__hidefilter4
: true,
891 group2__hidefilter5
: false,
892 group2__hidefilter6
: true
894 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
896 model
.initializeFilters( definition
);
899 model
.getSelectedState(),
901 group1__hidefilter1
: false,
902 group1__hidefilter2
: false,
903 group1__hidefilter3
: false,
904 group2__hidefilter4
: false,
905 group2__hidefilter5
: false,
906 group2__hidefilter6
: false
908 'Initial state: default filters are not selected (controller selects defaults explicitly).'
911 model
.toggleFiltersSelected( {
912 group1__hidefilter1
: false,
913 group1__hidefilter3
: false
916 model
.setFiltersToDefaults();
919 model
.getSelectedState(),
920 defaultFilterRepresentation
,
921 'Changing values of filters and then returning to defaults still results in default filters being selected.'
925 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
929 type
: 'string_options',
933 label
: 'Show filter 1',
934 description
: 'Description of Filter 1 in Group 1',
948 label
: 'Show filter 2',
949 description
: 'Description of Filter 2 in Group 1',
959 label
: 'Show filter 3',
960 description
: 'Description of Filter 3 in Group 1'
965 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
966 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
967 group1__filter3
: { selected
: false, conflicted
: false, included
: false }
969 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
971 model
.initializeFilters( definition
);
972 // Select a filter that has subset with another filter
973 model
.toggleFiltersSelected( {
974 group1__filter1
: true
977 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
979 model
.getFullState(),
980 $.extend( true, {}, baseFullState
, {
981 group1__filter1
: { selected
: true },
982 group1__filter2
: { included
: true },
983 group1__filter3
: { included
: true }
985 'Filters with subsets are represented in the model.'
988 // Select another filter that has a subset with the same previous filter
989 model
.toggleFiltersSelected( {
990 group1__filter2
: true
992 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
994 model
.getFullState(),
995 $.extend( true, {}, baseFullState
, {
996 group1__filter1
: { selected
: true },
997 group1__filter2
: { selected
: true, included
: true },
998 group1__filter3
: { included
: true }
1000 'Filters that have multiple subsets are represented.'
1003 // Remove one filter (but leave the other) that affects filter3
1004 model
.toggleFiltersSelected( {
1005 group1__filter1
: false
1007 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1009 model
.getFullState(),
1010 $.extend( true, {}, baseFullState
, {
1011 group1__filter2
: { selected
: true, included
: false },
1012 group1__filter3
: { included
: true }
1014 'Removing a filter only un-includes its subset if there is no other filter affecting.'
1017 model
.toggleFiltersSelected( {
1018 group1__filter2
: false
1020 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1022 model
.getFullState(),
1024 'Removing all supersets also un-includes the subsets.'
1028 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
1029 var definition
= [ {
1032 type
: 'string_options',
1033 fullCoverage
: false,
1035 { name
: 'filter1', label
: '1', description
: '1' },
1036 { name
: 'filter2', label
: '2', description
: '2' },
1037 { name
: 'filter3', label
: '3', description
: '3' }
1042 type
: 'send_unselected_if_any',
1045 { name
: 'filter4', label
: '4', description
: '4' },
1046 { name
: 'filter5', label
: '5', description
: '5' },
1047 { name
: 'filter6', label
: '6', description
: '6' }
1050 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
1051 isCapsuleItemMuted = function ( filterName
) {
1052 var itemModel
= model
.getItemByName( filterName
),
1053 groupModel
= itemModel
.getGroupModel();
1055 // This is the logic inside the capsule widget
1057 // The capsule item widget only appears if the item is selected
1058 itemModel
.isSelected() &&
1059 // Muted state is only valid if group is full coverage and all items are selected
1060 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
1063 getCurrentItemsMutedState = function () {
1065 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
1066 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
1067 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
1068 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
1069 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
1070 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
1074 group1__filter1
: false,
1075 group1__filter2
: false,
1076 group1__filter3
: false,
1077 group2__filter4
: false,
1078 group2__filter5
: false,
1079 group2__filter6
: false
1082 model
.initializeFilters( definition
);
1084 // Starting state, no selection, all items are non-muted
1086 getCurrentItemsMutedState(),
1088 'No selection - all items are non-muted'
1091 // Select most (but not all) items in each group
1092 model
.toggleFiltersSelected( {
1093 group1__filter1
: true,
1094 group1__filter2
: true,
1095 group2__filter4
: true,
1096 group2__filter5
: true
1099 // Both groups have multiple (but not all) items selected, all items are non-muted
1101 getCurrentItemsMutedState(),
1103 'Not all items in the group selected - all items are non-muted'
1106 // Select all items in 'fullCoverage' group (group2)
1107 model
.toggleFiltersSelected( {
1108 group2__filter6
: true
1111 // Group2 (full coverage) has all items selected, all its items are muted
1113 getCurrentItemsMutedState(),
1114 $.extend( {}, baseMuteState
, {
1115 group2__filter4
: true,
1116 group2__filter5
: true,
1117 group2__filter6
: true
1119 'All items in \'full coverage\' group are selected - all items in the group are muted'
1122 // Select all items in non 'fullCoverage' group (group1)
1123 model
.toggleFiltersSelected( {
1124 group1__filter3
: true
1127 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1129 getCurrentItemsMutedState(),
1130 $.extend( {}, baseMuteState
, {
1131 group2__filter4
: true,
1132 group2__filter5
: true,
1133 group2__filter6
: true
1135 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1138 // Uncheck an item from each group
1139 model
.toggleFiltersSelected( {
1140 group1__filter3
: false,
1141 group2__filter5
: false
1144 getCurrentItemsMutedState(),
1146 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1150 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
1151 var definition
= [ {
1154 type
: 'string_options',
1160 conflicts
: [ { group
: 'group2' } ]
1166 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ]
1177 type
: 'send_unselected_if_any',
1178 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
1194 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
1199 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1200 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1201 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
1202 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
1203 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
1204 group2__filter6
: { selected
: false, conflicted
: false, included
: false }
1206 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1208 model
.initializeFilters( definition
);
1211 model
.getFullState(),
1213 'Initial state: no conflicts because no selections.'
1216 // Select a filter that has a conflict with an entire group
1217 model
.toggleFiltersSelected( {
1218 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1221 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1224 model
.getFullState(),
1225 $.extend( true, {}, baseFullState
, {
1226 group1__filter1
: { selected
: true },
1227 group2__filter4
: { conflicted
: true },
1228 group2__filter5
: { conflicted
: true },
1229 group2__filter6
: { conflicted
: true }
1231 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1234 // Select one of the conflicts (both filters are now conflicted and selected)
1235 model
.toggleFiltersSelected( {
1236 group2__filter4
: true // conflicts: filter 1
1238 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
1241 model
.getFullState(),
1242 $.extend( true, {}, baseFullState
, {
1243 group1__filter1
: { selected
: true, conflicted
: true },
1244 group2__filter4
: { selected
: true, conflicted
: true },
1245 group2__filter5
: { conflicted
: true },
1246 group2__filter6
: { conflicted
: true }
1248 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1252 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1253 model
.initializeFilters( definition
);
1255 // Select a filter that has a conflict with a specific filter
1256 model
.toggleFiltersSelected( {
1257 group1__filter2
: true // conflicts: filter6
1259 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1262 model
.getFullState(),
1263 $.extend( true, {}, baseFullState
, {
1264 group1__filter2
: { selected
: true },
1265 group2__filter6
: { conflicted
: true }
1267 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1270 // Select the conflicting filter
1271 model
.toggleFiltersSelected( {
1272 group2__filter6
: true // conflicts: filter2
1275 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1278 model
.getFullState(),
1279 $.extend( true, {}, baseFullState
, {
1280 group1__filter2
: { selected
: true, conflicted
: true },
1281 group2__filter6
: { selected
: true, conflicted
: true },
1282 // This is added to the conflicts because filter6 is part of group2,
1283 // who is in conflict with filter1; note that filter2 also conflicts
1284 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1285 // and also because its **own sibling** (filter2) is **also** in conflict with the
1286 // selected items in group2 (filter6)
1287 group1__filter1
: { conflicted
: true }
1289 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1292 // Now choose a non-conflicting filter from the group
1293 model
.toggleFiltersSelected( {
1294 group2__filter5
: true
1297 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1300 model
.getFullState(),
1301 $.extend( true, {}, baseFullState
, {
1302 group1__filter2
: { selected
: true },
1303 group2__filter6
: { selected
: true },
1304 group2__filter5
: { selected
: true }
1305 // Filter6 and filter1 are no longer in conflict because
1306 // filter5, while it is in conflict with filter1, it is
1307 // not in conflict with filter2 - and since filter2 is
1308 // selected, it removes the conflict bidirectionally
1310 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1313 // Followup on the previous test, unselect filter2 so filter1
1314 // is now the only one selected in its own group, and since
1315 // it is in conflict with the entire of group2, it means
1316 // filter1 is once again conflicted
1317 model
.toggleFiltersSelected( {
1318 group1__filter2
: false
1321 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1324 model
.getFullState(),
1325 $.extend( true, {}, baseFullState
, {
1326 group1__filter1
: { conflicted
: true },
1327 group2__filter6
: { selected
: true },
1328 group2__filter5
: { selected
: true }
1330 'Unselecting an item that did not conflict returns the conflict state.'
1333 // Followup #2: Now actually select filter1, and make everything conflicted
1334 model
.toggleFiltersSelected( {
1335 group1__filter1
: true
1338 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1341 model
.getFullState(),
1342 $.extend( true, {}, baseFullState
, {
1343 group1__filter1
: { selected
: true, conflicted
: true },
1344 group2__filter6
: { selected
: true, conflicted
: true },
1345 group2__filter5
: { selected
: true, conflicted
: true },
1346 group2__filter4
: { conflicted
: true } // Not selected but conflicted because it's in group2
1348 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1353 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1354 model
.initializeFilters( definition
);
1356 // Select a filter that has a conflict with a specific filter
1357 model
.toggleFiltersSelected( {
1358 group1__filter2
: true // conflicts: filter6
1361 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1364 model
.getFullState(),
1365 $.extend( true, {}, baseFullState
, {
1366 group1__filter2
: { selected
: true },
1367 group2__filter6
: { conflicted
: true }
1369 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1372 model
.toggleFiltersSelected( {
1373 group1__filter3
: true // conflicts: filter6
1376 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1379 model
.getFullState(),
1380 $.extend( true, {}, baseFullState
, {
1381 group1__filter2
: { selected
: true },
1382 group1__filter3
: { selected
: true }
1384 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1389 QUnit
.test( 'Filter highlights', function ( assert
) {
1390 var definition
= [ {
1393 type
: 'string_options',
1395 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1396 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1397 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1398 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1399 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1400 { name
: 'filter6', label
: '6', description
: '6' }
1403 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1405 model
.initializeFilters( definition
);
1408 !model
.isHighlightEnabled(),
1409 'Initially, highlight is disabled.'
1412 model
.toggleHighlight( true );
1414 model
.isHighlightEnabled(),
1415 'Highlight is enabled on toggle.'
1418 model
.setHighlightColor( 'group1__filter1', 'color1' );
1419 model
.setHighlightColor( 'group1__filter2', 'color2' );
1422 model
.getHighlightedItems().map( function ( item
) {
1423 return item
.getName();
1429 'Highlighted items are highlighted.'
1433 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1435 'Item highlight color is set.'
1438 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1440 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1442 'Item highlight color is changed on setHighlightColor.'
1445 model
.clearHighlightColor( 'group1__filter1' );
1447 model
.getHighlightedItems().map( function ( item
) {
1448 return item
.getName();
1453 'Clear highlight from an item results in the item no longer being highlighted.'
1457 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1458 model
.initializeFilters( definition
);
1460 model
.setHighlightColor( 'group1__filter1', 'color1' );
1461 model
.setHighlightColor( 'group1__filter2', 'color2' );
1462 model
.setHighlightColor( 'group1__filter3', 'color3' );
1465 model
.getHighlightedItems().map( function ( item
) {
1466 return item
.getName();
1473 'Even if highlights are not enabled, the items remember their highlight state'
1474 // NOTE: When actually displaying the highlights, the UI checks whether
1475 // highlighting is generally active and then goes over the highlighted
1476 // items. The item models, however, and the view model in general, still
1477 // retains the knowledge about which filters have different colors, so we
1478 // can seamlessly return to the colors the user previously chose if they
1479 // reapply highlights.
1483 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1484 model
.initializeFilters( definition
);
1486 model
.setHighlightColor( 'group1__filter1', 'color1' );
1487 model
.setHighlightColor( 'group1__filter6', 'color6' );
1490 model
.getHighlightedItems().map( function ( item
) {
1491 return item
.getName();
1496 'Items without a specified class identifier are not highlighted.'
1499 }( mediaWiki
, jQuery
) );