[TEST] serialization of search domains & contexts by the search view

bzr revid: xmo@openerp.com-20120503125949-2m8euott3xzyivdm
This commit is contained in:
Xavier Morel 2012-05-03 14:59:49 +02:00
parent 836dfa6aa8
commit 6d12c155a4
3 changed files with 195 additions and 87 deletions

View File

@ -656,7 +656,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
do_search: function () {
var domains = [], contexts = [], groupbys = [], errors = [];
return this.on_search([], [], []);
this.query.each(function (facet) {
var field = facet.get('field');
try {
@ -902,12 +901,12 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
/**
* Fetches contexts for all enabled filters in the group
*
* @param {VS.model.SearchFacet} facet
* @param {openerp.web.search.Facet} facet
* @return {*} combined contexts of the enabled filters in this group
*/
get_context: function (facet) {
var contexts = _(facet.get('values')).chain()
.map(function (filter) { return filter.attrs.context; })
var contexts = _(facet.values).chain()
.map(function (f) { return f.get('value').attrs.context; })
.reject(_.isEmpty)
.value();
@ -924,8 +923,8 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
* @return {Array} enabled filters in this group
*/
get_groupby: function (facet) {
return _(facet.get('values')).chain()
.map(function (filter) { return filter.attrs.context; })
return _(facet.values).chain()
.map(function (f) { return f.get('value').attrs.context; })
.reject(_.isEmpty)
.value();
},
@ -936,8 +935,8 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
* @return {*} combined domains of the enabled filters in this group
*/
get_domain: function (facet) {
var domains = _(facet.get('values')).chain()
.map(function (filter) { return filter.attrs.domain; })
var domains = _(facet.values).chain()
.map(function (f) { return f.get('value').attrs.domain; })
.reject(_.isEmpty)
.value();
@ -1014,14 +1013,14 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
return facet.value();
},
get_context: function (facet) {
var val = this.get_value(facet);
// A field needs a value to be "active", and a context to send when
// active
var has_value = (val !== null && val !== '');
var context = this.attrs.context;
if (!(has_value && context)) {
if (!context) {
return;
}
var val = this.get_value(facet);
var has_value = (val !== null && val !== '');
return new instance.web.CompoundContext(context)
.set_eval_context({self: val});
},

View File

@ -4,6 +4,25 @@ $(document).ready(function () {
xhr.send(null);
var doc = xhr.responseXML;
var noop = function () {};
/**
* Make connection RPC responses mockable by setting keys on the
* Connection#responses object (key is the URL, value is the function to
* call with the RPC request payload)
*
* @param {openerp.web.Connection} connection connection instance to mockify
* @param {Object} [responses] url:function mapping to seed the mock connection
*/
var mockifyRPC = function (connection, responses) {
connection.responses = responses || {};
connection.rpc_function = function (url, payload) {
if (!(url.url in this.responses)) {
return $.Deferred().reject({}, 'failed', _.str.sprintf("Url %s not found in mock responses", url.url)).promise();
}
return $.when(this.responses[url.url](payload));
};
};
var instance;
module('query', {
setup: function () {
@ -157,16 +176,7 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc);
instance.connection.responses = {};
instance.connection.rpc_function = function (url, payload) {
if (!(url.url in this.responses)) {
return $.Deferred().reject(
{}, 'failed',
_.str.sprintf("Url %s not found in mock responses",
url.url)).promise();
}
return $.when(this.responses[url.url](payload));
};
mockifyRPC(instance.connection);
}
});
@ -220,7 +230,11 @@ $(document).ready(function () {
};
var dataset = {model: 'dummy.model', get_context: function () { return {}; }};
return new instance.web.SearchView(null, dataset, false, defaults);
var view = new instance.web.SearchView(null, dataset, false, defaults);
view.on_invalid.add(function () {
ok(false, JSON.stringify([].slice(arguments)));
});
return view;
}
asyncTest('calling', 2, function () {
var defaults_called = false;
@ -401,16 +415,7 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc);
instance.connection.responses = {};
instance.connection.rpc_function = function (url, payload) {
if (!(url.url in this.responses)) {
return $.Deferred().reject(
{}, 'failed',
_.str.sprintf("Url %s not found in mock responses",
url.url)).promise();
}
return $.when(this.responses[url.url](payload));
};
mockifyRPC(instance.connection);
}
});
asyncTest('calling', 4, function () {
@ -449,7 +454,7 @@ $(document).ready(function () {
var completion = {
label: "Dummy",
facet: {
field: {},
field: {get_domain: noop, get_context: noop, get_groupby: noop},
category: 'Dummy',
values: [{label: 'dummy', value: 42}]
}
@ -471,7 +476,7 @@ $(document).ready(function () {
});
});
asyncTest('facet selection: new value existing facet', 3, function () {
var field = {};
var field = {get_domain: noop, get_context: noop, get_groupby: noop};
var completion = {
label: "Dummy",
facet: {
@ -656,6 +661,115 @@ $(document).ready(function () {
});
});
module('search-serialization', {
setup: function () {
instance = window.openerp.init([]);
window.openerp.web.corelib(instance);
window.openerp.web.coresetup(instance);
window.openerp.web.chrome(instance);
window.openerp.web.data(instance);
window.openerp.web.search(instance);
instance.web.qweb.add_template(doc);
mockifyRPC(instance.connection);
}
});
asyncTest('No facet, no call', 6, function () {
var got_domain = false, got_context = false, got_groupby = false;
var $fix = $('#qunit-fixture');
var view = makeSearchView({
get_domain: function () {
got_domain = true;
return null;
},
get_context: function () {
got_context = true;
return null;
},
get_groupby: function () {
got_groupby = true;
return null;
}
});
var ds, cs, gs;
view.on_search.add(function (d, c, g) {
ds = d, cs = c, gs = g;
});
view.appendTo($fix)
.always(start)
.fail(function (error) { ok(false, error.message); })
.done(function () {
view.do_search();
ok(!got_domain, "no facet, should not have fetched domain");
ok(_(ds).isEmpty(), "domains list should be empty");
ok(!got_context, "no facet, should not have fetched context");
ok(_(cs).isEmpty(), "contexts list should be empty");
ok(!got_groupby, "no facet, should not have fetched groupby");
ok(_(gs).isEmpty(), "groupby list should be empty");
})
});
asyncTest('London, calling', 8, function () {
var got_domain = false, got_context = false, got_groupby = false;
var $fix = $('#qunit-fixture');
var view = makeSearchView({
get_domain: function (facet) {
equal(facet.get('category'), "dummy");
deepEqual(facet.values.toJSON(), [{label: "42", value: 42}]);
got_domain = true;
return null;
},
get_context: function () {
got_context = true;
return null;
},
get_groupby: function () {
got_groupby = true;
return null;
}
}, {dummy: 42});
var ds, cs, gs;
view.on_search.add(function (d, c, g) {
ds = d, cs = c, gs = g;
});
view.appendTo($fix)
.always(start)
.fail(function (error) { ok(false, error.message); })
.done(function () {
view.do_search();
ok(got_domain, "should have fetched domain");
ok(_(ds).isEmpty(), "domains list should be empty");
ok(got_context, "should have fetched context");
ok(_(cs).isEmpty(), "contexts list should be empty");
ok(got_groupby, "should have fetched groupby");
ok(_(gs).isEmpty(), "groupby list should be empty");
})
});
asyncTest('Generate domains', 1, function () {
var $fix = $('#qunit-fixture');
var view = makeSearchView({
get_domain: function (facet) {
return facet.values.map(function (value) {
return ['win', '4', value.get('value')];
});
}
}, {dummy: 42});
var ds;
view.on_search.add(function (d) { ds = d; });
view.appendTo($fix)
.always(start)
.fail(function (error) { ok(false, error.message); })
.done(function () {
view.do_search();
deepEqual(ds, [[['win', '4', 42]]],
"search should yield an array of contexts");
});
});
module('drawer', {
setup: function () {
instance = window.openerp.init([]);
@ -667,16 +781,7 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc);
instance.connection.responses = {};
instance.connection.rpc_function = function (url, payload) {
if (!(url.url in this.responses)) {
return $.Deferred().reject(
{}, 'failed',
_.str.sprintf("Url %s not found in mock responses",
url.url)).promise();
}
return $.when(this.responses[url.url](payload));
};
mockifyRPC(instance.connection);
}
});
asyncTest('is-drawn', 2, function () {
@ -704,43 +809,35 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc);
instance.connection.responses = {};
instance.connection.rpc_function = function (url, payload) {
if (!(url.url in this.responses)) {
return $.Deferred().reject(
{}, 'failed',
_.str.sprintf("Url %s not found in mock responses",
url.url)).promise();
mockifyRPC(instance.connection, {
'/web/searchview/load': function () {
// view with a single group of filters
return {result: {fields_view: {
type: 'search',
fields: {},
arch: {
tag: 'search',
attrs: {},
children: [{
tag: 'filter',
attrs: { string: "Foo1", domain: [ ['foo', '=', '1'] ] },
children: []
}, {
tag: 'filter',
attrs: {
name: 'foo2',
string: "Foo2",
domain: [ ['foo', '=', '2'] ] },
children: []
}, {
tag: 'filter',
attrs: { string: "Foo3", domain: [ ['foo', '=', '3'] ] },
children: []
}]
}
}}};
}
return $.when(this.responses[url.url](payload));
};
instance.connection.responses['/web/searchview/load'] = function () {
// view with a single group of filters
return {result: {fields_view: {
type: 'search',
fields: {},
arch: {
tag: 'search',
attrs: {},
children: [{
tag: 'filter',
attrs: { string: "Foo1", domain: [ ['foo', '=', '1'] ] },
children: []
}, {
tag: 'filter',
attrs: {
name: 'foo2',
string: "Foo2",
domain: [ ['foo', '=', '2'] ] },
children: []
}, {
tag: 'filter',
attrs: { string: "Foo3", domain: [ ['foo', '=', '3'] ] },
children: []
}]
}
}}};
};
});
}
});
asyncTest('drawn', 3, function () {

View File

@ -249,21 +249,33 @@ Converting from facet objects
Ultimately, the point of the search view is to allow searching. In
OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
the other hand, the OpenERP Web 7 search view's state is modelled
after a collection of :js:class:`~VS.model.SearchFacet`, and each
after a collection of :js:class:`~openerp.web.search.Facet`, and each
field of a search view may have special requirements when it comes to
the domains it produces [#]_.
So there needs to be some way of mapping
:js:class:`~VS.model.SearchFacet` objects to OpenERP search data.
:js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
This is done via an input's
:js:func:`~openerp.web.search.Input.get_domain` and
:js:func:`~openerp.web.search.Input.get_context`. Each takes a
:js:class:`~VS.model.SearchFacet` and returns whatever it's supposed
to generate (a domain or a context, respectively). Either can return
``null`` if the current value does not map to a domain or context, and
can throw an :js:class:`~openerp.web.search.Invalid` exception if the
value is not valid at all for the field.
:js:class:`~openerp.web.search.Facet` and returns whatever it's
supposed to generate (a domain or a context, respectively). Either can
return ``null`` if the current value does not map to a domain or
context, and can throw an :js:class:`~openerp.web.search.Invalid`
exception if the value is not valid at all for the field.
.. note::
The :js:class:`~openerp.web.search.Facet` object can have any
number of values (from 1 upwards)
.. note::
There is a third conversion method,
:js:func:`~openerp.web.search.Input.get_groupby`, which returns an
``Array`` of groupby domains rather than a single context. At this
point, it is only implemented on (and used by) filters.
Converting to facet objects
---------------------------