mirror of
https://github.com/n8n-io/n8n.git
synced 2026-08-28 17:22:01 +08:00
fix(Google Calendar Node): Allow switching an event between all-day and timed (#35802)
Co-authored-by: Ayyub Bhoraniya <314098225+ayyubbhoraniya@users.noreply.github.com> Co-authored-by: yehorkardash <yehor.kardash@n8n.io>
This commit is contained in:
@@ -623,16 +623,21 @@ export class GoogleCalendar implements INodeType {
|
||||
qs.sendUpdates = updateFields.sendUpdates as string;
|
||||
}
|
||||
const body: IEvent = {};
|
||||
// PATCH merges nested objects, so an event that is currently all-day keeps
|
||||
// its `date` unless we clear it, and Google rejects a start/end carrying
|
||||
// both `date` and `dateTime`. Same in reverse for the all-day branch below.
|
||||
if (updateFields.start) {
|
||||
body.start = {
|
||||
dateTime: moment.tz(updateFields.start, updateTimezone).utc().format(),
|
||||
timeZone: updateTimezone,
|
||||
date: null,
|
||||
};
|
||||
}
|
||||
if (updateFields.end) {
|
||||
body.end = {
|
||||
dateTime: moment.tz(updateFields.end, updateTimezone).utc().format(),
|
||||
timeZone: updateTimezone,
|
||||
date: null,
|
||||
};
|
||||
}
|
||||
// nodeVersion < 1.2
|
||||
@@ -725,11 +730,13 @@ export class GoogleCalendar implements INodeType {
|
||||
date: updateTimezone
|
||||
? moment.tz(updateFields.start, updateTimezone).utc(true).format('YYYY-MM-DD')
|
||||
: moment.tz(updateFields.start, moment.tz.guess()).utc(true).format('YYYY-MM-DD'),
|
||||
dateTime: null,
|
||||
};
|
||||
body.end = {
|
||||
date: updateTimezone
|
||||
? moment.tz(updateFields.end, updateTimezone).utc(true).format('YYYY-MM-DD')
|
||||
: moment.tz(updateFields.end, moment.tz.guess()).utc(true).format('YYYY-MM-DD'),
|
||||
dateTime: null,
|
||||
};
|
||||
}
|
||||
//example: RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=10;UNTIL=20110701T170000Z
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MockProxy } from 'vitest-mock-extended';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import type { INode, IExecuteFunctions } from 'n8n-workflow';
|
||||
import type { IDataObject, INode, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import * as genericFunctions from '../../GenericFunctions';
|
||||
import { GoogleCalendar } from '../../GoogleCalendar.node';
|
||||
@@ -126,5 +126,72 @@ describe('Google Calendar Node', () => {
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
describe('all-day <-> timed conversion', () => {
|
||||
const setupUpdate = (updateFields: IDataObject) => {
|
||||
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('event');
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('update');
|
||||
mockExecuteFunctions.getTimezone.mockReturnValueOnce('Europe/Berlin');
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ typeVersion: 1.2 }));
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myCalendar');
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myEvent');
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(true);
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(updateFields);
|
||||
};
|
||||
|
||||
const patchedBody = () =>
|
||||
(genericFunctions.googleApiRequest as Mock).mock.calls[0][2] as IDataObject;
|
||||
|
||||
it('should null out `date` when updating an event to a timed slot', async () => {
|
||||
setupUpdate({
|
||||
start: '2026-08-10T09:00:00',
|
||||
end: '2026-08-10T10:00:00',
|
||||
timezone: 'Europe/Berlin',
|
||||
});
|
||||
|
||||
await googleCalendar.execute.call(mockExecuteFunctions);
|
||||
|
||||
const body = patchedBody();
|
||||
// Without `date: null` the event keeps its all-day date and Google rejects
|
||||
// the start/end for carrying both keys.
|
||||
expect(body.start).toEqual({
|
||||
dateTime: '2026-08-10T07:00:00Z',
|
||||
timeZone: 'Europe/Berlin',
|
||||
date: null,
|
||||
});
|
||||
expect(body.end).toEqual({
|
||||
dateTime: '2026-08-10T08:00:00Z',
|
||||
timeZone: 'Europe/Berlin',
|
||||
date: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should null out `dateTime` when updating an event to all-day', async () => {
|
||||
setupUpdate({
|
||||
allday: 'yes',
|
||||
start: '2026-08-10T09:00:00',
|
||||
end: '2026-08-11T10:00:00',
|
||||
timezone: 'Europe/Berlin',
|
||||
});
|
||||
|
||||
await googleCalendar.execute.call(mockExecuteFunctions);
|
||||
|
||||
const body = patchedBody();
|
||||
expect(body.start).toEqual({ date: '2026-08-10', dateTime: null });
|
||||
expect(body.end).toEqual({ date: '2026-08-11', dateTime: null });
|
||||
});
|
||||
|
||||
it('should not send start or end when neither is being updated', async () => {
|
||||
setupUpdate({ summary: 'Renamed event' });
|
||||
|
||||
await googleCalendar.execute.call(mockExecuteFunctions);
|
||||
|
||||
const body = patchedBody();
|
||||
expect(body).not.toHaveProperty('start');
|
||||
expect(body).not.toHaveProperty('end');
|
||||
expect(body.summary).toBe('Renamed event');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user