Merge "Add some translations for Western Punjabi (pnb)"
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki.rcfilters / dm.FiltersViewModel.test.js
1 ( function ( mw, $ ) {
2 QUnit.module( 'mediawiki.rcfilters - FiltersViewModel' );
3
4 QUnit.test( 'Setting up filters', function ( assert ) {
5 var definition = {
6 group1: {
7 title: 'Group 1',
8 type: 'send_unselected_if_any',
9 filters: [
10 {
11 name: 'group1filter1',
12 label: 'Group 1: Filter 1',
13 description: 'Description of Filter 1 in Group 1'
14 },
15 {
16 name: 'group1filter2',
17 label: 'Group 1: Filter 2',
18 description: 'Description of Filter 2 in Group 1'
19 }
20 ]
21 },
22 group2: {
23 title: 'Group 2',
24 type: 'send_unselected_if_any',
25 filters: [
26 {
27 name: 'group2filter1',
28 label: 'Group 2: Filter 1',
29 description: 'Description of Filter 1 in Group 2'
30 },
31 {
32 name: 'group2filter2',
33 label: 'Group 2: Filter 2',
34 description: 'Description of Filter 2 in Group 2'
35 }
36 ]
37 },
38 group3: {
39 title: 'Group 3',
40 type: 'string_options',
41 filters: [
42 {
43 name: 'group3filter1',
44 label: 'Group 3: Filter 1',
45 description: 'Description of Filter 1 in Group 3'
46 },
47 {
48 name: 'group3filter2',
49 label: 'Group 3: Filter 2',
50 description: 'Description of Filter 2 in Group 3'
51 }
52 ]
53 }
54 },
55 model = new mw.rcfilters.dm.FiltersViewModel();
56
57 model.initializeFilters( definition );
58
59 assert.ok(
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'
67 );
68
69 assert.deepEqual(
70 model.getSelectedState(),
71 {
72 group1filter1: false,
73 group1filter2: false,
74 group2filter1: false,
75 group2filter2: false,
76 group3filter1: false,
77 group3filter2: false
78 },
79 'Initial state of filters'
80 );
81
82 model.toggleFiltersSelected( {
83 group1filter1: true,
84 group2filter2: true,
85 group3filter1: true
86 } );
87 assert.deepEqual(
88 model.getSelectedState(),
89 {
90 group1filter1: true,
91 group1filter2: false,
92 group2filter1: false,
93 group2filter2: true,
94 group3filter1: true,
95 group3filter2: false
96 },
97 'Updating filter states correctly'
98 );
99 } );
100
101 QUnit.test( 'Finding matching filters', function ( assert ) {
102 var matches,
103 definition = {
104 group1: {
105 title: 'Group 1 title',
106 type: 'send_unselected_if_any',
107 filters: [
108 {
109 name: 'group1filter1',
110 label: 'Group 1: Filter 1',
111 description: 'Description of Filter 1 in Group 1'
112 },
113 {
114 name: 'group1filter2',
115 label: 'Group 1: Filter 2',
116 description: 'Description of Filter 2 in Group 1'
117 }
118 ]
119 },
120 group2: {
121 title: 'Group 2 title',
122 type: 'send_unselected_if_any',
123 filters: [
124 {
125 name: 'group2filter1',
126 label: 'Group 2: Filter 1',
127 description: 'Description of Filter 1 in Group 2'
128 },
129 {
130 name: 'group2filter2',
131 label: 'xGroup 2: Filter 2',
132 description: 'Description of Filter 2 in Group 2'
133 }
134 ]
135 }
136 },
137 testCases = [
138 {
139 query: 'group',
140 expectedMatches: {
141 group1: [ 'group1filter1', 'group1filter2' ],
142 group2: [ 'group2filter1' ]
143 },
144 reason: 'Finds filters starting with the query string'
145 },
146 {
147 query: 'filter 2 in group',
148 expectedMatches: {
149 group1: [ 'group1filter2' ],
150 group2: [ 'group2filter2' ]
151 },
152 reason: 'Finds filters containing the query string in their description'
153 },
154 {
155 query: 'title',
156 expectedMatches: {
157 group1: [ 'group1filter1', 'group1filter2' ],
158 group2: [ 'group2filter1', 'group2filter2' ]
159 },
160 reason: 'Finds filters containing the query string in their group title'
161 }
162 ],
163 model = new mw.rcfilters.dm.FiltersViewModel(),
164 extractNames = function ( matches ) {
165 var result = {};
166 Object.keys( matches ).forEach( function ( groupName ) {
167 result[ groupName ] = matches[ groupName ].map( function ( item ) {
168 return item.getName();
169 } );
170 } );
171 return result;
172 };
173
174 model.initializeFilters( definition );
175
176 testCases.forEach( function ( testCase ) {
177 matches = model.findMatches( testCase.query );
178 assert.deepEqual(
179 extractNames( matches ),
180 testCase.expectedMatches,
181 testCase.reason
182 );
183 } );
184
185 matches = model.findMatches( 'foo' );
186 assert.ok(
187 $.isEmptyObject( matches ),
188 'findMatches returns an empty object when no results found'
189 );
190 } );
191
192 QUnit.test( 'getParametersFromFilters', function ( assert ) {
193 var definition = {
194 group1: {
195 title: 'Group 1',
196 type: 'send_unselected_if_any',
197 filters: [
198 {
199 name: 'hidefilter1',
200 label: 'Group 1: Filter 1',
201 description: 'Description of Filter 1 in Group 1'
202 },
203 {
204 name: 'hidefilter2',
205 label: 'Group 1: Filter 2',
206 description: 'Description of Filter 2 in Group 1'
207 },
208 {
209 name: 'hidefilter3',
210 label: 'Group 1: Filter 3',
211 description: 'Description of Filter 3 in Group 1'
212 }
213 ]
214 },
215 group2: {
216 title: 'Group 2',
217 type: 'send_unselected_if_any',
218 filters: [
219 {
220 name: 'hidefilter4',
221 label: 'Group 2: Filter 1',
222 description: 'Description of Filter 1 in Group 2'
223 },
224 {
225 name: 'hidefilter5',
226 label: 'Group 2: Filter 2',
227 description: 'Description of Filter 2 in Group 2'
228 },
229 {
230 name: 'hidefilter6',
231 label: 'Group 2: Filter 3',
232 description: 'Description of Filter 3 in Group 2'
233 }
234 ]
235 },
236 group3: {
237 title: 'Group 3',
238 type: 'string_options',
239 separator: ',',
240 filters: [
241 {
242 name: 'filter7',
243 label: 'Group 3: Filter 1',
244 description: 'Description of Filter 1 in Group 3'
245 },
246 {
247 name: 'filter8',
248 label: 'Group 3: Filter 2',
249 description: 'Description of Filter 2 in Group 3'
250 },
251 {
252 name: 'filter9',
253 label: 'Group 3: Filter 3',
254 description: 'Description of Filter 3 in Group 3'
255 }
256 ]
257 }
258 },
259 model = new mw.rcfilters.dm.FiltersViewModel();
260
261 model.initializeFilters( definition );
262
263 // Starting with all filters unselected
264 assert.deepEqual(
265 model.getParametersFromFilters(),
266 {
267 hidefilter1: 0,
268 hidefilter2: 0,
269 hidefilter3: 0,
270 hidefilter4: 0,
271 hidefilter5: 0,
272 hidefilter6: 0,
273 group3: 'all'
274 },
275 'Unselected filters return all parameters falsey or \'all\'.'
276 );
277
278 // Select 1 filter
279 model.toggleFiltersSelected( {
280 hidefilter1: true,
281 hidefilter2: false,
282 hidefilter3: false,
283 hidefilter4: false,
284 hidefilter5: false,
285 hidefilter6: false
286 } );
287 // Only one filter in one group
288 assert.deepEqual(
289 model.getParametersFromFilters(),
290 {
291 // Group 1 (one selected, the others are true)
292 hidefilter1: 0,
293 hidefilter2: 1,
294 hidefilter3: 1,
295 // Group 2 (nothing is selected, all false)
296 hidefilter4: 0,
297 hidefilter5: 0,
298 hidefilter6: 0,
299 group3: 'all'
300 },
301 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
302 );
303
304 // Select 2 filters
305 model.toggleFiltersSelected( {
306 hidefilter1: true,
307 hidefilter2: true,
308 hidefilter3: false,
309 hidefilter4: false,
310 hidefilter5: false,
311 hidefilter6: false
312 } );
313 // Two selected filters in one group
314 assert.deepEqual(
315 model.getParametersFromFilters(),
316 {
317 // Group 1 (two selected, the others are true)
318 hidefilter1: 0,
319 hidefilter2: 0,
320 hidefilter3: 1,
321 // Group 2 (nothing is selected, all false)
322 hidefilter4: 0,
323 hidefilter5: 0,
324 hidefilter6: 0,
325 group3: 'all'
326 },
327 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
328 );
329
330 // Select 3 filters
331 model.toggleFiltersSelected( {
332 hidefilter1: true,
333 hidefilter2: true,
334 hidefilter3: true,
335 hidefilter4: false,
336 hidefilter5: false,
337 hidefilter6: false
338 } );
339 // All filters of the group are selected == this is the same as not selecting any
340 assert.deepEqual(
341 model.getParametersFromFilters(),
342 {
343 // Group 1 (all selected, all false)
344 hidefilter1: 0,
345 hidefilter2: 0,
346 hidefilter3: 0,
347 // Group 2 (nothing is selected, all false)
348 hidefilter4: 0,
349 hidefilter5: 0,
350 hidefilter6: 0,
351 group3: 'all'
352 },
353 'All filters selected in one "send_unselected_if_any" group returns all parameters falsy.'
354 );
355
356 // Select 1 filter from string_options
357 model.toggleFiltersSelected( {
358 filter7: true,
359 filter8: false,
360 filter9: false
361 } );
362 // All filters of the group are selected == this is the same as not selecting any
363 assert.deepEqual(
364 model.getParametersFromFilters(),
365 {
366 // Group 1 (all selected, all)
367 hidefilter1: 0,
368 hidefilter2: 0,
369 hidefilter3: 0,
370 // Group 2 (nothing is selected, all false)
371 hidefilter4: 0,
372 hidefilter5: 0,
373 hidefilter6: 0,
374 group3: 'filter7'
375 },
376 'One filter selected in "string_option" group returns that filter in the value.'
377 );
378
379 // Select 2 filters from string_options
380 model.toggleFiltersSelected( {
381 filter7: true,
382 filter8: true,
383 filter9: false
384 } );
385 // All filters of the group are selected == this is the same as not selecting any
386 assert.deepEqual(
387 model.getParametersFromFilters(),
388 {
389 // Group 1 (all selected, all)
390 hidefilter1: 0,
391 hidefilter2: 0,
392 hidefilter3: 0,
393 // Group 2 (nothing is selected, all false)
394 hidefilter4: 0,
395 hidefilter5: 0,
396 hidefilter6: 0,
397 group3: 'filter7,filter8'
398 },
399 'Two filters selected in "string_option" group returns those filters in the value.'
400 );
401
402 // Select 3 filters from string_options
403 model.toggleFiltersSelected( {
404 filter7: true,
405 filter8: true,
406 filter9: true
407 } );
408 // All filters of the group are selected == this is the same as not selecting any
409 assert.deepEqual(
410 model.getParametersFromFilters(),
411 {
412 // Group 1 (all selected, all)
413 hidefilter1: 0,
414 hidefilter2: 0,
415 hidefilter3: 0,
416 // Group 2 (nothing is selected, all false)
417 hidefilter4: 0,
418 hidefilter5: 0,
419 hidefilter6: 0,
420 group3: 'all'
421 },
422 'All filters selected in "string_option" group returns \'all\'.'
423 );
424
425 } );
426
427 QUnit.test( 'getFiltersFromParameters', function ( assert ) {
428 var definition = {
429 group1: {
430 title: 'Group 1',
431 type: 'send_unselected_if_any',
432 filters: [
433 {
434 name: 'hidefilter1',
435 label: 'Show filter 1',
436 description: 'Description of Filter 1 in Group 1',
437 default: true
438 },
439 {
440 name: 'hidefilter2',
441 label: 'Show filter 2',
442 description: 'Description of Filter 2 in Group 1'
443 },
444 {
445 name: 'hidefilter3',
446 label: 'Show filter 3',
447 description: 'Description of Filter 3 in Group 1',
448 default: true
449 }
450 ]
451 },
452 group2: {
453 title: 'Group 2',
454 type: 'send_unselected_if_any',
455 filters: [
456 {
457 name: 'hidefilter4',
458 label: 'Show filter 4',
459 description: 'Description of Filter 1 in Group 2'
460 },
461 {
462 name: 'hidefilter5',
463 label: 'Show filter 5',
464 description: 'Description of Filter 2 in Group 2',
465 default: true
466 },
467 {
468 name: 'hidefilter6',
469 label: 'Show filter 6',
470 description: 'Description of Filter 3 in Group 2'
471 }
472 ]
473 },
474 group3: {
475 title: 'Group 3',
476 type: 'string_options',
477 separator: ',',
478 filters: [
479 {
480 name: 'filter7',
481 label: 'Group 3: Filter 1',
482 description: 'Description of Filter 1 in Group 3'
483 },
484 {
485 name: 'filter8',
486 label: 'Group 3: Filter 2',
487 description: 'Description of Filter 2 in Group 3',
488 default: true
489 },
490 {
491 name: 'filter9',
492 label: 'Group 3: Filter 3',
493 description: 'Description of Filter 3 in Group 3'
494 }
495 ]
496 }
497 },
498 defaultFilterRepresentation = {
499 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
500 hidefilter1: false,
501 hidefilter2: true,
502 hidefilter3: false,
503 hidefilter4: true,
504 hidefilter5: false,
505 hidefilter6: true,
506 // Group 3, "string_options", default values correspond to parameters and filters
507 filter7: false,
508 filter8: true,
509 filter9: false
510 },
511 model = new mw.rcfilters.dm.FiltersViewModel();
512
513 model.initializeFilters( definition );
514
515 // Empty query = only default values
516 assert.deepEqual(
517 model.getFiltersFromParameters( {} ),
518 defaultFilterRepresentation,
519 'Empty parameter query results in filters in initial default state'
520 );
521
522 assert.deepEqual(
523 model.getFiltersFromParameters( {
524 hidefilter2: '1'
525 } ),
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"
530 } ),
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)'
532 );
533
534 assert.deepEqual(
535 model.getFiltersFromParameters( {
536 hidefilter1: '1',
537 hidefilter2: '1',
538 hidefilter3: '1'
539 } ),
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"
544 } ),
545 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
546 );
547
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 toggleFiltersSelected() 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.
557 model.toggleFiltersSelected(
558 model.getFiltersFromParameters( {
559 hidefilter1: '1'
560 } )
561 );
562
563 model.toggleFiltersSelected(
564 model.getFiltersFromParameters( {
565 hidefilter6: '1'
566 } )
567 );
568
569 // The result here is ignoring the first toggleFiltersSelected call
570 // We should receive default values + hidefilter6 as false
571 assert.deepEqual(
572 model.getSelectedState(),
573 $.extend( {}, defaultFilterRepresentation, {
574 hidefilter5: false,
575 hidefilter6: false
576 } ),
577 'getFiltersFromParameters does not care about previous or existing state.'
578 );
579
580 // Reset
581 model = new mw.rcfilters.dm.FiltersViewModel();
582 model.initializeFilters( definition );
583
584 model.toggleFiltersSelected(
585 model.getFiltersFromParameters( {
586 hidefilter1: '0'
587 } )
588 );
589 model.toggleFiltersSelected(
590 model.getFiltersFromParameters( {
591 hidefilter1: '1'
592 } )
593 );
594
595 // Simulates minor edits being hidden in preferences, then unhidden via URL
596 // override.
597 assert.deepEqual(
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'
601 );
602
603 model.toggleFiltersSelected(
604 model.getFiltersFromParameters( {
605 group3: 'filter7'
606 } )
607 );
608 assert.deepEqual(
609 model.getSelectedState(),
610 $.extend( {}, defaultFilterRepresentation, {
611 filter7: true,
612 filter8: false,
613 filter9: false
614 } ),
615 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
616 );
617
618 model.toggleFiltersSelected(
619 model.getFiltersFromParameters( {
620 group3: 'filter7,filter8'
621 } )
622 );
623 assert.deepEqual(
624 model.getSelectedState(),
625 $.extend( {}, defaultFilterRepresentation, {
626 filter7: true,
627 filter8: true,
628 filter9: false
629 } ),
630 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
631 );
632
633 model.toggleFiltersSelected(
634 model.getFiltersFromParameters( {
635 group3: 'filter7,filter8,filter9'
636 } )
637 );
638 assert.deepEqual(
639 model.getSelectedState(),
640 $.extend( {}, defaultFilterRepresentation, {
641 filter7: false,
642 filter8: false,
643 filter9: false
644 } ),
645 'A \'string_options\' parameter containing all values, results in all filters of the group as unchecked.'
646 );
647
648 model.toggleFiltersSelected(
649 model.getFiltersFromParameters( {
650 group3: 'filter7,all,filter9'
651 } )
652 );
653 assert.deepEqual(
654 model.getSelectedState(),
655 $.extend( {}, defaultFilterRepresentation, {
656 filter7: false,
657 filter8: false,
658 filter9: false
659 } ),
660 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as unchecked.'
661 );
662
663 model.toggleFiltersSelected(
664 model.getFiltersFromParameters( {
665 group3: 'filter7,foo,filter9'
666 } )
667 );
668 assert.deepEqual(
669 model.getSelectedState(),
670 $.extend( {}, defaultFilterRepresentation, {
671 filter7: true,
672 filter8: false,
673 filter9: true
674 } ),
675 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
676 );
677 } );
678
679 QUnit.test( 'sanitizeStringOptionGroup', function ( assert ) {
680 var definition = {
681 group1: {
682 title: 'Group 1',
683 type: 'string_options',
684 filters: [
685 {
686 name: 'filter1',
687 label: 'Show filter 1',
688 description: 'Description of Filter 1 in Group 1'
689 },
690 {
691 name: 'filter2',
692 label: 'Show filter 2',
693 description: 'Description of Filter 2 in Group 1'
694 },
695 {
696 name: 'filter3',
697 label: 'Show filter 3',
698 description: 'Description of Filter 3 in Group 1'
699 }
700 ]
701 }
702 },
703 model = new mw.rcfilters.dm.FiltersViewModel();
704
705 model.initializeFilters( definition );
706
707 assert.deepEqual(
708 model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
709 [ 'filter1', 'filter2' ],
710 'Remove duplicate values'
711 );
712
713 assert.deepEqual(
714 model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
715 [ 'filter1', 'filter2' ],
716 'Remove invalid values'
717 );
718
719 assert.deepEqual(
720 model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
721 [ 'all' ],
722 'If any value is "all", the only value is "all".'
723 );
724 } );
725
726 QUnit.test( 'setFiltersToDefaults', function ( assert ) {
727 var definition = {
728 group1: {
729 title: 'Group 1',
730 type: 'send_unselected_if_any',
731 filters: [
732 {
733 name: 'hidefilter1',
734 label: 'Show filter 1',
735 description: 'Description of Filter 1 in Group 1',
736 default: true
737 },
738 {
739 name: 'hidefilter2',
740 label: 'Show filter 2',
741 description: 'Description of Filter 2 in Group 1'
742 },
743 {
744 name: 'hidefilter3',
745 label: 'Show filter 3',
746 description: 'Description of Filter 3 in Group 1',
747 default: true
748 }
749 ]
750 },
751 group2: {
752 title: 'Group 2',
753 type: 'send_unselected_if_any',
754 filters: [
755 {
756 name: 'hidefilter4',
757 label: 'Show filter 4',
758 description: 'Description of Filter 1 in Group 2'
759 },
760 {
761 name: 'hidefilter5',
762 label: 'Show filter 5',
763 description: 'Description of Filter 2 in Group 2',
764 default: true
765 },
766 {
767 name: 'hidefilter6',
768 label: 'Show filter 6',
769 description: 'Description of Filter 3 in Group 2'
770 }
771 ]
772 }
773 },
774 defaultFilterRepresentation = {
775 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
776 hidefilter1: false,
777 hidefilter2: true,
778 hidefilter3: false,
779 hidefilter4: true,
780 hidefilter5: false,
781 hidefilter6: true
782 },
783 model = new mw.rcfilters.dm.FiltersViewModel();
784
785 model.initializeFilters( definition );
786
787 assert.deepEqual(
788 model.getSelectedState(),
789 {
790 hidefilter1: false,
791 hidefilter2: false,
792 hidefilter3: false,
793 hidefilter4: false,
794 hidefilter5: false,
795 hidefilter6: false
796 },
797 'Initial state: default filters are not selected (controller selects defaults explicitly).'
798 );
799
800 model.toggleFiltersSelected( {
801 hidefilter1: false,
802 hidefilter3: false
803 } );
804
805 model.setFiltersToDefaults();
806
807 assert.deepEqual(
808 model.getSelectedState(),
809 defaultFilterRepresentation,
810 'Changing values of filters and then returning to defaults still results in default filters being selected.'
811 );
812 } );
813
814 QUnit.test( 'Filter interaction: subsets', function ( assert ) {
815 var definition = {
816 group1: {
817 title: 'Group 1',
818 type: 'string_options',
819 filters: [
820 {
821 name: 'filter1',
822 label: 'Show filter 1',
823 description: 'Description of Filter 1 in Group 1',
824 subset: [ 'filter2', 'filter5' ]
825 },
826 {
827 name: 'filter2',
828 label: 'Show filter 2',
829 description: 'Description of Filter 2 in Group 1'
830 },
831 {
832 name: 'filter3',
833 label: 'Show filter 3',
834 description: 'Description of Filter 3 in Group 1'
835 }
836 ]
837 },
838 group2: {
839 title: 'Group 2',
840 type: 'send_unselected_if_any',
841 filters: [
842 {
843 name: 'filter4',
844 label: 'Show filter 4',
845 description: 'Description of Filter 1 in Group 2',
846 subset: [ 'filter3', 'filter5' ]
847 },
848 {
849 name: 'filter5',
850 label: 'Show filter 5',
851 description: 'Description of Filter 2 in Group 2'
852 },
853 {
854 name: 'filter6',
855 label: 'Show filter 6',
856 description: 'Description of Filter 3 in Group 2'
857 }
858 ]
859 }
860 },
861 baseFullState = {
862 filter1: { selected: false, conflicted: false, included: false },
863 filter2: { selected: false, conflicted: false, included: false },
864 filter3: { selected: false, conflicted: false, included: false },
865 filter4: { selected: false, conflicted: false, included: false },
866 filter5: { selected: false, conflicted: false, included: false },
867 filter6: { selected: false, conflicted: false, included: false }
868 },
869 model = new mw.rcfilters.dm.FiltersViewModel();
870
871 model.initializeFilters( definition );
872 // Select a filter that has subset with another filter
873 model.toggleFiltersSelected( {
874 filter1: true
875 } );
876
877 model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
878 assert.deepEqual(
879 model.getFullState(),
880 $.extend( true, {}, baseFullState, {
881 filter1: { selected: true },
882 filter2: { included: true },
883 filter5: { included: true }
884 } ),
885 'Filters with subsets are represented in the model.'
886 );
887
888 // Select another filter that has a subset with the same previous filter
889 model.toggleFiltersSelected( {
890 filter4: true
891 } );
892 model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
893 assert.deepEqual(
894 model.getFullState(),
895 $.extend( true, {}, baseFullState, {
896 filter1: { selected: true },
897 filter2: { included: true },
898 filter3: { included: true },
899 filter4: { selected: true },
900 filter5: { included: true }
901 } ),
902 'Filters that have multiple subsets are represented.'
903 );
904
905 // Remove one filter (but leave the other) that affects filter2
906 model.toggleFiltersSelected( {
907 filter1: false
908 } );
909 model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
910 assert.deepEqual(
911 model.getFullState(),
912 $.extend( true, {}, baseFullState, {
913 filter2: { included: false },
914 filter3: { included: true },
915 filter4: { selected: true },
916 filter5: { included: true }
917 } ),
918 'Removing a filter only un-includes its subset if there is no other filter affecting.'
919 );
920
921 model.toggleFiltersSelected( {
922 filter4: false
923 } );
924 model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
925 assert.deepEqual(
926 model.getFullState(),
927 baseFullState,
928 'Removing all supersets also un-includes the subsets.'
929 );
930 } );
931
932 QUnit.test( 'Filter interaction: full coverage', function ( assert ) {
933 var definition = {
934 group1: {
935 title: 'Group 1',
936 type: 'string_options',
937 fullCoverage: false,
938 filters: [
939 { name: 'filter1' },
940 { name: 'filter2' },
941 { name: 'filter3' }
942 ]
943 },
944 group2: {
945 title: 'Group 2',
946 type: 'send_unselected_if_any',
947 fullCoverage: true,
948 filters: [
949 { name: 'filter4' },
950 { name: 'filter5' },
951 { name: 'filter6' }
952 ]
953 }
954 },
955 model = new mw.rcfilters.dm.FiltersViewModel(),
956 isCapsuleItemMuted = function ( filterName ) {
957 var itemModel = model.getItemByName( filterName ),
958 groupModel = itemModel.getGroupModel();
959
960 // This is the logic inside the capsule widget
961 return (
962 // The capsule item widget only appears if the item is selected
963 itemModel.isSelected() &&
964 // Muted state is only valid if group is full coverage and all items are selected
965 groupModel.isFullCoverage() && groupModel.areAllSelected()
966 );
967 },
968 getCurrentItemsMutedState = function () {
969 return {
970 filter1: isCapsuleItemMuted( 'filter1' ),
971 filter2: isCapsuleItemMuted( 'filter2' ),
972 filter3: isCapsuleItemMuted( 'filter3' ),
973 filter4: isCapsuleItemMuted( 'filter4' ),
974 filter5: isCapsuleItemMuted( 'filter5' ),
975 filter6: isCapsuleItemMuted( 'filter6' )
976 };
977 },
978 baseMuteState = {
979 filter1: false,
980 filter2: false,
981 filter3: false,
982 filter4: false,
983 filter5: false,
984 filter6: false
985 };
986
987 model.initializeFilters( definition );
988
989 // Starting state, no selection, all items are non-muted
990 assert.deepEqual(
991 getCurrentItemsMutedState(),
992 baseMuteState,
993 'No selection - all items are non-muted'
994 );
995
996 // Select most (but not all) items in each group
997 model.toggleFiltersSelected( {
998 filter1: true,
999 filter2: true,
1000 filter4: true,
1001 filter5: true
1002 } );
1003
1004 // Both groups have multiple (but not all) items selected, all items are non-muted
1005 assert.deepEqual(
1006 getCurrentItemsMutedState(),
1007 baseMuteState,
1008 'Not all items in the group selected - all items are non-muted'
1009 );
1010
1011 // Select all items in 'fullCoverage' group (group2)
1012 model.toggleFiltersSelected( {
1013 filter6: true
1014 } );
1015
1016 // Group2 (full coverage) has all items selected, all its items are muted
1017 assert.deepEqual(
1018 getCurrentItemsMutedState(),
1019 $.extend( {}, baseMuteState, {
1020 filter4: true,
1021 filter5: true,
1022 filter6: true
1023 } ),
1024 'All items in \'full coverage\' group are selected - all items in the group are muted'
1025 );
1026
1027 // Select all items in non 'fullCoverage' group (group1)
1028 model.toggleFiltersSelected( {
1029 filter3: true
1030 } );
1031
1032 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1033 assert.deepEqual(
1034 getCurrentItemsMutedState(),
1035 $.extend( {}, baseMuteState, {
1036 filter4: true,
1037 filter5: true,
1038 filter6: true
1039 } ),
1040 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1041 );
1042
1043 // Uncheck an item from each group
1044 model.toggleFiltersSelected( {
1045 filter3: false,
1046 filter5: false
1047 } );
1048 assert.deepEqual(
1049 getCurrentItemsMutedState(),
1050 baseMuteState,
1051 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1052 );
1053 } );
1054
1055 QUnit.test( 'Filter interaction: conflicts', function ( assert ) {
1056 var definition = {
1057 group1: {
1058 title: 'Group 1',
1059 type: 'string_options',
1060 filters: [
1061 {
1062 name: 'filter1',
1063 conflicts: [ 'filter2', 'filter4' ]
1064 },
1065 {
1066 name: 'filter2',
1067 conflicts: [ 'filter6' ]
1068 },
1069 {
1070 name: 'filter3'
1071 }
1072 ]
1073 },
1074 group2: {
1075 title: 'Group 2',
1076 type: 'send_unselected_if_any',
1077 filters: [
1078 {
1079 name: 'filter4'
1080 },
1081 {
1082 name: 'filter5',
1083 conflicts: [ 'filter3' ]
1084 },
1085 {
1086 name: 'filter6'
1087 }
1088 ]
1089 }
1090 },
1091 baseFullState = {
1092 filter1: { selected: false, conflicted: false, included: false },
1093 filter2: { selected: false, conflicted: false, included: false },
1094 filter3: { selected: false, conflicted: false, included: false },
1095 filter4: { selected: false, conflicted: false, included: false },
1096 filter5: { selected: false, conflicted: false, included: false },
1097 filter6: { selected: false, conflicted: false, included: false }
1098 },
1099 model = new mw.rcfilters.dm.FiltersViewModel();
1100
1101 model.initializeFilters( definition );
1102
1103 assert.deepEqual(
1104 model.getFullState(),
1105 baseFullState,
1106 'Initial state: no conflicts because no selections.'
1107 );
1108
1109 // Select a filter that has a conflict with another
1110 model.toggleFiltersSelected( {
1111 filter1: true // conflicts: filter2, filter4
1112 } );
1113
1114 model.reassessFilterInteractions( model.getItemByName( 'filter1' ) );
1115
1116 assert.deepEqual(
1117 model.getFullState(),
1118 $.extend( true, {}, baseFullState, {
1119 filter1: { selected: true },
1120 filter2: { conflicted: true },
1121 filter4: { conflicted: true }
1122 } ),
1123 'Selecting a filter set its conflicts list as "conflicted".'
1124 );
1125
1126 // Select one of the conflicts (both filters are now conflicted and selected)
1127 model.toggleFiltersSelected( {
1128 filter4: true // conflicts: filter 1
1129 } );
1130 model.reassessFilterInteractions( model.getItemByName( 'filter4' ) );
1131
1132 assert.deepEqual(
1133 model.getFullState(),
1134 $.extend( true, {}, baseFullState, {
1135 filter1: { selected: true, conflicted: true },
1136 filter2: { conflicted: true },
1137 filter4: { selected: true, conflicted: true }
1138 } ),
1139 'Selecting a conflicting filter sets both sides to conflicted and selected.'
1140 );
1141
1142 // Select another filter from filter4 group, meaning:
1143 // now filter1 no longer conflicts with filter4
1144 model.toggleFiltersSelected( {
1145 filter6: true // conflicts: filter2
1146 } );
1147 model.reassessFilterInteractions( model.getItemByName( 'filter6' ) );
1148
1149 assert.deepEqual(
1150 model.getFullState(),
1151 $.extend( true, {}, baseFullState, {
1152 filter1: { selected: true, conflicted: false }, // No longer conflicts (filter4 is not the only in the group)
1153 filter2: { conflicted: true }, // While not selected, still in conflict with filter1, which is selected
1154 filter4: { selected: true, conflicted: false }, // No longer conflicts with filter1
1155 filter6: { selected: true, conflicted: false }
1156 } ),
1157 'Selecting a non-conflicting filter from a conflicting group removes the conflict'
1158 );
1159 } );
1160
1161 QUnit.test( 'Filter highlights', function ( assert ) {
1162 var definition = {
1163 group1: {
1164 title: 'Group 1',
1165 type: 'string_options',
1166 filters: [
1167 { name: 'filter1', class: 'class1' },
1168 { name: 'filter2', class: 'class2' },
1169 { name: 'filter3', class: 'class3' },
1170 { name: 'filter4', class: 'class4' },
1171 { name: 'filter5', class: 'class5' },
1172 { name: 'filter6' }
1173 ]
1174 }
1175 },
1176 model = new mw.rcfilters.dm.FiltersViewModel();
1177
1178 model.initializeFilters( definition );
1179
1180 assert.ok(
1181 !model.isHighlightEnabled(),
1182 'Initially, highlight is disabled.'
1183 );
1184
1185 model.toggleHighlight( true );
1186 assert.ok(
1187 model.isHighlightEnabled(),
1188 'Highlight is enabled on toggle.'
1189 );
1190
1191 model.setHighlightColor( 'filter1', 'color1' );
1192 model.setHighlightColor( 'filter2', 'color2' );
1193
1194 assert.deepEqual(
1195 model.getHighlightedItems().map( function ( item ) {
1196 return item.getName();
1197 } ),
1198 [
1199 'filter1',
1200 'filter2'
1201 ],
1202 'Highlighted items are highlighted.'
1203 );
1204
1205 assert.equal(
1206 model.getItemByName( 'filter1' ).getHighlightColor(),
1207 'color1',
1208 'Item highlight color is set.'
1209 );
1210
1211 model.setHighlightColor( 'filter1', 'color1changed' );
1212 assert.equal(
1213 model.getItemByName( 'filter1' ).getHighlightColor(),
1214 'color1changed',
1215 'Item highlight color is changed on setHighlightColor.'
1216 );
1217
1218 model.clearHighlightColor( 'filter1' );
1219 assert.deepEqual(
1220 model.getHighlightedItems().map( function ( item ) {
1221 return item.getName();
1222 } ),
1223 [
1224 'filter2'
1225 ],
1226 'Clear highlight from an item results in the item no longer being highlighted.'
1227 );
1228
1229 // Reset
1230 model = new mw.rcfilters.dm.FiltersViewModel();
1231 model.initializeFilters( definition );
1232
1233 model.setHighlightColor( 'filter1', 'color1' );
1234 model.setHighlightColor( 'filter2', 'color2' );
1235 model.setHighlightColor( 'filter3', 'color3' );
1236
1237 assert.deepEqual(
1238 model.getHighlightedItems().map( function ( item ) {
1239 return item.getName();
1240 } ),
1241 [
1242 'filter1',
1243 'filter2',
1244 'filter3'
1245 ],
1246 'Even if highlights are not enabled, the items remember their highlight state'
1247 // NOTE: When actually displaying the highlights, the UI checks whether
1248 // highlighting is generally active and then goes over the highlighted
1249 // items. The item models, however, and the view model in general, still
1250 // retains the knowledge about which filters have different colors, so we
1251 // can seamlessly return to the colors the user previously chose if they
1252 // reapply highlights.
1253 );
1254
1255 // Reset
1256 model = new mw.rcfilters.dm.FiltersViewModel();
1257 model.initializeFilters( definition );
1258
1259 model.setHighlightColor( 'filter1', 'color1' );
1260 model.setHighlightColor( 'filter6', 'color6' );
1261
1262 assert.deepEqual(
1263 model.getHighlightedItems().map( function ( item ) {
1264 return item.getName();
1265 } ),
1266 [
1267 'filter1'
1268 ],
1269 'Items without a specified class identifier are not highlighted.'
1270 );
1271 } );
1272 }( mediaWiki, jQuery ) );