Merge "selenium: invoke jobs to enforce eventual consistency"
[lhc/web/wiklou.git] / tests / qunit / suites / resources / mediawiki.rcfilters / dm.SavedQueriesModel.test.js
1 /* eslint-disable camelcase */
2 ( function () {
3 var filterDefinition = [ {
4 name: 'group1',
5 type: 'send_unselected_if_any',
6 filters: [
7 // Note: The fact filter2 is default means that in the
8 // filter representation, filter1 and filter3 are 'true'
9 { name: 'filter1', cssClass: 'filter1class' },
10 { name: 'filter2', cssClass: 'filter2class', default: true },
11 { name: 'filter3', cssClass: 'filter3class' }
12 ]
13 }, {
14 name: 'group2',
15 type: 'string_options',
16 separator: ',',
17 filters: [
18 { name: 'filter4', cssClass: 'filter4class' },
19 { name: 'filter5' }, // NOTE: Not supporting highlights!
20 { name: 'filter6', cssClass: 'filter6class' }
21 ]
22 }, {
23 name: 'group3',
24 type: 'boolean',
25 sticky: true,
26 filters: [
27 { name: 'group3option1', cssClass: 'filter1class' },
28 { name: 'group3option2', cssClass: 'filter1class' },
29 { name: 'group3option3', cssClass: 'filter1class' }
30 ]
31 }, {
32 // Copy of the way the controller defines invert
33 // to check whether the conversion works
34 name: 'invertGroup',
35 type: 'boolean',
36 hidden: true,
37 filters: [ {
38 name: 'invert',
39 default: '0'
40 } ]
41 } ],
42 queriesFilterRepresentation = {
43 queries: {
44 1234: {
45 label: 'Item converted',
46 data: {
47 filters: {
48 // - This value is true, but the original filter-representation
49 // of the saved queries ran against defaults. Since filter1 was
50 // set as default in the definition, the value would actually
51 // not appear in the representation itself.
52 // It is considered 'true', though, and should appear in the
53 // converted result in its parameter representation.
54 // >> group1__filter1: true,
55 // - The reverse is true for filter3. Filter3 is set as default
56 // but we don't want it in this representation of the saved query.
57 // Since the filter representation ran against default values,
58 // it will appear as 'false' value in this representation explicitly
59 // and the resulting parameter representation should have that
60 // as the result as well
61 group1__filter3: false,
62 group2__filter4: true,
63 group3__group3option1: true
64 },
65 highlights: {
66 highlight: true,
67 group1__filter1: 'c5',
68 group3__group3option1: 'c1'
69 },
70 invert: true
71 }
72 }
73 }
74 },
75 queriesParamRepresentation = {
76 version: '2',
77 queries: {
78 1234: {
79 label: 'Item converted',
80 data: {
81 params: {
82 // filter1 is 'true' so filter2 and filter3 are both '1'
83 // in param representation
84 filter2: '1', filter3: '1',
85 // Group type string_options
86 group2: 'filter4'
87 // Note - Group3 is sticky, so it won't show in output
88 },
89 highlights: {
90 group1__filter1_color: 'c5',
91 group3__group3option1_color: 'c1'
92 }
93 }
94 }
95 }
96 },
97 removeHighlights = function ( data ) {
98 var copy = $.extend( true, {}, data );
99 copy.queries[ 1234 ].data.highlights = {};
100 return copy;
101 };
102
103 QUnit.module( 'mediawiki.rcfilters - SavedQueriesModel' );
104
105 QUnit.test( 'Initializing queries', function ( assert ) {
106 var filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
107 queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel ),
108 exampleQueryStructure = {
109 version: '2',
110 default: '1234',
111 queries: {
112 1234: {
113 label: 'Query 1234',
114 data: {
115 params: {
116 filter2: '1'
117 },
118 highlights: {
119 group1__filter3_color: 'c2'
120 }
121 }
122 }
123 }
124 },
125 cases = [
126 {
127 input: {},
128 finalState: { version: '2', queries: {} },
129 msg: 'Empty initial query structure results in base saved queries structure.'
130 },
131 {
132 input: $.extend( true, {}, exampleQueryStructure ),
133 finalState: $.extend( true, {}, exampleQueryStructure ),
134 msg: 'Initialization of given query structure does not corrupt the structure.'
135 },
136 {
137 // Converting from old structure
138 input: $.extend( true, {}, queriesFilterRepresentation ),
139 finalState: $.extend( true, {}, queriesParamRepresentation ),
140 msg: 'Conversion from filter representation to parameters retains data.'
141 },
142 {
143 // Converting from old structure
144 input: $.extend( true, {}, queriesFilterRepresentation, { queries: { 1234: { data: {
145 filters: {
146 // Entire group true: normalize params
147 filter1: true,
148 filter2: true,
149 filter3: true
150 },
151 highlights: {
152 filter3: null // Get rid of empty highlight
153 }
154 } } } } ),
155 finalState: $.extend( true, {}, queriesParamRepresentation ),
156 msg: 'Conversion from filter representation to parameters normalizes params and highlights.'
157 },
158 {
159 // Converting from old structure with default
160 input: $.extend( true, { default: '1234' }, queriesFilterRepresentation ),
161 finalState: $.extend( true, { default: '1234' }, queriesParamRepresentation ),
162 msg: 'Conversion from filter representation to parameters, with default set up, retains data.'
163 },
164 {
165 // Converting from old structure and cleaning up highlights
166 input: $.extend( true, queriesFilterRepresentation, { queries: { 1234: { data: { highlights: { highlight: false } } } } } ),
167 finalState: removeHighlights( queriesParamRepresentation ),
168 msg: 'Conversion from filter representation to parameters and highlight cleanup'
169 },
170 {
171 // New structure
172 input: $.extend( true, {}, queriesParamRepresentation ),
173 finalState: $.extend( true, {}, queriesParamRepresentation ),
174 msg: 'Parameter representation retains its queries structure'
175 },
176 {
177 // Do not touch invalid color parameters from the initialization routine
178 // (Normalization, or "fixing" the query should only happen when we add new query or actively convert queries)
179 input: $.extend( true, { queries: { 1234: { data: { highlights: { group2__filter5_color: 'c2' } } } } }, exampleQueryStructure ),
180 finalState: $.extend( true, { queries: { 1234: { data: { highlights: { group2__filter5_color: 'c2' } } } } }, exampleQueryStructure ),
181 msg: 'Structure that contains invalid highlights remains the same in initialization'
182 },
183 {
184 // Trim colors when highlight=false is stored
185 input: $.extend( true, { queries: { 1234: { data: { params: { highlight: '0' } } } } }, queriesParamRepresentation ),
186 finalState: removeHighlights( queriesParamRepresentation ),
187 msg: 'Colors are removed when highlight=false'
188 },
189 {
190 // Remove highlight when it is true but no colors are specified
191 input: $.extend( true, { queries: { 1234: { data: { params: { highlight: '1' } } } } }, removeHighlights( queriesParamRepresentation ) ),
192 finalState: removeHighlights( queriesParamRepresentation ),
193 msg: 'remove highlight when it is true but there is no colors'
194 }
195 ];
196
197 filtersModel.initializeFilters( filterDefinition );
198
199 cases.forEach( function ( testCase ) {
200 queriesModel.initialize( testCase.input );
201 assert.deepEqual(
202 queriesModel.getState(),
203 testCase.finalState,
204 testCase.msg
205 );
206 } );
207 } );
208
209 QUnit.test( 'Adding new queries', function ( assert ) {
210 var filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
211 queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel ),
212 cases = [
213 {
214 methodParams: [
215 'label1', // Label
216 { // Data
217 filter1: '1',
218 filter2: '2',
219 group1__filter1_color: 'c2',
220 group1__filter3_color: 'c5'
221 },
222 true, // isDefault
223 '1234' // ID
224 ],
225 result: {
226 itemState: {
227 label: 'label1',
228 data: {
229 params: {
230 filter1: '1',
231 filter2: '2'
232 },
233 highlights: {
234 group1__filter1_color: 'c2',
235 group1__filter3_color: 'c5'
236 }
237 }
238 },
239 isDefault: true,
240 id: '1234'
241 },
242 msg: 'Given valid data is preserved.'
243 },
244 {
245 methodParams: [
246 'label2',
247 {
248 filter1: '1',
249 invert: '1',
250 filter15: '1', // Invalid filter - removed
251 filter2: '0', // Falsey value - removed
252 group1__filter1_color: 'c3',
253 foobar: 'w00t' // Unrecognized parameter - removed
254 }
255 ],
256 result: {
257 itemState: {
258 label: 'label2',
259 data: {
260 params: {
261 filter1: '1' // Invert will be dropped because there are no namespaces
262 },
263 highlights: {
264 group1__filter1_color: 'c3'
265 }
266 }
267 },
268 isDefault: false
269 },
270 msg: 'Given data with invalid filters and highlights is normalized'
271 }
272 ];
273
274 filtersModel.initializeFilters( filterDefinition );
275
276 // Start with an empty saved queries model
277 queriesModel.initialize( {} );
278
279 cases.forEach( function ( testCase ) {
280 var itemID = queriesModel.addNewQuery.apply( queriesModel, testCase.methodParams ),
281 item = queriesModel.getItemByID( itemID );
282
283 assert.deepEqual(
284 item.getState(),
285 testCase.result.itemState,
286 testCase.msg + ' (itemState)'
287 );
288
289 assert.strictEqual(
290 item.isDefault(),
291 testCase.result.isDefault,
292 testCase.msg + ' (isDefault)'
293 );
294
295 if ( testCase.result.id !== undefined ) {
296 assert.strictEqual(
297 item.getID(),
298 testCase.result.id,
299 testCase.msg + ' (item ID)'
300 );
301 }
302 } );
303 } );
304
305 QUnit.test( 'Manipulating queries', function ( assert ) {
306 var id1, id2, item1, matchingItem,
307 queriesStructure = {},
308 filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
309 queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel );
310
311 filtersModel.initializeFilters( filterDefinition );
312
313 // Start with an empty saved queries model
314 queriesModel.initialize( {} );
315
316 // Add items
317 id1 = queriesModel.addNewQuery(
318 'New query 1',
319 {
320 group2: 'filter5',
321 group1__filter1_color: 'c5',
322 group3__group3option1_color: 'c1'
323 }
324 );
325 id2 = queriesModel.addNewQuery(
326 'New query 2',
327 {
328 filter1: '1',
329 filter2: '1',
330 invert: '1'
331 }
332 );
333 item1 = queriesModel.getItemByID( id1 );
334
335 assert.strictEqual(
336 item1.getID(),
337 id1,
338 'Item created and its data retained successfully'
339 );
340
341 // NOTE: All other methods that the item itself returns are
342 // tested in the dm.SavedQueryItemModel.test.js file
343
344 // Build the query structure we expect per item
345 queriesStructure[ id1 ] = {
346 label: 'New query 1',
347 data: {
348 params: {
349 group2: 'filter5'
350 },
351 highlights: {
352 group1__filter1_color: 'c5',
353 group3__group3option1_color: 'c1'
354 }
355 }
356 };
357 queriesStructure[ id2 ] = {
358 label: 'New query 2',
359 data: {
360 params: {
361 filter1: '1',
362 filter2: '1'
363 },
364 highlights: {}
365 }
366 };
367
368 assert.deepEqual(
369 queriesModel.getState(),
370 {
371 version: '2',
372 queries: queriesStructure
373 },
374 'Full query represents current state of items'
375 );
376
377 // Add default
378 queriesModel.setDefault( id2 );
379
380 assert.deepEqual(
381 queriesModel.getState(),
382 {
383 version: '2',
384 default: id2,
385 queries: queriesStructure
386 },
387 'Setting default is reflected in queries state'
388 );
389
390 // Remove default
391 queriesModel.setDefault( null );
392
393 assert.deepEqual(
394 queriesModel.getState(),
395 {
396 version: '2',
397 queries: queriesStructure
398 },
399 'Removing default is reflected in queries state'
400 );
401
402 // Find matching query
403 matchingItem = queriesModel.findMatchingQuery(
404 {
405 group2: 'filter5',
406 group1__filter1_color: 'c5',
407 group3__group3option1_color: 'c1'
408 }
409 );
410 assert.deepEqual(
411 matchingItem.getID(),
412 id1,
413 'Finding matching item by identical state'
414 );
415
416 // Find matching query with 0-values (base state)
417 matchingItem = queriesModel.findMatchingQuery(
418 {
419 group2: 'filter5',
420 filter1: '0',
421 filter2: '0',
422 group1__filter1_color: 'c5',
423 group3__group3option1_color: 'c1'
424 }
425 );
426 assert.deepEqual(
427 matchingItem.getID(),
428 id1,
429 'Finding matching item by "dirty" state with 0-base values'
430 );
431 } );
432
433 QUnit.test( 'Testing invert property', function ( assert ) {
434 var itemID, item,
435 filtersModel = new mw.rcfilters.dm.FiltersViewModel(),
436 queriesModel = new mw.rcfilters.dm.SavedQueriesModel( filtersModel ),
437 viewsDefinition = {
438 namespace: {
439 label: 'Namespaces',
440 trigger: ':',
441 groups: [ {
442 name: 'namespace',
443 label: 'Namespaces',
444 type: 'string_options',
445 separator: ';',
446 filters: [
447 { name: 0, label: 'Main', cssClass: 'namespace-0' },
448 { name: 1, label: 'Talk', cssClass: 'namespace-1' },
449 { name: 2, label: 'User', cssClass: 'namespace-2' },
450 { name: 3, label: 'User talk', cssClass: 'namespace-3' }
451 ]
452 } ]
453 }
454 };
455
456 filtersModel.initializeFilters( filterDefinition, viewsDefinition );
457
458 // Start with an empty saved queries model
459 queriesModel.initialize( {} );
460
461 filtersModel.toggleFiltersSelected( {
462 group1__filter3: true,
463 invertGroup__invert: true
464 } );
465 itemID = queriesModel.addNewQuery(
466 'label1', // Label
467 filtersModel.getMinimizedParamRepresentation(),
468 true, // isDefault
469 '2345' // ID
470 );
471 item = queriesModel.getItemByID( itemID );
472
473 assert.deepEqual(
474 item.getState(),
475 {
476 label: 'label1',
477 data: {
478 params: {
479 filter1: '1',
480 filter2: '1'
481 },
482 highlights: {}
483 }
484 },
485 'Invert parameter is not saved if there are no namespaces.'
486 );
487
488 // Reset
489 filtersModel.initializeFilters( filterDefinition, viewsDefinition );
490 filtersModel.toggleFiltersSelected( {
491 group1__filter3: true,
492 invertGroup__invert: true,
493 namespace__1: true
494 } );
495 itemID = queriesModel.addNewQuery(
496 'label1', // Label
497 filtersModel.getMinimizedParamRepresentation(),
498 true, // isDefault
499 '1234' // ID
500 );
501 item = queriesModel.getItemByID( itemID );
502
503 assert.deepEqual(
504 item.getState(),
505 {
506 label: 'label1',
507 data: {
508 params: {
509 filter1: '1',
510 filter2: '1',
511 invert: '1',
512 namespace: '1'
513 },
514 highlights: {}
515 }
516 },
517 'Invert parameter saved if there are namespaces.'
518 );
519 } );
520 }() );