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 'One 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( 'getFiltersFromParameters', function ( assert
) {
440 type
: 'send_unselected_if_any',
444 label
: 'Show filter 1',
445 description
: 'Description of Filter 1 in Group 1',
450 label
: 'Show filter 2',
451 description
: 'Description of Filter 2 in Group 1'
455 label
: 'Show filter 3',
456 description
: 'Description of Filter 3 in Group 1',
463 type
: 'send_unselected_if_any',
467 label
: 'Show filter 4',
468 description
: 'Description of Filter 1 in Group 2'
472 label
: 'Show filter 5',
473 description
: 'Description of Filter 2 in Group 2',
478 label
: 'Show filter 6',
479 description
: 'Description of Filter 3 in Group 2'
486 type
: 'string_options',
492 label
: 'Group 3: Filter 1',
493 description
: 'Description of Filter 1 in Group 3'
497 label
: 'Group 3: Filter 2',
498 description
: 'Description of Filter 2 in Group 3'
502 label
: 'Group 3: Filter 3',
503 description
: 'Description of Filter 3 in Group 3'
507 defaultFilterRepresentation
= {
508 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
509 group1__hidefilter1
: false,
510 group1__hidefilter2
: true,
511 group1__hidefilter3
: false,
512 group2__hidefilter4
: true,
513 group2__hidefilter5
: false,
514 group2__hidefilter6
: true,
515 // Group 3, "string_options", default values correspond to parameters and filters
516 group3__filter7
: false,
517 group3__filter8
: true,
518 group3__filter9
: false
520 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
522 model
.initializeFilters( definition
);
524 // Empty query = only default values
526 model
.getFiltersFromParameters( {} ),
527 defaultFilterRepresentation
,
528 'Empty parameter query results in filters in initial default state'
532 model
.getFiltersFromParameters( {
535 $.extend( {}, defaultFilterRepresentation
, {
536 group1__hidefilter1
: false, // The text is "show filter 1"
537 group1__hidefilter2
: false, // The text is "show filter 2"
538 group1__hidefilter3
: false // The text is "show filter 3"
540 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
544 model
.getFiltersFromParameters( {
549 $.extend( {}, defaultFilterRepresentation
, {
550 group1__hidefilter1
: false, // The text is "show filter 1"
551 group1__hidefilter2
: false, // The text is "show filter 2"
552 group1__hidefilter3
: false // The text is "show filter 3"
554 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
557 // The ones above don't update the model, so we have a clean state.
558 // getFiltersFromParameters is stateless; any change is unaffected by the current state
559 // This test is demonstrating wrong usage of the method;
560 // We should be aware that getFiltersFromParameters is stateless,
561 // so each call gives us a filter state that only reflects the query given.
562 // This means that the two calls to toggleFiltersSelected() below collide.
563 // The result of the first is overridden by the result of the second,
564 // since both get a full state object from getFiltersFromParameters that **only** relates
565 // to the input it receives.
566 model
.toggleFiltersSelected(
567 model
.getFiltersFromParameters( {
572 model
.toggleFiltersSelected(
573 model
.getFiltersFromParameters( {
578 // The result here is ignoring the first toggleFiltersSelected call
579 // We should receive default values + hidefilter6 as false
581 model
.getSelectedState(),
582 $.extend( {}, defaultFilterRepresentation
, {
583 group2__hidefilter5
: false,
584 group2__hidefilter6
: false
586 'getFiltersFromParameters does not care about previous or existing state.'
590 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
591 model
.initializeFilters( definition
);
593 model
.toggleFiltersSelected(
594 model
.getFiltersFromParameters( {
598 model
.toggleFiltersSelected(
599 model
.getFiltersFromParameters( {
604 // Simulates minor edits being hidden in preferences, then unhidden via URL
607 model
.getSelectedState(),
608 defaultFilterRepresentation
,
609 'After checking and then unchecking a \'send_unselected_if_any\' filter (without touching other filters in that group), results are default'
612 model
.toggleFiltersSelected(
613 model
.getFiltersFromParameters( {
618 model
.getSelectedState(),
619 $.extend( {}, defaultFilterRepresentation
, {
620 group3__filter7
: true,
621 group3__filter8
: false,
622 group3__filter9
: false
624 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
627 model
.toggleFiltersSelected(
628 model
.getFiltersFromParameters( {
629 group3
: 'filter7,filter8'
633 model
.getSelectedState(),
634 $.extend( {}, defaultFilterRepresentation
, {
635 group3__filter7
: true,
636 group3__filter8
: true,
637 group3__filter9
: false
639 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
642 model
.toggleFiltersSelected(
643 model
.getFiltersFromParameters( {
644 group3
: 'filter7,filter8,filter9'
648 model
.getSelectedState(),
649 $.extend( {}, defaultFilterRepresentation
, {
650 group3__filter7
: true,
651 group3__filter8
: true,
652 group3__filter9
: true
654 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
657 model
.toggleFiltersSelected(
658 model
.getFiltersFromParameters( {
659 group3
: 'filter7,all,filter9'
663 model
.getSelectedState(),
664 $.extend( {}, defaultFilterRepresentation
, {
665 group3__filter7
: true,
666 group3__filter8
: true,
667 group3__filter9
: true
669 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
672 model
.toggleFiltersSelected(
673 model
.getFiltersFromParameters( {
674 group3
: 'filter7,foo,filter9'
678 model
.getSelectedState(),
679 $.extend( {}, defaultFilterRepresentation
, {
680 group3__filter7
: true,
681 group3__filter8
: false,
682 group3__filter9
: true
684 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
688 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
692 type
: 'string_options',
696 label
: 'Show filter 1',
697 description
: 'Description of Filter 1 in Group 1'
701 label
: 'Show filter 2',
702 description
: 'Description of Filter 2 in Group 1'
706 label
: 'Show filter 3',
707 description
: 'Description of Filter 3 in Group 1'
711 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
713 model
.initializeFilters( definition
);
716 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
717 [ 'filter1', 'filter2' ],
718 'Remove duplicate values'
722 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
723 [ 'filter1', 'filter2' ],
724 'Remove invalid values'
728 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
730 'If any value is "all", the only value is "all".'
734 QUnit
.test( 'setFiltersToDefaults', function ( assert
) {
738 type
: 'send_unselected_if_any',
742 label
: 'Show filter 1',
743 description
: 'Description of Filter 1 in Group 1',
748 label
: 'Show filter 2',
749 description
: 'Description of Filter 2 in Group 1'
753 label
: 'Show filter 3',
754 description
: 'Description of Filter 3 in Group 1',
761 type
: 'send_unselected_if_any',
765 label
: 'Show filter 4',
766 description
: 'Description of Filter 1 in Group 2'
770 label
: 'Show filter 5',
771 description
: 'Description of Filter 2 in Group 2',
776 label
: 'Show filter 6',
777 description
: 'Description of Filter 3 in Group 2'
781 defaultFilterRepresentation
= {
782 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
783 group1__hidefilter1
: false,
784 group1__hidefilter2
: true,
785 group1__hidefilter3
: false,
786 group2__hidefilter4
: true,
787 group2__hidefilter5
: false,
788 group2__hidefilter6
: true
790 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
792 model
.initializeFilters( definition
);
795 model
.getSelectedState(),
797 group1__hidefilter1
: false,
798 group1__hidefilter2
: false,
799 group1__hidefilter3
: false,
800 group2__hidefilter4
: false,
801 group2__hidefilter5
: false,
802 group2__hidefilter6
: false
804 'Initial state: default filters are not selected (controller selects defaults explicitly).'
807 model
.toggleFiltersSelected( {
808 group1__hidefilter1
: false,
809 group1__hidefilter3
: false
812 model
.setFiltersToDefaults();
815 model
.getSelectedState(),
816 defaultFilterRepresentation
,
817 'Changing values of filters and then returning to defaults still results in default filters being selected.'
821 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
825 type
: 'string_options',
829 label
: 'Show filter 1',
830 description
: 'Description of Filter 1 in Group 1',
844 label
: 'Show filter 2',
845 description
: 'Description of Filter 2 in Group 1',
855 label
: 'Show filter 3',
856 description
: 'Description of Filter 3 in Group 1'
861 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
862 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
863 group1__filter3
: { selected
: false, conflicted
: false, included
: false }
865 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
867 model
.initializeFilters( definition
);
868 // Select a filter that has subset with another filter
869 model
.toggleFiltersSelected( {
870 group1__filter1
: true
873 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
875 model
.getFullState(),
876 $.extend( true, {}, baseFullState
, {
877 group1__filter1
: { selected
: true },
878 group1__filter2
: { included
: true },
879 group1__filter3
: { included
: true }
881 'Filters with subsets are represented in the model.'
884 // Select another filter that has a subset with the same previous filter
885 model
.toggleFiltersSelected( {
886 group1__filter2
: true
888 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
890 model
.getFullState(),
891 $.extend( true, {}, baseFullState
, {
892 group1__filter1
: { selected
: true },
893 group1__filter2
: { selected
: true, included
: true },
894 group1__filter3
: { included
: true }
896 'Filters that have multiple subsets are represented.'
899 // Remove one filter (but leave the other) that affects filter3
900 model
.toggleFiltersSelected( {
901 group1__filter1
: false
903 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
905 model
.getFullState(),
906 $.extend( true, {}, baseFullState
, {
907 group1__filter2
: { selected
: true, included
: false },
908 group1__filter3
: { included
: true }
910 'Removing a filter only un-includes its subset if there is no other filter affecting.'
913 model
.toggleFiltersSelected( {
914 group1__filter2
: false
916 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
918 model
.getFullState(),
920 'Removing all supersets also un-includes the subsets.'
924 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
928 type
: 'string_options',
931 { name
: 'filter1', label
: '1', description
: '1' },
932 { name
: 'filter2', label
: '2', description
: '2' },
933 { name
: 'filter3', label
: '3', description
: '3' }
938 type
: 'send_unselected_if_any',
941 { name
: 'filter4', label
: '4', description
: '4' },
942 { name
: 'filter5', label
: '5', description
: '5' },
943 { name
: 'filter6', label
: '6', description
: '6' }
946 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
947 isCapsuleItemMuted = function ( filterName
) {
948 var itemModel
= model
.getItemByName( filterName
),
949 groupModel
= itemModel
.getGroupModel();
951 // This is the logic inside the capsule widget
953 // The capsule item widget only appears if the item is selected
954 itemModel
.isSelected() &&
955 // Muted state is only valid if group is full coverage and all items are selected
956 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
959 getCurrentItemsMutedState = function () {
961 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
962 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
963 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
964 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
965 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
966 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
970 group1__filter1
: false,
971 group1__filter2
: false,
972 group1__filter3
: false,
973 group2__filter4
: false,
974 group2__filter5
: false,
975 group2__filter6
: false
978 model
.initializeFilters( definition
);
980 // Starting state, no selection, all items are non-muted
982 getCurrentItemsMutedState(),
984 'No selection - all items are non-muted'
987 // Select most (but not all) items in each group
988 model
.toggleFiltersSelected( {
989 group1__filter1
: true,
990 group1__filter2
: true,
991 group2__filter4
: true,
992 group2__filter5
: true
995 // Both groups have multiple (but not all) items selected, all items are non-muted
997 getCurrentItemsMutedState(),
999 'Not all items in the group selected - all items are non-muted'
1002 // Select all items in 'fullCoverage' group (group2)
1003 model
.toggleFiltersSelected( {
1004 group2__filter6
: true
1007 // Group2 (full coverage) has all items selected, all its items are muted
1009 getCurrentItemsMutedState(),
1010 $.extend( {}, baseMuteState
, {
1011 group2__filter4
: true,
1012 group2__filter5
: true,
1013 group2__filter6
: true
1015 'All items in \'full coverage\' group are selected - all items in the group are muted'
1018 // Select all items in non 'fullCoverage' group (group1)
1019 model
.toggleFiltersSelected( {
1020 group1__filter3
: true
1023 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1025 getCurrentItemsMutedState(),
1026 $.extend( {}, baseMuteState
, {
1027 group2__filter4
: true,
1028 group2__filter5
: true,
1029 group2__filter6
: true
1031 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1034 // Uncheck an item from each group
1035 model
.toggleFiltersSelected( {
1036 group1__filter3
: false,
1037 group2__filter5
: false
1040 getCurrentItemsMutedState(),
1042 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1046 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
1047 var definition
= [ {
1050 type
: 'string_options',
1056 conflicts
: [ { group
: 'group2' } ]
1062 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ]
1073 type
: 'send_unselected_if_any',
1074 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
1090 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
1095 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
1096 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
1097 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
1098 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
1099 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
1100 group2__filter6
: { selected
: false, conflicted
: false, included
: false }
1102 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1104 model
.initializeFilters( definition
);
1107 model
.getFullState(),
1109 'Initial state: no conflicts because no selections.'
1112 // Select a filter that has a conflict with an entire group
1113 model
.toggleFiltersSelected( {
1114 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1117 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1120 model
.getFullState(),
1121 $.extend( true, {}, baseFullState
, {
1122 group1__filter1
: { selected
: true },
1123 group2__filter4
: { conflicted
: true },
1124 group2__filter5
: { conflicted
: true },
1125 group2__filter6
: { conflicted
: true }
1127 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1130 // Select one of the conflicts (both filters are now conflicted and selected)
1131 model
.toggleFiltersSelected( {
1132 group2__filter4
: true // conflicts: filter 1
1134 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
1137 model
.getFullState(),
1138 $.extend( true, {}, baseFullState
, {
1139 group1__filter1
: { selected
: true, conflicted
: true },
1140 group2__filter4
: { selected
: true, conflicted
: true },
1141 group2__filter5
: { conflicted
: true },
1142 group2__filter6
: { conflicted
: true }
1144 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1148 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1149 model
.initializeFilters( definition
);
1151 // Select a filter that has a conflict with a specific filter
1152 model
.toggleFiltersSelected( {
1153 group1__filter2
: true // conflicts: filter6
1155 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1158 model
.getFullState(),
1159 $.extend( true, {}, baseFullState
, {
1160 group1__filter2
: { selected
: true },
1161 group2__filter6
: { conflicted
: true }
1163 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1166 // Select the conflicting filter
1167 model
.toggleFiltersSelected( {
1168 group2__filter6
: true // conflicts: filter2
1171 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1174 model
.getFullState(),
1175 $.extend( true, {}, baseFullState
, {
1176 group1__filter2
: { selected
: true, conflicted
: true },
1177 group2__filter6
: { selected
: true, conflicted
: true },
1178 // This is added to the conflicts because filter6 is part of group2,
1179 // who is in conflict with filter1; note that filter2 also conflicts
1180 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1181 // and also because its **own sibling** (filter2) is **also** in conflict with the
1182 // selected items in group2 (filter6)
1183 group1__filter1
: { conflicted
: true }
1185 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1188 // Now choose a non-conflicting filter from the group
1189 model
.toggleFiltersSelected( {
1190 group2__filter5
: true
1193 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1196 model
.getFullState(),
1197 $.extend( true, {}, baseFullState
, {
1198 group1__filter2
: { selected
: true },
1199 group2__filter6
: { selected
: true },
1200 group2__filter5
: { selected
: true }
1201 // Filter6 and filter1 are no longer in conflict because
1202 // filter5, while it is in conflict with filter1, it is
1203 // not in conflict with filter2 - and since filter2 is
1204 // selected, it removes the conflict bidirectionally
1206 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1209 // Followup on the previous test, unselect filter2 so filter1
1210 // is now the only one selected in its own group, and since
1211 // it is in conflict with the entire of group2, it means
1212 // filter1 is once again conflicted
1213 model
.toggleFiltersSelected( {
1214 group1__filter2
: false
1217 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1220 model
.getFullState(),
1221 $.extend( true, {}, baseFullState
, {
1222 group1__filter1
: { conflicted
: true },
1223 group2__filter6
: { selected
: true },
1224 group2__filter5
: { selected
: true }
1226 'Unselecting an item that did not conflict returns the conflict state.'
1229 // Followup #2: Now actually select filter1, and make everything conflicted
1230 model
.toggleFiltersSelected( {
1231 group1__filter1
: true
1234 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1237 model
.getFullState(),
1238 $.extend( true, {}, baseFullState
, {
1239 group1__filter1
: { selected
: true, conflicted
: true },
1240 group2__filter6
: { selected
: true, conflicted
: true },
1241 group2__filter5
: { selected
: true, conflicted
: true },
1242 group2__filter4
: { conflicted
: true } // Not selected but conflicted because it's in group2
1244 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1249 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1250 model
.initializeFilters( definition
);
1252 // Select a filter that has a conflict with a specific filter
1253 model
.toggleFiltersSelected( {
1254 group1__filter2
: true // conflicts: filter6
1257 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1260 model
.getFullState(),
1261 $.extend( true, {}, baseFullState
, {
1262 group1__filter2
: { selected
: true },
1263 group2__filter6
: { conflicted
: true }
1265 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1268 model
.toggleFiltersSelected( {
1269 group1__filter3
: true // conflicts: filter6
1272 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1275 model
.getFullState(),
1276 $.extend( true, {}, baseFullState
, {
1277 group1__filter2
: { selected
: true },
1278 group1__filter3
: { selected
: true }
1280 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1285 QUnit
.test( 'Filter highlights', function ( assert
) {
1286 var definition
= [ {
1289 type
: 'string_options',
1291 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1292 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1293 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1294 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1295 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1296 { name
: 'filter6', label
: '6', description
: '6' }
1299 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1301 model
.initializeFilters( definition
);
1304 !model
.isHighlightEnabled(),
1305 'Initially, highlight is disabled.'
1308 model
.toggleHighlight( true );
1310 model
.isHighlightEnabled(),
1311 'Highlight is enabled on toggle.'
1314 model
.setHighlightColor( 'group1__filter1', 'color1' );
1315 model
.setHighlightColor( 'group1__filter2', 'color2' );
1318 model
.getHighlightedItems().map( function ( item
) {
1319 return item
.getName();
1325 'Highlighted items are highlighted.'
1329 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1331 'Item highlight color is set.'
1334 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1336 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1338 'Item highlight color is changed on setHighlightColor.'
1341 model
.clearHighlightColor( 'group1__filter1' );
1343 model
.getHighlightedItems().map( function ( item
) {
1344 return item
.getName();
1349 'Clear highlight from an item results in the item no longer being highlighted.'
1353 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1354 model
.initializeFilters( definition
);
1356 model
.setHighlightColor( 'group1__filter1', 'color1' );
1357 model
.setHighlightColor( 'group1__filter2', 'color2' );
1358 model
.setHighlightColor( 'group1__filter3', 'color3' );
1361 model
.getHighlightedItems().map( function ( item
) {
1362 return item
.getName();
1369 'Even if highlights are not enabled, the items remember their highlight state'
1370 // NOTE: When actually displaying the highlights, the UI checks whether
1371 // highlighting is generally active and then goes over the highlighted
1372 // items. The item models, however, and the view model in general, still
1373 // retains the knowledge about which filters have different colors, so we
1374 // can seamlessly return to the colors the user previously chose if they
1375 // reapply highlights.
1379 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1380 model
.initializeFilters( definition
);
1382 model
.setHighlightColor( 'group1__filter1', 'color1' );
1383 model
.setHighlightColor( 'group1__filter6', 'color6' );
1386 model
.getHighlightedItems().map( function ( item
) {
1387 return item
.getName();
1392 'Items without a specified class identifier are not highlighted.'
1395 }( mediaWiki
, jQuery
) );