1 /* eslint-disable camelcase */
3 var filterDefinition
= [ {
5 type
: 'send_unselected_if_any',
8 name
: 'filter1', label
: 'group1filter1-label', description
: 'group1filter1-desc',
10 conflicts
: [ { group
: 'group2' } ],
23 name
: 'filter2', label
: 'group1filter2-label', description
: 'group1filter2-desc',
24 conflicts
: [ { group
: 'group2', filter
: 'filter6' } ],
32 { name
: 'filter3', label
: 'group1filter3-label', description
: 'group1filter3-desc', default: true }
36 type
: 'send_unselected_if_any',
38 conflicts
: [ { group
: 'group1', filter
: 'filter1' } ],
40 { name
: 'filter4', label
: 'group2filter4-label', description
: 'group2filter4-desc' },
41 { name
: 'filter5', label
: 'group2filter5-label', description
: 'group2filter5-desc', default: true },
43 name
: 'filter6', label
: 'group2filter6-label', description
: 'group2filter6-desc',
44 conflicts
: [ { group
: 'group1', filter
: 'filter2' } ]
49 type
: 'string_options',
53 { name
: 'filter7', label
: 'group3filter7-label', description
: 'group3filter7-desc' },
54 { name
: 'filter8', label
: 'group3filter8-label', description
: 'group3filter8-desc' },
55 { name
: 'filter9', label
: 'group3filter9-label', description
: 'group3filter9-desc' }
59 type
: 'single_option',
62 { name
: 'option1', label
: 'group4option1-label', description
: 'group4option1-desc' },
63 { name
: 'option2', label
: 'group4option2-label', description
: 'group4option2-desc' },
64 { name
: 'option3', label
: 'group4option3-label', description
: 'group4option3-desc' }
68 type
: 'single_option',
70 { name
: 'option1', label
: 'group5option1-label', description
: 'group5option1-desc' },
71 { name
: 'option2', label
: 'group5option2-label', description
: 'group5option2-desc' },
72 { name
: 'option3', label
: 'group5option3-label', description
: 'group5option3-desc' }
79 { name
: 'group6option1', label
: 'group6option1-label', description
: 'group6option1-desc' },
80 { name
: 'group6option2', label
: 'group6option2-label', description
: 'group6option2-desc', default: true },
81 { name
: 'group6option3', label
: 'group6option3-label', description
: 'group6option3-desc', default: true }
85 type
: 'single_option',
87 default: 'group7option2',
89 { name
: 'group7option1', label
: 'group7option1-label', description
: 'group7option1-desc' },
90 { name
: 'group7option2', label
: 'group7option2-label', description
: 'group7option2-desc' },
91 { name
: 'group7option3', label
: 'group7option3-label', description
: 'group7option3-desc' }
101 type
: 'string_options',
104 { name
: 0, label
: 'Main' },
105 { name
: 1, label
: 'Talk' },
106 { name
: 2, label
: 'User' },
107 { name
: 3, label
: 'User talk' }
112 defaultParameters
= {
125 group7
: 'group7option2',
128 baseParamRepresentation
= {
141 group7
: 'group7option2',
144 baseFilterRepresentation
= {
145 group1__filter1
: false,
146 group1__filter2
: false,
147 group1__filter3
: false,
148 group2__filter4
: false,
149 group2__filter5
: false,
150 group2__filter6
: false,
151 group3__filter7
: false,
152 group3__filter8
: false,
153 group3__filter9
: false,
154 // The 'single_value' type of group can't have empty value; it's either
155 // the default given or the first item that will get the truthy value
156 group4__option1
: false,
157 group4__option2
: true, // Default
158 group4__option3
: false,
159 group5__option1
: true, // No default set, first item is default value
160 group5__option2
: false,
161 group5__option3
: false,
162 group6__group6option1
: false,
163 group6__group6option2
: true,
164 group6__group6option3
: true,
165 group7__group7option1
: false,
166 group7__group7option2
: true,
167 group7__group7option3
: false,
173 baseFullFilterState
= {
174 group1__filter1
: { selected
: false, conflicted
: false, included
: false },
175 group1__filter2
: { selected
: false, conflicted
: false, included
: false },
176 group1__filter3
: { selected
: false, conflicted
: false, included
: false },
177 group2__filter4
: { selected
: false, conflicted
: false, included
: false },
178 group2__filter5
: { selected
: false, conflicted
: false, included
: false },
179 group2__filter6
: { selected
: false, conflicted
: false, included
: false },
180 group3__filter7
: { selected
: false, conflicted
: false, included
: false },
181 group3__filter8
: { selected
: false, conflicted
: false, included
: false },
182 group3__filter9
: { selected
: false, conflicted
: false, included
: false },
183 group4__option1
: { selected
: false, conflicted
: false, included
: false },
184 group4__option2
: { selected
: true, conflicted
: false, included
: false },
185 group4__option3
: { selected
: false, conflicted
: false, included
: false },
186 group5__option1
: { selected
: true, conflicted
: false, included
: false },
187 group5__option2
: { selected
: false, conflicted
: false, included
: false },
188 group5__option3
: { selected
: false, conflicted
: false, included
: false },
189 group6__group6option1
: { selected
: false, conflicted
: false, included
: false },
190 group6__group6option2
: { selected
: true, conflicted
: false, included
: false },
191 group6__group6option3
: { selected
: true, conflicted
: false, included
: false },
192 group7__group7option1
: { selected
: false, conflicted
: false, included
: false },
193 group7__group7option2
: { selected
: true, conflicted
: false, included
: false },
194 group7__group7option3
: { selected
: false, conflicted
: false, included
: false },
195 namespace__0
: { selected
: false, conflicted
: false, included
: false },
196 namespace__1
: { selected
: false, conflicted
: false, included
: false },
197 namespace__2
: { selected
: false, conflicted
: false, included
: false },
198 namespace__3
: { selected
: false, conflicted
: false, included
: false }
201 QUnit
.module( 'mediawiki.rcfilters - FiltersViewModel', QUnit
.newMwEnvironment( {
203 'group1filter1-label': 'Group 1: Filter 1 title',
204 'group1filter1-desc': 'Description of Filter 1 in Group 1',
205 'group1filter2-label': 'Group 1: Filter 2 title',
206 'group1filter2-desc': 'Description of Filter 2 in Group 1',
207 'group1filter3-label': 'Group 1: Filter 3',
208 'group1filter3-desc': 'Description of Filter 3 in Group 1',
210 'group2filter4-label': 'Group 2: Filter 4 title',
211 'group2filter4-desc': 'Description of Filter 4 in Group 2',
212 'group2filter5-label': 'Group 2: Filter 5',
213 'group2filter5-desc': 'Description of Filter 5 in Group 2',
214 'group2filter6-label': 'xGroup 2: Filter 6',
215 'group2filter6-desc': 'Description of Filter 6 in Group 2'
219 QUnit
.test( 'Setting up filters', function ( assert
) {
220 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
222 model
.initializeFilters( filterDefinition
, viewsDefinition
);
224 // Test that all items were created
226 Object
.keys( baseFilterRepresentation
).every( function ( filterName
) {
227 return model
.getItemByName( filterName
) instanceof mw
.rcfilters
.dm
.FilterItem
;
229 'Filters instantiated and stored correctly'
233 model
.getSelectedState(),
234 baseFilterRepresentation
,
235 'Initial state of filters'
238 model
.toggleFiltersSelected( {
239 group1__filter1
: true,
240 group2__filter5
: true,
241 group3__filter7
: true
244 model
.getSelectedState(),
245 $.extend( true, {}, baseFilterRepresentation
, {
246 group1__filter1
: true,
247 group2__filter5
: true,
248 group3__filter7
: true
250 'Updating filter states correctly'
254 QUnit
.test( 'Default filters', function ( assert
) {
255 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
257 model
.initializeFilters( filterDefinition
, viewsDefinition
);
259 // Empty query = only default values
261 model
.getDefaultParams(),
263 'Default parameters are stored properly per filter and group'
266 // Change sticky filter
267 model
.toggleFiltersSelected( {
268 group7__group7option1
: true
271 // Make sure defaults have changed
273 model
.getDefaultParams(),
274 $.extend( true, {}, defaultParameters
, {
275 group7
: 'group7option1'
277 'Default parameters are stored properly per filter and group'
281 QUnit
.test( 'Finding matching filters', function ( assert
) {
287 group1
: [ 'group1__filter1', 'group1__filter2', 'group1__filter3' ],
288 group2
: [ 'group2__filter4', 'group2__filter5' ]
290 reason
: 'Finds filters starting with the query string'
295 group2
: [ 'group2__filter4', 'group2__filter5', 'group2__filter6' ]
297 reason
: 'Finds filters containing the query string in their description'
302 group1
: [ 'group1__filter1', 'group1__filter2' ],
303 group2
: [ 'group2__filter4' ]
305 reason
: 'Finds filters containing the query string in their group title'
310 namespace: [ 'namespace__0' ]
312 reason
: 'Finds item in view when a prefix is used'
317 reason
: 'Finds no results if using namespaces prefix (:) to search for filter title'
320 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
321 extractNames = function ( matches
) {
323 Object
.keys( matches
).forEach( function ( groupName
) {
324 result
[ groupName
] = matches
[ groupName
].map( function ( item
) {
325 return item
.getName();
331 model
.initializeFilters( filterDefinition
, viewsDefinition
);
333 testCases
.forEach( function ( testCase
) {
334 matches
= model
.findMatches( testCase
.query
);
336 extractNames( matches
),
337 testCase
.expectedMatches
,
342 matches
= model
.findMatches( 'foo' );
344 $.isEmptyObject( matches
),
345 'findMatches returns an empty object when no results found'
349 QUnit
.test( 'getParametersFromFilters', function ( assert
) {
350 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
352 model
.initializeFilters( filterDefinition
, viewsDefinition
);
354 // Starting with all filters unselected
356 model
.getParametersFromFilters(),
357 baseParamRepresentation
,
358 'Unselected filters return all parameters falsey or \'\'.'
362 model
.toggleFiltersSelected( {
363 group1__filter1
: true
365 // Only one filter in one group
367 model
.getParametersFromFilters(),
368 $.extend( true, {}, baseParamRepresentation
, {
369 // Group 1 (one selected, the others are true)
373 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
377 model
.toggleFiltersSelected( {
378 group1__filter1
: true,
379 group1__filter2
: true
381 // Two selected filters in one group
383 model
.getParametersFromFilters(),
384 $.extend( true, {}, baseParamRepresentation
, {
385 // Group 1 (two selected, the other is true)
388 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
392 model
.toggleFiltersSelected( {
393 group1__filter1
: true,
394 group1__filter2
: true,
395 group1__filter3
: true
397 // All filters of the group are selected == this is the same as not selecting any
399 model
.getParametersFromFilters(),
400 baseParamRepresentation
,
401 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
404 // Select 1 filter from string_options
405 model
.toggleFiltersSelected( {
406 group3__filter7
: true,
407 group3__filter8
: false,
408 group3__filter9
: false
410 // All filters of the group are selected == this is the same as not selecting any
412 model
.getParametersFromFilters(),
413 $.extend( true, {}, baseParamRepresentation
, {
416 'One filter selected in "string_option" group returns that filter in the value.'
419 // Select 2 filters from string_options
420 model
.toggleFiltersSelected( {
421 group3__filter7
: true,
422 group3__filter8
: true,
423 group3__filter9
: false
425 // All filters of the group are selected == this is the same as not selecting any
427 model
.getParametersFromFilters(),
428 $.extend( true, {}, baseParamRepresentation
, {
429 group3
: 'filter7,filter8'
431 'Two filters selected in "string_option" group returns those filters in the value.'
434 // Select 3 filters from string_options
435 model
.toggleFiltersSelected( {
436 group3__filter7
: true,
437 group3__filter8
: true,
438 group3__filter9
: true
440 // All filters of the group are selected == this is the same as not selecting any
442 model
.getParametersFromFilters(),
443 $.extend( true, {}, baseParamRepresentation
, {
446 'All filters selected in "string_option" group returns \'all\'.'
450 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
451 model
.initializeFilters( filterDefinition
, viewsDefinition
);
453 // Select an option from single_option group
454 model
.toggleFiltersSelected( {
455 group4__option2
: true
457 // All filters of the group are selected == this is the same as not selecting any
459 model
.getParametersFromFilters(),
460 $.extend( true, {}, baseParamRepresentation
, {
463 'Selecting an option from "single_option" group returns that option as a value.'
466 // Select a different option from single_option group
467 model
.toggleFiltersSelected( {
468 group4__option3
: true
470 // All filters of the group are selected == this is the same as not selecting any
472 model
.getParametersFromFilters(),
473 $.extend( true, {}, baseParamRepresentation
, {
476 'Selecting a different option from "single_option" group changes the selection.'
480 QUnit
.test( 'getParametersFromFilters (custom object)', function ( assert
) {
481 // This entire test uses different base definition than the global one
482 // on purpose, to verify that the values inserted as a custom object
483 // are the ones we expect in return
485 model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
489 type
: 'send_unselected_if_any',
491 { name
: 'hidefilter1', label
: 'Hide filter 1', description
: '' },
492 { name
: 'hidefilter2', label
: 'Hide filter 2', description
: '' },
493 { name
: 'hidefilter3', label
: 'Hide filter 3', description
: '' }
498 type
: 'send_unselected_if_any',
500 { name
: 'hidefilter4', label
: 'Hide filter 4', description
: '' },
501 { name
: 'hidefilter5', label
: 'Hide filter 5', description
: '' },
502 { name
: 'hidefilter6', label
: 'Hide filter 6', description
: '' }
507 type
: 'string_options',
510 { name
: 'filter7', label
: 'Hide filter 7', description
: '' },
511 { name
: 'filter8', label
: 'Hide filter 8', description
: '' },
512 { name
: 'filter9', label
: 'Hide filter 9', description
: '' }
517 type
: 'single_option',
519 { name
: 'filter10', label
: 'Hide filter 10', description
: '' },
520 { name
: 'filter11', label
: 'Hide filter 11', description
: '' },
521 { name
: 'filter12', label
: 'Hide filter 12', description
: '' }
536 // This is mocking the cases above, both
537 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
538 // - 'Two filters selected in "string_option" group returns those filters in the value.'
540 group1__hidefilter1
: true,
541 group1__hidefilter2
: true,
542 group1__hidefilter3
: false,
543 group2__hidefilter4
: false,
544 group2__hidefilter5
: false,
545 group2__hidefilter6
: false,
546 group3__filter7
: true,
547 group3__filter8
: true,
548 group3__filter9
: false
550 expected
: $.extend( true, {}, baseResult
, {
551 // Group 1 (two selected, the others are true)
553 // Group 3 (two selected)
554 group3
: 'filter7,filter8'
556 msg
: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
559 // This is mocking case above
560 // - 'One filter in one "send_unselected_if_any" group returns the other parameters truthy.'
562 group1__hidefilter1
: 1
564 expected
: $.extend( true, {}, baseResult
, {
565 // Group 1 (one selected, the others are true)
569 msg
: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
573 group4__filter10
: true
575 expected
: $.extend( true, {}, baseResult
, {
578 msg
: 'Given a single value for "single_option" that option is represented in the result.'
582 group4__filter10
: true,
583 group4__filter11
: true
585 expected
: $.extend( true, {}, baseResult
, {
588 msg
: 'Given more than one true value for "single_option" (which should not happen!) only the first value counts, and the second is ignored.'
592 expected
: baseResult
,
593 msg
: 'Given an explicit empty object, the result is all filters set to their falsey unselected value.'
597 model
.initializeFilters( definition
);
598 // Store original state
599 originalState
= model
.getSelectedState();
602 cases
.forEach( function ( test
) {
604 model
.getParametersFromFilters( test
.input
),
610 // After doing the above tests, make sure the actual state
611 // of the filter stayed the same
613 model
.getSelectedState(),
615 'Running the method with external definition to parse does not actually change the state of the model'
619 QUnit
.test( 'getFiltersFromParameters', function ( assert
) {
620 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
622 model
.initializeFilters( filterDefinition
, viewsDefinition
);
624 // Empty query = only default values
626 model
.getFiltersFromParameters( {} ),
627 baseFilterRepresentation
,
628 'Empty parameter query results in an object representing all filters set to their base state'
632 model
.getFiltersFromParameters( {
635 $.extend( {}, baseFilterRepresentation
, {
636 group1__filter1
: true, // The text is "show filter 1"
637 group1__filter2
: false, // The text is "show filter 2"
638 group1__filter3
: true // The text is "show filter 3"
640 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
644 model
.getFiltersFromParameters( {
649 $.extend( {}, baseFilterRepresentation
, {
650 group1__filter1
: false, // The text is "show filter 1"
651 group1__filter2
: false, // The text is "show filter 2"
652 group1__filter3
: false // The text is "show filter 3"
654 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
657 // The ones above don't update the model, so we have a clean state.
658 // getFiltersFromParameters is stateless; any change is unaffected by the current state
659 // This test is demonstrating wrong usage of the method;
660 // We should be aware that getFiltersFromParameters is stateless,
661 // so each call gives us a filter state that only reflects the query given.
662 // This means that the two calls to toggleFiltersSelected() below collide.
663 // The result of the first is overridden by the result of the second,
664 // since both get a full state object from getFiltersFromParameters that **only** relates
665 // to the input it receives.
666 model
.toggleFiltersSelected(
667 model
.getFiltersFromParameters( {
672 model
.toggleFiltersSelected(
673 model
.getFiltersFromParameters( {
678 // The result here is ignoring the first toggleFiltersSelected call
680 model
.getSelectedState(),
681 $.extend( {}, baseFilterRepresentation
, {
682 group2__filter4
: true,
683 group2__filter5
: true,
684 group2__filter6
: false
686 'getFiltersFromParameters does not care about previous or existing state.'
690 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
691 model
.initializeFilters( filterDefinition
, viewsDefinition
);
693 model
.toggleFiltersSelected(
694 model
.getFiltersFromParameters( {
699 model
.getSelectedState(),
700 $.extend( {}, baseFilterRepresentation
, {
701 group3__filter7
: true,
702 group3__filter8
: false,
703 group3__filter9
: false
705 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
708 model
.toggleFiltersSelected(
709 model
.getFiltersFromParameters( {
710 group3
: 'filter7,filter8'
714 model
.getSelectedState(),
715 $.extend( {}, baseFilterRepresentation
, {
716 group3__filter7
: true,
717 group3__filter8
: true,
718 group3__filter9
: false
720 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
723 model
.toggleFiltersSelected(
724 model
.getFiltersFromParameters( {
725 group3
: 'filter7,filter8,filter9'
729 model
.getSelectedState(),
730 $.extend( {}, baseFilterRepresentation
, {
731 group3__filter7
: true,
732 group3__filter8
: true,
733 group3__filter9
: true
735 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
738 model
.toggleFiltersSelected(
739 model
.getFiltersFromParameters( {
740 group3
: 'filter7,all,filter9'
744 model
.getSelectedState(),
745 $.extend( {}, baseFilterRepresentation
, {
746 group3__filter7
: true,
747 group3__filter8
: true,
748 group3__filter9
: true
750 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
753 model
.toggleFiltersSelected(
754 model
.getFiltersFromParameters( {
755 group3
: 'filter7,foo,filter9'
759 model
.getSelectedState(),
760 $.extend( {}, baseFilterRepresentation
, {
761 group3__filter7
: true,
762 group3__filter8
: false,
763 group3__filter9
: true
765 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
768 model
.toggleFiltersSelected(
769 model
.getFiltersFromParameters( {
774 model
.getSelectedState(),
775 $.extend( {}, baseFilterRepresentation
, {
776 group4__option1
: true,
777 group4__option2
: false
779 'A \'single_option\' parameter reflects a single selected value.'
783 model
.getFiltersFromParameters( {
784 group4
: 'option1,option2'
786 baseFilterRepresentation
,
787 'An invalid \'single_option\' parameter is ignored.'
790 // Change to one value
791 model
.toggleFiltersSelected(
792 model
.getFiltersFromParameters( {
796 // Change again to another value
797 model
.toggleFiltersSelected(
798 model
.getFiltersFromParameters( {
803 model
.getSelectedState(),
804 $.extend( {}, baseFilterRepresentation
, {
805 group4__option2
: true
807 'A \'single_option\' parameter always reflects the latest selected value.'
811 QUnit
.test( 'sanitizeStringOptionGroup', function ( assert
) {
812 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
814 model
.initializeFilters( filterDefinition
, viewsDefinition
);
817 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
818 [ 'filter1', 'filter2' ],
819 'Remove duplicate values'
823 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
824 [ 'filter1', 'filter2' ],
825 'Remove invalid values'
829 model
.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
831 'If any value is "all", the only value is "all".'
835 QUnit
.test( 'Filter interaction: subsets', function ( assert
) {
836 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
838 model
.initializeFilters( filterDefinition
, viewsDefinition
);
840 // Select a filter that has subset with another filter
841 model
.toggleFiltersSelected( {
842 group1__filter1
: true
845 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
847 model
.getFullState(),
848 $.extend( true, {}, baseFullFilterState
, {
849 group1__filter1
: { selected
: true },
850 group1__filter2
: { included
: true },
851 group1__filter3
: { included
: true },
852 // Conflicts are affected
853 group2__filter4
: { conflicted
: true },
854 group2__filter5
: { conflicted
: true },
855 group2__filter6
: { conflicted
: true }
857 'Filters with subsets are represented in the model.'
860 // Select another filter that has a subset with the same previous filter
861 model
.toggleFiltersSelected( {
862 group1__filter2
: true
864 model
.reassessFilterInteractions( model
.getItemByName( 'filter2' ) );
866 model
.getFullState(),
867 $.extend( true, {}, baseFullFilterState
, {
868 group1__filter1
: { selected
: true },
869 group1__filter2
: { selected
: true, included
: true },
870 group1__filter3
: { included
: true },
871 // Conflicts are affected
872 group2__filter6
: { conflicted
: true }
874 'Filters that have multiple subsets are represented.'
877 // Remove one filter (but leave the other) that affects filter3
878 model
.toggleFiltersSelected( {
879 group1__filter1
: false
881 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
883 model
.getFullState(),
884 $.extend( true, {}, baseFullFilterState
, {
885 group1__filter2
: { selected
: true, included
: false },
886 group1__filter3
: { included
: true },
887 // Conflicts are affected
888 group2__filter6
: { conflicted
: true }
890 'Removing a filter only un-includes its subset if there is no other filter affecting.'
893 model
.toggleFiltersSelected( {
894 group1__filter2
: false
896 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
898 model
.getFullState(),
900 'Removing all supersets also un-includes the subsets.'
904 QUnit
.test( 'Filter interaction: full coverage', function ( assert
) {
905 var model
= new mw
.rcfilters
.dm
.FiltersViewModel(),
906 isCapsuleItemMuted = function ( filterName
) {
907 var itemModel
= model
.getItemByName( filterName
),
908 groupModel
= itemModel
.getGroupModel();
910 // This is the logic inside the capsule widget
912 // The capsule item widget only appears if the item is selected
913 itemModel
.isSelected() &&
914 // Muted state is only valid if group is full coverage and all items are selected
915 groupModel
.isFullCoverage() && groupModel
.areAllSelected()
918 getCurrentItemsMutedState = function () {
920 group1__filter1
: isCapsuleItemMuted( 'group1__filter1' ),
921 group1__filter2
: isCapsuleItemMuted( 'group1__filter2' ),
922 group1__filter3
: isCapsuleItemMuted( 'group1__filter3' ),
923 group2__filter4
: isCapsuleItemMuted( 'group2__filter4' ),
924 group2__filter5
: isCapsuleItemMuted( 'group2__filter5' ),
925 group2__filter6
: isCapsuleItemMuted( 'group2__filter6' )
929 group1__filter1
: false,
930 group1__filter2
: false,
931 group1__filter3
: false,
932 group2__filter4
: false,
933 group2__filter5
: false,
934 group2__filter6
: false
937 model
.initializeFilters( filterDefinition
, viewsDefinition
);
939 // Starting state, no selection, all items are non-muted
941 getCurrentItemsMutedState(),
943 'No selection - all items are non-muted'
946 // Select most (but not all) items in each group
947 model
.toggleFiltersSelected( {
948 group1__filter1
: true,
949 group1__filter2
: true,
950 group2__filter4
: true,
951 group2__filter5
: true
954 // Both groups have multiple (but not all) items selected, all items are non-muted
956 getCurrentItemsMutedState(),
958 'Not all items in the group selected - all items are non-muted'
961 // Select all items in 'fullCoverage' group (group2)
962 model
.toggleFiltersSelected( {
963 group2__filter6
: true
966 // Group2 (full coverage) has all items selected, all its items are muted
968 getCurrentItemsMutedState(),
969 $.extend( {}, baseMuteState
, {
970 group2__filter4
: true,
971 group2__filter5
: true,
972 group2__filter6
: true
974 'All items in \'full coverage\' group are selected - all items in the group are muted'
977 // Select all items in non 'fullCoverage' group (group1)
978 model
.toggleFiltersSelected( {
979 group1__filter3
: true
982 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
984 getCurrentItemsMutedState(),
985 $.extend( {}, baseMuteState
, {
986 group2__filter4
: true,
987 group2__filter5
: true,
988 group2__filter6
: true
990 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
993 // Uncheck an item from each group
994 model
.toggleFiltersSelected( {
995 group1__filter3
: false,
996 group2__filter5
: false
999 getCurrentItemsMutedState(),
1001 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1005 QUnit
.test( 'Filter interaction: conflicts', function ( assert
) {
1006 var model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1008 model
.initializeFilters( filterDefinition
, viewsDefinition
);
1011 model
.getFullState(),
1012 baseFullFilterState
,
1013 'Initial state: no conflicts because no selections.'
1016 // Select a filter that has a conflict with an entire group
1017 model
.toggleFiltersSelected( {
1018 group1__filter1
: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1021 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1024 model
.getFullState(),
1025 $.extend( true, {}, baseFullFilterState
, {
1026 group1__filter1
: { selected
: true },
1027 group2__filter4
: { conflicted
: true },
1028 group2__filter5
: { conflicted
: true },
1029 group2__filter6
: { conflicted
: true },
1030 // Subsets are affected by the selection
1031 group1__filter2
: { included
: true },
1032 group1__filter3
: { included
: true }
1034 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1037 // Select one of the conflicts (both filters are now conflicted and selected)
1038 model
.toggleFiltersSelected( {
1039 group2__filter4
: true // conflicts: filter 1
1041 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter4' ) );
1044 model
.getFullState(),
1045 $.extend( true, {}, baseFullFilterState
, {
1046 group1__filter1
: { selected
: true, conflicted
: true },
1047 group2__filter4
: { selected
: true, conflicted
: true },
1048 group2__filter5
: { conflicted
: true },
1049 group2__filter6
: { conflicted
: true },
1050 // Subsets are affected by the selection
1051 group1__filter2
: { included
: true },
1052 group1__filter3
: { included
: true }
1054 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1058 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1059 model
.initializeFilters( filterDefinition
, viewsDefinition
);
1061 // Select a filter that has a conflict with a specific filter
1062 model
.toggleFiltersSelected( {
1063 group1__filter2
: true // conflicts: filter6
1065 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1068 model
.getFullState(),
1069 $.extend( true, {}, baseFullFilterState
, {
1070 group1__filter2
: { selected
: true },
1071 group2__filter6
: { conflicted
: true },
1072 // Subsets are affected by the selection
1073 group1__filter3
: { included
: true }
1075 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1078 // Select the conflicting filter
1079 model
.toggleFiltersSelected( {
1080 group2__filter6
: true // conflicts: filter2
1083 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter6' ) );
1086 model
.getFullState(),
1087 $.extend( true, {}, baseFullFilterState
, {
1088 group1__filter2
: { selected
: true, conflicted
: true },
1089 group2__filter6
: { selected
: true, conflicted
: true },
1090 // This is added to the conflicts because filter6 is part of group2,
1091 // who is in conflict with filter1; note that filter2 also conflicts
1092 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1093 // and also because its **own sibling** (filter2) is **also** in conflict with the
1094 // selected items in group2 (filter6)
1095 group1__filter1
: { conflicted
: true },
1097 // Subsets are affected by the selection
1098 group1__filter3
: { included
: true }
1100 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1103 // Now choose a non-conflicting filter from the group
1104 model
.toggleFiltersSelected( {
1105 group2__filter5
: true
1108 model
.reassessFilterInteractions( model
.getItemByName( 'group2__filter5' ) );
1111 model
.getFullState(),
1112 $.extend( true, {}, baseFullFilterState
, {
1113 group1__filter2
: { selected
: true },
1114 group2__filter6
: { selected
: true },
1115 group2__filter5
: { selected
: true },
1116 // Filter6 and filter1 are no longer in conflict because
1117 // filter5, while it is in conflict with filter1, it is
1118 // not in conflict with filter2 - and since filter2 is
1119 // selected, it removes the conflict bidirectionally
1121 // Subsets are affected by the selection
1122 group1__filter3
: { included
: true }
1124 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1127 // Followup on the previous test, unselect filter2 so filter1
1128 // is now the only one selected in its own group, and since
1129 // it is in conflict with the entire of group2, it means
1130 // filter1 is once again conflicted
1131 model
.toggleFiltersSelected( {
1132 group1__filter2
: false
1135 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1138 model
.getFullState(),
1139 $.extend( true, {}, baseFullFilterState
, {
1140 group1__filter1
: { conflicted
: true },
1141 group2__filter6
: { selected
: true },
1142 group2__filter5
: { selected
: true }
1144 'Unselecting an item that did not conflict returns the conflict state.'
1147 // Followup #2: Now actually select filter1, and make everything conflicted
1148 model
.toggleFiltersSelected( {
1149 group1__filter1
: true
1152 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter1' ) );
1155 model
.getFullState(),
1156 $.extend( true, {}, baseFullFilterState
, {
1157 group1__filter1
: { selected
: true, conflicted
: true },
1158 group2__filter6
: { selected
: true, conflicted
: true },
1159 group2__filter5
: { selected
: true, conflicted
: true },
1160 group2__filter4
: { conflicted
: true }, // Not selected but conflicted because it's in group2
1161 // Subsets are affected by the selection
1162 group1__filter2
: { included
: true },
1163 group1__filter3
: { included
: true }
1165 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1170 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1171 model
.initializeFilters( filterDefinition
, viewsDefinition
);
1173 // Select a filter that has a conflict with a specific filter
1174 model
.toggleFiltersSelected( {
1175 group1__filter2
: true // conflicts: filter6
1178 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter2' ) );
1181 model
.getFullState(),
1182 $.extend( true, {}, baseFullFilterState
, {
1183 group1__filter2
: { selected
: true },
1184 group2__filter6
: { conflicted
: true },
1185 // Subsets are affected by the selection
1186 group1__filter3
: { included
: true }
1188 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1191 model
.toggleFiltersSelected( {
1192 group1__filter3
: true // conflicts: filter6
1195 model
.reassessFilterInteractions( model
.getItemByName( 'group1__filter3' ) );
1198 model
.getFullState(),
1199 $.extend( true, {}, baseFullFilterState
, {
1200 group1__filter2
: { selected
: true },
1201 // Subsets are affected by the selection
1202 group1__filter3
: { selected
: true, included
: true }
1204 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1208 QUnit
.test( 'Filter highlights', function ( assert
) {
1209 // We are using a different (smaller) definition here than the global one
1210 var definition
= [ {
1213 type
: 'string_options',
1215 { name
: 'filter1', cssClass
: 'class1', label
: '1', description
: '1' },
1216 { name
: 'filter2', cssClass
: 'class2', label
: '2', description
: '2' },
1217 { name
: 'filter3', cssClass
: 'class3', label
: '3', description
: '3' },
1218 { name
: 'filter4', cssClass
: 'class4', label
: '4', description
: '4' },
1219 { name
: 'filter5', cssClass
: 'class5', label
: '5', description
: '5' },
1220 { name
: 'filter6', label
: '6', description
: '6' }
1223 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1225 model
.initializeFilters( definition
);
1228 !model
.isHighlightEnabled(),
1229 'Initially, highlight is disabled.'
1232 model
.toggleHighlight( true );
1234 model
.isHighlightEnabled(),
1235 'Highlight is enabled on toggle.'
1238 model
.setHighlightColor( 'group1__filter1', 'color1' );
1239 model
.setHighlightColor( 'group1__filter2', 'color2' );
1242 model
.getHighlightedItems().map( function ( item
) {
1243 return item
.getName();
1249 'Highlighted items are highlighted.'
1253 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1255 'Item highlight color is set.'
1258 model
.setHighlightColor( 'group1__filter1', 'color1changed' );
1260 model
.getItemByName( 'group1__filter1' ).getHighlightColor(),
1262 'Item highlight color is changed on setHighlightColor.'
1265 model
.clearHighlightColor( 'group1__filter1' );
1267 model
.getHighlightedItems().map( function ( item
) {
1268 return item
.getName();
1273 'Clear highlight from an item results in the item no longer being highlighted.'
1277 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1278 model
.initializeFilters( definition
);
1280 model
.setHighlightColor( 'group1__filter1', 'color1' );
1281 model
.setHighlightColor( 'group1__filter2', 'color2' );
1282 model
.setHighlightColor( 'group1__filter3', 'color3' );
1285 model
.getHighlightedItems().map( function ( item
) {
1286 return item
.getName();
1293 'Even if highlights are not enabled, the items remember their highlight state'
1294 // NOTE: When actually displaying the highlights, the UI checks whether
1295 // highlighting is generally active and then goes over the highlighted
1296 // items. The item models, however, and the view model in general, still
1297 // retains the knowledge about which filters have different colors, so we
1298 // can seamlessly return to the colors the user previously chose if they
1299 // reapply highlights.
1303 model
= new mw
.rcfilters
.dm
.FiltersViewModel();
1304 model
.initializeFilters( definition
);
1306 model
.setHighlightColor( 'group1__filter1', 'color1' );
1307 model
.setHighlightColor( 'group1__filter6', 'color6' );
1310 model
.getHighlightedItems().map( function ( item
) {
1311 return item
.getName();
1316 'Items without a specified class identifier are not highlighted.'
1319 }( mediaWiki
, jQuery
) );