Merge lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo into lp:openerp-web/7.0

Proposed by Xavier (Open ERP)
Status: Merged
Merged at revision: 3803
Proposed branch: lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo
Merge into: lp:openerp-web/7.0
Diff against target: 643 lines (+284/-83)
4 files modified
addons/web/doc/search_view.rst (+11/-0)
addons/web/static/src/js/search.js (+107/-64)
addons/web/static/src/xml/base.xml (+1/-10)
addons/web/static/test/search.js (+165/-9)
To merge this branch: bzr merge lp:~openerp-dev/openerp-web/7.0-searchview-invisibles-xmo
Reviewer Review Type Date Requested Status
Yannick Vaucher @ Camptocamp (community) test Approve
OpenERP Core Team Pending
Review via email: mp+148386@code.launchpad.net

Description of the change

Re-introduces handling of invisible fields and groups in the search view, forgotten during the reimplementation of the unified search view for 7.0

This handling is important as it's how @groups is handled (invisible fields and filters can't be removed altogether as e.g. defaults still need to work correctly).

To post a comment you must log in.
Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

It works

Test done:
- Install crm.
- Add a user1 with group Sales / User: Own Leads Only
- Add a user2 with groups Sales / User: Own Leads Only and Technical Features
- Edit the view search to set Customers filter under Technical Features group.
groups="base.group_no_one"

Expected:
user1 doesn't see the filter in partner search view OK
user2 see the filter in partner search view OK

review: Approve (test)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'addons/web/doc/search_view.rst'
--- addons/web/doc/search_view.rst 2012-11-14 23:00:42 +0000
+++ addons/web/doc/search_view.rst 2013-02-14 08:53:20 +0000
@@ -107,6 +107,12 @@
107items, it *should* prefix those with a section title using its own107items, it *should* prefix those with a section title using its own
108name. This has no technical consequence but is clearer for users.108name. This has no technical consequence but is clearer for users.
109109
110.. note::
111
112 If a field is :js:func:`invisible
113 <openerp.web.search.Input.visible>`, its completion function will
114 *not* be called.
115
110Providing drawer/supplementary UI116Providing drawer/supplementary UI
111+++++++++++++++++++++++++++++++++117+++++++++++++++++++++++++++++++++
112118
@@ -145,6 +151,11 @@
145 dynamically collects, lays out and renders filters? =>151 dynamically collects, lays out and renders filters? =>
146 exercises drawer thingies152 exercises drawer thingies
147153
154.. note::
155
156 An :js:func:`invisible <openerp.web.search.Input.visible>` input
157 will not be inserted into the drawer.
158
148Converting from facet objects159Converting from facet objects
149+++++++++++++++++++++++++++++160+++++++++++++++++++++++++++++
150161
151162
=== modified file 'addons/web/static/src/js/search.js'
--- addons/web/static/src/js/search.js 2013-01-31 13:51:25 +0000
+++ addons/web/static/src/js/search.js 2013-02-14 08:53:20 +0000
@@ -359,7 +359,7 @@
359 this.has_defaults = !_.isEmpty(this.defaults);359 this.has_defaults = !_.isEmpty(this.defaults);
360360
361 this.inputs = [];361 this.inputs = [];
362 this.controls = {};362 this.controls = [];
363363
364 this.headless = this.options.hidden && !this.has_defaults;364 this.headless = this.options.hidden && !this.has_defaults;
365365
@@ -499,6 +499,7 @@
499 */499 */
500 complete_global_search: function (req, resp) {500 complete_global_search: function (req, resp) {
501 $.when.apply(null, _(this.inputs).chain()501 $.when.apply(null, _(this.inputs).chain()
502 .filter(function (input) { return input.visible(); })
502 .invoke('complete', req.term)503 .invoke('complete', req.term)
503 .value()).then(function () {504 .value()).then(function () {
504 resp(_(_(arguments).compact()).flatten(true));505 resp(_(_(arguments).compact()).flatten(true));
@@ -587,18 +588,18 @@
587 *588 *
588 * @param {Array} items a list of nodes to convert to widgets589 * @param {Array} items a list of nodes to convert to widgets
589 * @param {Object} fields a mapping of field names to (ORM) field attributes590 * @param {Object} fields a mapping of field names to (ORM) field attributes
590 * @param {String} [group_name] name of the group to put the new controls in591 * @param {Object} [group] group to put the new controls in
591 */592 */
592 make_widgets: function (items, fields, group_name) {593 make_widgets: function (items, fields, group) {
593 group_name = group_name || null;594 if (!group) {
594 if (!(group_name in this.controls)) {595 group = new instance.web.search.Group(
595 this.controls[group_name] = [];596 this, 'q', {attrs: {string: _t("Filters")}});
596 }597 }
597 var self = this, group = this.controls[group_name];598 var self = this;
598 var filters = [];599 var filters = [];
599 _.each(items, function (item) {600 _.each(items, function (item) {
600 if (filters.length && item.tag !== 'filter') {601 if (filters.length && item.tag !== 'filter') {
601 group.push(new instance.web.search.FilterGroup(filters, this));602 group.push(new instance.web.search.FilterGroup(filters, group));
602 filters = [];603 filters = [];
603 }604 }
604605
@@ -606,15 +607,18 @@
606 case 'separator': case 'newline':607 case 'separator': case 'newline':
607 break;608 break;
608 case 'filter':609 case 'filter':
609 filters.push(new instance.web.search.Filter(item, this));610 filters.push(new instance.web.search.Filter(item, group));
610 break;611 break;
611 case 'group':612 case 'group':
612 self.make_widgets(item.children, fields, item.attrs.string);613 self.make_widgets(item.children, fields,
614 new instance.web.search.Group(group, 'w', item));
613 break;615 break;
614 case 'field':616 case 'field':
615 group.push(this.make_field(item, fields[item['attrs'].name]));617 var field = this.make_field(
618 item, fields[item['attrs'].name], group);
619 group.push(field);
616 // filters620 // filters
617 self.make_widgets(item.children, fields, group_name);621 self.make_widgets(item.children, fields, group);
618 break;622 break;
619 }623 }
620 }, this);624 }, this);
@@ -629,12 +633,13 @@
629 *633 *
630 * @param {Object} item fields_view_get node for the field634 * @param {Object} item fields_view_get node for the field
631 * @param {Object} field fields_get result for the field635 * @param {Object} field fields_get result for the field
636 * @param {Object} [parent]
632 * @returns instance.web.search.Field637 * @returns instance.web.search.Field
633 */638 */
634 make_field: function (item, field) {639 make_field: function (item, field, parent) {
635 var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);640 var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
636 if(obj) {641 if(obj) {
637 return new (obj) (item, field, this);642 return new (obj) (item, field, parent || this);
638 } else {643 } else {
639 console.group('Unknown field type ' + field.type);644 console.group('Unknown field type ' + field.type);
640 console.error('View node', item);645 console.error('View node', item);
@@ -869,13 +874,18 @@
869 * @constructs instance.web.search.Widget874 * @constructs instance.web.search.Widget
870 * @extends instance.web.Widget875 * @extends instance.web.Widget
871 *876 *
872 * @param view the ancestor view of this widget877 * @param parent parent of this widget
873 */878 */
874 init: function (view) {879 init: function (parent) {
875 this._super(view);880 this._super(parent);
876 this.view = view;881 var ancestor = parent;
882 do {
883 this.view = ancestor;
884 } while (!(ancestor instanceof instance.web.SearchView)
885 && (ancestor = (ancestor.getParent && ancestor.getParent())));
877 }886 }
878});887});
888
879instance.web.search.add_expand_listener = function($root) {889instance.web.search.add_expand_listener = function($root) {
880 $root.find('a.searchview_group_string').click(function (e) {890 $root.find('a.searchview_group_string').click(function (e) {
881 $root.toggleClass('folded expanded');891 $root.toggleClass('folded expanded');
@@ -884,13 +894,24 @@
884 });894 });
885};895};
886instance.web.search.Group = instance.web.search.Widget.extend({896instance.web.search.Group = instance.web.search.Widget.extend({
887 template: 'SearchView.group',897 init: function (parent, icon, node) {
888 init: function (view_section, view, fields) {898 this._super(parent);
889 this._super(view);899 var attrs = node.attrs;
890 this.attrs = view_section.attrs;900 this.modifiers = attrs.modifiers =
891 this.lines = view.make_widgets(901 attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
892 view_section.children, fields);902 this.attrs = attrs;
893 }903 this.icon = icon;
904 this.name = attrs.string;
905 this.children = [];
906
907 this.view.controls.push(this);
908 },
909 push: function (input) {
910 this.children.push(input);
911 },
912 visible: function () {
913 return !this.modifiers.invisible;
914 },
894});915});
895916
896instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{917instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{
@@ -899,12 +920,12 @@
899 * @constructs instance.web.search.Input920 * @constructs instance.web.search.Input
900 * @extends instance.web.search.Widget921 * @extends instance.web.search.Widget
901 *922 *
902 * @param view923 * @param parent
903 */924 */
904 init: function (view) {925 init: function (parent) {
905 this._super(view);926 this._super(parent);
927 this.load_attrs({});
906 this.view.inputs.push(this);928 this.view.inputs.push(this);
907 this.style = undefined;
908 },929 },
909 /**930 /**
910 * Fetch auto-completion values for the widget.931 * Fetch auto-completion values for the widget.
@@ -952,15 +973,30 @@
952 "get_domain not implemented for widget " + this.attrs.type);973 "get_domain not implemented for widget " + this.attrs.type);
953 },974 },
954 load_attrs: function (attrs) {975 load_attrs: function (attrs) {
955 if (attrs.modifiers) {976 attrs.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
956 attrs.modifiers = JSON.parse(attrs.modifiers);977 this.attrs = attrs;
957 attrs.invisible = attrs.modifiers.invisible || false;978 },
958 if (attrs.invisible) {979 /**
959 this.style = 'display: none;'980 * Returns whether the input is "visible". The default behavior is to
981 * query the ``modifiers.invisible`` flag on the input's description or
982 * view node.
983 *
984 * @returns {Boolean}
985 */
986 visible: function () {
987 if (this.attrs.modifiers.invisible) {
988 return false;
989 }
990 var parent = this;
991 while ((parent = parent.getParent()) &&
992 ( (parent instanceof instance.web.search.Group)
993 || (parent instanceof instance.web.search.Input))) {
994 if (!parent.visible()) {
995 return false;
960 }996 }
961 }997 }
962 this.attrs = attrs;998 return true;
963 }999 },
964});1000});
965instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{1001instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{
966 template: 'SearchView.filters',1002 template: 'SearchView.filters',
@@ -974,17 +1010,17 @@
974 * @extends instance.web.search.Input1010 * @extends instance.web.search.Input
975 *1011 *
976 * @param {Array<instance.web.search.Filter>} filters elements of the group1012 * @param {Array<instance.web.search.Filter>} filters elements of the group
977 * @param {instance.web.SearchView} view view in which the filters are contained1013 * @param {instance.web.SearchView} parent parent in which the filters are contained
978 */1014 */
979 init: function (filters, view) {1015 init: function (filters, parent) {
980 // If all filters are group_by and we're not initializing a GroupbyGroup,1016 // If all filters are group_by and we're not initializing a GroupbyGroup,
981 // create a GroupbyGroup instead of the current FilterGroup1017 // create a GroupbyGroup instead of the current FilterGroup
982 if (!(this instanceof instance.web.search.GroupbyGroup) &&1018 if (!(this instanceof instance.web.search.GroupbyGroup) &&
983 _(filters).all(function (f) {1019 _(filters).all(function (f) {
984 return f.attrs.context && f.attrs.context.group_by; })) {1020 return f.attrs.context && f.attrs.context.group_by; })) {
985 return new instance.web.search.GroupbyGroup(filters, view);1021 return new instance.web.search.GroupbyGroup(filters, parent);
986 }1022 }
987 this._super(view);1023 this._super(parent);
988 this.filters = filters;1024 this.filters = filters;
989 this.view.query.on('add remove change reset', this.proxy('search_change'));1025 this.view.query.on('add remove change reset', this.proxy('search_change'));
990 },1026 },
@@ -1103,6 +1139,7 @@
1103 var self = this;1139 var self = this;
1104 item = item.toLowerCase();1140 item = item.toLowerCase();
1105 var facet_values = _(this.filters).chain()1141 var facet_values = _(this.filters).chain()
1142 .filter(function (filter) { return filter.visible(); })
1106 .filter(function (filter) {1143 .filter(function (filter) {
1107 var at = {1144 var at = {
1108 string: filter.attrs.string || '',1145 string: filter.attrs.string || '',
@@ -1129,8 +1166,8 @@
1129instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({1166instance.web.search.GroupbyGroup = instance.web.search.FilterGroup.extend({
1130 icon: 'w',1167 icon: 'w',
1131 completion_label: _lt("Group by: %s"),1168 completion_label: _lt("Group by: %s"),
1132 init: function (filters, view) {1169 init: function (filters, parent) {
1133 this._super(filters, view);1170 this._super(filters, parent);
1134 // Not flanders: facet unicity is handled through the1171 // Not flanders: facet unicity is handled through the
1135 // (category, field) pair of facet attributes. This is all well and1172 // (category, field) pair of facet attributes. This is all well and
1136 // good for regular filter groups where a group matches a facet, but for1173 // good for regular filter groups where a group matches a facet, but for
@@ -1138,8 +1175,8 @@
1138 // view which proxies to the first GroupbyGroup, so it can be used1175 // view which proxies to the first GroupbyGroup, so it can be used
1139 // for every GroupbyGroup and still provides the various methods needed1176 // for every GroupbyGroup and still provides the various methods needed
1140 // by the search view. Use weirdo name to avoid risks of conflicts1177 // by the search view. Use weirdo name to avoid risks of conflicts
1141 if (!this.getParent()._s_groupby) {1178 if (!this.view._s_groupby) {
1142 this.getParent()._s_groupby = {1179 this.view._s_groupby = {
1143 help: "See GroupbyGroup#init",1180 help: "See GroupbyGroup#init",
1144 get_context: this.proxy('get_context'),1181 get_context: this.proxy('get_context'),
1145 get_domain: this.proxy('get_domain'),1182 get_domain: this.proxy('get_domain'),
@@ -1148,7 +1185,7 @@
1148 }1185 }
1149 },1186 },
1150 match_facet: function (facet) {1187 match_facet: function (facet) {
1151 return facet.get('field') === this.getParent()._s_groupby;1188 return facet.get('field') === this.view._s_groupby;
1152 },1189 },
1153 make_facet: function (values) {1190 make_facet: function (values) {
1154 return {1191 return {
@@ -1173,10 +1210,10 @@
1173 * @extends instance.web.search.Input1210 * @extends instance.web.search.Input
1174 *1211 *
1175 * @param node1212 * @param node
1176 * @param view1213 * @param parent
1177 */1214 */
1178 init: function (node, view) {1215 init: function (node, parent) {
1179 this._super(view);1216 this._super(parent);
1180 this.load_attrs(node.attrs);1217 this.load_attrs(node.attrs);
1181 },1218 },
1182 facet_for: function () { return $.when(null); },1219 facet_for: function () { return $.when(null); },
@@ -1192,10 +1229,10 @@
1192 *1229 *
1193 * @param view_section1230 * @param view_section
1194 * @param field1231 * @param field
1195 * @param view1232 * @param parent
1196 */1233 */
1197 init: function (view_section, field, view) {1234 init: function (view_section, field, parent) {
1198 this._super(view);1235 this._super(parent);
1199 this.load_attrs(_.extend({}, field, view_section.attrs));1236 this.load_attrs(_.extend({}, field, view_section.attrs));
1200 },1237 },
1201 facet_for: function (value) {1238 facet_for: function (value) {
@@ -1235,7 +1272,7 @@
1235 *1272 *
1236 * @param {String} name the field's name1273 * @param {String} name the field's name
1237 * @param {String} operator the field's operator (either attribute-specified or default operator for the field1274 * @param {String} operator the field's operator (either attribute-specified or default operator for the field
1238 * @param {Number|String} value parsed value for the field1275 * @param {Number|String} facet parsed value for the field
1239 * @returns {Array<Array>} domain to include in the resulting search1276 * @returns {Array<Array>} domain to include in the resulting search
1240 */1277 */
1241 make_domain: function (name, operator, facet) {1278 make_domain: function (name, operator, facet) {
@@ -1467,8 +1504,8 @@
1467});1504});
1468instance.web.search.ManyToOneField = instance.web.search.CharField.extend({1505instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
1469 default_operator: {},1506 default_operator: {},
1470 init: function (view_section, field, view) {1507 init: function (view_section, field, parent) {
1471 this._super(view_section, field, view);1508 this._super(view_section, field, parent);
1472 this.model = new instance.web.Model(this.attrs.relation);1509 this.model = new instance.web.Model(this.attrs.relation);
1473 },1510 },
1474 complete: function (needle) {1511 complete: function (needle) {
@@ -1703,22 +1740,28 @@
1703 var running_count = 0;1740 var running_count = 0;
1704 // get total filters count1741 // get total filters count
1705 var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; };1742 var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; };
1706 var filters_count = _(this.view.controls).chain()1743 var visible_filters = _(this.view.controls).chain().reject(function (group) {
1744 return _(_(group.children).filter(is_group)).isEmpty()
1745 || group.modifiers.invisible;
1746 });
1747 var filters_count = visible_filters
1748 .pluck('children')
1707 .flatten()1749 .flatten()
1708 .filter(is_group)1750 .filter(is_group)
1709 .map(function (i) { return i.filters.length; })1751 .map(function (i) { return i.filters.length; })
1710 .sum()1752 .sum()
1711 .value();1753 .value();
17121754
1713 var col1 = [], col2 = _(this.view.controls).map(function (inputs, group) {1755 var col1 = [], col2 = visible_filters.map(function (group) {
1714 var filters = _(inputs).filter(is_group);1756 var filters = _(group.children).filter(is_group);
1715 return {1757 return {
1716 name: group === 'null' ? "<span class='oe_i'>q</span> " + _t("Filters") : "<span class='oe_i'>w</span> " + group,1758 name: _.str.sprintf("<span class='oe_i'>%s</span> %s",
1717 filters: filters,1759 group.icon, group.name),
1718 length: _(filters).chain().map(function (i) {1760 filters: filters,
1719 return i.filters.length; }).sum().value()1761 length: _(filters).chain().map(function (i) {
1720 };1762 return i.filters.length; }).sum().value()
1721 });1763 };
1764 }).value();
17221765
1723 while (col2.length) {1766 while (col2.length) {
1724 // col1 + group should be smaller than col2 + group1767 // col1 + group should be smaller than col2 + group
17251768
=== modified file 'addons/web/static/src/xml/base.xml'
--- addons/web/static/src/xml/base.xml 2013-02-13 15:21:58 +0000
+++ addons/web/static/src/xml/base.xml 2013-02-14 08:53:20 +0000
@@ -1496,7 +1496,7 @@
1496 <t t-esc="attrs.string"/>1496 <t t-esc="attrs.string"/>
1497</button>1497</button>
1498<ul t-name="SearchView.filters">1498<ul t-name="SearchView.filters">
1499 <li t-foreach="widget.filters" t-as="filter"1499 <li t-foreach="widget.filters" t-as="filter" t-if="filter.visible()"
1500 t-att-title="filter.attrs.string ? filter.attrs.help : undefined">1500 t-att-title="filter.attrs.string ? filter.attrs.help : undefined">
1501 <t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/>1501 <t t-esc="filter.attrs.string or filter.attrs.help or filter.attrs.name or 'Ω'"/>
1502 </li>1502 </li>
@@ -1584,15 +1584,6 @@
1584 </div>1584 </div>
1585 </div>1585 </div>
1586</t>1586</t>
1587<t t-name="SearchView.group">
1588 <t t-call="SearchView.util.expand">
1589 <t t-set="expand" t-value="attrs.expand"/>
1590 <t t-set="label" t-value="attrs.string"/>
1591 <t t-set="content">
1592 <t t-call="SearchView.render_lines"/>
1593 </t>
1594 </t>
1595</t>
1596<div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section">1587<div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section">
15971588
1598</div>1589</div>
15991590
=== modified file 'addons/web/static/test/search.js'
--- addons/web/static/test/search.js 2013-01-31 11:26:17 +0000
+++ addons/web/static/test/search.js 2013-02-14 08:53:20 +0000
@@ -1,4 +1,4 @@
1openerp.testing.section('query', {1openerp.testing.section('search.query', {
2 dependencies: ['web.search']2 dependencies: ['web.search']
3}, function (test) {3}, function (test) {
4 test('Adding a facet to the query creates a facet and a value', function (instance) {4 test('Adding a facet to the query creates a facet and a value', function (instance) {
@@ -180,7 +180,7 @@
180 });180 });
181 return view;181 return view;
182};182};
183openerp.testing.section('defaults', {183openerp.testing.section('search.defaults', {
184 dependencies: ['web.search'],184 dependencies: ['web.search'],
185 rpc: 'mock',185 rpc: 'mock',
186 templates: true,186 templates: true,
@@ -331,7 +331,7 @@
331 });331 });
332 });332 });
333});333});
334openerp.testing.section('completions', {334openerp.testing.section('search.completions', {
335 dependencies: ['web.search'],335 dependencies: ['web.search'],
336 rpc: 'mock',336 rpc: 'mock',
337 templates: true337 templates: true
@@ -564,7 +564,7 @@
564 });564 });
565 });565 });
566});566});
567openerp.testing.section('search-serialization', {567openerp.testing.section('search.serialization', {
568 dependencies: ['web.search'],568 dependencies: ['web.search'],
569 rpc: 'mock',569 rpc: 'mock',
570 templates: true570 templates: true
@@ -871,7 +871,7 @@
871 return $.when(t1, t2);871 return $.when(t1, t2);
872 });872 });
873});873});
874openerp.testing.section('removal', {874openerp.testing.section('search.removal', {
875 dependencies: ['web.search'],875 dependencies: ['web.search'],
876 rpc: 'mock',876 rpc: 'mock',
877 templates: true877 templates: true
@@ -894,7 +894,7 @@
894 });894 });
895 });895 });
896});896});
897openerp.testing.section('drawer', {897openerp.testing.section('search.drawer', {
898 dependencies: ['web.search'],898 dependencies: ['web.search'],
899 rpc: 'mock',899 rpc: 'mock',
900 templates: true900 templates: true
@@ -910,7 +910,7 @@
910 });910 });
911 });911 });
912});912});
913openerp.testing.section('filters', {913openerp.testing.section('search.filters', {
914 dependencies: ['web.search'],914 dependencies: ['web.search'],
915 rpc: 'mock',915 rpc: 'mock',
916 templates: true,916 templates: true,
@@ -995,7 +995,7 @@
995 });995 });
996 });996 });
997});997});
998openerp.testing.section('saved_filters', {998openerp.testing.section('search.filters.saved', {
999 dependencies: ['web.search'],999 dependencies: ['web.search'],
1000 rpc: 'mock',1000 rpc: 'mock',
1001 templates: true1001 templates: true
@@ -1077,7 +1077,7 @@
1077 });1077 });
1078 });1078 });
1079});1079});
1080openerp.testing.section('advanced', {1080openerp.testing.section('search.advanced', {
1081 dependencies: ['web.search'],1081 dependencies: ['web.search'],
1082 rpc: 'mock',1082 rpc: 'mock',
1083 templates: true1083 templates: true
@@ -1158,3 +1158,159 @@
1158 });1158 });
1159 // TODO: UI tests?1159 // TODO: UI tests?
1160});1160});
1161openerp.testing.section('search.invisible', {
1162 dependencies: ['web.search'],
1163 rpc: 'mock',
1164 templates: true,
1165}, function (test) {
1166 var registerTestField = function (instance, methods) {
1167 instance.web.search.fields.add('test', 'instance.testing.TestWidget');
1168 instance.testing = {
1169 TestWidget: instance.web.search.Field.extend(methods),
1170 };
1171 };
1172 var makeView = function (instance, mock, fields, arch, defaults) {
1173 mock('ir.filters:get_filters', function () { return []; });
1174 mock('test.model:fields_get', function () { return fields; });
1175 mock('test.model:fields_view_get', function () {
1176 return { type: 'search', fields: fields, arch: arch };
1177 });
1178 var ds = new instance.web.DataSet(null, 'test.model');
1179 return new instance.web.SearchView(null, ds, false, defaults);
1180 };
1181 // Invisible fields should not auto-complete
1182 test('invisible-field-no-autocomplete', {asserts: 1}, function (instance, $fix, mock) {
1183 registerTestField(instance, {
1184 complete: function () {
1185 return $.when([{label: this.attrs.string}]);
1186 },
1187 });
1188 var view = makeView(instance, mock, {
1189 field0: {type: 'test', string: 'Field 0'},
1190 field1: {type: 'test', string: 'Field 1'},
1191 }, ['<search>',
1192 '<field name="field0"/>',
1193 '<field name="field1" modifiers="{&quot;invisible&quot;: true}"/>',
1194 '</search>'].join());
1195 return view.appendTo($fix)
1196 .then(function () {
1197 var done = $.Deferred();
1198 view.complete_global_search({term: 'test'}, function (comps) {
1199 done.resolve(comps);
1200 });
1201 return done;
1202 }).then(function (completions) {
1203 deepEqual(completions, [{label: 'Field 0'}],
1204 "should only complete the visible field");
1205 });
1206 });
1207 // Invisible filters should not appear in the drawer
1208 test('invisible-filter-no-drawer', {asserts: 4}, function (instance, $fix, mock) {
1209 var view = makeView(instance, mock, {}, [
1210 '<search>',
1211 '<filter string="filter 0"/>',
1212 '<filter string="filter 1" modifiers="{&quot;invisible&quot;: true}"/>',
1213 '</search>'].join());
1214 return view.appendTo($fix)
1215 .then(function () {
1216 var $fs = $fix.find('.oe_searchview_filters ul');
1217 strictEqual($fs.children().length,
1218 1,
1219 "should only display one filter");
1220 strictEqual(_.str.trim($fs.children().text()),
1221 "filter 0",
1222 "should only display filter 0");
1223 var done = $.Deferred();
1224 view.complete_global_search({term: 'filter'}, function (comps) {
1225 done.resolve();
1226 strictEqual(comps.length, 1, "should only complete visible filter");
1227 strictEqual(comps[0].label, "Filter on: filter 0",
1228 "should complete filter 0");
1229 });
1230 return done;
1231 });
1232 });
1233 // Invisible filter groups should not appear in the drawer
1234 // Group invisibility should be inherited by children
1235 test('group-invisibility', {asserts: 6}, function (instance, $fix, mock) {
1236 registerTestField(instance, {
1237 complete: function () {
1238 return $.when([{label: this.attrs.string}]);
1239 },
1240 });
1241 var view = makeView(instance, mock, {
1242 field0: {type: 'test', string: 'Field 0'},
1243 field1: {type: 'test', string: 'Field 1'},
1244 }, [
1245 '<search>',
1246 '<group string="Visibles">',
1247 '<field name="field0"/>',
1248 '<filter string="Filter 0"/>',
1249 '</group>',
1250 '<group string="Invisibles" modifiers="{&quot;invisible&quot;: true}">',
1251 '<field name="field1"/>',
1252 '<filter string="Filter 1"/>',
1253 '</group>',
1254 '</search>'
1255 ].join(''));
1256 return view.appendTo($fix)
1257 .then(function () {
1258 strictEqual($fix.find('.oe_searchview_filters h3').length,
1259 1,
1260 "should only display one group");
1261 strictEqual($fix.find('.oe_searchview_filters h3').text(),
1262 'w Visibles',
1263 "should only display the Visibles group (and its icon char)");
1264
1265 var $fs = $fix.find('.oe_searchview_filters ul');
1266 strictEqual($fs.children().length, 1,
1267 "should only have one filter in the drawer");
1268 strictEqual(_.str.trim($fs.text()), "Filter 0",
1269 "should have filter 0 as sole filter");
1270
1271 var done = $.Deferred();
1272 view.complete_global_search({term: 'filter'}, function (compls) {
1273 done.resolve();
1274 strictEqual(compls.length, 2,
1275 "should have 2 completions");
1276 deepEqual(_.pluck(compls, 'label'),
1277 ['Field 0', 'Filter on: Filter 0'],
1278 "should complete on field 0 and filter 0");
1279 });
1280 return done;
1281 });
1282 });
1283 // Default on invisible fields should still work, for fields and filters both
1284 test('invisible-defaults', {asserts: 1}, function (instance, $fix, mock) {
1285 var view = makeView(instance, mock, {
1286 field: {type: 'char', string: "Field"},
1287 field2: {type: 'char', string: "Field 2"},
1288 }, [
1289 '<search>',
1290 '<field name="field2"/>',
1291 '<filter name="filter2" string="Filter"',
1292 ' domain="[[\'qwa\', \'=\', 42]]"/>',
1293 '<group string="Invisibles" modifiers="{&quot;invisible&quot;: true}">',
1294 '<field name="field"/>',
1295 '<filter name="filter" string="Filter"',
1296 ' domain="[[\'whee\', \'=\', \'42\']]"/>',
1297 '</group>',
1298 '</search>'
1299 ].join(''), {field: "foo", filter: true});
1300
1301 return view.appendTo($fix)
1302 .then(function () {
1303 deepEqual(view.build_search_data(), {
1304 errors: [],
1305 groupbys: [],
1306 contexts: [],
1307 domains: [
1308 // Generated from field
1309 [['field', 'ilike', 'foo']],
1310 // generated from filter
1311 "[['whee', '=', '42']]"
1312 ],
1313 }, "should yield invisible fields selected by defaults");
1314 });
1315 });
1316});