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
) {
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'
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
: 'Group 2: Filter 2',
132 description
: 'Description of Filter 2 in Group 2'
137 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
139 model
.initializeFilters( definition
);
141 matches
= model
.findMatches( 'group 1' );
143 matches
.group1
.length
,
145 'findMatches finds correct group with correct number of results'
149 matches
.group1
.map( function ( item
) { return item
.getName(); } ),
150 [ 'group1filter1', 'group1filter2' ],
151 'findMatches finds the correct items within a single group'
154 matches
= model
.findMatches( 'filter 1' );
156 matches
.group1
.length
=== 1 && matches
.group2
.length
=== 1,
157 'findMatches finds correct number of results in multiple groups'
162 matches
.group1
.map( function ( item
) { return item
.getName(); } ),
163 matches
.group2
.map( function ( item
) { return item
.getName(); } )
169 'findMatches finds the correct items within multiple groups'
172 matches
= model
.findMatches( 'foo' );
174 $.isEmptyObject( matches
),
175 'findMatches returns an empty object when no results found'
179 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
183 type
: 'send_unselected_if_any',
187 label
: 'Group 1: Filter 1',
188 description
: 'Description of Filter 1 in Group 1'
192 label
: 'Group 1: Filter 2',
193 description
: 'Description of Filter 2 in Group 1'
197 label
: 'Group 1: Filter 3',
198 description
: 'Description of Filter 3 in Group 1'
204 type
: 'send_unselected_if_any',
208 label
: 'Group 2: Filter 1',
209 description
: 'Description of Filter 1 in Group 2'
213 label
: 'Group 2: Filter 2',
214 description
: 'Description of Filter 2 in Group 2'
218 label
: 'Group 2: Filter 3',
219 description
: 'Description of Filter 3 in Group 2'
225 type
: 'string_options',
230 label
: 'Group 3: Filter 1',
231 description
: 'Description of Filter 1 in Group 3'
235 label
: 'Group 3: Filter 2',
236 description
: 'Description of Filter 2 in Group 3'
240 label
: 'Group 3: Filter 3',
241 description
: 'Description of Filter 3 in Group 3'
246 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
248 model
.initializeFilters( definition
);
250 // Starting with all filters unselected
252 model
.getParametersFromFilters(),
262 'Unselected filters return all parameters falsey or \'all\'.'
266 model
.updateFilters( {
274 // Only one filter in one group
276 model
.getParametersFromFilters(),
278 // Group 1 (one selected, the others are true)
282 // Group 2 (nothing is selected, all false)
288 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
292 model
.updateFilters( {
300 // Two selected filters in one group
302 model
.getParametersFromFilters(),
304 // Group 1 (two selected, the others are true)
308 // Group 2 (nothing is selected, all false)
314 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
318 model
.updateFilters( {
326 // All filters of the group are selected == this is the same as not selecting any
328 model
.getParametersFromFilters(),
330 // Group 1 (all selected, all false)
334 // Group 2 (nothing is selected, all false)
340 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
343 // Select 1 filter from string_options
344 model
.updateFilters( {
349 // All filters of the group are selected == this is the same as not selecting any
351 model
.getParametersFromFilters(),
353 // Group 1 (all selected, all)
357 // Group 2 (nothing is selected, all false)
363 'One filter selected in "string_option" group returns that filter in the value.'
366 // Select 2 filters from string_options
367 model
.updateFilters( {
372 // All filters of the group are selected == this is the same as not selecting any
374 model
.getParametersFromFilters(),
376 // Group 1 (all selected, all)
380 // Group 2 (nothing is selected, all false)
384 group3
: 'filter7,filter8'
386 'Two filters selected in "string_option" group returns those filters in the value.'
389 // Select 3 filters from string_options
390 model
.updateFilters( {
395 // All filters of the group are selected == this is the same as not selecting any
397 model
.getParametersFromFilters(),
399 // Group 1 (all selected, all)
403 // Group 2 (nothing is selected, all false)
409 'All filters selected in "string_option" group returns \'all\'.'
414 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
418 type
: 'send_unselected_if_any',
422 label
: 'Show filter 1',
423 description
: 'Description of Filter 1 in Group 1',
428 label
: 'Show filter 2',
429 description
: 'Description of Filter 2 in Group 1'
433 label
: 'Show filter 3',
434 description
: 'Description of Filter 3 in Group 1',
441 type
: 'send_unselected_if_any',
445 label
: 'Show filter 4',
446 description
: 'Description of Filter 1 in Group 2'
450 label
: 'Show filter 5',
451 description
: 'Description of Filter 2 in Group 2',
456 label
: 'Show filter 6',
457 description
: 'Description of Filter 3 in Group 2'
463 type
: 'string_options',
468 label
: 'Group 3: Filter 1',
469 description
: 'Description of Filter 1 in Group 3'
473 label
: 'Group 3: Filter 2',
474 description
: 'Description of Filter 2 in Group 3',
479 label
: 'Group 3: Filter 3',
480 description
: 'Description of Filter 3 in Group 3'
485 defaultFilterRepresentation
= {
486 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
493 // Group 3, "string_options", default values correspond to parameters and filters
498 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
500 model
.initializeFilters( definition
);
502 // Empty query = only default values
504 model
.getFiltersFromParameters( {} ),
505 defaultFilterRepresentation
,
506 'Empty parameter query results in filters in initial default state'
510 model
.getFiltersFromParameters( {
513 $.extend( {}, defaultFilterRepresentation
, {
514 hidefilter1
: false, // The text is "show filter 1"
515 hidefilter2
: false, // The text is "show filter 2"
516 hidefilter3
: false // The text is "show filter 3"
518 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
522 model
.getFiltersFromParameters( {
527 $.extend( {}, defaultFilterRepresentation
, {
528 hidefilter1
: false, // The text is "show filter 1"
529 hidefilter2
: false, // The text is "show filter 2"
530 hidefilter3
: false // The text is "show filter 3"
532 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
535 // The ones above don't update the model, so we have a clean state.
536 // getFiltersFromParameters is stateless; any change is unaffected by the current state
537 // This test is demonstrating wrong usage of the method;
538 // We should be aware that getFiltersFromParameters is stateless,
539 // so each call gives us a filter state that only reflects the query given.
540 // This means that the two calls to updateFilters() below collide.
541 // The result of the first is overridden by the result of the second,
542 // since both get a full state object from getFiltersFromParameters that **only** relates
543 // to the input it receives.
545 model
.getFiltersFromParameters( {
551 model
.getFiltersFromParameters( {
556 // The result here is ignoring the first updateFilters call
557 // We should receive default values + hidefilter6 as false
559 model
.getSelectedState(),
560 $.extend( {}, defaultFilterRepresentation
, {
564 'getFiltersFromParameters does not care about previous or existing state.'
568 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
569 model
.initializeFilters( definition
);
572 model
.getFiltersFromParameters( {
577 model
.getFiltersFromParameters( {
582 // Simulates minor edits being hidden in preferences, then unhidden via URL
585 model
.getSelectedState(),
586 defaultFilterRepresentation
,
587 'After checking and then unchecking a \'send_unselected_if_any\' filter (without touching other filters in that group), results are default'
591 model
.getFiltersFromParameters( {
596 model
.getSelectedState(),
597 $.extend( {}, defaultFilterRepresentation
, {
602 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
606 model
.getFiltersFromParameters( {
607 group3
: 'filter7,filter8'
611 model
.getSelectedState(),
612 $.extend( {}, defaultFilterRepresentation
, {
617 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
621 model
.getFiltersFromParameters( {
622 group3
: 'filter7,filter8,filter9'
626 model
.getSelectedState(),
627 $.extend( {}, defaultFilterRepresentation
, {
632 'A \'string_options\' parameter containing all values, results in all filters of the group as unchecked.'
636 model
.getFiltersFromParameters( {
637 group3
: 'filter7,all,filter9'
641 model
.getSelectedState(),
642 $.extend( {}, defaultFilterRepresentation
, {
647 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as unchecked.'
651 model
.getFiltersFromParameters( {
652 group3
: 'filter7,foo,filter9'
656 model
.getSelectedState(),
657 $.extend( {}, defaultFilterRepresentation
, {
662 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
666 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
670 type
: 'string_options',
674 label
: 'Show filter 1',
675 description
: 'Description of Filter 1 in Group 1'
679 label
: 'Show filter 2',
680 description
: 'Description of Filter 2 in Group 1'
684 label
: 'Show filter 3',
685 description
: 'Description of Filter 3 in Group 1'
690 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
692 model
.initializeFilters( definition
);
695 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
696 [ 'filter1', 'filter2' ],
697 'Remove duplicate values'
701 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
702 [ 'filter1', 'filter2' ],
703 'Remove invalid values'
707 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
709 'If any value is "all", the only value is "all".'
713 QUnit
.test( 'setFiltersToDefaults', function ( assert
) {
717 type
: 'send_unselected_if_any',
718 exclusionType
: 'default',
722 label
: 'Show filter 1',
723 description
: 'Description of Filter 1 in Group 1',
728 label
: 'Show filter 2',
729 description
: 'Description of Filter 2 in Group 1'
733 label
: 'Show filter 3',
734 description
: 'Description of Filter 3 in Group 1',
741 type
: 'send_unselected_if_any',
745 label
: 'Show filter 4',
746 description
: 'Description of Filter 1 in Group 2'
750 label
: 'Show filter 5',
751 description
: 'Description of Filter 2 in Group 2',
756 label
: 'Show filter 6',
757 description
: 'Description of Filter 3 in Group 2'
762 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
764 model
.initializeFilters( definition
);
767 model
.getFullState(),
770 hidefilter1
: { selected
: true, active
: true },
771 hidefilter2
: { selected
: false, active
: true },
772 hidefilter3
: { selected
: true, active
: true },
774 hidefilter4
: { selected
: false, active
: true },
775 hidefilter5
: { selected
: true, active
: true },
776 hidefilter6
: { selected
: false, active
: true },
778 'Initial state: all filters are active, and select states are default.'
781 // Default behavior for 'exclusion' type with only 1 item selected, means that:
782 // - The items in the same group that are *not* selected are *not* active
783 // - Items in other groups are unaffected (all active)
784 model
.updateFilters( {
793 model
.getFullState(),
795 // Group 1: not affected
796 hidefilter1
: { selected
: false, active
: true },
797 hidefilter2
: { selected
: false, active
: true },
798 hidefilter3
: { selected
: false, active
: true },
800 hidefilter4
: { selected
: false, active
: false },
801 hidefilter5
: { selected
: false, active
: false },
802 hidefilter6
: { selected
: true, active
: true },
804 'Default exclusion behavior with 1 item selected in the group.'
807 // Default behavior for 'exclusion' type with multiple items selected, but not all, means that:
808 // - The items in the same group that are *not* selected are *not* active
809 // - Items in other groups are unaffected (all active)
810 model
.updateFilters( {
811 // Literally updating filters to create a clean state
820 model
.getFullState(),
822 // Group 1: not affected
823 hidefilter1
: { selected
: false, active
: true },
824 hidefilter2
: { selected
: false, active
: true },
825 hidefilter3
: { selected
: false, active
: true },
827 hidefilter4
: { selected
: false, active
: false },
828 hidefilter5
: { selected
: true, active
: true },
829 hidefilter6
: { selected
: true, active
: true },
831 'Default exclusion behavior with multiple items (but not all) selected in the group.'
834 // Default behavior for 'exclusion' type with all items in the group selected, means that:
835 // - All items in the group are NOT active
836 // - Items in other groups are unaffected (all active)
837 model
.updateFilters( {
838 // Literally updating filters to create a clean state
847 model
.getFullState(),
849 // Group 1: not affected
850 hidefilter1
: { selected
: false, active
: true },
851 hidefilter2
: { selected
: false, active
: true },
852 hidefilter3
: { selected
: false, active
: true },
854 hidefilter4
: { selected
: true, active
: false },
855 hidefilter5
: { selected
: true, active
: false },
856 hidefilter6
: { selected
: true, active
: false },
858 'Default exclusion behavior with all items in the group.'
862 QUnit
.test( 'reapplyActiveFilters - "explicit" exclusion rules', function ( assert
) {
866 type
: 'send_unselected_if_any',
867 exclusionType
: 'explicit',
871 excludes
: [ 'filter2', 'filter3' ],
872 label
: 'Show filter 1',
873 description
: 'Description of Filter 1 in Group 1'
877 excludes
: [ 'filter3' ],
878 label
: 'Show filter 2',
879 description
: 'Description of Filter 2 in Group 1'
883 label
: 'Show filter 3',
884 excludes
: [ 'filter1' ],
885 description
: 'Description of Filter 3 in Group 1'
889 label
: 'Show filter 4',
890 description
: 'Description of Filter 4 in Group 1'
895 defaultFilterRepresentation
= {
896 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
903 // Group 3, "string_options", default values correspond to parameters and filters
908 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
910 model
.initializeFilters( definition
);
913 model
.getFullState(),
915 filter1
: { selected
: false, active
: true },
916 filter2
: { selected
: false, active
: true },
917 filter3
: { selected
: false, active
: true },
918 filter4
: { selected
: false, active
: true }
920 'Initial state: all filters are active.'
923 // "Explicit" behavior for 'exclusion' with one item checked:
924 // - Items in the 'excluded' list of the selected filter are inactive
925 model
.updateFilters( {
926 // Literally updating filters to create a clean state
927 filter1
: true, // Excludes 'hidefilter2', 'hidefilter3'
928 filter2
: false, // Excludes 'hidefilter3'
929 filter3
: false, // Excludes 'hidefilter1'
930 filter4
: false // No exclusion list
933 model
.getFullState(),
935 filter1
: { selected
: true, active
: true },
936 filter2
: { selected
: false, active
: false },
937 filter3
: { selected
: false, active
: false },
938 filter4
: { selected
: false, active
: true }
940 '"Explicit" exclusion behavior with one item selected that has an exclusion list.'
943 // "Explicit" behavior for 'exclusion' with two item checked:
944 // - Items in the 'excluded' list of each of the selected filter are inactive
945 model
.updateFilters( {
946 // Literally updating filters to create a clean state
947 filter1
: true, // Excludes 'hidefilter2', 'hidefilter3'
948 filter2
: false, // Excludes 'hidefilter3'
949 filter3
: true, // Excludes 'hidefilter1'
950 filter4
: false // No exclusion list
953 model
.getFullState(),
955 filter1
: { selected
: true, active
: false },
956 filter2
: { selected
: false, active
: false },
957 filter3
: { selected
: true, active
: false },
958 filter4
: { selected
: false, active
: true }
960 '"Explicit" exclusion behavior with two selected items that both have an exclusion list.'
963 // "Explicit behavior" with two filters that exclude the same item
965 // Two filters selected, both exclude 'hidefilter3'
966 model
.updateFilters( {
967 // Literally updating filters to create a clean state
968 filter1
: true, // Excludes 'hidefilter2', 'hidefilter3'
969 filter2
: true, // Excludes 'hidefilter3'
970 filter3
: false, // Excludes 'hidefilter1'
971 filter4
: false // No exclusion list
974 model
.getFullState(),
976 filter1
: { selected
: true, active
: true },
977 filter2
: { selected
: true, active
: false }, // Excluded by filter1
978 filter3
: { selected
: false, active
: false }, // Excluded by both filter1 and filter2
979 filter4
: { selected
: false, active
: true }
981 '"Explicit" exclusion behavior with two selected items that both exclude another item.'
984 // Unselect filter2: filter3 should still be excluded, because filter1 excludes it and is selected
985 model
.updateFilters( {
986 filter2
: false, // Excludes 'hidefilter3'
989 model
.getFullState(),
991 filter1
: { selected
: true, active
: true },
992 filter2
: { selected
: false, active
: false }, // Excluded by filter1
993 filter3
: { selected
: false, active
: false }, // Still excluded by filter1
994 filter4
: { selected
: false, active
: true }
996 '"Explicit" exclusion behavior unselecting one item that excludes another item, that is being excluded by a third active item.'
999 // Unselect filter1: filter3 should now be active, since both filters that exclude it are unselected
1000 model
.updateFilters( {
1001 filter1
: false, // Excludes 'hidefilter3' and 'hidefilter2'
1004 model
.getFullState(),
1006 filter1
: { selected
: false, active
: true },
1007 filter2
: { selected
: false, active
: true }, // No longer excluded by filter1
1008 filter3
: { selected
: false, active
: true }, // No longer excluded by either filter1 nor filter2
1009 filter4
: { selected
: false, active
: true }
1011 '"Explicit" exclusion behavior unselecting both items that excluded the same third item.'
1015 }( mediaWiki
, jQuery
) );