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