tududi/backend/tests/unit/modules/tasks/builders.test.js
Chris 65b9bbce39
Fix initial due date calculation for weekly tasks with multiple weekdays (#974) (#975)
Fix calculateInitialDueDate() to properly handle recurrence_weekdays array
when creating or updating weekly recurring tasks with multiple weekdays.

Previously, the function only checked for recurrence_weekday (singular) and
ignored recurrence_weekdays (plural array), causing tasks with multiple
weekdays to incorrectly get today's date instead of the next occurrence.

Changes:
- Add support for recurrence_weekdays array in calculateInitialDueDate()
- Fix buildUpdateAttributes() to pass recurrence_weekdays parameter
- Add 8 unit tests covering multiple weekdays scenarios
- Add 3 integration tests for API CREATE and UPDATE operations
- Maintain backward compatibility with single recurrence_weekday

The fix mirrors the proven logic from calculateWeeklyRecurrence() in
recurringTaskService.js and properly handles edge cases like unsorted
arrays, wrapping to next week, and JSON string parsing.

Fixes #974
2026-03-26 17:19:59 +02:00

250 lines
8.7 KiB
JavaScript

const {
calculateInitialDueDate,
} = require('../../../../modules/tasks/core/builders');
describe('calculateInitialDueDate', () => {
describe('Weekly recurrence with multiple weekdays', () => {
it('should find next occurrence when today is Monday and target is Tue/Thu', () => {
const monday = new Date(Date.UTC(2026, 2, 23, 0, 0, 0, 0));
expect(monday.getUTCDay()).toBe(1);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(monday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => monday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekdays: [2, 4],
});
expect(result).toBe('2026-03-24');
global.Date = RealDate;
});
it('should find next occurrence among three weekdays (Mon/Wed/Fri)', () => {
const monday = new Date(Date.UTC(2026, 2, 23, 0, 0, 0, 0));
expect(monday.getUTCDay()).toBe(1);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(monday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => monday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekdays: [1, 3, 5],
});
expect(result).toBe('2026-03-25');
global.Date = RealDate;
});
it('should wrap to first day of next week when today is after all weekdays', () => {
const saturday = new Date(Date.UTC(2026, 2, 28, 0, 0, 0, 0));
expect(saturday.getUTCDay()).toBe(6);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(saturday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => saturday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekdays: [1, 3, 5],
});
expect(result).toBe('2026-03-30');
global.Date = RealDate;
});
it('should handle unsorted weekdays array', () => {
const monday = new Date(Date.UTC(2026, 2, 23, 0, 0, 0, 0));
expect(monday.getUTCDay()).toBe(1);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(monday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => monday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekdays: [5, 1, 3],
});
expect(result).toBe('2026-03-25');
global.Date = RealDate;
});
it('should work with single-element array like single weekday', () => {
const monday = new Date(Date.UTC(2026, 2, 23, 0, 0, 0, 0));
expect(monday.getUTCDay()).toBe(1);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(monday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => monday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekdays: [5],
});
expect(result).toBe('2026-03-27');
global.Date = RealDate;
});
it('should parse JSON string for recurrence_weekdays', () => {
const monday = new Date(Date.UTC(2026, 2, 23, 0, 0, 0, 0));
expect(monday.getUTCDay()).toBe(1);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(monday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => monday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekdays: '[2, 4]',
});
expect(result).toBe('2026-03-24');
global.Date = RealDate;
});
it('should prioritize recurrence_weekdays over recurrence_weekday', () => {
const monday = new Date(Date.UTC(2026, 2, 23, 0, 0, 0, 0));
expect(monday.getUTCDay()).toBe(1);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(monday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => monday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekday: 0,
recurrence_weekdays: [2, 4],
});
expect(result).toBe('2026-03-24');
global.Date = RealDate;
});
});
describe('Weekly recurrence with single weekday (backward compatibility)', () => {
it('should calculate correct due date for single weekday', () => {
const monday = new Date(Date.UTC(2026, 2, 23, 0, 0, 0, 0));
expect(monday.getUTCDay()).toBe(1);
const RealDate = Date;
global.Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) {
super(monday);
} else {
super(...args);
}
}
static [Symbol.hasInstance](instance) {
return instance instanceof RealDate;
}
};
global.Date.UTC = RealDate.UTC;
global.Date.parse = RealDate.parse;
global.Date.now = () => monday.getTime();
const result = calculateInitialDueDate({
recurrence_type: 'weekly',
recurrence_weekday: 5,
});
expect(result).toBe('2026-03-27');
global.Date = RealDate;
});
});
});