summaryrefslogtreecommitdiff
path: root/addons/web/static/tests
diff options
context:
space:
mode:
authorMathieu Duckerts-Antoine <dam@odoo.com>2018-06-11 15:45:30 +0200
committerGéry Debongnie <ged@odoo.com>2018-06-12 09:22:20 +0200
commite1aac71769c8d07a358b2d8717efa28248e18a34 (patch)
treeba63c14ad7aee42fc1e5fbd9b9b25f31ef1e69cc /addons/web/static/tests
parent7bf5dd687be3919f6add10b1ac59a9cdee824632 (diff)
[IMP] web: add support for interval selection in groupbys
In this commit, we extend the search view to allow the selection of some intervals for date or datetime fields in groupby menu. The valid intervals are day, week, month, quarter, and year. It is also possible to add a button 'Group By' in the control panel owned by a graph view in case it is declared 'embedded' (e.g. in a dashboard view). Note that have three new generic reusable widgets have been introduced in this commit: DropdownMenu, GroupByMenu, and FiltersMenu.
Diffstat (limited to 'addons/web/static/tests')
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js6
-rw-r--r--addons/web/static/tests/views/graph_tests.js6
-rw-r--r--addons/web/static/tests/views/search_filters_menu_tests.js135
-rw-r--r--addons/web/static/tests/views/search_groupby_menu_tests.js133
-rw-r--r--addons/web/static/tests/views/search_view_tests.js299
-rw-r--r--addons/web/static/tests/widgets/dropdown_menu_tests.js201
6 files changed, 776 insertions, 4 deletions
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index b9aa2d30253..9b42c518891 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -425,9 +425,8 @@ QUnit.module('relational_fields', {
assert.strictEqual($('tr.o_data_row').length, 9, "should display 9 records");
- $('.o_searchview_more').click(); // Magnifying class for more filters
$('button:contains(Filters)').click();
- $('.o_add_filter').click(); // Add a custom filter, datetime field is selected
+ $('.o_add_custom_filter:visible').click(); // Add a custom filter, datetime field is selected
assert.strictEqual($('li.o_filter_condition select.o_searchview_extended_prop_field').val(), 'datetime',
"datetime field should be selected");
$('.o_apply_filter').click();
@@ -11132,11 +11131,12 @@ QUnit.module('relational_fields', {
},
});
+
var $dropdown = form.$('.o_field_many2one input').autocomplete('widget');
form.$('.o_field_many2one input').click();
$dropdown.find('.o_m2o_dropdown_option:contains(Search)').mouseenter().click(); // Open Search More
- $('.modal .o_searchview_more').click(); // Magnifying class for more filters
+ $('.modal .o_search_options .fa-bars').click();
$('.modal .o_search_options .o_group_by_menu a:contains(Bar)').click(); // group by 'Bar'
$('.modal .o_group_header:first').click();
diff --git a/addons/web/static/tests/views/graph_tests.js b/addons/web/static/tests/views/graph_tests.js
index 028eb4cb3a3..5f7227815c1 100644
--- a/addons/web/static/tests/views/graph_tests.js
+++ b/addons/web/static/tests/views/graph_tests.js
@@ -248,7 +248,7 @@ QUnit.module('Views', {
});
});
- QUnit.test('getContext correctly returns mode, measure and groupbys', function (assert) {
+ QUnit.test('getContext correctly returns mode, measure, groupbys and interval mapping', function (assert) {
var done = assert.async();
assert.expect(4);
@@ -265,6 +265,7 @@ QUnit.module('Views', {
graph_mode: 'bar',
graph_measure: '__count__',
graph_groupbys: ['product_id'],
+ graph_intervalMapping: {},
}, "context should be correct");
graph.$buttons.find('li[data-field="foo"] a').click(); // change measure
@@ -275,6 +276,7 @@ QUnit.module('Views', {
graph_mode: 'bar',
graph_measure: 'foo',
graph_groupbys: ['product_id'],
+ graph_intervalMapping: {},
}, "context should be correct");
graph.$buttons.find('button[data-mode="line"]').click(); // change mode
@@ -285,6 +287,7 @@ QUnit.module('Views', {
graph_mode: 'line',
graph_measure: 'foo',
graph_groupbys: ['product_id'],
+ graph_intervalMapping: {},
}, "context should be correct");
graph.update({groupBy: ['product_id', 'color_id']}); // change groupbys
@@ -295,6 +298,7 @@ QUnit.module('Views', {
graph_mode: 'line',
graph_measure: 'foo',
graph_groupbys: ['product_id', 'color_id'],
+ graph_intervalMapping: {},
}, "context should be correct");
graph.destroy();
diff --git a/addons/web/static/tests/views/search_filters_menu_tests.js b/addons/web/static/tests/views/search_filters_menu_tests.js
new file mode 100644
index 00000000000..84bc1df86d8
--- /dev/null
+++ b/addons/web/static/tests/views/search_filters_menu_tests.js
@@ -0,0 +1,135 @@
+odoo.define('web.search_filters_menu_tests', function (require) {
+"use strict";
+
+var FiltersMenu = require('web.FiltersMenu');
+var testUtils = require('web.test_utils');
+
+function createFiltersMenu(filters, fields, params) {
+ params = params || {};
+ var target = params.debug ? document.body : $('#qunit-fixture');
+ var menu = new FiltersMenu(null, filters, fields);
+ testUtils.addMockEnvironment(menu, params);
+ menu.appendTo(target);
+ return menu;
+}
+
+QUnit.module('FiltersMenu', {
+ beforeEach: function () {
+ this.filters = [
+ {
+ isActive: false,
+ description: 'some filter',
+ domain: '',
+ name: 'red',
+ groupId: 1,
+ },
+ ];
+ this.fields = {
+ boolean_field: {string: "Boolean Field", type: "boolean", default: true, searchable: true},
+ date_field: {string: "A date", type: "date", searchable: true},
+ char_field: {string: "Char Field", type: "char", default: "foo", searchable: true, trim: true},
+ };
+ },
+}, function () {
+
+ QUnit.test('simple rendering with no filter', function (assert) {
+ assert.expect(4);
+
+ var filterMenu = createFiltersMenu([], this.fields);
+ filterMenu.$('span.fa-filter').click();
+ assert.strictEqual(filterMenu.$('.divider').length, 1);
+ assert.ok(!filterMenu.$('.divider').is(':visible'));
+ assert.strictEqual(filterMenu.$('li').length, 3,
+ 'should have 3 li: a hidden divider, a add custom filter item, a apply button + add condition button');
+ assert.strictEqual(filterMenu.$('li.o_add_custom_filter.o_closed_menu').length, 1);
+ filterMenu.destroy();
+ });
+
+ QUnit.test('simple rendering with a filter', function (assert) {
+ assert.expect(2);
+
+ var filterMenu = createFiltersMenu(this.filters, this.fields);
+ assert.strictEqual(filterMenu.$('li').length, 5,
+ 'should have 4 li: a hidden, separator, a filter, a separator, a add custom filter item, a apply button + add condition button');
+ assert.strictEqual(filterMenu.$('li.o_add_custom_filter.o_closed_menu').length, 1);
+ filterMenu.destroy();
+ });
+
+ QUnit.test('click on add custom filter opens the submenu', function (assert) {
+ assert.expect(3);
+
+ var filterMenu = createFiltersMenu([], this.fields);
+ // open menu dropdown
+ filterMenu.$('span.fa-filter').click();
+ // open add custom filter submenu
+ filterMenu.$('li.o_add_custom_filter').click();
+ assert.ok(filterMenu.$('li.o_add_custom_filter').hasClass('o_open_menu'));
+ assert.ok(filterMenu.$('li.o_add_filter_menu').is(':visible'));
+ assert.strictEqual(filterMenu.$('li').length, 4,
+ 'should have 3 li: a hidden divider, a add custom filter item, a proposition, a apply button + add condition button');
+
+ filterMenu.destroy();
+ });
+
+ QUnit.test('removing last prop disable the apply button', function (assert) {
+ assert.expect(2);
+
+ var filterMenu = createFiltersMenu([], this.fields);
+ // open menu dropdown and custom filter submenu
+ filterMenu.$('span.fa-filter').click();
+ filterMenu.$('li.o_add_custom_filter').click();
+
+ // remove the current unique proposition
+ filterMenu.$('.o_searchview_extended_delete_prop').click();
+
+ assert.ok(filterMenu.$('.o_apply_filter').attr('disabled'));
+
+ assert.strictEqual(filterMenu.$('li').length, 3,
+ 'should have 3 li: a hidden separator, a add custom filter item, a apply button + add condition button');
+
+ filterMenu.destroy();
+ });
+
+ QUnit.test('readding a proposition reenable apply button', function (assert) {
+ assert.expect(1);
+
+ var filterMenu = createFiltersMenu([], this.fields);
+
+ // open menu dropdown and custom filter submenu, remove existing prop
+ filterMenu.$('span.fa-filter').click();
+ filterMenu.$('li.o_add_custom_filter').click();
+ filterMenu.$('.o_searchview_extended_delete_prop').click();
+
+ // read a proposition
+ filterMenu.$('.o_add_condition').click();
+
+ assert.ok(!filterMenu.$('.o_apply_filter').attr('disabled'));
+
+ filterMenu.destroy();
+ });
+
+ QUnit.test('adding a simple filter works', function (assert) {
+ assert.expect(3);
+ delete this.fields.date_field;
+
+ var filterMenu = createFiltersMenu([], this.fields);
+
+ // open menu dropdown and custom filter submenu, remove existing prop
+ filterMenu.$('span.fa-filter').click();
+ filterMenu.$('li.o_add_custom_filter').click();
+
+ // click on apply to activate filter
+ filterMenu.$('.o_apply_filter').click();
+
+ assert.ok(filterMenu.$('.o_add_custom_filter').hasClass('o_closed_menu'));
+ assert.strictEqual(filterMenu.$('.o_filter_condition').length, 0);
+ assert.strictEqual(filterMenu.$('.divider:visible').length, 1,
+ 'there should be a separator between filters and add custom filter menu');
+
+ filterMenu.destroy();
+ });
+
+
+
+});
+});
diff --git a/addons/web/static/tests/views/search_groupby_menu_tests.js b/addons/web/static/tests/views/search_groupby_menu_tests.js
new file mode 100644
index 00000000000..e403c08a97c
--- /dev/null
+++ b/addons/web/static/tests/views/search_groupby_menu_tests.js
@@ -0,0 +1,133 @@
+odoo.define('web.search_groupby_menu_tests', function (require) {
+"use strict";
+
+var GroupByMenu = require('web.GroupByMenu');
+var testUtils = require('web.test_utils');
+
+function createGroupByMenu(groupbys, fields, params) {
+ params = params || {};
+ var target = params.debug ? document.body : $('#qunit-fixture');
+ var menu = new GroupByMenu(null, groupbys, fields);
+ testUtils.addMockEnvironment(menu, params);
+ menu.appendTo(target);
+ return menu;
+}
+
+QUnit.module('GroupByMenu', {
+ beforeEach: function () {
+ this.groupbys = [
+ {
+ isActive: false,
+ description: 'some group by',
+ fieldName: 'fieldname',
+ itemId: 'red',
+ groupId: 1,
+ },
+ ];
+ this.fields = {
+ fieldname: {sortable: true, string: 'Super Date', type: 'date', isDate: true}
+ };
+ },
+}, function () {
+
+ QUnit.test('simple rendering', function (assert) {
+ assert.expect(2);
+
+ var groupByMenu = createGroupByMenu(this.groupbys, this.fields);
+ groupByMenu.$('button:first').click();
+ assert.strictEqual(groupByMenu.$('li').length, 2, 'should have 2 li');
+ assert.strictEqual(groupByMenu.$('li:nth-child(2)').text().trim(), 'some group by',
+ 'should have proper filter name');
+ groupByMenu.destroy();
+ });
+
+ QUnit.test('simple rendering with no filter and no field', function (assert) {
+ assert.expect(1);
+
+ var groupByMenu = createGroupByMenu([], {});
+ groupByMenu.$('button:first').click();
+ assert.strictEqual(groupByMenu.$('li').length, 0, 'should have 0 li');
+ groupByMenu.destroy();
+ });
+
+ QUnit.test('simple rendering with no filter but fields', function (assert) {
+ assert.expect(1);
+
+ var groupByMenu = createGroupByMenu(
+ [],
+ {fieldname: {sortable: true, string: 'Super Date', type: 'date', isDate: true}}
+ );
+ groupByMenu.$('button:first').click();
+ assert.strictEqual(groupByMenu.$('li').length, 1, 'should have 1 li');
+ groupByMenu.destroy();
+ });
+
+ QUnit.test('click on add custom group toggle group selector', function (assert) {
+ assert.expect(2);
+
+ var groupByMenu = createGroupByMenu([], {fieldname: {sortable: true, string: 'Super Date', type: 'date', isDate: true}});
+ groupByMenu.$('button:first').click();
+ var selector = groupByMenu.$('select.o_group_selector');
+ assert.ok(!selector.is(":visible"), 'should be invisible');
+ groupByMenu.$('.o_add_custom_group a').click();
+ selector = groupByMenu.$('select.o_group_selector');
+ assert.ok(selector.is(":visible"), 'should be visible');
+ groupByMenu.destroy();
+ });
+
+ QUnit.test('select a group using the group selector add properly add that group to menu', function (assert) {
+ assert.expect(2);
+
+ var groupByMenu = createGroupByMenu(
+ [],
+ {
+ fieldName: {sortable: true, name: 'candlelight', string: 'Candlelight', type: 'boolean'},
+ }
+ );
+ groupByMenu.$('button:first').click();
+ groupByMenu.$('li.o_add_custom_group a').click();
+ assert.strictEqual(groupByMenu.$('select').val(), 'fieldName',
+ 'the select value should be "fieldName"');
+ groupByMenu.$('button.o_apply_group').click();
+ assert.strictEqual(groupByMenu.$('li.o_menu_item.selected').length, 1, 'there should be a groupby selected');
+ groupByMenu.destroy();
+ });
+
+ QUnit.test('click on groupby filter should activate it', function (assert) {
+ assert.expect(5);
+
+ var groupByMenu = createGroupByMenu(this.groupbys,
+ {fieldname: {sortable: true, string: 'Super Date', type: 'date', isDate: true}}, {
+ intercepts: {
+ menu_item_toggled: function(ev) {
+ assert.strictEqual(ev.data.itemId, 'red');
+ assert.strictEqual(ev.data.isActive, true);
+ },
+ },
+ });
+ groupByMenu.$('button:first').click();
+ assert.ok(!groupByMenu.$('.o_menu_item:first').hasClass('selected'));
+ groupByMenu.$('.o_menu_item a').first().click();
+ assert.ok(groupByMenu.$('.o_menu_item:first').hasClass('selected'));
+ assert.ok(groupByMenu.$('.o_menu_item:first').is(':visible'),
+ 'group by filter should still be visible');
+
+ groupByMenu.destroy();
+ });
+
+ QUnit.test('click on groupby filter should not change url', function (assert) {
+ assert.expect(0);
+
+ var groupByMenu = createGroupByMenu(this.groupbys,
+ {fieldname: {sortable: true, string: 'Super Date', type: 'date', isDate: true}}
+ );
+ groupByMenu.$el.click(function(event) {
+ // we do not want a click to get out and change the url, for example
+ throw new Error('No click should get out of the groupby menu');
+ });
+ groupByMenu.$('.o_menu_item a').first().click();
+
+ groupByMenu.destroy();
+ });
+});
+});
diff --git a/addons/web/static/tests/views/search_view_tests.js b/addons/web/static/tests/views/search_view_tests.js
new file mode 100644
index 00000000000..391b3ed46f5
--- /dev/null
+++ b/addons/web/static/tests/views/search_view_tests.js
@@ -0,0 +1,299 @@
+odoo.define('web.search_view_tests', function (require) {
+"use strict";
+
+var testUtils = require('web.test_utils');
+var createActionManager = testUtils.createActionManager;
+
+QUnit.module('Search View', {
+ beforeEach: function () {
+ this.data = {
+ partner: {
+ fields: {
+ date_field: {string: "Date", type: "date", store: true, sortable: true},
+ Birthday: {string: "Birthday", type: "date", store: true, sortable: true},
+ foo: {string: "Foo", type: "char"},
+ bar: {string: "Bar", type: "many2one", relation: 'partner'},
+ float_field: {string: "Float", type: "float"},
+ },
+ records: [
+ {id: 1, display_name: "First record", foo: "yop", bar: 2, date_field: "2017-01-25", Birthday: "1983-07-15", float_field: 1},
+ {id: 2, display_name: "Second record", foo: "blip", bar: 1, date_field: "2017-01-24", Birthday: "1982-06-04",float_field: 2},
+ {id: 3, display_name: "Third record", foo: "gnap", bar: 1, date_field: "2017-01-13", Birthday: "1985-09-13",float_field: 1.618},
+ {id: 4, display_name: "Fourth record", foo: "plop", bar: 2, date_field: "2017-02-25", Birthday: "1983-05-05",float_field: -1},
+ {id: 5, display_name: "Fifth record", foo: "zoup", bar: 2, date_field: "2016-01-25", Birthday: "1800-01-01",float_field: 13},
+ ],
+ },
+ pony: {
+ fields: {
+ name: {string: 'Name', type: 'char'},
+ },
+ records: [
+ {id: 4, name: 'Twilight Sparkle'},
+ {id: 6, name: 'Applejack'},
+ {id: 9, name: 'Fluttershy'}
+ ],
+ },
+ };
+
+ this.actions = [{
+ id: 1,
+ name: 'Partners Action 1',
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[false, 'list']],
+ }, {
+ id: 2,
+ name: 'Partners Action 2',
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[false, 'list']],
+ search_view_id: [2, 'search'],
+ }, {
+ id: 3,
+ name: 'Partners Action 3',
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[false, 'kanban']],
+ search_view_id: [2, 'search'],
+ }, {
+ id: 4,
+ name: 'Partners Action 4',
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[false, 'graph']],
+ search_view_id: [3, 'search'],
+ }, {
+ id: 5,
+ name: 'Partners Action 5',
+ res_model: 'partner',
+ type: 'ir.actions.act_window',
+ views: [[2, 'list']],
+ search_view_id: [4, 'search'],
+ }
+ ];
+
+ this.archs = {
+ // list views
+ 'partner,false,list': '<tree><field name="foo"/></tree>',
+ 'partner,2,list': '<tree><field name="foo"/></tree>',
+
+ // kanban views
+ 'partner,false,kanban': '<kanban><templates><t t-name="kanban-box">' +
+ '<div class="oe_kanban_global_click"><field name="foo"/></div>' +
+ '</t></templates></kanban>',
+
+ // graph views
+ 'partner,false,graph': '<graph>' +
+ '<field name="date_field" type="row" interval="day"/>' +
+ '<field name="float_field" type="measure"/>' +
+ '</graph>',
+
+ // search views
+ 'partner,false,search': '<search>'+
+ '<field name="foo" string="Foo"/>' +
+ '<filter string="candle" name="itsName" context="{\'group_by\': \'foo\'}"/>' +
+ '</search>',
+ 'partner,2,search': '<search>'+
+ '<field name="date_field" string="Date"/>' +
+ '<filter string="Date" name="coolName" context="{\'group_by\': \'date_field\'}"/>' +
+ '</search>',
+ 'partner,3,search': '<search>'+
+ '<field name="date_field" string="Date"/>' +
+ '<filter string="float" name="positive" domain="[(\'float_field\', \'>=\', 0)]"/>' +
+ '</search>',
+ 'partner,4,search': '<search>'+
+ '<field name="date_field" string="Date"/>' +
+ '<filter string="Date" name="coolName" context="{\'group_by\': \'date_field:day\'}"/>' +
+ '</search>',
+ };
+ },
+}, function () {
+ QUnit.module('Groupby Menu');
+
+ QUnit.test('click on groupby filter adds a facet', function (assert) {
+ assert.expect(1);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(1);
+ $('span.fa-bars').prev().click();
+ $('li.o_menu_item a').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').text().trim(), 'candle',
+ 'should have a facet with candle name');
+ actionManager.destroy();
+ });
+
+ QUnit.test('remove a "Group By" facet properly unchecks groupbys in groupby menu', function (assert) {
+ assert.expect(2);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(1);
+ $('span.fa-bars').prev().click();
+ $('li.o_menu_item a').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').text().trim(), 'candle',
+ 'should have a facet with candle name');
+ $('.o_facet_remove:first').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0,
+ 'there should be no facet');
+ actionManager.destroy();
+ });
+
+ QUnit.test('change option of a "Group By" does not remove groupy in facet "Group By"', function (assert) {
+ assert.expect(3);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(2);
+ $('span.fa-bars').click();
+ $('.o_submenu_switcher').click();
+ $('.o_item_option:first').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 1,
+ 'should have a facet');
+ $('.o_item_option:nth-child(2)').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 1,
+ 'should have a facet');
+ $('.o_item_option:nth-child(2)').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0,
+ 'should have no facet');
+ actionManager.destroy();
+ });
+
+ QUnit.test('select and unselect quickly groupby does not crash', function (assert) {
+ assert.expect(1);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(3);
+ $('span.fa-bars').click();
+ $('.o_menu_item:first').click();
+ $('.o_menu_item:first').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0,
+ 'should have a facet');
+ actionManager.destroy();
+ });
+
+ QUnit.test('groupby selected within graph subview are not deleted when modifying search view content', function (assert) {
+ assert.expect(2);
+
+ this.actions[3].flags = {isEmbedded: true};
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(4);
+ $('.o_graph_buttons div.o_graph_groupbys_menu > button').click();
+ $('.o_graph_buttons div.o_graph_groupbys_menu .o_menu_item').click();
+ assert.ok(!$('.o_graph_buttons div.o_graph_groupbys_menu .o_menu_item').hasClass('selected'),
+ 'groupby should be unselected');
+ $('.o_search_options button span.fa-filter').click();
+ $('.o_filters_menu .o_menu_item a').click();
+ assert.ok(!$('.o_graph_buttons div.o_graph_groupbys_menu .o_menu_item').hasClass('selected'),
+ 'groupby should be still unselected');
+ actionManager.destroy();
+ });
+
+ QUnit.test('group by a date field using interval works', function (assert) {
+ assert.expect(9);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(5);
+ // open menu 'Group By'
+ $('.o_search_options .fa-bars').click();
+ // Activate the groupby 'Date'
+ $('.o_group_by_menu .o_menu_item').click();
+ // by default, data should be grouped by the field 'Date' using the interval 'day'
+ assert.strictEqual($('div.o_facet_values span').text().trim(),'Date: Day');
+ assert.strictEqual($('.o_content tr.o_group_header').length, 5);
+ // open submenu with interval options
+ $('.o_group_by_menu .o_menu_item .o_submenu_switcher').click();
+ // select option 'month'
+ $('.o_group_by_menu .o_menu_item .o_item_option:nth-child(3)').click();
+ // data should be grouped by the field 'Date' using the interval 'month'
+ assert.strictEqual($('div.o_facet_values span').text().trim(),'Date: Month');
+ assert.strictEqual($('.o_content tr.o_group_header').length, 3);
+ // deactivate option 'month'
+ $('.o_group_by_menu .o_menu_item .o_item_option:nth-child(3)').click();
+ // no groupby is applied
+ assert.strictEqual($('div.o_facet_values span').length, 0);
+ // open 'Add custom Groupby' menu
+ $('.o_group_by_menu .o_add_custom_group a').click();
+ // click on 'Apply' button
+ $('.o_group_by_menu .o_generator_menu button').click();
+ // data should be grouped by the field 'Birthday' using the interval 'month'
+ assert.strictEqual($('div.o_facet_values span').text().trim(),'Birthday: Month');
+ assert.strictEqual($('.o_content tr.o_group_header').length, 5);
+ // open submenu with interval options
+ $('.o_group_by_menu .o_menu_item .o_submenu_switcher').eq(1).click();
+ // select option 'year'
+ $('.o_group_by_menu .o_menu_item .o_item_option').eq(9).click();
+ // data should be grouped by the field 'Birthday' using the interval 'year'
+ assert.strictEqual($('div.o_facet_values span').text().trim(),'Birthday: Year');
+ assert.strictEqual($('.o_content tr.o_group_header').length, 4);
+ actionManager.destroy();
+ });
+
+ QUnit.module('Filters Menu');
+
+ QUnit.test('add a custom filter works', function (assert) {
+ assert.expect(1);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(1);
+ $('span.fa-filter').click();
+ $('li.o_add_custom_filter').click();
+ $('.o_apply_filter').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').text().trim(), 'ID is \"0\"',
+ 'should have a facet with candle name');
+ actionManager.destroy();
+ });
+
+ QUnit.test('deactivate a new custom filter works', function (assert) {
+ assert.expect(1);
+
+ var actionManager = createActionManager({
+ actions: this.actions,
+ archs: this.archs,
+ data: this.data,
+ });
+
+ actionManager.doAction(1);
+ $('span.fa-filter').click();
+ $('li.o_add_custom_filter').click();
+ $('.o_apply_filter').click();
+ $('li.o_menu_item').click();
+ assert.strictEqual($('.o_searchview .o_searchview_facet .o_facet_values span').length, 0,
+ 'no facet should be in the search view');
+ actionManager.destroy();
+ });
+});
+}); \ No newline at end of file
diff --git a/addons/web/static/tests/widgets/dropdown_menu_tests.js b/addons/web/static/tests/widgets/dropdown_menu_tests.js
new file mode 100644
index 00000000000..ae8426c5b4c
--- /dev/null
+++ b/addons/web/static/tests/widgets/dropdown_menu_tests.js
@@ -0,0 +1,201 @@
+odoo.define('web.dropdown_menu_tests', function (require) {
+"use strict";
+
+var DropdownMenu = require('web.DropdownMenu');
+var testUtils = require('web.test_utils');
+
+function createDropdownMenu(dropdownTitle, groups, params) {
+ params = params || {};
+ var target = params.debug ? document.body : $('#qunit-fixture');
+ var menu = new DropdownMenu(null, dropdownTitle, groups);
+ testUtils.addMockEnvironment(menu, params);
+ menu.appendTo(target);
+ return menu;
+}
+
+
+QUnit.module('Web', {
+ beforeEach: function () {
+ this.items = [
+ {
+ isActive: false,
+ isOpen: false,
+ description: 'Some Item',
+ itemId: 1,
+ groupId: 1,
+ },
+ {
+ isActive: true,
+ description: 'Some other Item',
+ itemId: 2,
+ options: [],
+ isRemovable: true,
+ groupId: 2,
+ },
+ ];
+ this.dropdownHeader = {
+ title: "Menu",
+ icon: "fa fa-bars",
+ };
+ },
+}, function () {
+ QUnit.module('DropdownMenu');
+
+ QUnit.test('simple rendering', function (assert) {
+ assert.expect(2);
+
+ var dropdownMenu = createDropdownMenu(this.dropdownHeader, this.items);
+ assert.strictEqual(dropdownMenu.$('li').length, 4, 'should have 4 li counting the dividers');
+ assert.strictEqual(dropdownMenu.$('li:nth-child(2)').text().trim(), 'Some Item',
+ 'first li should have "Some Item" description');
+ dropdownMenu.destroy();
+ });
+
+ QUnit.test('click on an item should toggle item', function (assert) {
+ assert.expect(9);
+
+ var eventNumber = 0;
+
+ var dropdownMenu = createDropdownMenu(this.dropdownHeader, this.items, {
+ intercepts: {
+ menu_item_toggled: function(ev) {
+ eventNumber++;
+ assert.strictEqual(ev.data.itemId, 1);
+ if (eventNumber === 1) {
+ assert.strictEqual(ev.data.isActive, true);
+ } else {
+ assert.strictEqual(ev.data.isActive, false);
+ }
+ },
+ },
+ });
+ dropdownMenu.$('button:first').click();
+ assert.ok(!dropdownMenu.$('.o_menu_item:first').hasClass('selected'));
+ dropdownMenu.$('.o_menu_item a').first().click();
+ assert.ok(dropdownMenu.$('.o_menu_item:first').hasClass('selected'));
+ assert.ok(dropdownMenu.$('.o_menu_item:first').is(':visible'),
+ 'item should still be visible');
+ dropdownMenu.$('.o_menu_item a').first().click();
+ assert.ok(!dropdownMenu.$('.o_menu_item:first').hasClass('selected'));
+ assert.ok(dropdownMenu.$('.o_menu_item:first').is(':visible'),
+ 'item should still be visible');
+
+ dropdownMenu.destroy();
+ });
+
+ QUnit.test('click on an item should not change url', function (assert) {
+ assert.expect(0);
+
+ var dropdownMenu = createDropdownMenu(this.dropdownHeader, this.items);
+ dropdownMenu.$el.click(function() {
+ // we do not want a click to get out and change the url, for example
+ throw new Error('No click should get out of the dropdown menu');
+ });
+ dropdownMenu.$('.o_menu_item a').first().click();
+
+ dropdownMenu.destroy();
+ });
+
+ QUnit.test('options rendering', function (assert) {
+ assert.expect(3);
+
+ this.items[0].options = [{optionId: 1, description: "First Option"}, {optionId: 2, description: "Second Option"}];
+
+ var dropdownMenu = createDropdownMenu(this.dropdownHeader, this.items);
+ // open dropdown
+ dropdownMenu.$('button:first').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 4);
+ dropdownMenu.$('span.fa-caret-down').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 6);
+ dropdownMenu.$('span.fa-caret-down').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 4);
+
+ dropdownMenu.destroy();
+ });
+
+ QUnit.test('close menu closes also submenu', function (assert) {
+ assert.expect(2);
+
+ this.items[0].options = [{optionId: 1, description: "First Option"}, {optionId: 2, description: "Second Option"}];
+
+ var dropdownMenu = createDropdownMenu(this.dropdownHeader, this.items);
+ dropdownMenu.$('button:first').click();
+ dropdownMenu.$('span.fa-caret-down').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 6);
+ dropdownMenu.$('button:first').click();
+ dropdownMenu.$('button:first').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 4);
+
+ dropdownMenu.destroy();
+ });
+
+ QUnit.test('click on an option should toggle options and item states properly', function (assert) {
+ assert.expect(22);
+
+ this.items[0].options = [{optionId: 1, description: "First Option"}, {optionId: 2, description: "Second Option"}];
+
+ var eventNumber = 0;
+
+ var dropdownMenu = createDropdownMenu(this.dropdownHeader, this.items, {
+ intercepts: {
+ menu_item_toggled: function(ev) {
+ eventNumber++;
+ if (eventNumber === 1) {
+ assert.strictEqual(ev.data.itemId, 1);
+ assert.strictEqual(ev.data.isActive, true);
+ assert.strictEqual(ev.data.optionId, 1);
+ } else {
+ assert.strictEqual(ev.data.itemId, 1);
+ assert.strictEqual(ev.data.isActive, false);
+ assert.strictEqual(ev.data.optionId, false);
+ }
+ },
+ item_option_changed: function(ev) {
+ if (eventNumber === 1) {
+ assert.strictEqual(ev.data.itemId, 1);
+ assert.strictEqual(ev.data.isActive, true);
+ assert.strictEqual(ev.data.optionId, 2);
+ }
+ },
+ },
+ });
+ dropdownMenu.$('button:first').click();
+ dropdownMenu.$('span.fa-caret-down').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 6);
+ assert.ok(!dropdownMenu.$('.o_menu_item:first').hasClass('selected'));
+ assert.ok(!dropdownMenu.$('.o_item_option:first').hasClass('selected'));
+ assert.ok(!dropdownMenu.$('.o_item_option:nth-child(2)').hasClass('selected'));
+ dropdownMenu.$('li.o_item_option:first').click();
+ assert.ok(dropdownMenu.$('.o_menu_item:first').hasClass('selected'));
+ assert.ok(dropdownMenu.$('.o_item_option:first').hasClass('selected'));
+ assert.ok(!dropdownMenu.$('.o_item_option:nth-child(2)').hasClass('selected'));
+ dropdownMenu.$('li.o_item_option:nth-child(2)').click();
+ assert.ok(dropdownMenu.$('.o_menu_item:first').hasClass('selected'));
+ assert.ok(!dropdownMenu.$('.o_item_option:first').hasClass('selected'));
+ assert.ok(dropdownMenu.$('.o_item_option:nth-child(2)').hasClass('selected'));
+ dropdownMenu.$('li.o_item_option:nth-child(2)').click();
+ assert.ok(!dropdownMenu.$('.o_menu_item:first').hasClass('selected'));
+ assert.ok(!dropdownMenu.$('.o_item_option:first').hasClass('selected'));
+ assert.ok(!dropdownMenu.$('.o_item_option:nth-child(2)').hasClass('selected'));
+ dropdownMenu.destroy();
+ });
+
+ QUnit.test('trash an item should be possible', function (assert) {
+ assert.expect(3);
+
+ var dropdownMenu = createDropdownMenu(this.dropdownHeader, this.items, {
+ intercepts: {
+ menu_item_deleted: function(ev) {
+ assert.strictEqual(ev.data.itemId, 2);
+ },
+ },
+ });
+ dropdownMenu.$('button:first').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 4);
+ dropdownMenu.$('span.o_trash_button').click();
+ assert.strictEqual(dropdownMenu.$('li').length, 2);
+ dropdownMenu.destroy();
+ });
+
+});
+}); \ No newline at end of file