diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-10-17 10:54:19 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-10-17 10:54:19 +0200 |
| commit | e9ae419fca0a9ebd0071c280bd98fc84f268bb74 (patch) | |
| tree | 8f33c2a81a60133b04df10d7e81afbc9d54c20a6 /addons/web/static | |
| parent | 156d099e328304d0f19146f4714d13b7b572358a (diff) | |
| parent | 0de06a41326662d4505401764035bf9ddf31fc8b (diff) | |
[MERGE] forward port branch saas-11.3 up to 0de06a41326
Diffstat (limited to 'addons/web/static')
16 files changed, 427 insertions, 95 deletions
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js index c98d25989e9..9f2e9431bb1 100644 --- a/addons/web/static/src/js/fields/basic_fields.js +++ b/addons/web/static/src/js/fields/basic_fields.js @@ -434,6 +434,10 @@ var FieldDate = InputField.extend({ this._super.apply(this, arguments); // use the session timezone when formatting dates this.formatOptions.timezone = true; + this.datepickerOptions = _.defaults( + this.nodeOptions.datepicker || {}, + {defaultDate: this.value} + ); }, /** * In edit mode, instantiates a DateWidget datepicker and listen to changes. @@ -498,13 +502,7 @@ var FieldDate = InputField.extend({ * @private */ _makeDatePicker: function () { - return new datepicker.DateWidget( - this, - _.defaults( - this.nodeOptions.datepicker || {}, - {defaultDate: this.value} - ) - ); + return new datepicker.DateWidget(this, this.datepickerOptions); }, /** @@ -522,6 +520,17 @@ var FieldDate = InputField.extend({ var FieldDateTime = FieldDate.extend({ supportedFieldTypes: ['datetime'], + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + if (this.value) { + var offset = this.getSession().getTZOffset(this.value); + var displayedValue = this.value.clone().add(offset, 'minutes'); + this.datepickerOptions.defaultDate = displayedValue; + } + }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -552,10 +561,8 @@ var FieldDateTime = FieldDate.extend({ * @private */ _makeDatePicker: function () { - var value = this.value && this.value.clone().add(this.getSession().getTZOffset(this.value), 'minutes'); - return new datepicker.DateTimeWidget(this, {defaultDate: value}); + return new datepicker.DateTimeWidget(this, this.datepickerOptions); }, - /** * Set the datepicker to the right value rather than the default one. * @@ -952,6 +959,9 @@ var HandleWidget = AbstractField.extend({ var FieldEmail = InputField.extend({ className: 'o_field_email', + events: _.extend({}, InputField.prototype.events, { + 'click': '_onClick', + }), prefix: 'mailto', supportedFieldTypes: ['char'], @@ -992,7 +1002,21 @@ var FieldEmail = InputField.extend({ this.$el.text(this.value) .addClass('o_form_uri o_text_overflow') .attr('href', this.prefix + ':' + this.value); - } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Prevent the URL click from opening the record (when used on a list). + * + * @private + * @param {MouseEvent} ev + */ + _onClick: function (ev) { + ev.stopPropagation(); + }, }); var FieldPhone = FieldEmail.extend({ @@ -1019,6 +1043,9 @@ var FieldPhone = FieldEmail.extend({ var UrlWidget = InputField.extend({ className: 'o_field_url', + events: _.extend({}, InputField.prototype.events, { + 'click': '_onClick', + }), supportedFieldTypes: ['char'], /** @@ -1060,7 +1087,21 @@ var UrlWidget = InputField.extend({ .addClass('o_form_uri o_text_overflow') .attr('target', '_blank') .attr('href', this.value); - } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Prevent the URL click from opening the record (when used on a list). + * + * @private + * @param {MouseEvent} ev + */ + _onClick: function (ev) { + ev.stopPropagation(); + }, }); var AbstractFieldBinary = AbstractField.extend({ @@ -1322,19 +1363,23 @@ var FieldPdfViewer = FieldBinaryFile.extend({ }, /** * @private + * @param {string} [fileURI] file URI if specified * @returns {string} the pdf viewer URI */ - _getURI: function () { - var queryObj = { - model: this.model, - field: this.name, - id: this.res_id, - }; + _getURI: function (fileURI) { var page = this.recordData[this.name + '_page'] || 1; - var queryString = $.param(queryObj); - var url = encodeURIComponent('/web/image?' + queryString); + if (!fileURI) { + var queryObj = { + model: this.model, + field: this.name, + id: this.res_id, + }; + var queryString = $.param(queryObj); + fileURI = '/web/image?' + queryString + } + fileURI = encodeURIComponent(fileURI); var viewerURL = '/web/static/lib/pdfjs/web/viewer.html?file='; - return viewerURL + url + '#page=' + page; + return viewerURL + fileURI + '#page=' + page; }, /** * @private @@ -1378,14 +1423,16 @@ var FieldPdfViewer = FieldBinaryFile.extend({ */ on_file_change: function (ev) { this._super.apply(this, arguments); + var files = ev.target.files; + if (!files || files.length === 0) { + return; + } + // TOCheck: is there requirement to fallback on FileReader if browser don't support URL + var fileURI = URL.createObjectURL(files[0]); if (this.PDFViewerApplication) { - var files = ev.target.files; - if (!files || files.length === 0) { - return; - } - var file = files[0]; - // TOCheck: is there requirement to fallback on FileReader if browser don't support URL - this.PDFViewerApplication.open(URL.createObjectURL(file), 0); + this.PDFViewerApplication.open(fileURI, 0); + } else { + this.$('.o_pdfview_iframe').attr('src', this._getURI(fileURI)); } }, /** diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 25722cd3db9..7ae9e0addf3 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -2377,10 +2377,12 @@ var FieldSelectionBadge = FieldSelection.extend({ * a FieldMany2one for its value. * Its intern representation is similar to the many2one (a datapoint with a * `name_get` as data). + * Note that there is some logic to support char field because of one use in our + * codebase, but this use should be removed along with this note. */ var FieldReference = FieldMany2One.extend({ specialData: "_fetchSpecialReference", - supportedFieldTypes: ['char', 'reference'], + supportedFieldTypes: ['reference'], template: 'FieldReference', events: _.extend({}, FieldMany2One.prototype.events, { 'change select': '_onSelectionChange', 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 10283c308f6..9b5dec6d169 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -904,27 +904,51 @@ var BasicModel = AbstractModel.extend({ route: '/web/dataset/resequence', params: params, }) - .then(function () { - var offset = options.offset ? options.offset : 0; - var old_data = data.data.slice(); - data.data = _.sortBy(data.data, function (d) { - if (_.contains(resIDs, self.localData[d].res_id)) { - return _.indexOf(resIDs, self.localData[d].res_id) + offset; - } else { - return _.indexOf(old_data, d); - } - }); - data.res_ids = []; - _.each(data.data, function (d) { - var dataPoint = self.localData[d]; - if (dataPoint.type === 'record') { - data.res_ids.push(dataPoint.res_id); - } else { - data.res_ids = data.res_ids.concat(dataPoint.res_ids); + .then(function (wasResequenced) { + if (!wasResequenced) { + // the field on which the resequence was triggered does not + // exist, so no resequence happened server-side + return $.when(); + } + var field = params.field ? params.field : 'sequence'; + + return self._rpc({ + model: modelName, + method: 'read', + args: [resIDs, [field]], + }).then(function (records) { + if (data.data.length) { + var dataType = self.localData[data.data[0]].type; + if (dataType === 'record') { + _.each(data.data, function (dataPoint) { + var recordData = self.localData[dataPoint].data; + var inRecords = _.findWhere(records, {id: recordData.id}); + if (inRecords) { + recordData[field] = inRecords[field]; + } + }); + data.data = _.sortBy(data.data, function (d) { + return self.localData[d].data[field]; + }); + } + if (dataType === 'list') { + data.data = _.sortBy(data.data, function (d) { + return _.indexOf(resIDs, self.localData[d].res_id) + }); + } } - }); - self._updateParentResIDs(data); - return parentID; + data.res_ids = []; + _.each(data.data, function (d) { + var dataPoint = self.localData[d]; + if (dataPoint.type === 'record') { + data.res_ids.push(dataPoint.res_id); + } else { + data.res_ids = data.res_ids.concat(dataPoint.res_ids); + } + }); + self._updateParentResIDs(data); + return parentID; + }) }); }, /** diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index 2c4ed711223..e1befc9daf1 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -378,6 +378,10 @@ var BasicView = AbstractView.extend({ if (!(dependency_name in fields)) { fields[dependency_name] = dependency_dict; } + + if (fv.fields && !(dependency_name in fv.fields)) { + fv.fields[dependency_name] = dependency_dict; + } } } return false; 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 4b4050b67cc..06043f69846 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -77,7 +77,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { self.model.get(recordID), { mode: 'edit', - can_create: false, + attrs: {can_create: false}, attrs: { 'placeholder': _.str.sprintf(_t("Add %s"), self.title), }, diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index f1e89698e97..46429189e12 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -948,7 +948,6 @@ var FormRenderer = BasicRenderer.extend({ */ _onNavigationMove: function (ev) { ev.stopPropagation(); - var index; if (ev.data.direction === "next") { index = this.allFieldWidgets[this.state.id].indexOf(ev.data.target || ev.target); diff --git a/addons/web/static/src/js/views/kanban/kanban_record.js b/addons/web/static/src/js/views/kanban/kanban_record.js index 30bafaab117..654617c9847 100644 --- a/addons/web/static/src/js/views/kanban/kanban_record.js +++ b/addons/web/static/src/js/views/kanban/kanban_record.js @@ -450,10 +450,12 @@ var KanbanRecord = Widget.extend({ ischild = false; } var test_event = events && events.click && (events.click.length > 1 || events.click[0].namespace !== "tooltip"); + var testLinkWithHref = elem.nodeName.toLowerCase() === 'a' && elem.href; if (ischild) { children.push(elem); - if (test_event) { - // do not trigger global click if one child has a click event registered + if (test_event || testLinkWithHref) { + // Do not trigger global click if one child has a click + // event registered (or it is a link with href) trigger = false; } } diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js index 8f87b36c874..d3f4d134229 100644 --- a/addons/web/static/src/js/widgets/date_picker.js +++ b/addons/web/static/src/js/widgets/date_picker.js @@ -22,8 +22,6 @@ var DateWidget = Widget.extend({ init: function(parent, options) { this._super.apply(this, arguments); - var l10n = _t.database.parameters; - this.name = parent.name; this.options = _.defaults(options || {}, { format : this.type_of_date === 'datetime' ? time.getLangDatetimeFormat() : time.getLangDateFormat(), @@ -51,12 +49,8 @@ var DateWidget = Widget.extend({ */ start: function() { this.$input = this.$('input.o_datepicker_input'); - this.$input.focus(function(e) { - e.stopImmediatePropagation(); - }); this.$input.datetimepicker(this.options); this.picker = this.$input.data('DateTimePicker'); - this.$input.click(this.picker.toggle.bind(this.picker)); this._setReadonly(false); }, /** diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 6b828397ba8..34a2302cd18 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -10,6 +10,7 @@ var KanbanView = require('web.KanbanView'); var ListView = require('web.ListView'); var session = require('web.session'); var testUtils = require('web.test_utils'); +var field_registry = require('web.field_registry'); var createView = testUtils.createView; var DebouncedField = basicFields.DebouncedField; @@ -1653,6 +1654,49 @@ QUnit.module('basic_fields', { form.destroy(); }); + QUnit.test("pdf_viewer: upload rendering", function (assert) { + assert.expect(6); + + testUtils.patch(field_registry.map.pdf_viewer, { + on_file_change: function (ev) { + ev.target = {files: [new Blob()]}; + this._super.apply(this, arguments); + }, + _getURI: function (fileURI) { + var res = this._super.apply(this, arguments); + assert.step('_getURI'); + assert.ok(_.str.startsWith(fileURI, 'blob:')); + this.PDFViewerApplication = { + open: function (URI) { + assert.step('open'); + assert.ok(_.str.startsWith(URI, 'blob:')); + }, + }; + return 'about:blank'; + }, + }); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '<form>' + + '<field name="document" widget="pdf_viewer"/>' + + '</form>', + }); + + // first upload initialize iframe + form.$('input[type="file"]').trigger('change'); + assert.verifySteps(['_getURI']); + // second upload call pdfjs method inside iframe + form.$('input[type="file"]').trigger('change'); + assert.verifySteps(['_getURI', 'open']); + + testUtils.unpatch(field_registry.map.pdf_viewer); + form.destroy(); + }); + QUnit.test('text field rendering in list view', function (assert) { assert.expect(1); @@ -1852,6 +1896,40 @@ QUnit.module('basic_fields', { form.destroy(); }); + QUnit.test('image fields in x2many list are loaded correctly', function (assert) { + assert.expect(2); + + this.data.partner_type.fields.image = {name: 'image', type: 'binary'}; + this.data.partner_type.records[0].image = 'product_image'; + this.data.partner.records[0].timmy = [12]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '<form string="Partners">' + + '<field name="timmy" widget="many2many">' + + '<tree>' + + '<field name="image" widget="image"/>' + + '</tree>' + + '</field>' + + '</form>', + res_id: 1, + mockRPC: function (route, args) { + if (route === 'data:image/png;base64,product_image') { + assert.ok(true, "The list's image should have been fetched"); + return $.when(); + } + return this._super.apply(this, arguments); + }, + }); + + assert.strictEqual(form.$('tr.o_data_row').length, 1, + 'There should be one record in the many2many'); + + form.destroy(); + }); + QUnit.test('image fields with required attribute', function (assert) { assert.expect(2); @@ -2148,7 +2226,7 @@ QUnit.module('basic_fields', { }); QUnit.test('date field value should not set on first click', function (assert) { - assert.expect(2); + assert.expect(3); var form = createView({ View: FormView, @@ -2158,10 +2236,10 @@ QUnit.module('basic_fields', { res_id: 4, }); form.$buttons.find('.o_form_button_edit').click(); - form.$('.o_datepicker_input').click(); + assert.strictEqual($('.bootstrap-datetimepicker-widget').length, 1, "there should be a datepicker (autofocus)"); assert.strictEqual(form.$('.o_datepicker_input').val(), '', "date field's input should be empty on first click"); $('.day:contains(22)').click(); - form.$('.o_datepicker_input').click(); // Open Datepicker second time + form.$('.o_datepicker_input').focus(); // Open Datepicker second time assert.strictEqual($('.day.active').text(), '22', 'datepicker should be highlight with 22nd day of month'); form.destroy(); }); @@ -2199,8 +2277,7 @@ QUnit.module('basic_fields', { assert.strictEqual(form.$('.o_datepicker_input').val(), '02/03/2017', 'the date should be correct in edit mode'); - // click on the input and select another value - form.$('.o_datepicker_input').click(); + // select another value assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open'); assert.strictEqual($('.day.active').data('day'), '02/03/2017', 'datepicker should be highlight February 3'); $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection @@ -2283,8 +2360,7 @@ QUnit.module('basic_fields', { assert.strictEqual(list.$('input.o_datepicker_input').val(), '02/03/2017', 'the date should be correct in edit mode'); - // click on the input and select another value - list.$('input.o_datepicker_input').click(); + // select another value assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open'); $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection @@ -2401,8 +2477,7 @@ QUnit.module('basic_fields', { form.$buttons.find('.o_form_button_edit').click(); assert.strictEqual(form.$('.o_datepicker_input').val(), expectedDateString, 'the datetime should be correct in edit mode'); - // click on the input and select 22 February at 8:23:33 - form.$('.o_datepicker_input').click(); + // select 22 February at 8:23:33 assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open'); $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection @@ -2500,8 +2575,7 @@ QUnit.module('basic_fields', { assert.strictEqual(list.$('input.o_datepicker_input').val(), expectedDateString, 'the date should be correct in edit mode'); - // click on the input and select 22 February at 8:23:33 - list.$('input.o_datepicker_input').click(); + // select 22 February at 8:23:33 assert.ok($('.bootstrap-datetimepicker-widget').length, 'datepicker should be open'); $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Month selection $('.bootstrap-datetimepicker-widget .picker-switch').first().click(); // Year selection @@ -2659,6 +2733,32 @@ QUnit.module('basic_fields', { form.destroy(); }); + QUnit.test('datepicker option: daysOfWeekDisabled', function (assert) { + assert.expect(2); + + this.data.partner.fields.datetime.default = "2017-08-02 12:00:05"; + this.data.partner.fields.datetime.required = true; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'<form string="Partners">' + + '<field name="datetime" ' + + 'options=\'{"datepicker": {"daysOfWeekDisabled": [0, 6]}}\'/>' + + '</form>', + res_id: 1, + }); + + form.$buttons.find('.o_form_button_create').click(); + assert.ok($('.day:last-child(),.day:nth-child(2)').hasClass('disabled'), + 'first and last days must be disabled'); + assert.notOk($('.day:not(:last-child()):not(:nth-child(2))').hasClass('disabled'), + 'other days must stay clickable'); + + form.destroy(); + }); + QUnit.module('FieldMonetary'); QUnit.test('monetary field in form view', function (assert) { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 2763933bae7..20008a92c21 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -9819,6 +9819,37 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('click on URL should not open the record', function (assert) { + assert.expect(2); + + this.data.partner.records[0].turtles = [1]; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch:'<form string="Partners">' + + '<field name="turtles">' + + '<tree>' + + '<field name="display_name" widget="email"/>' + + '<field name="turtle_foo" widget="url"/>' + + '</tree>' + + '<form></form>' + + '</field>' + + '</form>', + res_id: 1, + }); + + form.$('.o_email_cell a').click(); + assert.strictEqual($('.modal .o_form_view').length, 0, + 'click should not open the modal'); + + form.$('.o_url_cell a').click(); + assert.strictEqual($('.modal .o_form_view').length, 0, + 'click should not open the modal'); + form.destroy(); + }); + QUnit.test('create and edit on m2o in o2m, and press ESCAPE', function (assert) { assert.expect(4); @@ -12952,6 +12983,59 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('when creating a new many2one on a x2many then discarding it immediately with ESCAPE, it should not crash', function (assert) { + var done = assert.async(); + assert.expect(1); + + this.data.partner.records[0].turtles = []; + + var form = createView({ + View: FormView, + model: 'partner', + viewOptions: { + mode: 'edit', + }, + data: this.data, + arch:'<form string="Partners">' + + '<sheet>' + + '<field name="turtles">' + + '<tree editable="top">' + + '<field name="turtle_foo"/>' + + '<field name="turtle_trululu"/>' + + '</tree>' + + '</field>' + + '</sheet>' + + '</form>', + res_id: 1, + archs: { + 'partner,false,form': '<form><group><field name="foo"/><field name="bar"/></group></form>' + }, + }); + + // add a new line + form.$el.find('.o_field_x2many_list_row_add>a').click(); + + // open the field turtle_trululu (one2many) + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + form.$el.find('.o_input_dropdown>input').click(); + + concurrency.delay(0).then(function () { + // click create and edit + $('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter').click(); + + // hit escape immediately + var escapeKey = $.ui.keyCode.ESCAPE; + $(document.activeElement).trigger( + $.Event('keydown', {which: escapeKey, keyCode: escapeKey})); + + assert.ok('did not crash'); + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + done(); + }); + }); + }); }); }); diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 6a6dd62f2df..e0b59ae5119 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -904,10 +904,14 @@ var MockServer = Class.extend({ var offset = args.offset ? Number(args.offset) : 0; var field = args.field ? args.field : 'sequence'; var records = this.data[args.model].records; + if (!(field in this.data[args.model].fields)) { + return false; + } for (var i in args.ids) { var record = _.findWhere(records, {id: args.ids[i]}); record[field] = Number(i) + offset; } + return true; }, /** * Simulate a 'search_count' operation diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js index beb39687364..db40c2b5049 100644 --- a/addons/web/static/tests/helpers/test_utils.js +++ b/addons/web/static/tests/helpers/test_utils.js @@ -218,6 +218,12 @@ function createAsyncView(params) { controlPanel.appendTo($web_client); var $content = $('<div>').addClass('o_content').appendTo($web_client); + if (params.interceptsPropagate) { + _.each(params.interceptsPropagate, function (cb, name) { + intercept(widget, name, cb, true); + }); + } + return view.getController(widget).then(function (view) { // override the view's 'destroy' so that it calls 'destroy' on the widget // instead, as the widget is the parent of the view and the mockServer. diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 0a7ca319c2f..71dd1e155c3 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -645,14 +645,14 @@ QUnit.module('Views', { "should display the datetime from the date with the timezone"); // use datepicker to enter a date: 12/13/2016 08:00:00 - $('.o_field_widget[name="start"] input').trigger('click'); + $('.o_field_widget[name="start"] input').trigger('focus'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)').trigger('click'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); // use datepicker to enter a date: 12/13/2016 10:00:00 - $('.o_field_widget[name="stop"] input').trigger('click'); + $('.o_field_widget[name="stop"] input').trigger('focus'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)').trigger('click'); @@ -894,14 +894,14 @@ QUnit.module('Views', { "should display the datetime from the date with the timezone"); // use datepicker to enter a date: 12/13/2016 08:00:00 - $('.o_field_widget[name="start"] input').trigger('click'); + $('.o_field_widget[name="start"] input').trigger('focus'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(08)').trigger('click'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]').trigger('click'); // use datepicker to enter a date: 12/13/2016 10:00:00 - $('.o_field_widget[name="stop"] input').trigger('click'); + $('.o_field_widget[name="stop"] input').trigger('focus'); $('.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker .timepicker-hour').trigger('click'); $('.bootstrap-datetimepicker-widget .timepicker-hours td.hour:contains(10)').trigger('click'); diff --git a/addons/web/static/tests/views/kanban_model_tests.js b/addons/web/static/tests/views/kanban_model_tests.js index 1a3391c720d..20eb2918d04 100644 --- a/addons/web/static/tests/views/kanban_model_tests.js +++ b/addons/web/static/tests/views/kanban_model_tests.js @@ -192,6 +192,8 @@ QUnit.module('Views', { var done = assert.async(); assert.expect(8); + this.data.product.fields.sequence = {string: "Sequence", type: "integer"}; + this.data.partner.fields.sequence = {string: "Sequence", type: "integer"}; this.data.partner.records.push({id: 3, foo: 'aaa', product_id: 37}); var nbReseq = 0; @@ -204,7 +206,7 @@ QUnit.module('Views', { if (nbReseq === 1) { // resequencing columns assert.deepEqual(args.ids, [41, 37], "ids should be correct"); - assert.strictEqual(args.model, 'product_id', + assert.strictEqual(args.model, 'product', "model should be correct"); } else if (nbReseq === 2) { // resequencing records assert.deepEqual(args.ids, [3, 1], @@ -212,7 +214,6 @@ QUnit.module('Views', { assert.strictEqual(args.model, 'partner', "model should be correct"); } - return $.when(); } return this._super.apply(this, arguments); }, @@ -229,7 +230,7 @@ QUnit.module('Views', { "first group should be res_id 37"); // resequence columns - return model.resequence('product_id', [41, 37], stateID); + return model.resequence('product', [41, 37], stateID); }) .then(function (stateID) { var state = model.get(stateID); diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 7680d8877b1..9db68d8a26e 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -1335,6 +1335,72 @@ QUnit.module('Views', { kanban.destroy(); }); + QUnit.test('Do not open record when clicking on `a` with `href`', function (assert) { + assert.expect(5); + + this.data.partner.records = [ + { id: 1, foo: 'yop' }, + ]; + + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '<kanban class="o_kanban_test">' + + '<templates>' + + '<t t-name="kanban-box">' + + '<div class="oe_kanban_global_click">' + + '<field name="foo"/>' + + '<div>' + + '<a class="o_test_link" href="#">test link</a>' + + '</div>' + + '</div>' + + '</t>' + + '</templates>' + + '</kanban>', + intercepts: { + // when clicking on a record in kanban view, + // it switches to form view. + switch_view: function () { + throw new Error("should not switch view"); + }, + }, + }); + + var $record = kanban.$('.o_kanban_record:not(.o_kanban_ghost)'); + assert.strictEqual($record.length, 1, + "should display a kanban record"); + + var $testLink = $record.find('a'); + assert.strictEqual($testLink.length, 1, + "should contain a link in the kanban record"); + assert.ok(!!$testLink[0].href, + "link inside kanban record should have non-empty href"); + + // Mocked views prevent accessing a link with href. This is intented + // most of the time, but not in this test which specifically needs to + // let the browser access a link with href. + kanban.$el.off('click', 'a'); + // Prevent the browser default behaviour when clicking on anything. + // This includes clicking on a `<a>` with `href`, so that it does not + // change the URL in the address bar. + // Note that we should not specify a click listener on 'a', otherwise + // it may influence the kanban record global click handler to not open + // the record. + $(document.body).on('click.o_test', function (ev) { + assert.notOk(ev.isDefaultPrevented(), + "should not prevented browser default behaviour beforehand"); + assert.strictEqual(ev.target, $testLink[0], + "should have clicked on the test link in the kanban record"); + ev.preventDefault(); + }); + + $testLink.click(); + + $(document.body).off('click.o_test'); + kanban.destroy(); + }); + QUnit.test('can drag and drop a record from one column to the next', function (assert) { assert.expect(9); @@ -1731,7 +1797,7 @@ QUnit.module('Views', { }); QUnit.test('delete a column in grouped on m2o', function (assert) { - assert.expect(33); + assert.expect(36); testUtils.patch(KanbanRenderer, { _renderGrouped: function () { @@ -2697,6 +2763,7 @@ QUnit.module('Views', { QUnit.test('resequence columns in grouped by m2o', function (assert) { assert.expect(7); + this.data.product.fields.sequence = {string: "Sequence", type: "integer"}; var envIDs = [1, 3, 2, 4]; // the ids that should be in the environment during this test var kanban = createView({ @@ -2710,12 +2777,6 @@ QUnit.module('Views', { '</t></templates>' + '</kanban>', groupBy: ['product_id'], - mockRPC: function (route) { - if (route === '/web/dataset/resequence') { - return $.when(); - } - return this._super.apply(this, arguments); - }, intercepts: { env_updated: function (event) { assert.deepEqual(event.data.env.ids, envIDs, @@ -2739,7 +2800,7 @@ QUnit.module('Views', { kanban.update({}, {reload: false}); // re-render without reloading assert.strictEqual(kanban.$('.o_kanban_group:first').data('id'), 5, - "first column should be id 5 before resequencing"); + "first column should be id 5 after resequencing"); kanban.destroy(); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 9f476a6f1c9..05f7495e312 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -3085,10 +3085,10 @@ QUnit.module('Views', { foo: { fields: {int_field: {string: "int_field", type: "integer", sortable: true}}, records: [ - {id: 1, int_field: 0}, - {id: 2, int_field: 1}, - {id: 3, int_field: 2}, - {id: 4, int_field: 3}, + {id: 1, int_field: 11}, + {id: 2, int_field: 12}, + {id: 3, int_field: 13}, + {id: 4, int_field: 14}, ] } }; @@ -3107,14 +3107,15 @@ QUnit.module('Views', { assert.deepEqual(args, { model: "foo", ids: [4, 3], - offset: 2, + offset: 13, field: "int_field", }); } if (moves === 1) { assert.deepEqual(args, { model: "foo", - ids: [1, 4, 2, 3], + ids: [4, 2], + offset: 12, field: "int_field", }); } @@ -3122,14 +3123,15 @@ QUnit.module('Views', { assert.deepEqual(args, { model: "foo", ids: [2, 4], - offset: 1, + offset: 12, field: "int_field", }); } if (moves === 3) { assert.deepEqual(args, { model: "foo", - ids: [1, 4, 2, 3], + ids: [4, 2], + offset: 12, field: "int_field", }); } @@ -3196,7 +3198,6 @@ QUnit.module('Views', { "should write the right field as sequence"); assert.deepEqual(args.ids, [4, 2, 3], "should write the sequence in correct order"); - return $.when(); } return this._super.apply(this, arguments); }, @@ -3326,13 +3327,16 @@ QUnit.module('Views', { '</tree>', mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { + var _super = this._super.bind(this); assert.strictEqual(args.offset, 1, "should write the sequence starting from the lowest current one"); assert.strictEqual(args.field, 'int_field', "should write the right field as sequence"); assert.deepEqual(args.ids, [4, 2, 3], "should write the sequence in correct order"); - return $.when(def); + return $.when(def).then(function () { + return _super(route, args); + }); } return this._super.apply(this, arguments); }, |
