[IMP] mail: You have one unread message
[odoo/odoo.git] / addons / web / static / test / search.js
1 openerp.testing.section('query', {
2     dependencies: ['web.search']
3 }, function (test) {
4     test('Adding a facet to the query creates a facet and a value', function (instance) {
5         var query = new instance.web.search.SearchQuery;
6         var field = {};
7         query.add({
8             category: 'Foo',
9             field: field,
10             values: [{label: 'Value', value: 3}]
11         });
12
13         var facet = query.at(0);
14         equal(facet.get('category'), 'Foo');
15         equal(facet.get('field'), field);
16         deepEqual(facet.get('values'), [{label: 'Value', value: 3}]);
17     });
18     test('Adding two facets', function (instance) {
19         var query = new instance.web.search.SearchQuery;
20         query.add([
21             { category: 'Foo', field: {}, values: [{label: 'Value', value: 3}] },
22             { category: 'Bar', field: {}, values: [{label: 'Value 2', value: 4}] }
23         ]);
24
25         equal(query.length, 2);
26         equal(query.at(0).values.length, 1);
27         equal(query.at(1).values.length, 1);
28     });
29     test('If a facet already exists, add values to it', function (instance) {
30         var query = new instance.web.search.SearchQuery;
31         var field = {};
32         query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
33         query.add({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
34
35         equal(query.length, 1, "adding an existing facet should merge new values into old facet");
36         var facet = query.at(0);
37         deepEqual(facet.get('values'), [
38             {label: 'V1', value: 0},
39             {label: 'V2', value: 1}
40         ]);
41     });
42     test('Facet being implicitly changed should trigger change, not add', function (instance) {
43         var query = new instance.web.search.SearchQuery;
44         var field = {}, added = false, changed = false;
45         query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
46         query.on('add', function () { added = true; })
47              .on('change', function () { changed = true });
48         query.add({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
49
50         ok(!added, "query.add adding values to a facet should not trigger an add");
51         ok(changed, "query.add adding values to a facet should not trigger a change");
52     });
53     test('Toggling a facet, value which does not exist should add it', function (instance) {
54         var query = new instance.web.search.SearchQuery;
55         var field = {};
56         query.toggle({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
57
58         equal(query.length, 1, "Should have created a single facet");
59         var facet = query.at(0);
60         equal(facet.values.length, 1, "Facet should have a single value");
61         deepEqual(facet.get('values'), [{label: 'V1', value: 0}],
62                   "Facet's value should match input");
63     });
64     test('Toggling a facet which exists with a value which does not should add the value to the facet', function (instance) {
65         var field = {};
66         var query = new instance.web.search.SearchQuery;
67         query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
68         query.toggle({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
69
70         equal(query.length, 1, "Should have edited the existing facet");
71         var facet = query.at(0);
72         equal(facet.values.length, 2, "Should have added the value to the existing facet");
73         deepEqual(facet.get('values'), [
74             {label: 'V1', value: 0},
75             {label: 'V2', value: 1}
76         ]);
77     });
78     test('Toggling a facet which exists with a value which does as well should remove the value from the facet', function (instance) {
79         var field = {};
80         var query = new instance.web.search.SearchQuery;
81         query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
82         query.add({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
83
84         query.toggle({category: 'A', field: field, values: [{label: 'V2', value: 1}]});
85
86         equal(query.length, 1, 'Should have the same single facet');
87         var facet = query.at(0);
88         equal(facet.values.length, 1, "Should only have one value left in the facet");
89         deepEqual(facet.get('values'), [
90             {label: 'V1', value: 0}
91         ]);
92     });
93     test('Toggling off the last value of a facet should remove the facet', function (instance) {
94         var field = {};
95         var query = new instance.web.search.SearchQuery;
96         query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
97
98         query.toggle({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
99
100         equal(query.length, 0, 'Should have removed the facet');
101     });
102     test('Intermediate emptiness should not remove the facet', function (instance) {
103         var field = {};
104         var query = new instance.web.search.SearchQuery;
105         query.add({category: 'A', field: field, values: [{label: 'V1', value: 0}]});
106
107         query.toggle({category: 'A', field: field, values: [
108             {label: 'V1', value: 0},
109             {label: 'V2', value: 1}
110         ]});
111
112         equal(query.length, 1, 'Should not have removed the facet');
113         var facet = query.at(0);
114         equal(facet.values.length, 1, "Should have one value");
115         deepEqual(facet.get('values'), [
116             {label: 'V2', value: 1}
117         ]);
118     });
119
120     test('Reseting with multiple facets should still work to load defaults', function (instance) {
121         var query = new instance.web.search.SearchQuery;
122         var field = {};
123         query.reset([
124             {category: 'A', field: field, values: [{label: 'V1', value: 0}]},
125             {category: 'A', field: field, values: [{label: 'V2', value: 1}]}]);
126
127         equal(query.length, 1, 'Should have created a single facet');
128         equal(query.at(0).values.length, 2, 'the facet should have merged two values');
129         deepEqual(query.at(0).get('values'), [
130             {label: 'V1', value: 0},
131             {label: 'V2', value: 1}
132         ])
133     });
134 });
135
136 /**
137  * Builds a basic search view with a single "dummy" field. The dummy
138  * extends `instance.web.search.Field`, it does not add any (class)
139  * attributes beyond what is provided through ``dummy_widget_attributes``.
140  *
141  * The view is returned un-started, it is the caller's role to start it
142  * (or use DOM-insertion methods to start it indirectly).
143  *
144  * @param instance
145  * @param [dummy_widget_attributes={}]
146  * @param [defaults={}]
147  * @return {instance.web.SearchView}
148  */
149 var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
150     instance.web.search.fields.add(
151         'dummy', 'instance.dummy.DummyWidget');
152     instance.dummy = {};
153     instance.dummy.DummyWidget = instance.web.search.Field.extend(
154         dummy_widget_attributes || {});
155     if (!('dummy.model:fields_view_get' in instance.session.responses)) {
156         instance.session.responses['dummy.model:fields_view_get'] = function () {
157             return {
158                 type: 'search',
159                 fields: {
160                     dummy: {type: 'char', string: "Dummy"}
161                 },
162                 arch: '<search><field name="dummy" widget="dummy"/></search>'
163             };
164         };
165     }
166     instance.session.responses['ir.filters:get_filters'] = function () {
167         return [];
168     };
169     instance.session.responses['dummy.model:fields_get'] = function () {
170         return {
171             dummy: {type: 'char', string: 'Dummy'}
172         };
173     };
174
175     var dataset = new instance.web.DataSet(null, 'dummy.model');
176     var view = new instance.web.SearchView(null, dataset, false, defaults);
177     var self = this;
178     view.on('invalid_search', self, function () {
179         ok(false, JSON.stringify([].slice(arguments)));
180     });
181     return view;
182 };
183 openerp.testing.section('defaults', {
184     dependencies: ['web.search'],
185     rpc: 'mock',
186     templates: true,
187 }, function (test) {
188     test('calling', {asserts: 2}, function (instance, $s) {
189         var defaults_called = false;
190
191         var view = makeSearchView(instance, {
192             facet_for_defaults: function (defaults) {
193                 defaults_called = true;
194                 return $.when({
195                     field: this,
196                     category: 'Dummy',
197                     values: [{label: 'dummy', value: defaults.dummy}]
198                 });
199             }
200         }, {dummy: 42});
201         return view.appendTo($s)
202             .done(function () {
203                 ok(defaults_called, "should have called defaults");
204                 deepEqual(
205                     view.query.toJSON(),
206                     [{category: 'Dummy', values: [{label: 'dummy', value: 42}]}],
207                     "should have generated a facet with the default value");
208             });
209     });
210     test('FilterGroup', {asserts: 3}, function (instance) {
211         var view = {inputs: [], query: {on: function () {}}};
212         var filter_a = new instance.web.search.Filter(
213             {attrs: {name: 'a'}}, view);
214         var filter_b = new instance.web.search.Filter(
215             {attrs: {name: 'b'}}, view);
216         var group = new instance.web.search.FilterGroup(
217             [filter_a, filter_b], view);
218         return group.facet_for_defaults({a: true, b: true})
219             .done(function (facet) {
220                 var model = facet;
221                 if (!(model instanceof instance.web.search.Facet)) {
222                     model = new instance.web.search.Facet(facet);
223                 }
224                 var values = model.values;
225                 equal(values.length, 2, 'facet should have two values');
226                 strictEqual(values.at(0).get('value'), filter_a);
227                 strictEqual(values.at(1).get('value'), filter_b);
228             });
229     });
230     test('Field', {asserts: 4}, function (instance) {
231         var view = {inputs: []};
232         var f = new instance.web.search.Field(
233             {attrs: {string: 'Dummy', name: 'dummy'}}, {}, view);
234         return f.facet_for_defaults({dummy: 42})
235             .done(function (facet) {
236                 var model = facet;
237                 if (!(model instanceof instance.web.search.Facet)) {
238                     model = new instance.web.search.Facet(facet);
239                 }
240                 strictEqual(
241                     model.get('category'),
242                     f.attrs.string,
243                     "facet category should be field label");
244                 strictEqual(
245                     model.get('field'), f,
246                     "facet field should be field which created default");
247                 equal(model.values.length, 1, "facet should have a single value");
248                 deepEqual(
249                     model.values.toJSON(),
250                     [{label: '42', value: 42}],
251                     "facet value should match provided default");
252                 });
253     });
254     test('Selection: valid value', {asserts: 4}, function (instance) {
255         var view = {inputs: []};
256         var f = new instance.web.search.SelectionField(
257             {attrs: {name: 'dummy', string: 'Dummy'}},
258             {selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Qux"]]},
259             view);
260         return f.facet_for_defaults({dummy: 3})
261             .done(function (facet) {
262                 var model = facet;
263                 if (!(model instanceof instance.web.search.Facet)) {
264                     model = new instance.web.search.Facet(facet);
265                 }
266                 strictEqual(
267                     model.get('category'),
268                     f.attrs.string,
269                     "facet category should be field label");
270                 strictEqual(
271                     model.get('field'), f,
272                     "facet field should be field which created default");
273                 equal(model.values.length, 1, "facet should have a single value");
274                 deepEqual(
275                     model.values.toJSON(),
276                     [{label: 'Baz', value: 3}],
277                     "facet value should match provided default's selection");
278             });
279     });
280     test('Selection: invalid value', {asserts: 1}, function (instance) {
281         var view = {inputs: []};
282         var f = new instance.web.search.SelectionField(
283             {attrs: {name: 'dummy', string: 'Dummy'}},
284             {selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Qux"]]},
285             view);
286         return f.facet_for_defaults({dummy: 42})
287             .done(function (facet) {
288                 ok(!facet, "an invalid value should result in a not-facet");
289             });
290     });
291     test("M2O default: value", {asserts: 5}, function (instance, $s, mock) {
292         var view = {inputs: []}, id = 4;
293         var f = new instance.web.search.ManyToOneField(
294             {attrs: {name: 'dummy', string: 'Dummy'}},
295             {relation: 'dummy.model.name'},
296             view);
297         mock('dummy.model.name:name_get', function (args) {
298             equal(args[0], id);
299             return [[id, "DumDumDum"]];
300         });
301         return f.facet_for_defaults({dummy: id})
302             .done(function (facet) {
303                 var model = facet;
304                 if (!(model instanceof instance.web.search.Facet)) {
305                     model = new instance.web.search.Facet(facet);
306                 }
307                 strictEqual(
308                     model.get('category'),
309                     f.attrs.string,
310                     "facet category should be field label");
311                 strictEqual(
312                     model.get('field'), f,
313                     "facet field should be field which created default");
314                 equal(model.values.length, 1, "facet should have a single value");
315                 deepEqual(
316                     model.values.toJSON(),
317                     [{label: 'DumDumDum', value: id}],
318                     "facet value should match provided default's selection");
319             });
320     });
321     test("M2O default: value", {asserts: 1}, function (instance, $s, mock) {
322         var view = {inputs: []}, id = 4;
323         var f = new instance.web.search.ManyToOneField(
324             {attrs: {name: 'dummy', string: 'Dummy'}},
325             {relation: 'dummy.model.name'},
326             view);
327         mock('dummy.model.name:name_get', function () { return [] });
328         return f.facet_for_defaults({dummy: id})
329             .done(function (facet) {
330                 ok(!facet, "an invalid m2o default should yield a non-facet");
331             });
332     });
333 });
334 openerp.testing.section('completions', {
335     dependencies: ['web.search'],
336     rpc: 'mock',
337     templates: true
338 }, function (test) {
339     test('calling', {asserts: 4}, function (instance, $s) {
340         var view = makeSearchView(instance, {
341             complete: function () {
342                 return $.when({
343                     label: "Dummy",
344                     facet: {
345                         field: this,
346                         category: 'Dummy',
347                         values: [{label: 'dummy', value: 42}]
348                     }
349                 });
350             }
351         });
352         var done = $.Deferred();
353         view.appendTo($s)
354             .then(function () {
355                 view.complete_global_search({term: "dum"}, function (completions) {
356                     done.resolve();
357                     equal(completions.length, 1, "should have a single completion");
358                     var completion = completions[0];
359                     equal(completion.label, "Dummy",
360                           "should have provided label");
361                     equal(completion.facet.category, "Dummy",
362                           "should have provided category");
363                     deepEqual(completion.facet.values,
364                               [{label: 'dummy', value: 42}],
365                               "should have provided values");
366                 });
367             }).fail(function () { done.reject.apply(done, arguments); });
368         return done;
369     });
370     test('facet selection', {asserts: 2}, function (instance, $s) {
371         var completion = {
372             label: "Dummy",
373             facet: {
374                 field: {
375                     get_domain: openerp.testing.noop,
376                     get_context: openerp.testing.noop,
377                     get_groupby: openerp.testing.noop
378                 },
379                 category: 'Dummy',
380                 values: [{label: 'dummy', value: 42}]
381             }
382         };
383
384         var view = makeSearchView(instance);
385         return view.appendTo($s)
386             .done(function () {
387                 view.select_completion(
388                     {preventDefault: function () {}},
389                     {item: completion});
390                 equal(view.query.length, 1, "should have one facet in the query");
391                 deepEqual(
392                     view.query.at(0).toJSON(),
393                     {category: 'Dummy', values: [{label: 'dummy', value: 42}]},
394                     "should have the right facet in the query");
395             });
396     });
397     test('facet selection: new value existing facet', {asserts: 3}, function (instance, $s) {
398         var field = {
399             get_domain: openerp.testing.noop,
400             get_context: openerp.testing.noop,
401             get_groupby: openerp.testing.noop
402         };
403         var completion = {
404             label: "Dummy",
405             facet: {
406                 field: field,
407                 category: 'Dummy',
408                 values: [{label: 'dummy', value: 42}]
409             }
410         };
411
412         var view = makeSearchView(instance);
413         return view.appendTo($s)
414             .done(function () {
415                 view.query.add({field: field, category: 'Dummy',
416                                 values: [{label: 'previous', value: 41}]});
417                 equal(view.query.length, 1, 'should have newly added facet');
418                 view.select_completion(
419                     {preventDefault: function () {}},
420                     {item: completion});
421                 equal(view.query.length, 1, "should still have only one facet");
422                 var facet = view.query.at(0);
423                 deepEqual(
424                     facet.get('values'),
425                     [{label: 'previous', value: 41}, {label: 'dummy', value: 42}],
426                     "should have added selected value to old one");
427             });
428     });
429     test('Field', {asserts: 1}, function (instance) {
430         var view = {inputs: []};
431         var f = new instance.web.search.Field({attrs: {}}, {}, view);
432         return f.complete('foo')
433             .done(function (completions) {
434                 ok(_(completions).isEmpty(), "field should not provide any completion");
435             });
436     });
437     test('CharField', {asserts: 6}, function (instance) {
438         var view = {inputs: []};
439         var f = new instance.web.search.CharField(
440             {attrs: {string: "Dummy"}}, {}, view);
441         return f.complete('foo<')
442             .done(function (completions) {
443                 equal(completions.length, 1, "should provide a single completion");
444                 var c = completions[0];
445                 equal(c.label, "Search <em>Dummy</em> for: <strong>foo&lt;</strong>",
446                       "should propose a fuzzy matching/searching, with the" +
447                       " value escaped");
448                 ok(c.facet, "completion should contain a facet proposition");
449                 var facet = new instance.web.search.Facet(c.facet);
450                 equal(facet.get('category'), f.attrs.string,
451                       "completion facet should bear the field's name");
452                 strictEqual(facet.get('field'), f,
453                             "completion facet should yield the field");
454                 deepEqual(facet.values.toJSON(), [{label: 'foo<', value: 'foo<'}],
455                           "facet should have single value using completion item");
456             });
457     });
458     test('Selection: match found', {asserts: 14}, function (instance) {
459         var view = {inputs: []};
460         var f = new instance.web.search.SelectionField(
461             {attrs: {string: "Dummy"}},
462             {selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Bazador"]]},
463             view);
464         return f.complete("ba")
465             .done(function (completions) {
466                 equal(completions.length, 4,
467                     "should provide two completions and a section title");
468                 deepEqual(completions[0], {label: "Dummy"});
469
470                 var c1 = completions[1];
471                 equal(c1.label, "Bar");
472                 equal(c1.facet.category, f.attrs.string);
473                 strictEqual(c1.facet.field, f);
474                 deepEqual(c1.facet.values, [{label: "Bar", value: 2}]);
475
476                 var c2 = completions[2];
477                 equal(c2.label, "Baz");
478                 equal(c2.facet.category, f.attrs.string);
479                 strictEqual(c2.facet.field, f);
480                 deepEqual(c2.facet.values, [{label: "Baz", value: 3}]);
481
482                 var c3 = completions[3];
483                 equal(c3.label, "Bazador");
484                 equal(c3.facet.category, f.attrs.string);
485                 strictEqual(c3.facet.field, f);
486                 deepEqual(c3.facet.values, [{label: "Bazador", value: 4}]);
487             });
488     });
489     test('Selection: no match', {asserts: 1}, function (instance) {
490         var view = {inputs: []};
491         var f = new instance.web.search.SelectionField(
492             {attrs: {string: "Dummy"}},
493             {selection: [[1, "Foo"], [2, "Bar"], [3, "Baz"], [4, "Bazador"]]},
494             view);
495         return f.complete("qux")
496             .done(function (completions) {
497                 ok(!completions, "if no value matches the needle, no completion shall be provided");
498             });
499     });
500     test('Date', {asserts: 6}, function (instance) {
501         instance.web._t.database.parameters = {
502             date_format: '%Y-%m-%d',
503             time_format: '%H:%M:%S'
504         };
505         var view = {inputs: []};
506         var f = new instance.web.search.DateField(
507             {attrs: {string: "Dummy"}}, {type: 'datetime'}, view);
508         return f.complete('2012-05-21T21:21:21')
509             .done(function (completions) {
510                 equal(completions.length, 1, "should provide a single completion");
511                 var c = completions[0];
512                 equal(c.label, "Search <em>Dummy</em> at: <strong>2012-05-21 21:21:21</strong>");
513                 var facet = new instance.web.search.Facet(c.facet);
514                 equal(facet.get('category'), f.attrs.string);
515                 equal(facet.get('field'), f);
516                 var value = facet.values.at(0);
517                 equal(value.get('label'), "2012-05-21 21:21:21");
518                 equal(value.get('value').getTime(),
519                       new Date(2012, 4, 21, 21, 21, 21).getTime());
520             });
521     });
522     test("M2O", {asserts: 13}, function (instance, $s, mock) {
523         mock('dummy.model:name_search', function (args, kwargs) {
524             deepEqual(args, []);
525             strictEqual(kwargs.name, 'bob');
526             return [[42, "choice 1"], [43, "choice @"]];
527         });
528
529         var view = {inputs: []};
530         var f = new instance.web.search.ManyToOneField(
531             {attrs: {string: 'Dummy'}}, {relation: 'dummy.model'}, view);
532         return f.complete("bob")
533             .done(function (c) {
534                 equal(c.length, 3, "should return results + title");
535                 var title = c[0];
536                 equal(title.label, f.attrs.string, "title should match field name");
537                 ok(!title.facet, "title should not have a facet");
538
539                 var f1 = new instance.web.search.Facet(c[1].facet);
540                 equal(c[1].label, "choice 1");
541                 equal(f1.get('category'), f.attrs.string);
542                 equal(f1.get('field'), f);
543                 deepEqual(f1.values.toJSON(), [{label: 'choice 1', value: 42}]);
544
545                 var f2 = new instance.web.search.Facet(c[2].facet);
546                 equal(c[2].label, "choice @");
547                 equal(f2.get('category'), f.attrs.string);
548                 equal(f2.get('field'), f);
549                 deepEqual(f2.values.toJSON(), [{label: 'choice @', value: 43}]);
550             });
551     });
552     test("M2O no match", {asserts: 3}, function (instance, $s, mock) {
553         mock('dummy.model:name_search', function (args, kwargs) {
554             deepEqual(args, []);
555             strictEqual(kwargs.name, 'bob');
556             return [];
557         });
558         var view = {inputs: []};
559         var f = new instance.web.search.ManyToOneField(
560             {attrs: {string: 'Dummy'}}, {relation: 'dummy.model'}, view);
561         return f.complete("bob")
562             .done(function (c) {
563                 ok(!c, "no match should yield no completion");
564             });
565     });
566 });
567 openerp.testing.section('search-serialization', {
568     dependencies: ['web.search'],
569     rpc: 'mock',
570     templates: true
571 }, function (test) {
572     test('No facet, no call', {asserts: 6}, function (instance, $s) {
573         var got_domain = false, got_context = false, got_groupby = false;
574         var view = makeSearchView(instance, {
575             get_domain: function () {
576                 got_domain = true;
577                 return null;
578             },
579             get_context: function () {
580                 got_context = true;
581                 return null;
582             },
583             get_groupby: function () {
584                 got_groupby = true;
585                 return null;
586             }
587         });
588         var ds, cs, gs;
589         view.on('search_data', this, function (d, c, g) {
590             ds = d; cs = c; gs = g;
591         });
592         return view.appendTo($s)
593             .done(function () {
594                 view.do_search();
595                 ok(!got_domain, "no facet, should not have fetched domain");
596                 ok(_(ds).isEmpty(), "domains list should be empty");
597
598                 ok(!got_context, "no facet, should not have fetched context");
599                 ok(_(cs).isEmpty(), "contexts list should be empty");
600
601                 ok(!got_groupby, "no facet, should not have fetched groupby");
602                 ok(_(gs).isEmpty(), "groupby list should be empty");
603             })
604     });
605     test('London, calling', {asserts: 8}, function (instance, $fix) {
606         var got_domain = false, got_context = false, got_groupby = false;
607         var view = makeSearchView(instance, {
608             get_domain: function (facet) {
609                 equal(facet.get('category'), "Dummy");
610                 deepEqual(facet.values.toJSON(), [{label: "42", value: 42}]);
611                 got_domain = true;
612                 return null;
613             },
614             get_context: function () {
615                 got_context = true;
616                 return null;
617             },
618             get_groupby: function () {
619                 got_groupby = true;
620                 return null;
621             }
622         }, {dummy: 42});
623         var ds, cs, gs;
624         view.on('search_data', this, function (d, c, g) {
625             ds = d; cs = c; gs = g;
626         });
627         return view.appendTo($fix)
628             .done(function () {
629                 view.do_search();
630                 ok(got_domain, "should have fetched domain");
631                 ok(_(ds).isEmpty(), "domains list should be empty");
632
633                 ok(got_context, "should have fetched context");
634                 ok(_(cs).isEmpty(), "contexts list should be empty");
635
636                 ok(got_groupby, "should have fetched groupby");
637                 ok(_(gs).isEmpty(), "groupby list should be empty");
638             })
639     });
640     test('Generate domains', {asserts: 1}, function (instance, $fix) {
641         var view = makeSearchView(instance, {
642             get_domain: function (facet) {
643                 return facet.values.map(function (value) {
644                     return ['win', '4', value.get('value')];
645                 });
646             }
647         }, {dummy: 42});
648         var ds;
649         view.on('search_data', this, function (d) { ds = d; });
650         return view.appendTo($fix)
651             .done(function () {
652                 view.do_search();
653                 deepEqual(ds, [[['win', '4', 42]]],
654                     "search should yield an array of contexts");
655             });
656     });
657
658     test('Field single value, default domain & context', {
659         rpc: false
660     }, function (instance) {
661         var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
662         var facet = new instance.web.search.Facet({
663             field: f,
664             values: [{value: 42}]
665         });
666
667         deepEqual(f.get_domain(facet), [['foo', '=', 42]],
668             "default field domain is a strict equality of name to facet's value");
669         equal(f.get_context(facet), null,
670             "default field context is null");
671     });
672     test('Field multiple values, default domain & context', {
673         rpc: false
674     }, function (instance) {
675         var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
676         var facet = new instance.web.search.Facet({
677             field: f,
678             values: [{value: 42}, {value: 68}, {value: 999}]
679         });
680
681         var actual_domain = f.get_domain(facet);
682         equal(actual_domain.__ref, "compound_domain",
683               "multiple value should yield compound domain");
684         deepEqual(actual_domain.__domains, [
685                     ['|'],
686                     ['|'],
687                     [['foo', '=', 42]],
688                     [['foo', '=', 68]],
689                     [['foo', '=', 999]]
690             ],
691             "domain should OR a default domain for each value");
692         equal(f.get_context(facet), null,
693             "default field context is null");
694     });
695     test('Field single value, custom domain & context', {
696         rpc: false
697     }, function (instance) {
698         var f = new instance.web.search.Field({attrs:{
699             context: "{'bob': self}",
700             filter_domain: "[['edmund', 'is', self]]"
701         }}, {name: 'foo'}, {inputs: []});
702         var facet = new instance.web.search.Facet({
703             field: f,
704             values: [{value: "great"}]
705         });
706
707         var actual_domain = f.get_domain(facet);
708         equal(actual_domain.__ref, "compound_domain",
709               "@filter_domain should yield compound domain");
710         deepEqual(actual_domain.__domains, [
711             "[['edmund', 'is', self]]"
712         ], 'should hold unevaluated custom domain');
713         deepEqual(actual_domain.get_eval_context(), {
714             self: "great"
715         }, "evaluation context should hold facet value as self");
716
717         var actual_context = f.get_context(facet);
718         equal(actual_context.__ref, "compound_context",
719               "@context should yield compound context");
720         deepEqual(actual_context.__contexts, [
721             "{'bob': self}"
722         ], 'should hold unevaluated custom context');
723         deepEqual(actual_context.get_eval_context(), {
724             self: "great"
725         }, "evaluation context should hold facet value as self");
726     });
727     test("M2O default", {
728         rpc: false
729     }, function (instance) {
730         var f = new instance.web.search.ManyToOneField(
731             {}, {name: 'foo'}, {inputs: []});
732         var facet = new instance.web.search.Facet({
733             field: f,
734             values: [{label: "Foo", value: 42}]
735         });
736
737         deepEqual(f.get_domain(facet), [['foo', '=', 42]],
738             "m2o should use identity if default domain");
739         deepEqual(f.get_context(facet), {default_foo: 42},
740             "m2o should use value as context default");
741     });
742     test("M2O default multiple values", {
743         rpc: false
744     }, function (instance) {
745         var f = new instance.web.search.ManyToOneField(
746             {}, {name: 'foo'}, {inputs: []});
747         var facet = new instance.web.search.Facet({
748             field: f,
749             values: [
750                 {label: "Foo", value: 42},
751                 {label: "Bar", value: 36}
752             ]
753         });
754
755         deepEqual(f.get_domain(facet).__domains,
756             [['|'], [['foo', '=', 42]], [['foo', '=', 36]]],
757             "m2o should or multiple values");
758         equal(f.get_context(facet), null,
759             "m2o should not have default context in case of multiple values");
760     });
761     test("M2O custom operator", {
762         rpc: false
763     }, function (instance) {
764         var f = new instance.web.search.ManyToOneField(
765             {attrs: {operator: 'boos'}}, {name: 'foo'}, {inputs: []});
766         var facet = new instance.web.search.Facet({
767             field: f,
768             values: [{label: "Foo", value: 42}]
769         });
770
771         deepEqual(f.get_domain(facet), [['foo', 'boos', 'Foo']],
772             "m2o should use label with custom operators");
773         deepEqual(f.get_context(facet), {default_foo: 42},
774             "m2o should use value as context default");
775     });
776     test("M2O custom domain & context", {
777         rpc: false
778     }, function (instance) {
779         var f = new instance.web.search.ManyToOneField({attrs: {
780             context: "{'whee': self}",
781             filter_domain: "[['filter', 'is', self]]"
782         }}, {name: 'foo'}, {inputs: []});
783         var facet = new instance.web.search.Facet({
784             field: f,
785             values: [{label: "Foo", value: 42}]
786         });
787
788         var domain = f.get_domain(facet);
789         deepEqual(domain.__domains, [
790             "[['filter', 'is', self]]"
791         ]);
792         deepEqual(domain.get_eval_context(), {
793             self: "Foo"
794         }, "custom domain's self should be label");
795         var context = f.get_context(facet);
796         deepEqual(context.__contexts, [
797             "{'whee': self}"
798         ]);
799         deepEqual(context.get_eval_context(), {
800             self: "Foo"
801         }, "custom context's self should be label");
802     });
803
804     test('FilterGroup', {asserts: 6}, function (instance) {
805         var view = {inputs: [], query: {on: function () {}}};
806         var filter_a = new instance.web.search.Filter(
807             {attrs: {name: 'a', context: 'c1', domain: 'd1'}}, view);
808         var filter_b = new instance.web.search.Filter(
809             {attrs: {name: 'b', context: 'c2', domain: 'd2'}}, view);
810         var filter_c = new instance.web.search.Filter(
811             {attrs: {name: 'c', context: 'c3', domain: 'd3'}}, view);
812         var group = new instance.web.search.FilterGroup(
813             [filter_a, filter_b, filter_c], view);
814         return group.facet_for_defaults({a: true, c: true})
815             .done(function (facet) {
816                 var model = facet;
817                 if (!(model instanceof instance.web.search.Facet)) {
818                     model = new instance.web.search.Facet(facet);
819                 }
820
821                 var domain = group.get_domain(model);
822                 equal(domain.__ref, 'compound_domain',
823                     "domain should be compound");
824                 deepEqual(domain.__domains, [
825                     ['|'], 'd1', 'd3'
826                 ], "domain should OR filter domains");
827                 ok(!domain.get_eval_context(), "domain should have no evaluation context");
828                 var context = group.get_context(model);
829                 equal(context.__ref, 'compound_context',
830                     "context should be compound");
831                 deepEqual(context.__contexts, [
832                     'c1', 'c3'
833                 ], "context should merge all filter contexts");
834                 ok(!context.get_eval_context(), "context should have no evaluation context");
835             });
836     });
837     test('Empty filter domains', {asserts: 4}, function (instance) {
838         var view = {inputs: [], query: {on: function () {}}};
839         var filter_a = new instance.web.search.Filter(
840             {attrs: {name: 'a', context: '{}', domain: '[]'}}, view);
841         var filter_b = new instance.web.search.Filter(
842             {attrs: {name: 'b', context: '{}', domain: '[]'}}, view);
843         var filter_c = new instance.web.search.Filter(
844             {attrs: {name: 'c', context: '{b: 42}', domain: '[["a", "=", 3]]'}}, view);
845         var group = new instance.web.search.FilterGroup(
846             [filter_a, filter_b, filter_c], view);
847         var t1 = group.facet_for_defaults({a: true, c: true})
848         .done(function (facet) {
849             var model = facet;
850             if (!(model instanceof instance.web.search.Facet)) {
851                 model = new instance.web.search.Facet(facet);
852             }
853
854             var domain = group.get_domain(model);
855             deepEqual(domain, '[["a", "=", 3]]', "domain should ignore empties");
856             var context = group.get_context(model);
857             deepEqual(context, '{b: 42}', "context should ignore empties");
858         });
859         var t2 = group.facet_for_defaults({a: true, b: true})
860         .done(function (facet) {
861             var model = facet;
862             if (!(model instanceof instance.web.search.Facet)) {
863                 model = new instance.web.search.Facet(facet);
864             }
865
866             var domain = group.get_domain(model);
867             equal(domain, null, "domain should ignore empties");
868             var context = group.get_context(model);
869             equal(context, null, "context should ignore empties");
870         });
871         return $.when(t1, t2);
872     });
873 });
874 openerp.testing.section('removal', {
875     dependencies: ['web.search'],
876     rpc: 'mock',
877     templates: true
878 }, function (test) {
879     test('clear button', {asserts: 2}, function (instance, $fix) {
880         var view = makeSearchView(instance, {
881             facet_for_defaults: function (defaults) {
882                 return $.when({
883                     field: this,
884                     category: 'Dummy',
885                     values: [{label: 'dummy', value: defaults.dummy}]
886                 });
887             }
888         }, {dummy: 42});
889         return view.appendTo($fix)
890             .done(function () {
891                 equal(view.query.length, 1, "view should have default facet");
892                 $fix.find('.oe_searchview_clear').click();
893                 equal(view.query.length, 0, "cleared view should not have any facet");
894             });
895     });
896 });
897 openerp.testing.section('drawer', {
898     dependencies: ['web.search'],
899     rpc: 'mock',
900     templates: true
901 }, function (test) {
902     test('is-drawn', {asserts: 2}, function (instance, $fix) {
903         var view = makeSearchView(instance);
904         return view.appendTo($fix)
905             .done(function () {
906                 ok($fix.find('.oe_searchview_filters').length,
907                    "filters drawer control has been drawn");
908                 ok($fix.find('.oe_searchview_advanced').length,
909                    "filters advanced search has been drawn");
910             });
911     });
912 });
913 openerp.testing.section('filters', {
914     dependencies: ['web.search'],
915     rpc: 'mock',
916     templates: true,
917     setup: function (instance, $s, mock) {
918         mock('dummy.model:fields_view_get', function () {
919             // view with a single group of filters
920             return {
921                 type: 'search',
922                 fields: {},
923                 arch: '<search>' +
924                         '<filter string="Foo1" domain="[ [\'foo\', \'=\', \'1\'] ]"/>' +
925                         '<filter name="foo2" string="Foo2" domain="[ [\'foo\', \'=\', \'2\'] ]"/>' +
926                         '<filter string="Foo3" domain="[ [\'foo\', \'=\', \'3\'] ]"/>' +
927                         '</search>',
928             };
929         });
930     }
931 }, function (test) {
932     test('drawn', {asserts: 3}, function (instance, $fix) {
933         var view = makeSearchView(instance);
934         return view.appendTo($fix)
935             .done(function () {
936                 var $fs = $fix.find('.oe_searchview_filters ul');
937                 // 3 filters, 1 filtergroup, 1 custom filters widget,
938                 // 1 advanced and 1 Filters widget
939                 equal(view.inputs.length, 7,
940                       'view should have 7 inputs total');
941                 equal($fs.children().length, 3,
942                       "drawer should have a filter group with 3 filters");
943                 equal(_.str.strip($fs.children().eq(0).text()), "Foo1",
944                       "Text content of first filter option should match filter string");
945             });
946     });
947     test('click adding from empty query', {asserts: 4}, function (instance, $fix) {
948         var view = makeSearchView(instance);
949         return view.appendTo($fix)
950             .done(function () {
951                 var $fs = $fix.find('.oe_searchview_filters ul');
952                 $fs.children(':eq(2)').trigger('click');
953                 equal(view.query.length, 1, "click should have added a facet");
954                 var facet = view.query.at(0);
955                 equal(facet.values.length, 1, "facet should have a single value");
956                 var value = facet.values.at(0);
957                 ok(value.get('value') instanceof instance.web.search.Filter,
958                    "value should be a filter");
959                 equal(value.get('label'), "Foo3",
960                       "value should be third filter");
961             });
962     });
963     test('click adding from existing query', {asserts: 4}, function (instance, $fix) {
964         var view = makeSearchView(instance, {}, {foo2: true});
965         return view.appendTo($fix)
966             .done(function () {
967                 var $fs = $fix.find('.oe_searchview_filters ul');
968                 $fs.children(':eq(2)').trigger('click');
969                 equal(view.query.length, 1, "click should not have changed facet count");
970                 var facet = view.query.at(0);
971                 equal(facet.values.length, 2, "facet should have a second value");
972                 var v1 = facet.values.at(0);
973                 equal(v1.get('label'), "Foo2",
974                       "first value should be default");
975                 var v2 = facet.values.at(1);
976                 equal(v2.get('label'), "Foo3",
977                       "second value should be clicked filter");
978             });
979     });
980     test('click removing from query', {asserts: 4}, function (instance, $fix) {
981         var calls = 0;
982         var view = makeSearchView(instance, {}, {foo2: true});
983         view.on('search_data', null, function () {
984             ++calls;
985         });
986         return view.appendTo($fix)
987             .done(function () {
988                 var $fs = $fix.find('.oe_searchview_filters ul');
989                 // sanity check
990                 equal(view.query.length, 1, "query should have default facet");
991                 strictEqual(calls, 0);
992                 $fs.children(':eq(1)').trigger('click');
993                 equal(view.query.length, 0, "click should have removed facet");
994                 strictEqual(calls, 1, "one search should have been triggered");
995             });
996     });
997 });
998 openerp.testing.section('saved_filters', {
999     dependencies: ['web.search'],
1000     rpc: 'mock',
1001     templates: true
1002 }, function (test) {
1003     test('checkboxing', {asserts: 6}, function (instance, $fix, mock) {
1004         var view = makeSearchView(instance);
1005         mock('ir.filters:get_filters', function () {
1006             return [{ name: "filter name", user_id: 42 }];
1007         });
1008
1009         return view.appendTo($fix)
1010             .done(function () {
1011                 var $row = $fix.find('.oe_searchview_custom li:first').click();
1012
1013                 ok($row.hasClass('oe_selected'), "should check/select the filter's row");
1014                 ok($row.hasClass("oe_searchview_custom_private"),
1015                     "should have private filter note/class");
1016                 equal(view.query.length, 1, "should have only one facet");
1017                 var values = view.query.at(0).values;
1018                 equal(values.length, 1,
1019                     "should have only one value in the facet");
1020                 equal(values.at(0).get('label'), 'filter name',
1021                     "displayed label should be the name of the filter");
1022                 equal(values.at(0).get('value'), null,
1023                     "should have no value set");
1024             })
1025     });
1026     test('removal', {asserts: 1}, function (instance, $fix, mock) {
1027         var view = makeSearchView(instance);
1028         mock('ir.filters:get_filters', function () {
1029             return [{ name: "filter name", user_id: 42 }];
1030         });
1031
1032         return view.appendTo($fix)
1033             .done(function () {
1034                 var $row = $fix.find('.oe_searchview_custom li:first').click();
1035
1036                 view.query.remove(view.query.at(0));
1037                 ok(!$row.hasClass('oe_selected'),
1038                     "should not be checked anymore");
1039             });
1040     });
1041     test('toggling', {asserts: 2}, function (instance, $fix, mock) {
1042         var view = makeSearchView(instance);
1043         mock('ir.filters:get_filters', function () {
1044             return [{name: 'filter name', user_id: 42, id: 1}];
1045         });
1046
1047         return view.appendTo($fix)
1048             .done(function () {
1049                 var $row = $fix.find('.oe_searchview_custom li:first').click();
1050                 equal(view.query.length, 1, "should have one facet");
1051                 $row.click();
1052                 equal(view.query.length, 0, "should have removed facet");
1053             });
1054     });
1055     test('replacement', {asserts: 4}, function (instance, $fix, mock) {
1056         var view = makeSearchView(instance);
1057         mock('ir.filters:get_filters', function () {
1058             return [
1059                 {name: 'f', user_id: 42, id: 1, context: {'private': 1}},
1060                 {name: 'f', user_id: false, id: 2, context: {'private': 0}}
1061             ];
1062         });
1063         return view.appendTo($fix)
1064             .done(function () {
1065                 $fix.find('.oe_searchview_custom li:eq(0)').click();
1066                 equal(view.query.length, 1, "should have one facet");
1067                 deepEqual(
1068                     view.query.at(0).get('field').get_context(),
1069                     {'private': 1},
1070                     "should have selected first filter");
1071                 $fix.find('.oe_searchview_custom li:eq(1)').click();
1072                 equal(view.query.length, 1, "should have one facet");
1073                 deepEqual(
1074                     view.query.at(0).get('field').get_context(),
1075                     {'private': 0},
1076                     "should have selected second filter");
1077             });
1078     });
1079 });
1080 openerp.testing.section('advanced', {
1081     dependencies: ['web.search'],
1082     rpc: 'mock',
1083     templates: true
1084 }, function (test) {
1085     test('single-advanced', {asserts: 6}, function (instance, $fix) {
1086         var view = makeSearchView(instance);
1087
1088         return view.appendTo($fix)
1089             .done(function () {
1090                 var $advanced = $fix.find('.oe_searchview_advanced');
1091                 // open advanced search (not actually useful)
1092                 $advanced.find('> h4').click();
1093                 // select proposition (only one)
1094                 var $prop = $advanced.find('> form li:first');
1095                 // field select should have two possible values, dummy and id
1096                 equal($prop.find('.searchview_extended_prop_field option').length,
1097                       2, "advanced search should provide choice between two fields");
1098                 // field should be dummy
1099                 equal($prop.find('.searchview_extended_prop_field').val(),
1100                       'dummy',
1101                       "only field should be dummy");
1102                 // operator should be "contains"/'ilike'
1103                 equal($prop.find('.searchview_extended_prop_op').val(),
1104                       'ilike', "default char operator should be ilike");
1105                 // put value in
1106                 $prop.find('.searchview_extended_prop_value input')
1107                      .val("stupid value");
1108                 // validate advanced search
1109                 $advanced.find('button.oe_apply').click();
1110
1111                 // resulting search
1112                 equal(view.query.length, 1, "search query should have a single facet");
1113                 var facet = view.query.at(0);
1114                 ok(!facet.get('field').get_context(facet),
1115                    "advanced search facets should yield no context");
1116                 deepEqual(facet.get('field').get_domain(facet),
1117                           [['dummy', 'ilike', "stupid value"]],
1118                           "advanced search facet should return proposed domain");
1119             });
1120     });
1121     test('multiple-advanced', {asserts: 3}, function (instance, $fix) {
1122         var view = makeSearchView(instance);
1123
1124         return view.appendTo($fix)
1125             .done(function () {
1126                 var $advanced = $fix.find('.oe_searchview_advanced');
1127                 // open advanced search (not actually useful)
1128                 $advanced.find('> h4').click();
1129                 // open second condition
1130                 $advanced.find('button.oe_add_condition').click();
1131                 // select first proposition
1132                 var $prop1 = $advanced.find('> form li:first');
1133                 $prop1.find('.searchview_extended_prop_field').val('dummy').change();
1134                 $prop1.find('.searchview_extended_prop_op').val('ilike');
1135                 $prop1.find('.searchview_extended_prop_value input')
1136                      .val("stupid value");
1137
1138                 // select first proposition
1139                 var $prop2 = $advanced.find('> form li:last');
1140                 // need to trigger event manually or op not changed
1141                 $prop2.find('.searchview_extended_prop_field').val('id').change();
1142                 $prop2.find('.searchview_extended_prop_op').val('=');
1143                 $prop2.find('.searchview_extended_prop_value input')
1144                      .val(42);
1145                 // validate advanced search
1146                 $advanced.find('button.oe_apply').click();
1147
1148                 // resulting search
1149                 equal(view.query.length, 1, "search query should have a single facet");
1150                 var facet = view.query.at(0);
1151                 ok(!facet.get('field').get_context(facet),
1152                    "advanced search facets should yield no context");
1153                 deepEqual(facet.get('field').get_domain(facet),
1154                           ['|', ['dummy', 'ilike', "stupid value"],
1155                                 ['id', '=', 42]],
1156                           "advanced search facet should return proposed domain");
1157             });
1158     });
1159     // TODO: UI tests?
1160 });