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( 'Filter interaction: subsets', function ( assert
) {
842 type
: 'string_options',
846 label
: 'Show filter 1',
847 description
: 'Description of Filter 1 in Group 1',
861 label
: 'Show filter 2',
862 description
: 'Description of Filter 2 in Group 1',
872 label
: 'Show filter 3',
873 description
: 'Description of Filter 3 in Group 1'
878 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
879 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
880 group1__filter3
: { selected
: false, conflicted
: false, included
: false }
882 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
884 model
.initializeFilters( definition
);
885 // Select a filter that has subset with another filter
886 model
.toggleFiltersSelected( {
887 group1__filter1
: true
890 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
892 model
.getFullState(),
893 $.extend( true, {}, baseFullState
, {
894 group1__filter1
: { selected
: true },
895 group1__filter2
: { included
: true },
896 group1__filter3
: { included
: true }
898 'Filters with subsets are represented in the model.'
901 // Select another filter that has a subset with the same previous filter
902 model
.toggleFiltersSelected( {
903 group1__filter2
: true
905 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
907 model
.getFullState(),
908 $.extend( true, {}, baseFullState
, {
909 group1__filter1
: { selected
: true },
910 group1__filter2
: { selected
: true, included
: true },
911 group1__filter3
: { included
: true }
913 'Filters that have multiple subsets are represented.'
916 // Remove one filter (but leave the other) that affects filter3
917 model
.toggleFiltersSelected( {
918 group1__filter1
: false
920 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
922 model
.getFullState(),
923 $.extend( true, {}, baseFullState
, {
924 group1__filter2
: { selected
: true, included
: false },
925 group1__filter3
: { included
: true }
927 'Removing a filter only un-includes its subset if there is no other filter affecting.'
930 model
.toggleFiltersSelected( {
931 group1__filter2
: false
933 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
935 model
.getFullState(),
937 'Removing all supersets also un-includes the subsets.'
941 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
945 type
: 'string_options',
948 { name
: 'filter1', label
: '1', description
: '1' },
949 { name
: 'filter2', label
: '2', description
: '2' },
950 { name
: 'filter3', label
: '3', description
: '3' }
955 type
: 'send_unselected_if_any',
958 { name
: 'filter4', label
: '4', description
: '4' },
959 { name
: 'filter5', label
: '5', description
: '5' },
960 { name
: 'filter6', label
: '6', description
: '6' }
963 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
964 isCapsuleItemMuted = function ( filterName
) {
965 var itemModel
= model
.getItemByName( filterName
),
966 groupModel
= itemModel
.getGroupModel();
968 // This is the logic inside the capsule widget
970 // The capsule item widget only appears if the item is selected
971 itemModel
.isSelected() &&
972 // Muted state is only valid if group is full coverage and all items are selected
973 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
976 getCurrentItemsMutedState = function () {
978 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
979 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
980 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
981 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
982 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
983 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
987 group1__filter1
: false,
988 group1__filter2
: false,
989 group1__filter3
: false,
990 group2__filter4
: false,
991 group2__filter5
: false,
992 group2__filter6
: false
995 model
.initializeFilters( definition
);
997 // Starting state, no selection, all items are non-muted
999 getCurrentItemsMutedState(),
1001 'No selection - all items are non-muted'
1004 // Select most (but not all) items in each group
1005 model
.toggleFiltersSelected( {
1006 group1__filter1
: true,
1007 group1__filter2
: true,
1008 group2__filter4
: true,
1009 group2__filter5
: true
1012 // Both groups have multiple (but not all) items selected, all items are non-muted
1014 getCurrentItemsMutedState(),
1016 'Not all items in the group selected - all items are non-muted'
1019 // Select all items in 'fullCoverage' group (group2)
1020 model
.toggleFiltersSelected( {
1021 group2__filter6
: true
1024 // Group2 (full coverage) has all items selected, all its items are muted
1026 getCurrentItemsMutedState(),
1027 $.extend( {}, baseMuteState
, {
1028 group2__filter4
: true,
1029 group2__filter5
: true,
1030 group2__filter6
: true
1032 'All items in \'full coverage\' group are selected - all items in the group are muted'
1035 // Select all items in non 'fullCoverage' group (group1)
1036 model
.toggleFiltersSelected( {
1037 group1__filter3
: true
1040 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1042 getCurrentItemsMutedState(),
1043 $.extend( {}, baseMuteState
, {
1044 group2__filter4
: true,
1045 group2__filter5
: true,
1046 group2__filter6
: true
1048 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1051 // Uncheck an item from each group
1052 model
.toggleFiltersSelected( {
1053 group1__filter3
: false,
1054 group2__filter5
: false
1057 getCurrentItemsMutedState(),
1059 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1063 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
1064 var definition
= [ {
1067 type
: 'string_options',
1073 conflicts
: [ { group
: 'group2' } ]
1079 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ]
1090 type
: 'send_unselected_if_any',
1091 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
1107 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
1112 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1113 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1114 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
1115 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
1116 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
1117 group2__filter6
: { selected
: false, conflicted
: false, included
: false }
1119 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1121 model
.initializeFilters( definition
);
1124 model
.getFullState(),
1126 'Initial state: no conflicts because no selections.'
1129 // Select a filter that has a conflict with an entire group
1130 model
.toggleFiltersSelected( {
1131 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1134 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1137 model
.getFullState(),
1138 $.extend( true, {}, baseFullState
, {
1139 group1__filter1
: { selected
: true },
1140 group2__filter4
: { conflicted
: true },
1141 group2__filter5
: { conflicted
: true },
1142 group2__filter6
: { conflicted
: true }
1144 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1147 // Select one of the conflicts (both filters are now conflicted and selected)
1148 model
.toggleFiltersSelected( {
1149 group2__filter4
: true // conflicts: filter 1
1151 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
1154 model
.getFullState(),
1155 $.extend( true, {}, baseFullState
, {
1156 group1__filter1
: { selected
: true, conflicted
: true },
1157 group2__filter4
: { selected
: true, conflicted
: true },
1158 group2__filter5
: { conflicted
: true },
1159 group2__filter6
: { conflicted
: true }
1161 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1165 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1166 model
.initializeFilters( definition
);
1168 // Select a filter that has a conflict with a specific filter
1169 model
.toggleFiltersSelected( {
1170 group1__filter2
: true // conflicts: filter6
1172 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1175 model
.getFullState(),
1176 $.extend( true, {}, baseFullState
, {
1177 group1__filter2
: { selected
: true },
1178 group2__filter6
: { conflicted
: true }
1180 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1183 // Select the conflicting filter
1184 model
.toggleFiltersSelected( {
1185 group2__filter6
: true // conflicts: filter2
1188 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1191 model
.getFullState(),
1192 $.extend( true, {}, baseFullState
, {
1193 group1__filter2
: { selected
: true, conflicted
: true },
1194 group2__filter6
: { selected
: true, conflicted
: true },
1195 // This is added to the conflicts because filter6 is part of group2,
1196 // who is in conflict with filter1; note that filter2 also conflicts
1197 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1198 // and also because its **own sibling** (filter2) is **also** in conflict with the
1199 // selected items in group2 (filter6)
1200 group1__filter1
: { conflicted
: true }
1202 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1205 // Now choose a non-conflicting filter from the group
1206 model
.toggleFiltersSelected( {
1207 group2__filter5
: true
1210 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1213 model
.getFullState(),
1214 $.extend( true, {}, baseFullState
, {
1215 group1__filter2
: { selected
: true },
1216 group2__filter6
: { selected
: true },
1217 group2__filter5
: { selected
: true }
1218 // Filter6 and filter1 are no longer in conflict because
1219 // filter5, while it is in conflict with filter1, it is
1220 // not in conflict with filter2 - and since filter2 is
1221 // selected, it removes the conflict bidirectionally
1223 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1226 // Followup on the previous test, unselect filter2 so filter1
1227 // is now the only one selected in its own group, and since
1228 // it is in conflict with the entire of group2, it means
1229 // filter1 is once again conflicted
1230 model
.toggleFiltersSelected( {
1231 group1__filter2
: false
1234 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1237 model
.getFullState(),
1238 $.extend( true, {}, baseFullState
, {
1239 group1__filter1
: { conflicted
: true },
1240 group2__filter6
: { selected
: true },
1241 group2__filter5
: { selected
: true }
1243 'Unselecting an item that did not conflict returns the conflict state.'
1246 // Followup #2: Now actually select filter1, and make everything conflicted
1247 model
.toggleFiltersSelected( {
1248 group1__filter1
: true
1251 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1254 model
.getFullState(),
1255 $.extend( true, {}, baseFullState
, {
1256 group1__filter1
: { selected
: true, conflicted
: true },
1257 group2__filter6
: { selected
: true, conflicted
: true },
1258 group2__filter5
: { selected
: true, conflicted
: true },
1259 group2__filter4
: { conflicted
: true } // Not selected but conflicted because it's in group2
1261 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1266 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1267 model
.initializeFilters( definition
);
1269 // Select a filter that has a conflict with a specific filter
1270 model
.toggleFiltersSelected( {
1271 group1__filter2
: true // conflicts: filter6
1274 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1277 model
.getFullState(),
1278 $.extend( true, {}, baseFullState
, {
1279 group1__filter2
: { selected
: true },
1280 group2__filter6
: { conflicted
: true }
1282 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1285 model
.toggleFiltersSelected( {
1286 group1__filter3
: true // conflicts: filter6
1289 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1292 model
.getFullState(),
1293 $.extend( true, {}, baseFullState
, {
1294 group1__filter2
: { selected
: true },
1295 group1__filter3
: { selected
: true }
1297 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1302 QUnit
.test( 'Filter highlights', function ( assert
) {
1303 var definition
= [ {
1306 type
: 'string_options',
1308 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1309 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1310 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1311 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1312 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1313 { name
: 'filter6', label
: '6', description
: '6' }
1316 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1318 model
.initializeFilters( definition
);
1321 !model
.isHighlightEnabled(),
1322 'Initially, highlight is disabled.'
1325 model
.toggleHighlight( true );
1327 model
.isHighlightEnabled(),
1328 'Highlight is enabled on toggle.'
1331 model
.setHighlightColor( 'group1__filter1', 'color1' );
1332 model
.setHighlightColor( 'group1__filter2', 'color2' );
1335 model
.getHighlightedItems().map( function ( item
) {
1336 return item
.getName();
1342 'Highlighted items are highlighted.'
1346 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1348 'Item highlight color is set.'
1351 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1353 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1355 'Item highlight color is changed on setHighlightColor.'
1358 model
.clearHighlightColor( 'group1__filter1' );
1360 model
.getHighlightedItems().map( function ( item
) {
1361 return item
.getName();
1366 'Clear highlight from an item results in the item no longer being highlighted.'
1370 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1371 model
.initializeFilters( definition
);
1373 model
.setHighlightColor( 'group1__filter1', 'color1' );
1374 model
.setHighlightColor( 'group1__filter2', 'color2' );
1375 model
.setHighlightColor( 'group1__filter3', 'color3' );
1378 model
.getHighlightedItems().map( function ( item
) {
1379 return item
.getName();
1386 'Even if highlights are not enabled, the items remember their highlight state'
1387 // NOTE: When actually displaying the highlights, the UI checks whether
1388 // highlighting is generally active and then goes over the highlighted
1389 // items. The item models, however, and the view model in general, still
1390 // retains the knowledge about which filters have different colors, so we
1391 // can seamlessly return to the colors the user previously chose if they
1392 // reapply highlights.
1396 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1397 model
.initializeFilters( definition
);
1399 model
.setHighlightColor( 'group1__filter1', 'color1' );
1400 model
.setHighlightColor( 'group1__filter6', 'color6' );
1403 model
.getHighlightedItems().map( function ( item
) {
1404 return item
.getName();
1409 'Items without a specified class identifier are not highlighted.'
1412 }( mediaWiki
, jQuery
) );