From bbf63b0b563dbf55c07fb0c5bfe5cf17f3b84fb8 Mon Sep 17 00:00:00 2001 From: Adrien Dieudonne Date: Thu, 10 Aug 2017 15:52:49 +0200 Subject: [PATCH] [FIX] web: Calendar: correctly display events Before this rev., the events in Calendar views (month mode) weren't correctly displayed: the last day of the event wasn't displayed as being part of the event. For instance, in leaves, a leave of 3 days from the 16th of Augustus until the 18th was only displayed over the 16th and the 17th. Moreover, when an event was created from a Calendar view with option all_day=False, the default start and end dates weren't correctly computed according to the user timezone, which could lead to the creation of multiple days event whereas the user clicked on a specific day. For instance, with timezone GMT+2, when clicking on the 18th of Augustus in the Leave Calendar view (month mode), it opened a dialog for the creation of a 2-day leave from the 18th to the 19th. This rev. fixes those two issues. --- .../src/js/views/calendar/calendar_model.js | 18 ++- .../web/static/tests/views/calendar_tests.js | 138 +++++++++++++++++- 2 files changed, 150 insertions(+), 6 deletions(-) diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js index ed02983d957..e338ae55ece 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -45,20 +45,26 @@ return AbstractModel.extend({ if (event.allDay || end.diff(start) % 86400000 === 0) { event.allDay = true; - if (this.scale === 'month') { + if (this.data.scale === 'month') { + // In month, FullCalendar gives the end day as the + // next day at midnight (instead of 23h59). + end.add(-1, 'days'); if (event.r_start) { start.hours(event.r_start.hours()) .minutes(event.r_start.minutes()) .seconds(event.r_start.seconds()) .utc(); - end.add(-1, 'days') - .hours(event.r_end.hours()) + end.hours(event.r_end.hours()) .minutes(event.r_end.minutes()) .seconds(event.r_end.seconds()) .utc(); - } else { + } else if (this.mapping.all_day) { start.utc(); end.utc(); + } else { + // default hours in the user's timezone + start.hours(7).add(-this.getSession().getTZOffset(start), 'minutes'); + end.hours(19).add(-this.getSession().getTZOffset(end), 'minutes'); } } else if (this.mapping.all_day) { start.startOf('day'); @@ -622,6 +628,10 @@ return AbstractModel.extend({ // r.start = date_start.format('YYYY-MM-DD'); // r.end = date_stop.format('YYYY-MM-DD'); } else if (this.data.scale === 'month' && this.fields[this.mapping.date_start].type !== 'date') { + // In month, FullCalendar gives the end day as the + // next day at midnight (instead of 23h59). + date_stop.add(1, 'days'); + // allow to resize in month mode r.reset_allday = r.allDay; r.allDay = true; diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 29e76182bb4..31891d6c28b 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -644,8 +644,8 @@ QUnit.module('Views', { $view.remove(); }); - QUnit.test('create all day event', function (assert) { - assert.expect(2); + QUnit.test('create all day event in week mode', function (assert) { + assert.expect(3); this.data.event.records = []; @@ -696,6 +696,8 @@ QUnit.module('Views', { assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", "should display the new event with time and title"); + assert.strictEqual($newevent.parent().attr('colspan'), "2", + "should appear over two days."); assert.deepEqual($newevent.data('fcSeg').event.record, { @@ -712,6 +714,82 @@ QUnit.module('Views', { $view.remove(); }); + QUnit.test('create event in month mode', function (assert) { + assert.expect(4); + + this.data.event.records = []; + + var calendar = createView({ + View: CalendarView, + model: 'event', + data: this.data, + arch: + ''+ + ''+ + '', + archs: archs, + viewOptions: { + initialDate: initialDate, + }, + session: { + getTZOffset: function () { + return 120; + }, + }, + mockRPC: function (route, args) { + if (args.method === "create") { + assert.deepEqual(args.args[0], { + "name": "new event", + "start": "2016-12-14 05:00:00", + "stop": "2016-12-15 17:00:00", + }, + "should send the correct data to create events"); + } + return this._super(route, args); + }, + }); + + var $view = $('#qunit-fixture').contents(); + $view.prependTo('body'); // => select with click position + + var pos = calendar.$('.fc-bg td:eq(20)').offset(); + try { + testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousedown"); + } catch (e) { + calendar.destroy(); + $view.remove(); + throw new Error('The test fails to simulate a click in the screen. Your screen is probably too small or your dev tools is open.'); + } + pos = calendar.$('.fc-bg td:eq(21)').offset(); + testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mousemove"); + testUtils.triggerPositionalMouseEvent(pos.left+15, pos.top+15, "mouseup"); + + $('.modal input:first').val('new event').trigger('input'); + $('.modal button.btn:contains(Create)').trigger('click'); + var $newevent = calendar.$('.fc-event:contains(new event)'); + + assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "newevent", + "should display the new event with time and title"); + assert.strictEqual($newevent.parent().attr('colspan'), "2", + "should appear over two days."); + + assert.deepEqual($newevent.data('fcSeg').event.record, { + display_name: "new event", + start: fieldUtils.parse.datetime("2016-12-14 05:00:00", this.data.event.fields.start, {isUTC: true}), + stop: fieldUtils.parse.datetime("2016-12-15 17:00:00", this.data.event.fields.stop, {isUTC: true}), + name: "new event", + id: 1 + }, "the new record should have the utc datetime (quickCreate)"); + + calendar.destroy(); + $view.remove(); + }); + QUnit.test('use mini calendar', function (assert) { assert.expect(12); @@ -874,6 +952,62 @@ QUnit.module('Views', { assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); }); + QUnit.test('create and edit event in month mode (all_day: false)', function (assert) { + assert.expect(2); + + var calendar = createView({ + View: CalendarView, + model: 'event', + data: this.data, + arch: + ''+ + ''+ + '', + archs: archs, + viewOptions: { + initialDate: initialDate, + }, + session: { + getTZOffset: function () { + return -240; + }, + }, + }); + + // create a new event and edit it + var $cell = calendar.$('.fc-day-grid .fc-row:eq(4) .fc-day:eq(2)'); + testUtils.triggerMouseEvent($cell, "mousedown"); + testUtils.triggerMouseEvent($cell, "mouseup"); + $('.modal-body input:first').val('coucou').trigger('input'); + + testUtils.intercept(calendar, 'do_action', function (event) { + assert.deepEqual(event.data.action, + { + type: "ir.actions.act_window", + res_model: "event", + views: [[false, "form"]], + target: "current", + context: { + "default_name": "coucou", + "default_start": "2016-12-27 11:00:00", // 7:00 + 4h + "default_stop": "2016-12-27 23:00:00", // 19:00 + 4h + } + }, + "should open the form view with the context default values"); + }); + + $('.modal button.btn:contains(Edit)').trigger('click'); + + calendar.destroy(); + assert.strictEqual($('#ui-datepicker-div:empty').length, 0, "should have a clean body"); + }); + + QUnit.test('readonly date_start field', function (assert) { assert.expect(4);