-
Julian Dominguez-Schatz authored
* Add "end" field with date/count options * Use "end" field to generate schedule * Show "end" field in recurring description * Disable weekend before/after picker when not enabled * Add release notes * Fix failing typechecks * Add some description tests * PR feedback * 'Features', not 'Feature' * Fix goal templates infinite loop * Empty commit to bump ci * Fix bug where schedule templates in the past would apply incorrectly For example, if you had a schedule which started in November 2023 for 1.00, and you applied the schedule in October 2023, then you would end up with a value of 0.50 applied in October. * Fix handling of schedules with an end date This commit also includes a refactor of the skip-weekend logic: rather than referring only to dates with skipped weekends (which requires checking whether the "next date" request worked correctly), we track a "base date" which is the previous value of the schedule according to the rrule, excluding any weekend-skipping. This lets us use `addDays(baseDate, 1)` to get the next occurrence, regardless of the weekend behaviour. Doing things this way ensures that the loop will always make progress. * Only compute skipped weekend if weekend skips were requested * Fix typo in iterate-schedule-occurrences code We should be using `nextBaseDate` to derive the next base date, not `nextDate`; this is because we want the base date to be guaranteed to make progress in each loop iteration, so we can finish in at most 30 iterations without duplicate base dates. * Use const * Revert const -> let for one mutable variable
Julian Dominguez-Schatz authored* Add "end" field with date/count options * Use "end" field to generate schedule * Show "end" field in recurring description * Disable weekend before/after picker when not enabled * Add release notes * Fix failing typechecks * Add some description tests * PR feedback * 'Features', not 'Feature' * Fix goal templates infinite loop * Empty commit to bump ci * Fix bug where schedule templates in the past would apply incorrectly For example, if you had a schedule which started in November 2023 for 1.00, and you applied the schedule in October 2023, then you would end up with a value of 0.50 applied in October. * Fix handling of schedules with an end date This commit also includes a refactor of the skip-weekend logic: rather than referring only to dates with skipped weekends (which requires checking whether the "next date" request worked correctly), we track a "base date" which is the previous value of the schedule according to the rrule, excluding any weekend-skipping. This lets us use `addDays(baseDate, 1)` to get the next occurrence, regardless of the weekend behaviour. Doing things this way ensures that the loop will always make progress. * Only compute skipped weekend if weekend skips were requested * Fix typo in iterate-schedule-occurrences code We should be using `nextBaseDate` to derive the next base date, not `nextDate`; this is because we want the base date to be guaranteed to make progress in each loop iteration, so we can finish in at most 30 iterations without duplicate base dates. * Use const * Revert const -> let for one mutable variable
schedules.test.ts 6.70 KiB
import MockDate from 'mockdate';
import { getRecurringDescription } from './schedules';
describe('recurring date description', () => {
beforeEach(() => {
MockDate.set(new Date(2021, 4, 14));
});
it('describes weekly interval', () => {
expect(
getRecurringDescription(
{ start: '2021-05-17', frequency: 'weekly' },
'MM/dd/yyyy',
),
).toBe('Every week on Monday');
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'weekly',
interval: 2,
},
'MM/dd/yyyy',
),
).toBe('Every 2 weeks on Monday');
});
it('describes monthly interval', () => {
expect(
getRecurringDescription(
{ start: '2021-04-25', frequency: 'monthly' },
'MM/dd/yyyy',
),
).toBe('Every month on the 25th');
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
interval: 2,
},
'MM/dd/yyyy',
),
).toBe('Every 2 months on the 25th');
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [{ type: 'day', value: 25 }],
},
'MM/dd/yyyy',
),
).toBe('Every month on the 25th');
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
interval: 2,
patterns: [{ type: 'day', value: 25 }],
},
'MM/dd/yyyy',
),
).toBe('Every 2 months on the 25th');
// Last day should work
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [{ type: 'day', value: 31 }],
},
'MM/dd/yyyy',
),
).toBe('Every month on the 31st');
// -1 should work, representing the last day
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [{ type: 'day', value: -1 }],
},
'MM/dd/yyyy',
),
).toBe('Every month on the last day');
// Day names should work
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [{ type: 'FR', value: 2 }],
},
'MM/dd/yyyy',
),
).toBe('Every month on the 2nd Friday');
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [{ type: 'FR', value: -1 }],
},
'MM/dd/yyyy',
),
).toBe('Every month on the last Friday');
});
it('describes monthly interval with multiple days', () => {
// Note how order doesn't matter - the day should be sorted
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [
{ type: 'day', value: 15 },
{ type: 'day', value: 3 },
{ type: 'day', value: 20 },
],
},
'MM/dd/yyyy',
),
).toBe('Every month on the 3rd, 15th, and 20th');
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [
{ type: 'day', value: 3 },
{ type: 'day', value: -1 },
{ type: 'day', value: 20 },
],
},
'MM/dd/yyyy',
),
).toBe('Every month on the 3rd, 20th, and last day');
// Mix days and day names
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [
{ type: 'day', value: 3 },
{ type: 'day', value: -1 },
{ type: 'FR', value: 2 },
],
},
'MM/dd/yyyy',
),
).toBe('Every month on the 2nd Friday, 3rd, and last day');
// When there is a mixture of types, day names should always come first
expect(
getRecurringDescription(
{
start: '2021-04-25',
frequency: 'monthly',
patterns: [
{ type: 'SA', value: 1 },
{ type: 'day', value: 2 },
{ type: 'FR', value: 3 },
{ type: 'day', value: 10 },
],
},
'MM/dd/yyyy',
),
).toBe('Every month on the 1st Saturday, 3rd Friday, 2nd, and 10th');
});
it('describes yearly interval', () => {
expect(
getRecurringDescription(
{ start: '2021-05-17', frequency: 'yearly' },
'MM/dd/yyyy',
),
).toBe('Every year on May 17th');
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'yearly',
interval: 2,
},
'MM/dd/yyyy',
),
).toBe('Every 2 years on May 17th');
});
it('describes intervals with limited occurrences', () => {
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'weekly',
interval: 2,
endMode: 'after_n_occurrences',
endOccurrences: 2,
},
'MM/dd/yyyy',
),
).toBe('Every 2 weeks on Monday, 2 times');
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'weekly',
interval: 2,
endMode: 'after_n_occurrences',
endOccurrences: 1,
},
'MM/dd/yyyy',
),
).toBe('Every 2 weeks on Monday, once');
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'monthly',
interval: 2,
endMode: 'after_n_occurrences',
endOccurrences: 2,
},
'MM/dd/yyyy',
),
).toBe('Every 2 months on the 17th, 2 times');
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'yearly',
interval: 2,
endMode: 'after_n_occurrences',
endOccurrences: 2,
},
'MM/dd/yyyy',
),
).toBe('Every 2 years on May 17th, 2 times');
});
it('describes intervals with an end date', () => {
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'weekly',
interval: 2,
endMode: 'on_date',
endDate: '2021-06-01',
},
'MM/dd/yyyy',
),
).toBe('Every 2 weeks on Monday, until 06/01/2021');
expect(
getRecurringDescription(
{
start: '2021-05-17',
frequency: 'monthly',
interval: 2,
endMode: 'on_date',
endDate: '2021-06-01',
},
'yyyy-MM-dd',
),
).toBe('Every 2 months on the 17th, until 2021-06-01');
});
});