diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index 32e90e0..fce2f85 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -71,6 +71,18 @@ const events = await calendar.getEventsFromCalendar( const meetingDate = await calendar.findNextMeetingDate(events, meetingConfig); +// If no meeting is found, exit gracefully +if (!meetingDate) { + const [weekStart, weekEnd] = calendar.getWeekBounds(); + + console.log( + `No meeting found for ${meetingConfig.properties.GROUP_NAME || 'this group'} ` + + `in the next week (${weekStart.toISOString().split('T')[0]} to ${weekEnd.toISOString().split('T')[0]}). ` + + `This is expected for bi-weekly meetings or meetings that don't occur every week.` + ); + process.exit(0); +} + // Step 8: Get Meeting Title const meetingTitle = meetings.generateMeetingTitle( config, diff --git a/package.json b/package.json index 03d6156..a29b4d4 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ "format": "prettier --write .", "format:check": "prettier --check .", "check": "npm run lint && npm run format:check", - "test": "node --test test/**/*.test.mjs", - "test:watch": "node --test --watch test/**/*.test.mjs", - "test:coverage": "node --test --experimental-test-coverage test/**/*.test.mjs" + "test": "node --test", + "test:watch": "node --test --watch", + "test:coverage": "node --test --experimental-test-coverage" }, "author": "", "license": "MIT" diff --git a/src/calendar.mjs b/src/calendar.mjs index 6278354..aa4112e 100644 --- a/src/calendar.mjs +++ b/src/calendar.mjs @@ -14,7 +14,7 @@ export const getEventsFromCalendar = async url => { /** * @param {Date} start */ -const getWeekBounds = (start = new Date()) => { +export const getWeekBounds = (start = new Date()) => { start.setUTCHours(0, 0, 0, 0); const end = new Date(start); @@ -27,7 +27,7 @@ const getWeekBounds = (start = new Date()) => { * Finds the next meeting event in any iCal feed for the current week * @param {ical.CalendarComponent[]} allEvents - The events * @param {import('./types').MeetingConfig} meetingConfig - Meeting configuration object - * @returns {Promise} The date of the next meeting + * @returns {Promise} The date of the next meeting, or null if no meeting is found */ export const findNextMeetingDate = async (allEvents, { properties }) => { const [weekStart, weekEnd] = getWeekBounds(); @@ -50,9 +50,5 @@ export const findNextMeetingDate = async (allEvents, { properties }) => { } } - throw new Error( - `No meeting found for ${properties.GROUP_NAME || 'this group'} ` + - `in the next week (${weekStart.toISOString().split('T')[0]} to ${weekEnd.toISOString().split('T')[0]}). ` + - `This is expected for bi-weekly meetings or meetings that don't occur every week.` - ); + return null; }; diff --git a/test/calendar.test.mjs b/test/calendar.test.mjs new file mode 100644 index 0000000..d605959 --- /dev/null +++ b/test/calendar.test.mjs @@ -0,0 +1,135 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; + +import { findNextMeetingDate } from '../src/calendar.mjs'; + +describe('Calendar', () => { + describe('findNextMeetingDate', () => { + it('should return null when no matching events are found', async () => { + const allEvents = []; + const meetingConfig = { + properties: { + CALENDAR_FILTER: 'Test Meeting', + GROUP_NAME: 'Test Group', + }, + }; + + const result = await findNextMeetingDate(allEvents, meetingConfig); + + assert.strictEqual(result, null); + }); + + it('should return null when events exist but do not match the filter', async () => { + const allEvents = [ + { + summary: 'Different Meeting', + rrule: { + options: {}, + between: () => [new Date('2025-11-04T14:00:00Z')], + }, + tzid: 'UTC', + }, + ]; + const meetingConfig = { + properties: { + CALENDAR_FILTER: 'Test Meeting', + GROUP_NAME: 'Test Group', + }, + }; + + const result = await findNextMeetingDate(allEvents, meetingConfig); + + assert.strictEqual(result, null); + }); + + it('should return null when matching events exist but have no recurrences in the week', async () => { + const allEvents = [ + { + summary: 'Test Meeting', + rrule: { + options: {}, + between: () => [], + }, + tzid: 'UTC', + }, + ]; + const meetingConfig = { + properties: { + CALENDAR_FILTER: 'Test Meeting', + GROUP_NAME: 'Test Group', + }, + }; + + const result = await findNextMeetingDate(allEvents, meetingConfig); + + assert.strictEqual(result, null); + }); + + it('should return the meeting date when a matching event with recurrence is found', async () => { + const expectedDate = new Date('2025-11-04T14:00:00Z'); + const allEvents = [ + { + summary: 'Test Meeting', + rrule: { + options: {}, + between: () => [expectedDate], + }, + tzid: 'UTC', + }, + ]; + const meetingConfig = { + properties: { + CALENDAR_FILTER: 'Test Meeting', + GROUP_NAME: 'Test Group', + }, + }; + + const result = await findNextMeetingDate(allEvents, meetingConfig); + + assert.strictEqual(result, expectedDate); + }); + + it('should match events using the description field', async () => { + const expectedDate = new Date('2025-11-04T14:00:00Z'); + const allEvents = [ + { + description: 'This is a Test Meeting', + rrule: { + options: {}, + between: () => [expectedDate], + }, + tzid: 'UTC', + }, + ]; + const meetingConfig = { + properties: { + CALENDAR_FILTER: 'Test Meeting', + GROUP_NAME: 'Test Group', + }, + }; + + const result = await findNextMeetingDate(allEvents, meetingConfig); + + assert.strictEqual(result, expectedDate); + }); + + it('should return null when events do not have rrule', async () => { + const allEvents = [ + { + summary: 'Test Meeting', + tzid: 'UTC', + }, + ]; + const meetingConfig = { + properties: { + CALENDAR_FILTER: 'Test Meeting', + GROUP_NAME: 'Test Group', + }, + }; + + const result = await findNextMeetingDate(allEvents, meetingConfig); + + assert.strictEqual(result, null); + }); + }); +});