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