[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 () { do_search: function () {
var domains = [], contexts = [], groupbys = [], errors = []; var domains = [], contexts = [], groupbys = [], errors = [];
return this.on_search([], [], []);
this.query.each(function (facet) { this.query.each(function (facet) {
var field = facet.get('field'); var field = facet.get('field');
try { 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 * 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 * @return {*} combined contexts of the enabled filters in this group
*/ */
get_context: function (facet) { get_context: function (facet) {
var contexts = _(facet.get('values')).chain() var contexts = _(facet.values).chain()
.map(function (filter) { return filter.attrs.context; }) .map(function (f) { return f.get('value').attrs.context; })
.reject(_.isEmpty) .reject(_.isEmpty)
.value(); .value();
@ -924,8 +923,8 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
* @return {Array} enabled filters in this group * @return {Array} enabled filters in this group
*/ */
get_groupby: function (facet) { get_groupby: function (facet) {
return _(facet.get('values')).chain() return _(facet.values).chain()
.map(function (filter) { return filter.attrs.context; }) .map(function (f) { return f.get('value').attrs.context; })
.reject(_.isEmpty) .reject(_.isEmpty)
.value(); .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 * @return {*} combined domains of the enabled filters in this group
*/ */
get_domain: function (facet) { get_domain: function (facet) {
var domains = _(facet.get('values')).chain() var domains = _(facet.values).chain()
.map(function (filter) { return filter.attrs.domain; }) .map(function (f) { return f.get('value').attrs.domain; })
.reject(_.isEmpty) .reject(_.isEmpty)
.value(); .value();
@ -1014,14 +1013,14 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
return facet.value(); return facet.value();
}, },
get_context: function (facet) { get_context: function (facet) {
var val = this.get_value(facet);
// A field needs a value to be "active", and a context to send when // A field needs a value to be "active", and a context to send when
// active // active
var has_value = (val !== null && val !== '');
var context = this.attrs.context; var context = this.attrs.context;
if (!(has_value && context)) { if (!context) {
return; return;
} }
var val = this.get_value(facet);
var has_value = (val !== null && val !== '');
return new instance.web.CompoundContext(context) return new instance.web.CompoundContext(context)
.set_eval_context({self: val}); .set_eval_context({self: val});
}, },

View File

@ -4,6 +4,25 @@ $(document).ready(function () {
xhr.send(null); xhr.send(null);
var doc = xhr.responseXML; 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; var instance;
module('query', { module('query', {
setup: function () { setup: function () {
@ -157,16 +176,7 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc); instance.web.qweb.add_template(doc);
instance.connection.responses = {}; mockifyRPC(instance.connection);
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));
};
} }
}); });
@ -220,7 +230,11 @@ $(document).ready(function () {
}; };
var dataset = {model: 'dummy.model', get_context: function () { return {}; }}; 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 () { asyncTest('calling', 2, function () {
var defaults_called = false; var defaults_called = false;
@ -401,16 +415,7 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc); instance.web.qweb.add_template(doc);
instance.connection.responses = {}; mockifyRPC(instance.connection);
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));
};
} }
}); });
asyncTest('calling', 4, function () { asyncTest('calling', 4, function () {
@ -449,7 +454,7 @@ $(document).ready(function () {
var completion = { var completion = {
label: "Dummy", label: "Dummy",
facet: { facet: {
field: {}, field: {get_domain: noop, get_context: noop, get_groupby: noop},
category: 'Dummy', category: 'Dummy',
values: [{label: 'dummy', value: 42}] values: [{label: 'dummy', value: 42}]
} }
@ -471,7 +476,7 @@ $(document).ready(function () {
}); });
}); });
asyncTest('facet selection: new value existing facet', 3, 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 = { var completion = {
label: "Dummy", label: "Dummy",
facet: { 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', { module('drawer', {
setup: function () { setup: function () {
instance = window.openerp.init([]); instance = window.openerp.init([]);
@ -667,16 +781,7 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc); instance.web.qweb.add_template(doc);
instance.connection.responses = {}; mockifyRPC(instance.connection);
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));
};
} }
}); });
asyncTest('is-drawn', 2, function () { asyncTest('is-drawn', 2, function () {
@ -704,17 +809,8 @@ $(document).ready(function () {
instance.web.qweb.add_template(doc); instance.web.qweb.add_template(doc);
instance.connection.responses = {}; mockifyRPC(instance.connection, {
instance.connection.rpc_function = function (url, payload) { '/web/searchview/load': function () {
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));
};
instance.connection.responses['/web/searchview/load'] = function () {
// view with a single group of filters // view with a single group of filters
return {result: {fields_view: { return {result: {fields_view: {
type: 'search', type: 'search',
@ -740,7 +836,8 @@ $(document).ready(function () {
}] }]
} }
}}}; }}};
}; }
});
} }
}); });
asyncTest('drawn', 3, function () { 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 Ultimately, the point of the search view is to allow searching. In
OpenERP this is done via :ref:`domains <openerpserver:domains>`. On OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
the other hand, the OpenERP Web 7 search view's state is modelled 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 field of a search view may have special requirements when it comes to
the domains it produces [#]_. the domains it produces [#]_.
So there needs to be some way of mapping 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 This is done via an input's
:js:func:`~openerp.web.search.Input.get_domain` and :js:func:`~openerp.web.search.Input.get_domain` and
:js:func:`~openerp.web.search.Input.get_context`. Each takes a :js:func:`~openerp.web.search.Input.get_context`. Each takes a
:js:class:`~VS.model.SearchFacet` and returns whatever it's supposed :js:class:`~openerp.web.search.Facet` and returns whatever it's
to generate (a domain or a context, respectively). Either can return supposed to generate (a domain or a context, respectively). Either can
``null`` if the current value does not map to a domain or context, and return ``null`` if the current value does not map to a domain or
can throw an :js:class:`~openerp.web.search.Invalid` exception if the context, and can throw an :js:class:`~openerp.web.search.Invalid`
value is not valid at all for the field. 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 Converting to facet objects
--------------------------- ---------------------------