2 QUnit
.module( 'mediawiki.rcfilters - FiltersViewModel' );
4 QUnit
.test( 'Setting up filters', function ( assert
) {
8 type
: 'send_unselected_if_any',
11 name
: 'group1filter1',
12 label
: 'Group 1: Filter 1',
13 description
: 'Description of Filter 1 in Group 1'
16 name
: 'group1filter2',
17 label
: 'Group 1: Filter 2',
18 description
: 'Description of Filter 2 in Group 1'
24 type
: 'send_unselected_if_any',
27 name
: 'group2filter1',
28 label
: 'Group 2: Filter 1',
29 description
: 'Description of Filter 1 in Group 2'
32 name
: 'group2filter2',
33 label
: 'Group 2: Filter 2',
34 description
: 'Description of Filter 2 in Group 2'
40 type
: 'string_options',
43 name
: 'group3filter1',
44 label
: 'Group 3: Filter 1',
45 description
: 'Description of Filter 1 in Group 3'
48 name
: 'group3filter2',
49 label
: 'Group 3: Filter 2',
50 description
: 'Description of Filter 2 in Group 3'
55 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
57 model
.initializeFilters( definition
);
60 model
.getItemByName( 'group1filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
61 model
.getItemByName( 'group1filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
62 model
.getItemByName( 'group2filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
63 model
.getItemByName( 'group2filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
64 model
.getItemByName( 'group3filter1' ) instanceof mw
.rcfilters
.dm
.FilterItem
&&
65 model
.getItemByName( 'group3filter2' ) instanceof mw
.rcfilters
.dm
.FilterItem
,
66 'Filters instantiated and stored correctly'
70 model
.getSelectedState(),
79 'Initial state of filters'
82 model
.updateFilters( {
88 model
.getSelectedState(),
97 'Updating filter states correctly'
101 QUnit
.test( 'Finding matching filters', function ( assert
) {
105 title
: 'Group 1 title',
106 type
: 'send_unselected_if_any',
109 name
: 'group1filter1',
110 label
: 'Group 1: Filter 1',
111 description
: 'Description of Filter 1 in Group 1'
114 name
: 'group1filter2',
115 label
: 'Group 1: Filter 2',
116 description
: 'Description of Filter 2 in Group 1'
121 title
: 'Group 2 title',
122 type
: 'send_unselected_if_any',
125 name
: 'group2filter1',
126 label
: 'Group 2: Filter 1',
127 description
: 'Description of Filter 1 in Group 2'
130 name
: 'group2filter2',
131 label
: 'xGroup 2: Filter 2',
132 description
: 'Description of Filter 2 in Group 2'
141 group1
: [ 'group1filter1', 'group1filter2' ],
142 group2
: [ 'group2filter1' ]
144 reason
: 'Finds filters starting with the query string'
147 query
: 'filter 2 in group',
149 group1
: [ 'group1filter2' ],
150 group2
: [ 'group2filter2' ]
152 reason
: 'Finds filters containing the query string in their description'
157 group1
: [ 'group1filter1', 'group1filter2' ],
158 group2
: [ 'group2filter1', 'group2filter2' ]
160 reason
: 'Finds filters containing the query string in their group title'
163 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
164 extractNames = function ( matches
) {
166 Object
.keys( matches
).forEach( function ( groupName
) {
167 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
168 return item
.getName();
174 model
.initializeFilters( definition
);
176 testCases
.forEach( function ( testCase
) {
177 matches
= model
.findMatches( testCase
.query
);
179 extractNames( matches
),
180 testCase
.expectedMatches
,
185 matches
= model
.findMatches( 'foo' );
187 $.isEmptyObject( matches
),
188 'findMatches returns an empty object when no results found'
192 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
196 type
: 'send_unselected_if_any',
200 label
: 'Group 1: Filter 1',
201 description
: 'Description of Filter 1 in Group 1'
205 label
: 'Group 1: Filter 2',
206 description
: 'Description of Filter 2 in Group 1'
210 label
: 'Group 1: Filter 3',
211 description
: 'Description of Filter 3 in Group 1'
217 type
: 'send_unselected_if_any',
221 label
: 'Group 2: Filter 1',
222 description
: 'Description of Filter 1 in Group 2'
226 label
: 'Group 2: Filter 2',
227 description
: 'Description of Filter 2 in Group 2'
231 label
: 'Group 2: Filter 3',
232 description
: 'Description of Filter 3 in Group 2'
238 type
: 'string_options',
243 label
: 'Group 3: Filter 1',
244 description
: 'Description of Filter 1 in Group 3'
248 label
: 'Group 3: Filter 2',
249 description
: 'Description of Filter 2 in Group 3'
253 label
: 'Group 3: Filter 3',
254 description
: 'Description of Filter 3 in Group 3'
259 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
261 model
.initializeFilters( definition
);
263 // Starting with all filters unselected
265 model
.getParametersFromFilters(),
275 'Unselected filters return all parameters falsey or \'all\'.'
279 model
.updateFilters( {
287 // Only one filter in one group
289 model
.getParametersFromFilters(),
291 // Group 1 (one selected, the others are true)
295 // Group 2 (nothing is selected, all false)
301 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
305 model
.updateFilters( {
313 // Two selected filters in one group
315 model
.getParametersFromFilters(),
317 // Group 1 (two selected, the others are true)
321 // Group 2 (nothing is selected, all false)
327 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
331 model
.updateFilters( {
339 // All filters of the group are selected == this is the same as not selecting any
341 model
.getParametersFromFilters(),
343 // Group 1 (all selected, all false)
347 // Group 2 (nothing is selected, all false)
353 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
356 // Select 1 filter from string_options
357 model
.updateFilters( {
362 // All filters of the group are selected == this is the same as not selecting any
364 model
.getParametersFromFilters(),
366 // Group 1 (all selected, all)
370 // Group 2 (nothing is selected, all false)
376 'One filter selected in "string_option" group returns that filter in the value.'
379 // Select 2 filters from string_options
380 model
.updateFilters( {
385 // All filters of the group are selected == this is the same as not selecting any
387 model
.getParametersFromFilters(),
389 // Group 1 (all selected, all)
393 // Group 2 (nothing is selected, all false)
397 group3
: 'filter7,filter8'
399 'Two filters selected in "string_option" group returns those filters in the value.'
402 // Select 3 filters from string_options
403 model
.updateFilters( {
408 // All filters of the group are selected == this is the same as not selecting any
410 model
.getParametersFromFilters(),
412 // Group 1 (all selected, all)
416 // Group 2 (nothing is selected, all false)
422 'All filters selected in "string_option" group returns \'all\'.'
427 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
431 type
: 'send_unselected_if_any',
435 label
: 'Show filter 1',
436 description
: 'Description of Filter 1 in Group 1',
441 label
: 'Show filter 2',
442 description
: 'Description of Filter 2 in Group 1'
446 label
: 'Show filter 3',
447 description
: 'Description of Filter 3 in Group 1',
454 type
: 'send_unselected_if_any',
458 label
: 'Show filter 4',
459 description
: 'Description of Filter 1 in Group 2'
463 label
: 'Show filter 5',
464 description
: 'Description of Filter 2 in Group 2',
469 label
: 'Show filter 6',
470 description
: 'Description of Filter 3 in Group 2'
476 type
: 'string_options',
481 label
: 'Group 3: Filter 1',
482 description
: 'Description of Filter 1 in Group 3'
486 label
: 'Group 3: Filter 2',
487 description
: 'Description of Filter 2 in Group 3',
492 label
: 'Group 3: Filter 3',
493 description
: 'Description of Filter 3 in Group 3'
498 defaultFilterRepresentation
= {
499 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
506 // Group 3, "string_options", default values correspond to parameters and filters
511 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
513 model
.initializeFilters( definition
);
515 // Empty query = only default values
517 model
.getFiltersFromParameters( {} ),
518 defaultFilterRepresentation
,
519 'Empty parameter query results in filters in initial default state'
523 model
.getFiltersFromParameters( {
526 $.extend( {}, defaultFilterRepresentation
, {
527 hidefilter1
: false, // The text is "show filter 1"
528 hidefilter2
: false, // The text is "show filter 2"
529 hidefilter3
: false // The text is "show filter 3"
531 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
535 model
.getFiltersFromParameters( {
540 $.extend( {}, defaultFilterRepresentation
, {
541 hidefilter1
: false, // The text is "show filter 1"
542 hidefilter2
: false, // The text is "show filter 2"
543 hidefilter3
: false // The text is "show filter 3"
545 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
548 // The ones above don't update the model, so we have a clean state.
549 // getFiltersFromParameters is stateless; any change is unaffected by the current state
550 // This test is demonstrating wrong usage of the method;
551 // We should be aware that getFiltersFromParameters is stateless,
552 // so each call gives us a filter state that only reflects the query given.
553 // This means that the two calls to updateFilters() below collide.
554 // The result of the first is overridden by the result of the second,
555 // since both get a full state object from getFiltersFromParameters that **only** relates
556 // to the input it receives.
558 model
.getFiltersFromParameters( {
564 model
.getFiltersFromParameters( {
569 // The result here is ignoring the first updateFilters call
570 // We should receive default values + hidefilter6 as false
572 model
.getSelectedState(),
573 $.extend( {}, defaultFilterRepresentation
, {
577 'getFiltersFromParameters does not care about previous or existing state.'
581 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
582 model
.initializeFilters( definition
);
585 model
.getFiltersFromParameters( {
590 model
.getFiltersFromParameters( {
595 // Simulates minor edits being hidden in preferences, then unhidden via URL
598 model
.getSelectedState(),
599 defaultFilterRepresentation
,
600 'After checking and then unchecking a \'send_unselected_if_any\' filter (without touching other filters in that group), results are default'
604 model
.getFiltersFromParameters( {
609 model
.getSelectedState(),
610 $.extend( {}, defaultFilterRepresentation
, {
615 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
619 model
.getFiltersFromParameters( {
620 group3
: 'filter7,filter8'
624 model
.getSelectedState(),
625 $.extend( {}, defaultFilterRepresentation
, {
630 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
634 model
.getFiltersFromParameters( {
635 group3
: 'filter7,filter8,filter9'
639 model
.getSelectedState(),
640 $.extend( {}, defaultFilterRepresentation
, {
645 'A \'string_options\' parameter containing all values, results in all filters of the group as unchecked.'
649 model
.getFiltersFromParameters( {
650 group3
: 'filter7,all,filter9'
654 model
.getSelectedState(),
655 $.extend( {}, defaultFilterRepresentation
, {
660 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as unchecked.'
664 model
.getFiltersFromParameters( {
665 group3
: 'filter7,foo,filter9'
669 model
.getSelectedState(),
670 $.extend( {}, defaultFilterRepresentation
, {
675 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
679 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
683 type
: 'string_options',
687 label
: 'Show filter 1',
688 description
: 'Description of Filter 1 in Group 1'
692 label
: 'Show filter 2',
693 description
: 'Description of Filter 2 in Group 1'
697 label
: 'Show filter 3',
698 description
: 'Description of Filter 3 in Group 1'
703 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
705 model
.initializeFilters( definition
);
708 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
709 [ 'filter1', 'filter2' ],
710 'Remove duplicate values'
714 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
715 [ 'filter1', 'filter2' ],
716 'Remove invalid values'
720 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
722 'If any value is "all", the only value is "all".'
726 QUnit
.test( 'setFiltersToDefaults', function ( assert
) {
730 type
: 'send_unselected_if_any',
731 exclusionType
: 'default',
735 label
: 'Show filter 1',
736 description
: 'Description of Filter 1 in Group 1',
741 label
: 'Show filter 2',
742 description
: 'Description of Filter 2 in Group 1'
746 label
: 'Show filter 3',
747 description
: 'Description of Filter 3 in Group 1',
754 type
: 'send_unselected_if_any',
758 label
: 'Show filter 4',
759 description
: 'Description of Filter 1 in Group 2'
763 label
: 'Show filter 5',
764 description
: 'Description of Filter 2 in Group 2',
769 label
: 'Show filter 6',
770 description
: 'Description of Filter 3 in Group 2'
775 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
777 model
.initializeFilters( definition
);
780 model
.getFullState(),
783 hidefilter1
: { selected
: true, active
: true },
784 hidefilter2
: { selected
: false, active
: true },
785 hidefilter3
: { selected
: true, active
: true },
787 hidefilter4
: { selected
: false, active
: true },
788 hidefilter5
: { selected
: true, active
: true },
789 hidefilter6
: { selected
: false, active
: true },
791 'Initial state: all filters are active, and select states are default.'
794 // Default behavior for 'exclusion' type with only 1 item selected, means that:
795 // - The items in the same group that are *not* selected are *not* active
796 // - Items in other groups are unaffected (all active)
797 model
.updateFilters( {
806 model
.getFullState(),
808 // Group 1: not affected
809 hidefilter1
: { selected
: false, active
: true },
810 hidefilter2
: { selected
: false, active
: true },
811 hidefilter3
: { selected
: false, active
: true },
813 hidefilter4
: { selected
: false, active
: false },
814 hidefilter5
: { selected
: false, active
: false },
815 hidefilter6
: { selected
: true, active
: true },
817 'Default exclusion behavior with 1 item selected in the group.'
820 // Default behavior for 'exclusion' type with multiple items selected, but not all, means that:
821 // - The items in the same group that are *not* selected are *not* active
822 // - Items in other groups are unaffected (all active)
823 model
.updateFilters( {
824 // Literally updating filters to create a clean state
833 model
.getFullState(),
835 // Group 1: not affected
836 hidefilter1
: { selected
: false, active
: true },
837 hidefilter2
: { selected
: false, active
: true },
838 hidefilter3
: { selected
: false, active
: true },
840 hidefilter4
: { selected
: false, active
: false },
841 hidefilter5
: { selected
: true, active
: true },
842 hidefilter6
: { selected
: true, active
: true },
844 'Default exclusion behavior with multiple items (but not all) selected in the group.'
847 // Default behavior for 'exclusion' type with all items in the group selected, means that:
848 // - All items in the group are NOT active
849 // - Items in other groups are unaffected (all active)
850 model
.updateFilters( {
851 // Literally updating filters to create a clean state
860 model
.getFullState(),
862 // Group 1: not affected
863 hidefilter1
: { selected
: false, active
: true },
864 hidefilter2
: { selected
: false, active
: true },
865 hidefilter3
: { selected
: false, active
: true },
867 hidefilter4
: { selected
: true, active
: false },
868 hidefilter5
: { selected
: true, active
: false },
869 hidefilter6
: { selected
: true, active
: false },
871 'Default exclusion behavior with all items in the group.'
875 QUnit
.test( 'reapplyActiveFilters - "explicit" exclusion rules', function ( assert
) {
879 type
: 'send_unselected_if_any',
880 exclusionType
: 'explicit',
884 excludes
: [ 'filter2', 'filter3' ],
885 label
: 'Show filter 1',
886 description
: 'Description of Filter 1 in Group 1'
890 excludes
: [ 'filter3' ],
891 label
: 'Show filter 2',
892 description
: 'Description of Filter 2 in Group 1'
896 label
: 'Show filter 3',
897 excludes
: [ 'filter1' ],
898 description
: 'Description of Filter 3 in Group 1'
902 label
: 'Show filter 4',
903 description
: 'Description of Filter 4 in Group 1'
908 defaultFilterRepresentation
= {
909 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
916 // Group 3, "string_options", default values correspond to parameters and filters
921 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
923 model
.initializeFilters( definition
);
926 model
.getFullState(),
928 filter1
: { selected
: false, active
: true },
929 filter2
: { selected
: false, active
: true },
930 filter3
: { selected
: false, active
: true },
931 filter4
: { selected
: false, active
: true }
933 'Initial state: all filters are active.'
936 // "Explicit" behavior for 'exclusion' with one item checked:
937 // - Items in the 'excluded' list of the selected filter are inactive
938 model
.updateFilters( {
939 // Literally updating filters to create a clean state
940 filter1
: true, // Excludes 'hidefilter2', 'hidefilter3'
941 filter2
: false, // Excludes 'hidefilter3'
942 filter3
: false, // Excludes 'hidefilter1'
943 filter4
: false // No exclusion list
946 model
.getFullState(),
948 filter1
: { selected
: true, active
: true },
949 filter2
: { selected
: false, active
: false },
950 filter3
: { selected
: false, active
: false },
951 filter4
: { selected
: false, active
: true }
953 '"Explicit" exclusion behavior with one item selected that has an exclusion list.'
956 // "Explicit" behavior for 'exclusion' with two item checked:
957 // - Items in the 'excluded' list of each of the selected filter are inactive
958 model
.updateFilters( {
959 // Literally updating filters to create a clean state
960 filter1
: true, // Excludes 'hidefilter2', 'hidefilter3'
961 filter2
: false, // Excludes 'hidefilter3'
962 filter3
: true, // Excludes 'hidefilter1'
963 filter4
: false // No exclusion list
966 model
.getFullState(),
968 filter1
: { selected
: true, active
: false },
969 filter2
: { selected
: false, active
: false },
970 filter3
: { selected
: true, active
: false },
971 filter4
: { selected
: false, active
: true }
973 '"Explicit" exclusion behavior with two selected items that both have an exclusion list.'
976 // "Explicit behavior" with two filters that exclude the same item
978 // Two filters selected, both exclude 'hidefilter3'
979 model
.updateFilters( {
980 // Literally updating filters to create a clean state
981 filter1
: true, // Excludes 'hidefilter2', 'hidefilter3'
982 filter2
: true, // Excludes 'hidefilter3'
983 filter3
: false, // Excludes 'hidefilter1'
984 filter4
: false // No exclusion list
987 model
.getFullState(),
989 filter1
: { selected
: true, active
: true },
990 filter2
: { selected
: true, active
: false }, // Excluded by filter1
991 filter3
: { selected
: false, active
: false }, // Excluded by both filter1 and filter2
992 filter4
: { selected
: false, active
: true }
994 '"Explicit" exclusion behavior with two selected items that both exclude another item.'
997 // Unselect filter2: filter3 should still be excluded, because filter1 excludes it and is selected
998 model
.updateFilters( {
999 filter2
: false, // Excludes 'hidefilter3'
1002 model
.getFullState(),
1004 filter1
: { selected
: true, active
: true },
1005 filter2
: { selected
: false, active
: false }, // Excluded by filter1
1006 filter3
: { selected
: false, active
: false }, // Still excluded by filter1
1007 filter4
: { selected
: false, active
: true }
1009 '"Explicit" exclusion behavior unselecting one item that excludes another item, that is being excluded by a third active item.'
1012 // Unselect filter1: filter3 should now be active, since both filters that exclude it are unselected
1013 model
.updateFilters( {
1014 filter1
: false, // Excludes 'hidefilter3' and 'hidefilter2'
1017 model
.getFullState(),
1019 filter1
: { selected
: false, active
: true },
1020 filter2
: { selected
: false, active
: true }, // No longer excluded by filter1
1021 filter3
: { selected
: false, active
: true }, // No longer excluded by either filter1 nor filter2
1022 filter4
: { selected
: false, active
: true }
1024 '"Explicit" exclusion behavior unselecting both items that excluded the same third item.'
1028 }( mediaWiki
, jQuery
) );