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