diff options
| author | Christophe Simonis <chs@odoo.com> | 2017-08-10 17:01:05 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2017-08-10 17:01:05 +0200 |
| commit | 48b2ce60ed12b9b0f3aaa0322465504798bd154e (patch) | |
| tree | 0ac2c53a78f678ad4701943eb9eab6a4c8f5ff48 /addons/web/static | |
| parent | 298eaca4d1e268c43185e9c1dd6d6973e02d9b57 (diff) | |
| parent | 85571bb78c9acc4e867cc3b480be9236a756fc22 (diff) | |
[MERGE] forward port branch saas-16 up to 85571bb78c9
Diffstat (limited to 'addons/web/static')
15 files changed, 469 insertions, 72 deletions
diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index a4a1d65f7b6..5962bb28acb 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -20,7 +20,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { custom_events: { execute_action: function(event) { var data = event.data; - this.do_execute_action(data.action_data, data.model, data.res_ids, data.on_closed) + this.do_execute_action(data.action_data, data.env, data.on_closed) .then(data.on_success, data.on_fail); }, search: function(event) { @@ -554,16 +554,19 @@ var ViewManager = Widget.extend(ControlPanelMixin, { * @param {string} [action_data.effect] if given, a visual effect (a * rainbowman by default) will be displayed when the action is complete, * with the string (evaluated) given as options. - * @param {DataSet} dataset a dataset object used to communicate with the server - * @param {integer[]} [res_ids] the res_ids of the objects on which the action is to be applied (fallback on env.ids if not set) + * @param {Object} env + * @param {string} env.model the model of the record(s) triggering the action + * @param {integer[]} [env.resIDs] the current ids in the environment where the action is triggered + * @param {integer} [env.currentID] the id of the record triggering the action + * @param {Object} [env.context] a context to pass to the action * @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action) */ - do_execute_action: function (action_data, model, res_ids, on_closed) { + do_execute_action: function (action_data, env, on_closed) { var self = this; var result_handler = on_closed || function () {}; - var context = new Context(this.env.context, action_data.context || {}); + var context = new Context(env.context, action_data.context || {}); // OR NULL hereunder: pyeval waits specifically for a null value, different from undefined - var record_id = res_ids && res_ids[0] || null; + var recordID = env.currentID || null; // response handler var handler = function (action) { @@ -585,11 +588,11 @@ var ViewManager = Widget.extend(ControlPanelMixin, { })) ); ncontext.add(action_data.context || {}); - ncontext.add({active_model: self.env.modelName}); - if (res_ids) { + ncontext.add({active_model: env.model}); + if (recordID) { ncontext.add({ - active_id: record_id, - active_ids: res_ids, + active_id: recordID, + active_ids: [recordID], }); } ncontext.add(action.context || {}); @@ -611,7 +614,7 @@ var ViewManager = Widget.extend(ControlPanelMixin, { if (action_data.special) { return handler({"type":"ir.actions.act_window_close"}); } else if (action_data.type === "object") { - var args = res_ids ? [res_ids] : [this.env.ids]; + var args = recordID ? [[recordID]] : [env.resIDs]; if (action_data.args) { try { // Warning: quotes and double quotes problem due to json and xml clash @@ -623,13 +626,13 @@ var ViewManager = Widget.extend(ControlPanelMixin, { } } args.push(context); - var dataset = new data.DataSet(this, model, this.env.context); + var dataset = new data.DataSet(this, env.model, env.context); return dataset.call_button(action_data.name, args).then(handler); } else if (action_data.type === "action") { return data_manager.load_action(action_data.name, _.extend(pyeval.eval('context', context), { - active_model: this.env.modelName, - active_ids: this.env.ids, - active_id: record_id, + active_model: env.model, + active_ids: env.resIDs, + active_id: recordID, })).then(handler); } }, diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 64049d8b1ec..0dce6ec2bb4 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -219,10 +219,16 @@ var FieldMany2One = AbstractField.extend({ /** * @private * @param {string} name + * @returns {Deferred} resolved after the name_create or when the slowcreate + * modal is closed. */ _quickCreate: function (name) { var self = this; - var slowCreate = this._searchCreatePopup.bind(this, "form", false, this._createContext(name)); + var def = $.Deferred(); + var slowCreate = function () { + var dialog = self._searchCreatePopup.bind(self, "form", false, self._createContext(name)); + dialog.on('closed', self, def.resolve.bind(def)); + }; if (this.nodeOptions.quick_create) { this.trigger_up('mutexify', { action: function () { @@ -235,12 +241,16 @@ var FieldMany2One = AbstractField.extend({ if (self.mode === "edit") { self.reinitialize({id: result[0], display_name: result[1]}); } - }, slowCreate); + def.resolve(); + }).fail(function () { + slowCreate(); + }); }, }); } else { slowCreate(); } + return def; }, /** * @private diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 8de118b29b7..644ba8ac985 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -29,6 +29,7 @@ var ajax = require('web.ajax'); var AbstractModel = require('web.AbstractModel'); var AbstractRenderer = require('web.AbstractRenderer'); var AbstractController = require('web.AbstractController'); +var Context = require('web.Context'); var AbstractView = Class.extend({ // name displayed in view switchers @@ -152,9 +153,9 @@ var AbstractView = Class.extend({ }); }, /** - * Returns the view model or create an instance of it if there is noney + * Returns the view model or create an instance of it if none * - * @param {Widget} parent the parent of the model + * @param {Widget} parent the parent of the model, if it has to be created * @return {Object} instance of the view model */ getModel: function (parent) { @@ -167,7 +168,7 @@ var AbstractView = Class.extend({ /** * Returns the a new view renderer instance * - * @param {Widget} parent the parent of the model + * @param {Widget} parent the parent of the model, if it has to be created * @param {Object} state the information related to the rendered view * @return {Object} instance of the view renderer */ @@ -239,7 +240,7 @@ var AbstractView = Class.extend({ * Loads the subviews for x2many fields when they are not inline * * @private - * @param {Widget} parent the parent of the model + * @param {Widget} parent the parent of the model, if it has to be created * @returns {Deferred} */ _loadSubviews: function (parent) { diff --git a/addons/web/static/src/js/views/basic/basic_controller.js b/addons/web/static/src/js/views/basic/basic_controller.js index 3a47f65ab03..6ba9dbea477 100644 --- a/addons/web/static/src/js/views/basic/basic_controller.js +++ b/addons/web/static/src/js/views/basic/basic_controller.js @@ -232,13 +232,17 @@ var BasicController = AbstractController.extend(FieldManagerMixin, { return self.isDestroyed() ? $.when() : self.reload(); }; record = record || this.model.get(this.handle); - var recordID = record.data.id; + this.trigger_up('execute_action', { action_data: _.extend({}, attrs, { - context: record.getContext({additionalContext: attrs.context || {}}), + context: record.getContext({additionalContext: attrs.context || {}}), }), - model: record.model, - res_ids: [recordID], + env: { + context: record.getContext(), + currentID: record.data.id, + model: record.model, + resIDs: record.res_ids, + }, on_closed: function (reason) { if (!_.isObject(reason)) { reload(reason); diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 04fe384afdc..8755805f993 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -649,7 +649,7 @@ var BasicModel = AbstractModel.extend({ var element = this.localData[id]; if (element.type === 'record') { - if ('currentId' in options && !options.currentId) { + if (!options.currentId && (('currentId' in options) || this.isNew(id))) { var params = { context: element.context, fieldsInfo: element.fieldsInfo, @@ -2310,12 +2310,20 @@ var BasicModel = AbstractModel.extend({ }); list = this._applyX2ManyOperations(list); if (type === 'many2many' || list._forceM2MLink) { + var relRecordCreated = _.filter(relRecordAdded, function (rec) { + return typeof rec.res_id === 'string'; + }); + var realIDs = _.difference(list.res_ids, _.pluck(relRecordCreated, 'res_id')); // deliberately generate a single 'replace' command instead // of a 'delete' and a 'link' commands with the exact diff // because 1) performance-wise it doesn't change anything // and 2) to guard against concurrent updates (policy: force // a complete override of the actual value of the m2m) - commands[fieldName].push(x2ManyCommands.replace_with(list.res_ids)); + commands[fieldName].push(x2ManyCommands.replace_with(realIDs)); + _.each(relRecordCreated, function (relRecord) { + var changes = self._generateChanges(relRecord, options); + commands[fieldName].push(x2ManyCommands.create(changes)); + }); // generate update commands for records that have been // updated (it may happen with editable lists) _.each(relRecordUpdated, function (relRecord) { @@ -2435,6 +2443,12 @@ var BasicModel = AbstractModel.extend({ */ _getDomain: function (element, options) { if (options && options.fieldName) { + if (element._domains[options.fieldName]) { + return Domain.prototype.stringToArray( + element._domains[options.fieldName], + this._getEvalContext(element, true) + ); + } var viewType = options.viewType || element.viewType; var fieldInfo = element.fieldsInfo[viewType][options.fieldName]; if (fieldInfo && fieldInfo.domain) { @@ -2697,6 +2711,7 @@ var BasicModel = AbstractModel.extend({ var dataPoint = { _cache: type === 'list' ? {} : undefined, _changes: null, + _domains: {}, aggregateValues: params.aggregateValues || {}, context: params.context || {}, count: params.count || res_ids.length, @@ -2870,6 +2885,9 @@ var BasicModel = AbstractModel.extend({ r._changes = _.defaults(value[2], r.data); for (var name in r._changes) { + if (r._changes[name] === null) { + continue; + } var isFieldInView = name in r.fields; if (isFieldInView && r.fields[name].type === 'many2one') { var rec = self._makeDataPoint({ @@ -3021,12 +3039,7 @@ var BasicModel = AbstractModel.extend({ record._warning = true; } if (result.domain) { - var fieldsInfo = record.fieldsInfo[viewType || record.viewType]; - for (var fieldName in result.domain) { - if (fieldsInfo[fieldName]) { - fieldsInfo[fieldName].domain = result.domain[fieldName]; - } - } + record._domains = _.extend(record._domains, result.domain); } return self._applyOnChange(result.value, record).then(function () { return result; @@ -3083,7 +3096,9 @@ var BasicModel = AbstractModel.extend({ */ _readGroup: function (list) { var self = this; - var fields = _.uniq(list.getFieldNames().concat(list.groupedBy)); + var groupByField = list.groupedBy[0]; + var rawGroupBy = groupByField.split(':')[0]; + var fields = _.uniq(list.getFieldNames().concat(rawGroupBy)); return this._rpc({ model: list.model, method: 'read_group', @@ -3094,8 +3109,6 @@ var BasicModel = AbstractModel.extend({ lazy: true, }) .then(function (groups) { - var groupByField = list.groupedBy[0]; - var rawGroupBy = groupByField.split(':')[0]; var previousGroups = _.map(list.data, function (groupID) { return self.localData[groupID]; }); diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js index 4f5719063ff..5fb79cfa3a0 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -322,10 +322,11 @@ return AbstractRenderer.extend({ element.addClass($render.attr('class')); var display_hour = ''; if (!event.allDay) { - display_hour = (event.start.format('HH:mm') === '00:00' ? event.r_start.format('HH:mm') : event.start.format('HH:mm')) + ' - ' + - (event.end && event.end.format('HH:mm') !== '00:00' ? event.end.format('HH:mm') : event.r_end.format('HH:mm')); + var start = event.r_start || event.start; + var end = event.r_end || event.end; + display_hour = start.format('HH:mm') + ' - ' + end.format('HH:mm'); if (display_hour === '00:00 - 00:00') { - display_hour = _t('All the day'); + display_hour = _t('All day'); } } element.find('.fc-content .fc-time').text(display_hour); diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js index cdfe2cb8d5d..0718b4e202e 100644 --- a/addons/web/static/src/js/views/form/form_controller.js +++ b/addons/web/static/src/js/views/form/form_controller.js @@ -217,7 +217,7 @@ var FormController = BasicController.extend({ * * @private * @override method from field manager mixin - * @param {string} id + * @param {string} id - id of the previously changed record * @returns {Deferred} */ _confirmSave: function (id) { @@ -228,15 +228,21 @@ var FormController = BasicController.extend({ return this._setMode('readonly'); } } else { - // a subrecord changed, so update the corresponding relational field + // A subrecord has changed, so update the corresponding relational field // i.e. the one whose value is a record with the given id or a list // having a record with the given id in its data var record = this.model.get(this.handle); - var fieldsChanged = _.findKey(record.data, function (d) { - return _.isObject(d) && - (d.id === id || _.findWhere(d.data, {id: id})); - }); - return this.renderer.confirmChange(record, record.id, [fieldsChanged]); + + // Callback function which returns true + // if a value recursively contains a record with the given id. + // This will be used to determine the list of fields to reload. + var containsChangedRecord = function (value) { + return _.isObject(value) && + (value.id === id || _.find(value.data, containsChangedRecord)); + }; + + var changedFields = _.findKey(record.data, containsChangedRecord); + return this.renderer.confirmChange(record, record.id, [changedFields]); } }, /** diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 2cf6a94285e..6870dbbbe24 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -237,8 +237,12 @@ var KanbanController = BasicController.extend({ } this.trigger_up('execute_action', { action_data: attrs, - model: record.model, - res_ids: [record.res_id], + env: { + context: record.getContext(), + currentID: record.res_id, + model: record.model, + resIDs: record.res_ids, + }, on_closed: function () { self.model.reload(record.id).then(function (db_id) { var data = self.model.get(db_id); diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index 3977ac0df87..71402fc92ec 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -240,7 +240,7 @@ var KanbanModel = BasicModel.extend({ */ _readTooltipFields: function (list) { var self = this; - var groupedByField = list.fields[list.groupedBy[0]]; + var groupedByField = list.fields[list.groupedBy[0].split(':')[0]]; if (groupedByField.type !== 'many2one') { return $.when(); } diff --git a/addons/web/static/src/js/views/list/list_renderer.js b/addons/web/static/src/js/views/list/list_renderer.js index 295266db276..6e69c0217b2 100644 --- a/addons/web/static/src/js/views/list/list_renderer.js +++ b/addons/web/static/src/js/views/list/list_renderer.js @@ -281,15 +281,19 @@ var ListRenderer = BasicRenderer.extend({ this._registerModifiers(node, record, $button); - // TODO this should be moved to a handler - var self = this; - $button.on("click", function (e) { - e.stopPropagation(); - self.trigger_up('button_clicked', { - attrs: node.attrs, - record: record, + if (record.res_id) { + // TODO this should be moved to a handler + var self = this; + $button.on("click", function (e) { + e.stopPropagation(); + self.trigger_up('button_clicked', { + attrs: node.attrs, + record: record, + }); }); - }); + } else { + $button.prop('disabled', true); + } return $button; }, diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 0ad9aa2a416..29f8666455a 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -922,6 +922,140 @@ QUnit.module('relational_fields', { }); }); + QUnit.test('list in form: create with one2many with many2one', function (assert) { + assert.expect(1); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<sheet>' + + '<field name="p">' + + '<tree editable="bottom">' + + '<field name="display_name"/>' + + '<field name="trululu"/>' + + '</tree>' + + '</field>' + + '</sheet>' + + '</form>', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({p: [[0, 0, {display_name: 'new record'}]]}); + } else if (args.method === 'name_get') { + // This should not be called at all and thus is not accounted for + // in the assert.expect. If this is called, you broke this test. + assert.notOk(_.str.startsWith(args.args[0][0], 'virtual_'), + "should not call name_get for the m2o inside o2m which has no value"); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual($('td.o_data_cell:first').text(), 'new record', + "should have created the new record in the o2m with the correct name"); + + form.destroy(); + }); + + QUnit.test('list in form: default_get with x2many create', function (assert) { + assert.expect(3); + + this.data.partner.onchanges.timmy = function (obj) { + assert.deepEqual( + obj.timmy, + [ + [6, false, []], + [0, false, {display_name: 'brandon is the new timmy', name: 'brandon'}] + ], + "should have properly created the x2many command list"); + obj.int_field = obj.timmy.length; + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<sheet>' + + '<field name="timmy">' + + '<tree editable="bottom">' + + '<field name="display_name"/>' + + '</tree>' + + '</field>' + + '<field name="int_field"/>' + + '</sheet>' + + '</form>', + mockRPC: function (route, args) { + if (args.method === 'default_get') { + return $.when({timmy: [[0, 0, {display_name: 'brandon is the new timmy', name: 'brandon'}]]}); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual($('td.o_data_cell:first').text(), 'brandon is the new timmy', + "should have created the new record in the m2m with the correct name"); + assert.strictEqual($('input.o_field_integer').val(), '2', + "should have called and executed the onchange properly"); + + form.destroy(); + }); + + QUnit.test('list in form: call button in sub view', function (assert) { + assert.expect(6); + + this.data.partner.records[0].p = [2]; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<sheet>' + + '<field name="p">' + + '<tree editable="bottom">' + + '<field name="product_id"/>' + + '</tree>' + + '</field>' + + '</sheet>' + + '</form>', + res_id: 1, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/product/get_formview_id') { + return $.when(false); + } + return this._super.apply(this, arguments); + }, + intercepts: { + execute_action: function (event) { + assert.strictEqual(event.data.env.model, 'product', + 'should call with correct model in env'); + assert.strictEqual(event.data.env.currentID, 37, + 'should call with correct currentID in env'); + assert.deepEqual(event.data.env.resIDs, [37], + 'should call with correct resIDs in env'); + }, + }, + archs: { + 'product,false,form': '<form string="Partners">' + + '<header>' + + '<button name="action" type="action" string="Just do it !"/>' + + '<button name="object" type="object" string="Just don\'t do it !"/>' + + '<field name="display_name"/>' + + '</header>' + + '</form>', + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('td.o_data_cell:first').click(); // edit first one2many line + form.$('.o_external_button').click(); // open product sub view in modal + $('button:contains("Just do it !")').click(); // click on action button + $('button:contains("Just don\'t do it !")').click(); // click on object button + + form.destroy(); + }); + QUnit.test('autocompletion in a many2one, in form view with a domain', function (assert) { assert.expect(1); @@ -1418,6 +1552,115 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('many2one in one2many: domain updated by an onchange', function (assert) { + assert.expect(3); + + this.data.partner.onchanges = { + trululu: function () {}, + }; + + var domain = []; + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form>' + + '<field name="p">' + + '<tree editable="bttom">' + + '<field name="trululu"/>' + + '</tree>' + + '</field>' + + '</form>', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === 'onchange') { + return $.when({ + domain: { + trululu: domain, + }, + }); + } + if (args.method === 'name_search') { + assert.deepEqual(args.kwargs.args, domain, + "sent domain should be correct"); + } + return this._super(route, args); + }, + viewOptions: { + mode: 'edit', + }, + }); + + // add a first row with a specific domain for the m2o + domain = [['id', 'in', [10]]]; // domain for subrecord 1 + form.$('.o_field_x2many_list_row_add a').click(); // triggers the onchange + form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search + + // add a second row with another domain for the m2o + domain = [['id', 'in', [5]]]; // domain for subrecord 2 + form.$('.o_field_x2many_list_row_add a').click(); // triggers the onchange + form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search + + // check again the first row to ensure that the domain hasn't change + domain = [['id', 'in', [10]]]; // domain for subrecord 1 should have been kept + form.$('.o_data_row:first .o_data_cell').click(); + form.$('.o_field_widget[name=trululu] input').click(); // triggers the name_search + + form.destroy(); + }); + + QUnit.test('updating a many2one from a many2many', function (assert) { + assert.expect(4); + + this.data.turtle.records[1].turtle_trululu = 1; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<group>' + + '<field name="turtles">' + + '<tree editable="bottom">' + + '<field name="display_name"/>' + + '<field name="turtle_trululu"/>' + + '</tree>' + + '</field>' + + '</group>' + + '</form>', + res_id: 1, + archs: { + 'partner,false,form': '<form string="Trululu"><field name="display_name"/></form>', + }, + mockRPC: function (route, args) { + if (args.method === 'get_formview_id') { + assert.deepEqual(args.args[0], [1], "should call get_formview_id with correct id"); + return $.when(false); + } + return this._super(route, args); + }, + }); + + // Opening the modal + form.$buttons.find('.o_form_button_edit').click(); + form.$('.o_data_row td:contains(first record)').click(); + form.$('.o_external_button').click(); + assert.strictEqual($('.modal').length, 1, + "should have one modal in body"); + + // Changing the 'trululu' value + $('.modal input[name="display_name"]').val('test').trigger('input'); + $('.modal button.btn-primary').click(); + + // Test whether the value has changed + assert.strictEqual($('.modal').length, 0, + "the modal should be closed"); + assert.equal(form.$('.o_data_cell:contains(test)').text(), 'test', + "the partner name should have been updated to 'test'"); + + form.destroy(); + }); + QUnit.module('FieldOne2Many'); QUnit.test('one2many basic properties', function (assert) { @@ -3847,9 +4090,9 @@ QUnit.module('relational_fields', { res_id: 1, intercepts: { execute_action: function (event) { - assert.deepEqual(event.data.res_ids, [2], + assert.deepEqual(event.data.env.currentID, 2, 'should call with correct id'); - assert.strictEqual(event.data.model, 'partner', + assert.strictEqual(event.data.env.model, 'partner', 'should call with correct model'); assert.strictEqual(event.data.action_data.name, 'method_name', "should call correct method"); @@ -3891,9 +4134,9 @@ QUnit.module('relational_fields', { res_id: 1, intercepts: { execute_action: function (event) { - assert.deepEqual(event.data.res_ids, [2], + assert.deepEqual(event.data.env.currentID, 2, 'should call with correct id'); - assert.strictEqual(event.data.model, 'partner', + assert.strictEqual(event.data.env.model, 'partner', 'should call with correct model'); assert.strictEqual(event.data.action_data.name, 'method_name', "should call correct method"); diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 53577d90e40..8c23fc4f97c 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -956,6 +956,30 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('group on date field with magic grouping method', function (assert) { + assert.expect(1); + + this.params.fieldNames = ['foo']; + this.params.groupedBy = ['date:month']; + this.params.res_id = undefined; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'read_group') { + assert.deepEqual(args.kwargs.fields, ['foo', 'date'], + "should have correctly trimmed the magic grouping info from the field name"); + } + return this._super.apply(this, arguments); + }, + }); + + model.load(this.params); + model.destroy(); + }); + + QUnit.test('read group when grouped by a selection field', function (assert) { assert.expect(5); @@ -1949,4 +1973,39 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('reload a new record', function (assert) { + assert.expect(6); + + this.params.context = {}; + this.params.fieldNames = ['product_id', 'category', 'product_ids']; + this.params.res_id = undefined; + this.params.type = 'record'; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(args.method); + return this._super(route, args); + }, + }); + + model.load(this.params).then(function (recordID) { + model.reload(recordID).then(function (recordID) { + assert.verifySteps(['default_get', 'default_get'], + "two default_get RPCs should have been done"); + var record = model.get(recordID); + assert.strictEqual(record.data.product_id, false, + "m2o default value should be false"); + assert.deepEqual(record.data.product_ids.data, [], + "o2m default should be []"); + assert.deepEqual(record.data.category.data, [], + "m2m default should be []"); + }); + }); + + model.destroy(); + }); + + });}); diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 31b1babc9b3..6aa01daac5a 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -789,7 +789,7 @@ QUnit.module('Views', { assert.strictEqual($event1Avatars.find('span').length, 0, "should not have a span for more attendees since there is only one"); - var $event2Avatars = calendar.$('.fc-event:contains(All the day) .o_calendar_avatars'); + var $event2Avatars = calendar.$('.fc-event:contains(All day) .o_calendar_avatars'); assert.strictEqual($event2Avatars.find('img').length, 3, "should have 3 avatars"); assert.strictEqual($event2Avatars.find('span').text(), '+2', "should indicate there are 2 more attendees that we don't show"); @@ -1034,6 +1034,46 @@ QUnit.module('Views', { "should display 5 events"); calendar.destroy(); }); + + QUnit.test('events starting at midnight', function (assert) { + assert.expect(2); + + var calendar = createView({ + View: CalendarView, + model: 'event', + data: this.data, + arch: '<calendar mode="week" date_start="start"/>', + viewOptions: { + initialDate: initialDate, + }, + }); + + // Click on Tuesday 12am + var $view = $('#qunit-fixture').contents(); + $view.prependTo('body'); + var top = calendar.$('.fc-axis:contains(12am)').offset().top + 5; + var left = calendar.$('.fc-day:eq(2)').offset().left + 5; + try { + testUtils.triggerPositionalMouseEvent(left, top, "mousedown"); + testUtils.triggerPositionalMouseEvent(left, top, "mouseup"); + } catch (e) { + calendar.destroy(); + $view.remove(); + throw new Error('The test failed to simulate a click on the screen.' + + 'Your screen is probably too small or your dev tools are open.'); + } + assert.ok($('.modal-dialog.modal-sm').length, + "should open the quick create dialog"); + + // Creating the event + $('.modal-body input:first').val('new event in quick create').trigger('input'); + $('.modal button.btn:contains(Create)').trigger('click').trigger('click'); + assert.strictEqual(calendar.$('.fc-event:contains(new event in quick create)').length, 1, + "should display the new record"); + + calendar.destroy(); + $view.remove(); + }); }); }); diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 7eed1a2d100..26d15e33c2a 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -903,7 +903,7 @@ QUnit.module('Views', { testUtils.intercept(form, 'execute_action', function (event) { assert.strictEqual(event.data.action_data.name, "post", "should trigger execute_action with correct method name"); - assert.deepEqual(event.data.res_ids, [2], "should have correct id in event data"); + assert.deepEqual(event.data.env.currentID, 2, "should have correct id in event data"); event.data.on_success(); event.data.on_closed(); }); @@ -956,7 +956,7 @@ QUnit.module('Views', { testUtils.intercept(form, 'execute_action', function (event) { assert.step('execute_action'); - assert.deepEqual(event.data.res_ids, [resID], + assert.deepEqual(event.data.env.currentID, resID, "execute action should be done on correct record id"); event.data.on_success(); event.data.on_closed(); @@ -1006,7 +1006,7 @@ QUnit.module('Views', { testUtils.intercept(form, 'execute_action', function (event) { assert.step('execute_action'); - assert.deepEqual(event.data.res_ids, [resID], + assert.deepEqual(event.data.env.currentID, resID, "execute action should be done on correct record id"); event.data.on_success(); event.data.on_closed(); @@ -3597,8 +3597,9 @@ QUnit.module('Views', { }, intercepts: { execute_action: function (e) { - assert.deepEqual(e.data.action_data.context, {test: 2}, - "button context should have been evaluated and given to the action, without previous context"); + assert.deepEqual(e.data.action_data.context, { + 'test': 2 + }, "button context should have been evaluated and given to the action, with magicc without previous context"); }, }, }); @@ -3631,8 +3632,8 @@ QUnit.module('Views', { }, intercepts: { execute_action: function (e) { - assert.deepEqual(e.data.action_data.context, {}, - "button context should have been evaluated and given to the action, without previous context"); + assert.deepEqual(e.data.action_data.context, { + }, "button context should have been evaluated and given to the action, with magic keys but without previous context"); }, }, }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index f0370ac7cf8..2247a64fa3c 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -984,7 +984,7 @@ QUnit.module('Views', { }); QUnit.test('list view, editable, without data', function (assert) { - assert.expect(9); + assert.expect(11); this.data.foo.records = []; @@ -998,6 +998,7 @@ QUnit.module('Views', { '<field name="date"/>' + '<field name="m2o"/>' + '<field name="foo"/>' + + '<button type="object" icon="fa-plus-square" name="method"/>' + '</tree>', viewOptions: { action: { @@ -1033,7 +1034,14 @@ QUnit.module('Views', { assert.strictEqual(list.$('tbody tr:eq(0) td:eq(1)').text().trim(), "", "the date field td should not have any content"); + assert.strictEqual(list.$('.o_list_button button').prop('disabled'), true, + "buttons should be disabled while the record is not yet created"); + list.$buttons.find('.o_list_button_save').click(); + + assert.strictEqual(list.$('.o_list_button button').prop('disabled'), false, + "buttons should not be disabled once the record is created"); + list.destroy(); }); @@ -1163,9 +1171,9 @@ QUnit.module('Views', { }, intercepts: { execute_action: function (event) { - assert.deepEqual(event.data.res_ids, [1], + assert.deepEqual(event.data.env.currentID, 1, 'should call with correct id'); - assert.strictEqual(event.data.model, 'foo', + assert.strictEqual(event.data.env.model, 'foo', 'should call with correct model'); assert.strictEqual(event.data.action_data.name, 'button_action', "should call correct method"); |
