Merge "Update mediawiki/mediawiki-codesniffer to 0.8.0"
[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 'Two 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( 'getParametersFromFilters (custom object)', function ( assert ) {
437 var originalState,
438 model = new mw.rcfilters.dm.FiltersViewModel(),
439 definition = [ {
440 name: 'group1',
441 title: 'Group 1',
442 type: 'send_unselected_if_any',
443 filters: [
444 { name: 'hidefilter1', label: 'Hide filter 1', description: '' },
445 { name: 'hidefilter2', label: 'Hide filter 2', description: '' },
446 { name: 'hidefilter3', label: 'Hide filter 3', description: '' }
447 ]
448 }, {
449 name: 'group2',
450 title: 'Group 2',
451 type: 'send_unselected_if_any',
452 filters: [
453 { name: 'hidefilter4', label: 'Hide filter 4', description: '' },
454 { name: 'hidefilter5', label: 'Hide filter 5', description: '' },
455 { name: 'hidefilter6', label: 'Hide filter 6', description: '' }
456 ]
457 }, {
458 name: 'group3',
459 title: 'Group 3',
460 type: 'string_options',
461 separator: ',',
462 filters: [
463 { name: 'filter7', label: 'Hide filter 7', description: '' },
464 { name: 'filter8', label: 'Hide filter 8', description: '' },
465 { name: 'filter9', label: 'Hide filter 9', description: '' }
466 ]
467 } ],
468 cases = [
469 {
470 // This is mocking the cases above, both
471 // - 'Two filters in one "send_unselected_if_any" group returns the other parameters truthy.'
472 // - 'Two filters selected in "string_option" group returns those filters in the value.'
473 input: {
474 group1__hidefilter1: true,
475 group1__hidefilter2: true,
476 group1__hidefilter3: false,
477 group2__hidefilter4: false,
478 group2__hidefilter5: false,
479 group2__hidefilter6: false,
480 group3__filter7: true,
481 group3__filter8: true,
482 group3__filter9: false
483 },
484 expected: {
485 // Group 1 (two selected, the others are true)
486 hidefilter1: 0,
487 hidefilter2: 0,
488 hidefilter3: 1,
489 // Group 2 (nothing is selected, all false)
490 hidefilter4: 0,
491 hidefilter5: 0,
492 hidefilter6: 0,
493 group3: 'filter7,filter8'
494 },
495 msg: 'Given an explicit (complete) filter state object, the result is the same as if the object given represented the model state.'
496 },
497 {
498 // This is mocking case above
499 // - 'One filters in one "send_unselected_if_any" group returns the other parameters truthy.'
500 input: {
501 group1__hidefilter1: 1
502 },
503 expected: {
504 // Group 1 (one selected, the others are true)
505 hidefilter1: 0,
506 hidefilter2: 1,
507 hidefilter3: 1,
508 // Group 2 (nothing is selected, all false)
509 hidefilter4: 0,
510 hidefilter5: 0,
511 hidefilter6: 0,
512 group3: ''
513 },
514 msg: 'Given an explicit (incomplete) filter state object, the result is the same as if the object give represented the model state.'
515 }
516 ];
517
518 model.initializeFilters( definition );
519 // Store original state
520 originalState = model.getSelectedState();
521
522 // Test each case
523 cases.forEach( function ( test ) {
524 assert.deepEqual(
525 model.getParametersFromFilters( test.input ),
526 test.expected,
527 test.msg
528 );
529 } );
530
531 // After doing the above tests, make sure the actual state
532 // of the filter stayed the same
533 assert.deepEqual(
534 model.getSelectedState(),
535 originalState,
536 'Running the method with external definition to parse does not actually change the state of the model'
537 );
538 } );
539
540 QUnit.test( 'getFiltersFromParameters', function ( assert ) {
541 var definition = [ {
542 name: 'group1',
543 title: 'Group 1',
544 type: 'send_unselected_if_any',
545 filters: [
546 {
547 name: 'hidefilter1',
548 label: 'Show filter 1',
549 description: 'Description of Filter 1 in Group 1',
550 default: true
551 },
552 {
553 name: 'hidefilter2',
554 label: 'Show filter 2',
555 description: 'Description of Filter 2 in Group 1'
556 },
557 {
558 name: 'hidefilter3',
559 label: 'Show filter 3',
560 description: 'Description of Filter 3 in Group 1',
561 default: true
562 }
563 ]
564 }, {
565 name: 'group2',
566 title: 'Group 2',
567 type: 'send_unselected_if_any',
568 filters: [
569 {
570 name: 'hidefilter4',
571 label: 'Show filter 4',
572 description: 'Description of Filter 1 in Group 2'
573 },
574 {
575 name: 'hidefilter5',
576 label: 'Show filter 5',
577 description: 'Description of Filter 2 in Group 2',
578 default: true
579 },
580 {
581 name: 'hidefilter6',
582 label: 'Show filter 6',
583 description: 'Description of Filter 3 in Group 2'
584 }
585 ]
586 }, {
587
588 name: 'group3',
589 title: 'Group 3',
590 type: 'string_options',
591 separator: ',',
592 default: 'filter8',
593 filters: [
594 {
595 name: 'filter7',
596 label: 'Group 3: Filter 1',
597 description: 'Description of Filter 1 in Group 3'
598 },
599 {
600 name: 'filter8',
601 label: 'Group 3: Filter 2',
602 description: 'Description of Filter 2 in Group 3'
603 },
604 {
605 name: 'filter9',
606 label: 'Group 3: Filter 3',
607 description: 'Description of Filter 3 in Group 3'
608 }
609 ]
610 } ],
611 defaultFilterRepresentation = {
612 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
613 group1__hidefilter1: false,
614 group1__hidefilter2: true,
615 group1__hidefilter3: false,
616 group2__hidefilter4: true,
617 group2__hidefilter5: false,
618 group2__hidefilter6: true,
619 // Group 3, "string_options", default values correspond to parameters and filters
620 group3__filter7: false,
621 group3__filter8: true,
622 group3__filter9: false
623 },
624 model = new mw.rcfilters.dm.FiltersViewModel();
625
626 model.initializeFilters( definition );
627
628 // Empty query = only default values
629 assert.deepEqual(
630 model.getFiltersFromParameters( {} ),
631 defaultFilterRepresentation,
632 'Empty parameter query results in filters in initial default state'
633 );
634
635 assert.deepEqual(
636 model.getFiltersFromParameters( {
637 hidefilter2: '1'
638 } ),
639 $.extend( {}, defaultFilterRepresentation, {
640 group1__hidefilter1: false, // The text is "show filter 1"
641 group1__hidefilter2: false, // The text is "show filter 2"
642 group1__hidefilter3: false // The text is "show filter 3"
643 } ),
644 'One truthy parameter in a group whose other parameters are true by default makes the rest of the filters in the group false (unchecked)'
645 );
646
647 assert.deepEqual(
648 model.getFiltersFromParameters( {
649 hidefilter1: '1',
650 hidefilter2: '1',
651 hidefilter3: '1'
652 } ),
653 $.extend( {}, defaultFilterRepresentation, {
654 group1__hidefilter1: false, // The text is "show filter 1"
655 group1__hidefilter2: false, // The text is "show filter 2"
656 group1__hidefilter3: false // The text is "show filter 3"
657 } ),
658 'All paremeters in the same \'send_unselected_if_any\' group false is equivalent to none are truthy (checked) in the interface'
659 );
660
661 // The ones above don't update the model, so we have a clean state.
662 // getFiltersFromParameters is stateless; any change is unaffected by the current state
663 // This test is demonstrating wrong usage of the method;
664 // We should be aware that getFiltersFromParameters is stateless,
665 // so each call gives us a filter state that only reflects the query given.
666 // This means that the two calls to toggleFiltersSelected() below collide.
667 // The result of the first is overridden by the result of the second,
668 // since both get a full state object from getFiltersFromParameters that **only** relates
669 // to the input it receives.
670 model.toggleFiltersSelected(
671 model.getFiltersFromParameters( {
672 hidefilter1: '1'
673 } )
674 );
675
676 model.toggleFiltersSelected(
677 model.getFiltersFromParameters( {
678 hidefilter6: '1'
679 } )
680 );
681
682 // The result here is ignoring the first toggleFiltersSelected call
683 // We should receive default values + hidefilter6 as false
684 assert.deepEqual(
685 model.getSelectedState(),
686 $.extend( {}, defaultFilterRepresentation, {
687 group2__hidefilter5: false,
688 group2__hidefilter6: false
689 } ),
690 'getFiltersFromParameters does not care about previous or existing state.'
691 );
692
693 // Reset
694 model = new mw.rcfilters.dm.FiltersViewModel();
695 model.initializeFilters( definition );
696
697 model.toggleFiltersSelected(
698 model.getFiltersFromParameters( {
699 hidefilter1: '0'
700 } )
701 );
702 model.toggleFiltersSelected(
703 model.getFiltersFromParameters( {
704 hidefilter1: '1'
705 } )
706 );
707
708 // Simulates minor edits being hidden in preferences, then unhidden via URL
709 // override.
710 assert.deepEqual(
711 model.getSelectedState(),
712 defaultFilterRepresentation,
713 'After checking and then unchecking a \'send_unselected_if_any\' filter (without touching other filters in that group), results are default'
714 );
715
716 model.toggleFiltersSelected(
717 model.getFiltersFromParameters( {
718 group3: 'filter7'
719 } )
720 );
721 assert.deepEqual(
722 model.getSelectedState(),
723 $.extend( {}, defaultFilterRepresentation, {
724 group3__filter7: true,
725 group3__filter8: false,
726 group3__filter9: false
727 } ),
728 'A \'string_options\' parameter containing 1 value, results in the corresponding filter as checked'
729 );
730
731 model.toggleFiltersSelected(
732 model.getFiltersFromParameters( {
733 group3: 'filter7,filter8'
734 } )
735 );
736 assert.deepEqual(
737 model.getSelectedState(),
738 $.extend( {}, defaultFilterRepresentation, {
739 group3__filter7: true,
740 group3__filter8: true,
741 group3__filter9: false
742 } ),
743 'A \'string_options\' parameter containing 2 values, results in both corresponding filters as checked'
744 );
745
746 model.toggleFiltersSelected(
747 model.getFiltersFromParameters( {
748 group3: 'filter7,filter8,filter9'
749 } )
750 );
751 assert.deepEqual(
752 model.getSelectedState(),
753 $.extend( {}, defaultFilterRepresentation, {
754 group3__filter7: true,
755 group3__filter8: true,
756 group3__filter9: true
757 } ),
758 'A \'string_options\' parameter containing all values, results in all filters of the group as checked.'
759 );
760
761 model.toggleFiltersSelected(
762 model.getFiltersFromParameters( {
763 group3: 'filter7,all,filter9'
764 } )
765 );
766 assert.deepEqual(
767 model.getSelectedState(),
768 $.extend( {}, defaultFilterRepresentation, {
769 group3__filter7: true,
770 group3__filter8: true,
771 group3__filter9: true
772 } ),
773 'A \'string_options\' parameter containing the value \'all\', results in all filters of the group as checked.'
774 );
775
776 model.toggleFiltersSelected(
777 model.getFiltersFromParameters( {
778 group3: 'filter7,foo,filter9'
779 } )
780 );
781 assert.deepEqual(
782 model.getSelectedState(),
783 $.extend( {}, defaultFilterRepresentation, {
784 group3__filter7: true,
785 group3__filter8: false,
786 group3__filter9: true
787 } ),
788 'A \'string_options\' parameter containing an invalid value, results in the invalid value ignored and the valid corresponding filters checked.'
789 );
790 } );
791
792 QUnit.test( 'sanitizeStringOptionGroup', function ( assert ) {
793 var definition = [ {
794 name: 'group1',
795 title: 'Group 1',
796 type: 'string_options',
797 filters: [
798 {
799 name: 'filter1',
800 label: 'Show filter 1',
801 description: 'Description of Filter 1 in Group 1'
802 },
803 {
804 name: 'filter2',
805 label: 'Show filter 2',
806 description: 'Description of Filter 2 in Group 1'
807 },
808 {
809 name: 'filter3',
810 label: 'Show filter 3',
811 description: 'Description of Filter 3 in Group 1'
812 }
813 ]
814 } ],
815 model = new mw.rcfilters.dm.FiltersViewModel();
816
817 model.initializeFilters( definition );
818
819 assert.deepEqual(
820 model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'filter1', 'filter2' ] ),
821 [ 'filter1', 'filter2' ],
822 'Remove duplicate values'
823 );
824
825 assert.deepEqual(
826 model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'foo', 'filter2' ] ),
827 [ 'filter1', 'filter2' ],
828 'Remove invalid values'
829 );
830
831 assert.deepEqual(
832 model.sanitizeStringOptionGroup( 'group1', [ 'filter1', 'all', 'filter2' ] ),
833 [ 'all' ],
834 'If any value is "all", the only value is "all".'
835 );
836 } );
837
838 QUnit.test( 'setFiltersToDefaults', function ( assert ) {
839 var definition = [ {
840 name: 'group1',
841 title: 'Group 1',
842 type: 'send_unselected_if_any',
843 filters: [
844 {
845 name: 'hidefilter1',
846 label: 'Show filter 1',
847 description: 'Description of Filter 1 in Group 1',
848 default: true
849 },
850 {
851 name: 'hidefilter2',
852 label: 'Show filter 2',
853 description: 'Description of Filter 2 in Group 1'
854 },
855 {
856 name: 'hidefilter3',
857 label: 'Show filter 3',
858 description: 'Description of Filter 3 in Group 1',
859 default: true
860 }
861 ]
862 }, {
863 name: 'group2',
864 title: 'Group 2',
865 type: 'send_unselected_if_any',
866 filters: [
867 {
868 name: 'hidefilter4',
869 label: 'Show filter 4',
870 description: 'Description of Filter 1 in Group 2'
871 },
872 {
873 name: 'hidefilter5',
874 label: 'Show filter 5',
875 description: 'Description of Filter 2 in Group 2',
876 default: true
877 },
878 {
879 name: 'hidefilter6',
880 label: 'Show filter 6',
881 description: 'Description of Filter 3 in Group 2'
882 }
883 ]
884 } ],
885 defaultFilterRepresentation = {
886 // Group 1 and 2, "send_unselected_if_any", the values of the filters are "flipped" from the values of the parameters
887 group1__hidefilter1: false,
888 group1__hidefilter2: true,
889 group1__hidefilter3: false,
890 group2__hidefilter4: true,
891 group2__hidefilter5: false,
892 group2__hidefilter6: true
893 },
894 model = new mw.rcfilters.dm.FiltersViewModel();
895
896 model.initializeFilters( definition );
897
898 assert.deepEqual(
899 model.getSelectedState(),
900 {
901 group1__hidefilter1: false,
902 group1__hidefilter2: false,
903 group1__hidefilter3: false,
904 group2__hidefilter4: false,
905 group2__hidefilter5: false,
906 group2__hidefilter6: false
907 },
908 'Initial state: default filters are not selected (controller selects defaults explicitly).'
909 );
910
911 model.toggleFiltersSelected( {
912 group1__hidefilter1: false,
913 group1__hidefilter3: false
914 } );
915
916 model.setFiltersToDefaults();
917
918 assert.deepEqual(
919 model.getSelectedState(),
920 defaultFilterRepresentation,
921 'Changing values of filters and then returning to defaults still results in default filters being selected.'
922 );
923 } );
924
925 QUnit.test( 'Filter interaction: subsets', function ( assert ) {
926 var definition = [ {
927 name: 'group1',
928 title: 'Group 1',
929 type: 'string_options',
930 filters: [
931 {
932 name: 'filter1',
933 label: 'Show filter 1',
934 description: 'Description of Filter 1 in Group 1',
935 subset: [
936 {
937 group: 'group1',
938 filter: 'filter2'
939 },
940 {
941 group: 'group1',
942 filter: 'filter3'
943 }
944 ]
945 },
946 {
947 name: 'filter2',
948 label: 'Show filter 2',
949 description: 'Description of Filter 2 in Group 1',
950 subset: [
951 {
952 group: 'group1',
953 filter: 'filter3'
954 }
955 ]
956 },
957 {
958 name: 'filter3',
959 label: 'Show filter 3',
960 description: 'Description of Filter 3 in Group 1'
961 }
962 ]
963 } ],
964 baseFullState = {
965 group1__filter1: { selected: false, conflicted: false, included: false },
966 group1__filter2: { selected: false, conflicted: false, included: false },
967 group1__filter3: { selected: false, conflicted: false, included: false }
968 },
969 model = new mw.rcfilters.dm.FiltersViewModel();
970
971 model.initializeFilters( definition );
972 // Select a filter that has subset with another filter
973 model.toggleFiltersSelected( {
974 group1__filter1: true
975 } );
976
977 model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
978 assert.deepEqual(
979 model.getFullState(),
980 $.extend( true, {}, baseFullState, {
981 group1__filter1: { selected: true },
982 group1__filter2: { included: true },
983 group1__filter3: { included: true }
984 } ),
985 'Filters with subsets are represented in the model.'
986 );
987
988 // Select another filter that has a subset with the same previous filter
989 model.toggleFiltersSelected( {
990 group1__filter2: true
991 } );
992 model.reassessFilterInteractions( model.getItemByName( 'filter2' ) );
993 assert.deepEqual(
994 model.getFullState(),
995 $.extend( true, {}, baseFullState, {
996 group1__filter1: { selected: true },
997 group1__filter2: { selected: true, included: true },
998 group1__filter3: { included: true }
999 } ),
1000 'Filters that have multiple subsets are represented.'
1001 );
1002
1003 // Remove one filter (but leave the other) that affects filter3
1004 model.toggleFiltersSelected( {
1005 group1__filter1: false
1006 } );
1007 model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
1008 assert.deepEqual(
1009 model.getFullState(),
1010 $.extend( true, {}, baseFullState, {
1011 group1__filter2: { selected: true, included: false },
1012 group1__filter3: { included: true }
1013 } ),
1014 'Removing a filter only un-includes its subset if there is no other filter affecting.'
1015 );
1016
1017 model.toggleFiltersSelected( {
1018 group1__filter2: false
1019 } );
1020 model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
1021 assert.deepEqual(
1022 model.getFullState(),
1023 baseFullState,
1024 'Removing all supersets also un-includes the subsets.'
1025 );
1026 } );
1027
1028 QUnit.test( 'Filter interaction: full coverage', function ( assert ) {
1029 var definition = [ {
1030 name: 'group1',
1031 title: 'Group 1',
1032 type: 'string_options',
1033 fullCoverage: false,
1034 filters: [
1035 { name: 'filter1', label: '1', description: '1' },
1036 { name: 'filter2', label: '2', description: '2' },
1037 { name: 'filter3', label: '3', description: '3' }
1038 ]
1039 }, {
1040 name: 'group2',
1041 title: 'Group 2',
1042 type: 'send_unselected_if_any',
1043 fullCoverage: true,
1044 filters: [
1045 { name: 'filter4', label: '4', description: '4' },
1046 { name: 'filter5', label: '5', description: '5' },
1047 { name: 'filter6', label: '6', description: '6' }
1048 ]
1049 } ],
1050 model = new mw.rcfilters.dm.FiltersViewModel(),
1051 isCapsuleItemMuted = function ( filterName ) {
1052 var itemModel = model.getItemByName( filterName ),
1053 groupModel = itemModel.getGroupModel();
1054
1055 // This is the logic inside the capsule widget
1056 return (
1057 // The capsule item widget only appears if the item is selected
1058 itemModel.isSelected() &&
1059 // Muted state is only valid if group is full coverage and all items are selected
1060 groupModel.isFullCoverage() && groupModel.areAllSelected()
1061 );
1062 },
1063 getCurrentItemsMutedState = function () {
1064 return {
1065 group1__filter1: isCapsuleItemMuted( 'group1__filter1' ),
1066 group1__filter2: isCapsuleItemMuted( 'group1__filter2' ),
1067 group1__filter3: isCapsuleItemMuted( 'group1__filter3' ),
1068 group2__filter4: isCapsuleItemMuted( 'group2__filter4' ),
1069 group2__filter5: isCapsuleItemMuted( 'group2__filter5' ),
1070 group2__filter6: isCapsuleItemMuted( 'group2__filter6' )
1071 };
1072 },
1073 baseMuteState = {
1074 group1__filter1: false,
1075 group1__filter2: false,
1076 group1__filter3: false,
1077 group2__filter4: false,
1078 group2__filter5: false,
1079 group2__filter6: false
1080 };
1081
1082 model.initializeFilters( definition );
1083
1084 // Starting state, no selection, all items are non-muted
1085 assert.deepEqual(
1086 getCurrentItemsMutedState(),
1087 baseMuteState,
1088 'No selection - all items are non-muted'
1089 );
1090
1091 // Select most (but not all) items in each group
1092 model.toggleFiltersSelected( {
1093 group1__filter1: true,
1094 group1__filter2: true,
1095 group2__filter4: true,
1096 group2__filter5: true
1097 } );
1098
1099 // Both groups have multiple (but not all) items selected, all items are non-muted
1100 assert.deepEqual(
1101 getCurrentItemsMutedState(),
1102 baseMuteState,
1103 'Not all items in the group selected - all items are non-muted'
1104 );
1105
1106 // Select all items in 'fullCoverage' group (group2)
1107 model.toggleFiltersSelected( {
1108 group2__filter6: true
1109 } );
1110
1111 // Group2 (full coverage) has all items selected, all its items are muted
1112 assert.deepEqual(
1113 getCurrentItemsMutedState(),
1114 $.extend( {}, baseMuteState, {
1115 group2__filter4: true,
1116 group2__filter5: true,
1117 group2__filter6: true
1118 } ),
1119 'All items in \'full coverage\' group are selected - all items in the group are muted'
1120 );
1121
1122 // Select all items in non 'fullCoverage' group (group1)
1123 model.toggleFiltersSelected( {
1124 group1__filter3: true
1125 } );
1126
1127 // Group1 (full coverage) has all items selected, no items in it are muted (non full coverage)
1128 assert.deepEqual(
1129 getCurrentItemsMutedState(),
1130 $.extend( {}, baseMuteState, {
1131 group2__filter4: true,
1132 group2__filter5: true,
1133 group2__filter6: true
1134 } ),
1135 'All items in a non \'full coverage\' group are selected - none of the items in the group are muted'
1136 );
1137
1138 // Uncheck an item from each group
1139 model.toggleFiltersSelected( {
1140 group1__filter3: false,
1141 group2__filter5: false
1142 } );
1143 assert.deepEqual(
1144 getCurrentItemsMutedState(),
1145 baseMuteState,
1146 'Not all items in the group are checked - all items are non-muted regardless of group coverage'
1147 );
1148 } );
1149
1150 QUnit.test( 'Filter interaction: conflicts', function ( assert ) {
1151 var definition = [ {
1152 name: 'group1',
1153 title: 'Group 1',
1154 type: 'string_options',
1155 filters: [
1156 {
1157 name: 'filter1',
1158 label: '1',
1159 description: '1',
1160 conflicts: [ { group: 'group2' } ]
1161 },
1162 {
1163 name: 'filter2',
1164 label: '2',
1165 description: '2',
1166 conflicts: [ { group: 'group2', filter: 'filter6' } ]
1167 },
1168 {
1169 name: 'filter3',
1170 label: '3',
1171 description: '3'
1172 }
1173 ]
1174 }, {
1175 name: 'group2',
1176 title: 'Group 2',
1177 type: 'send_unselected_if_any',
1178 conflicts: [ { group: 'group1', filter: 'filter1' } ],
1179 filters: [
1180 {
1181 name: 'filter4',
1182 label: '1',
1183 description: '1'
1184 },
1185 {
1186 name: 'filter5',
1187 label: '5',
1188 description: '5'
1189 },
1190 {
1191 name: 'filter6',
1192 label: '6',
1193 description: '6',
1194 conflicts: [ { group: 'group1', filter: 'filter2' } ]
1195 }
1196 ]
1197 } ],
1198 baseFullState = {
1199 group1__filter1: { selected: false, conflicted: false, included: false },
1200 group1__filter2: { selected: false, conflicted: false, included: false },
1201 group1__filter3: { selected: false, conflicted: false, included: false },
1202 group2__filter4: { selected: false, conflicted: false, included: false },
1203 group2__filter5: { selected: false, conflicted: false, included: false },
1204 group2__filter6: { selected: false, conflicted: false, included: false }
1205 },
1206 model = new mw.rcfilters.dm.FiltersViewModel();
1207
1208 model.initializeFilters( definition );
1209
1210 assert.deepEqual(
1211 model.getFullState(),
1212 baseFullState,
1213 'Initial state: no conflicts because no selections.'
1214 );
1215
1216 // Select a filter that has a conflict with an entire group
1217 model.toggleFiltersSelected( {
1218 group1__filter1: true // conflicts: entire of group 2 ( filter4, filter5, filter6)
1219 } );
1220
1221 model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
1222
1223 assert.deepEqual(
1224 model.getFullState(),
1225 $.extend( true, {}, baseFullState, {
1226 group1__filter1: { selected: true },
1227 group2__filter4: { conflicted: true },
1228 group2__filter5: { conflicted: true },
1229 group2__filter6: { conflicted: true }
1230 } ),
1231 'Selecting a filter that conflicts with a group sets all the conflicted group items as "conflicted".'
1232 );
1233
1234 // Select one of the conflicts (both filters are now conflicted and selected)
1235 model.toggleFiltersSelected( {
1236 group2__filter4: true // conflicts: filter 1
1237 } );
1238 model.reassessFilterInteractions( model.getItemByName( 'group2__filter4' ) );
1239
1240 assert.deepEqual(
1241 model.getFullState(),
1242 $.extend( true, {}, baseFullState, {
1243 group1__filter1: { selected: true, conflicted: true },
1244 group2__filter4: { selected: true, conflicted: true },
1245 group2__filter5: { conflicted: true },
1246 group2__filter6: { conflicted: true }
1247 } ),
1248 'Selecting a conflicting filter inside a group, sets both sides to conflicted and selected.'
1249 );
1250
1251 // Reset
1252 model = new mw.rcfilters.dm.FiltersViewModel();
1253 model.initializeFilters( definition );
1254
1255 // Select a filter that has a conflict with a specific filter
1256 model.toggleFiltersSelected( {
1257 group1__filter2: true // conflicts: filter6
1258 } );
1259 model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
1260
1261 assert.deepEqual(
1262 model.getFullState(),
1263 $.extend( true, {}, baseFullState, {
1264 group1__filter2: { selected: true },
1265 group2__filter6: { conflicted: true }
1266 } ),
1267 'Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1268 );
1269
1270 // Select the conflicting filter
1271 model.toggleFiltersSelected( {
1272 group2__filter6: true // conflicts: filter2
1273 } );
1274
1275 model.reassessFilterInteractions( model.getItemByName( 'group2__filter6' ) );
1276
1277 assert.deepEqual(
1278 model.getFullState(),
1279 $.extend( true, {}, baseFullState, {
1280 group1__filter2: { selected: true, conflicted: true },
1281 group2__filter6: { selected: true, conflicted: true },
1282 // This is added to the conflicts because filter6 is part of group2,
1283 // who is in conflict with filter1; note that filter2 also conflicts
1284 // with filter6 which means that filter1 conflicts with filter6 (because it's in group2)
1285 // and also because its **own sibling** (filter2) is **also** in conflict with the
1286 // selected items in group2 (filter6)
1287 group1__filter1: { conflicted: true }
1288 } ),
1289 'Selecting a conflicting filter with an individual filter, sets both sides to conflicted and selected.'
1290 );
1291
1292 // Now choose a non-conflicting filter from the group
1293 model.toggleFiltersSelected( {
1294 group2__filter5: true
1295 } );
1296
1297 model.reassessFilterInteractions( model.getItemByName( 'group2__filter5' ) );
1298
1299 assert.deepEqual(
1300 model.getFullState(),
1301 $.extend( true, {}, baseFullState, {
1302 group1__filter2: { selected: true },
1303 group2__filter6: { selected: true },
1304 group2__filter5: { selected: true }
1305 // Filter6 and filter1 are no longer in conflict because
1306 // filter5, while it is in conflict with filter1, it is
1307 // not in conflict with filter2 - and since filter2 is
1308 // selected, it removes the conflict bidirectionally
1309 } ),
1310 'Selecting a non-conflicting filter within the group of a conflicting filter removes the conflicts.'
1311 );
1312
1313 // Followup on the previous test, unselect filter2 so filter1
1314 // is now the only one selected in its own group, and since
1315 // it is in conflict with the entire of group2, it means
1316 // filter1 is once again conflicted
1317 model.toggleFiltersSelected( {
1318 group1__filter2: false
1319 } );
1320
1321 model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
1322
1323 assert.deepEqual(
1324 model.getFullState(),
1325 $.extend( true, {}, baseFullState, {
1326 group1__filter1: { conflicted: true },
1327 group2__filter6: { selected: true },
1328 group2__filter5: { selected: true }
1329 } ),
1330 'Unselecting an item that did not conflict returns the conflict state.'
1331 );
1332
1333 // Followup #2: Now actually select filter1, and make everything conflicted
1334 model.toggleFiltersSelected( {
1335 group1__filter1: true
1336 } );
1337
1338 model.reassessFilterInteractions( model.getItemByName( 'group1__filter1' ) );
1339
1340 assert.deepEqual(
1341 model.getFullState(),
1342 $.extend( true, {}, baseFullState, {
1343 group1__filter1: { selected: true, conflicted: true },
1344 group2__filter6: { selected: true, conflicted: true },
1345 group2__filter5: { selected: true, conflicted: true },
1346 group2__filter4: { conflicted: true } // Not selected but conflicted because it's in group2
1347 } ),
1348 'Selecting an item that conflicts with a whole group makes all selections in that group conflicted.'
1349 );
1350
1351 /* Simple case */
1352 // Reset
1353 model = new mw.rcfilters.dm.FiltersViewModel();
1354 model.initializeFilters( definition );
1355
1356 // Select a filter that has a conflict with a specific filter
1357 model.toggleFiltersSelected( {
1358 group1__filter2: true // conflicts: filter6
1359 } );
1360
1361 model.reassessFilterInteractions( model.getItemByName( 'group1__filter2' ) );
1362
1363 assert.deepEqual(
1364 model.getFullState(),
1365 $.extend( true, {}, baseFullState, {
1366 group1__filter2: { selected: true },
1367 group2__filter6: { conflicted: true }
1368 } ),
1369 'Simple case: Selecting a filter that conflicts with another filter sets the other as "conflicted".'
1370 );
1371
1372 model.toggleFiltersSelected( {
1373 group1__filter3: true // conflicts: filter6
1374 } );
1375
1376 model.reassessFilterInteractions( model.getItemByName( 'group1__filter3' ) );
1377
1378 assert.deepEqual(
1379 model.getFullState(),
1380 $.extend( true, {}, baseFullState, {
1381 group1__filter2: { selected: true },
1382 group1__filter3: { selected: true }
1383 } ),
1384 'Simple case: Selecting a filter that is not in conflict removes the conflict.'
1385 );
1386
1387 } );
1388
1389 QUnit.test( 'Filter highlights', function ( assert ) {
1390 var definition = [ {
1391 name: 'group1',
1392 title: 'Group 1',
1393 type: 'string_options',
1394 filters: [
1395 { name: 'filter1', cssClass: 'class1', label: '1', description: '1' },
1396 { name: 'filter2', cssClass: 'class2', label: '2', description: '2' },
1397 { name: 'filter3', cssClass: 'class3', label: '3', description: '3' },
1398 { name: 'filter4', cssClass: 'class4', label: '4', description: '4' },
1399 { name: 'filter5', cssClass: 'class5', label: '5', description: '5' },
1400 { name: 'filter6', label: '6', description: '6' }
1401 ]
1402 } ],
1403 model = new mw.rcfilters.dm.FiltersViewModel();
1404
1405 model.initializeFilters( definition );
1406
1407 assert.ok(
1408 !model.isHighlightEnabled(),
1409 'Initially, highlight is disabled.'
1410 );
1411
1412 model.toggleHighlight( true );
1413 assert.ok(
1414 model.isHighlightEnabled(),
1415 'Highlight is enabled on toggle.'
1416 );
1417
1418 model.setHighlightColor( 'group1__filter1', 'color1' );
1419 model.setHighlightColor( 'group1__filter2', 'color2' );
1420
1421 assert.deepEqual(
1422 model.getHighlightedItems().map( function ( item ) {
1423 return item.getName();
1424 } ),
1425 [
1426 'group1__filter1',
1427 'group1__filter2'
1428 ],
1429 'Highlighted items are highlighted.'
1430 );
1431
1432 assert.equal(
1433 model.getItemByName( 'group1__filter1' ).getHighlightColor(),
1434 'color1',
1435 'Item highlight color is set.'
1436 );
1437
1438 model.setHighlightColor( 'group1__filter1', 'color1changed' );
1439 assert.equal(
1440 model.getItemByName( 'group1__filter1' ).getHighlightColor(),
1441 'color1changed',
1442 'Item highlight color is changed on setHighlightColor.'
1443 );
1444
1445 model.clearHighlightColor( 'group1__filter1' );
1446 assert.deepEqual(
1447 model.getHighlightedItems().map( function ( item ) {
1448 return item.getName();
1449 } ),
1450 [
1451 'group1__filter2'
1452 ],
1453 'Clear highlight from an item results in the item no longer being highlighted.'
1454 );
1455
1456 // Reset
1457 model = new mw.rcfilters.dm.FiltersViewModel();
1458 model.initializeFilters( definition );
1459
1460 model.setHighlightColor( 'group1__filter1', 'color1' );
1461 model.setHighlightColor( 'group1__filter2', 'color2' );
1462 model.setHighlightColor( 'group1__filter3', 'color3' );
1463
1464 assert.deepEqual(
1465 model.getHighlightedItems().map( function ( item ) {
1466 return item.getName();
1467 } ),
1468 [
1469 'group1__filter1',
1470 'group1__filter2',
1471 'group1__filter3'
1472 ],
1473 'Even if highlights are not enabled, the items remember their highlight state'
1474 // NOTE: When actually displaying the highlights, the UI checks whether
1475 // highlighting is generally active and then goes over the highlighted
1476 // items. The item models, however, and the view model in general, still
1477 // retains the knowledge about which filters have different colors, so we
1478 // can seamlessly return to the colors the user previously chose if they
1479 // reapply highlights.
1480 );
1481
1482 // Reset
1483 model = new mw.rcfilters.dm.FiltersViewModel();
1484 model.initializeFilters( definition );
1485
1486 model.setHighlightColor( 'group1__filter1', 'color1' );
1487 model.setHighlightColor( 'group1__filter6', 'color6' );
1488
1489 assert.deepEqual(
1490 model.getHighlightedItems().map( function ( item ) {
1491 return item.getName();
1492 } ),
1493 [
1494 'group1__filter1'
1495 ],
1496 'Items without a specified class identifier are not highlighted.'
1497 );
1498 } );
1499 }( mediaWiki, jQuery ) );