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