diff --git a/src/lib/ics.test.ts b/src/lib/ics.test.ts index ea7d583..db45b74 100644 --- a/src/lib/ics.test.ts +++ b/src/lib/ics.test.ts @@ -128,4 +128,23 @@ describe("exportICS", () => { const eventCount = (ics.match(/BEGIN:VEVENT/g) || []).length; expect(eventCount).toBe(1); }); + + it("correctly exports section when section numbers have gaps", () => { + // Simulate real Caltech data where section numbers skip (e.g., 1, 2, 4, 5) + const section1 = makeSection(1, "M 09:00 - 09:55", "Baxter 101"); + const section2 = makeSection(2, "T 10:00 - 10:55", "Baxter 102"); + const section4 = makeSection(4, "W 11:00 - 11:55", "Sloan 151"); + const section5 = makeSection(5, "R 13:00 - 13:55", "Sloan 152"); + const courseData = makeCourseData(1, "CS 1", [section1, section2, section4, section5]); + + // sectionId 2 = array index 2 = section number 4 (Wednesday) + const course = makeCourse(courseData, { sectionId: 2 }); + + const ics = exportICS("sp2026", [course]); + + expect(ics).toContain("BEGIN:VEVENT"); + const eventCount = (ics.match(/BEGIN:VEVENT/g) || []).length; + expect(eventCount).toBe(1); + expect(ics).toContain("LOCATION:Sloan 151"); + }); }); diff --git a/src/lib/ics.ts b/src/lib/ics.ts index 0779ac7..1caf0a4 100644 --- a/src/lib/ics.ts +++ b/src/lib/ics.ts @@ -63,28 +63,27 @@ export function exportICS(term: string, courses: CourseStorage[]): string { // Flatten the courses and parse times with start and end times, matching locations const parsedEvents = courses - .filter(course => course.enabled) + .filter(course => course.enabled && course.sectionId !== null) .flatMap(course => { - return course.courseData.sections - .filter(section => section.number - 1 === course.sectionId) // Filter by selected section - .flatMap(section => { - const times = section.times.split('\n'); // Split multiple times on newline - const locations = section.locations.split('\n'); // Split multiple locations on newline - - // Zip times and locations together - return times.flatMap((time, index) => { - const location = locations[index] || 'Unknown'; // Match time with corresponding location - const [days, startTime, , endTime] = time.split(' '); // Separate days and time range - if (days === 'A') return []; // skip to-be-announced times - - return days.split('').map(day => ({ - name: course.courseData.number, // Use course number for the title - location, // Set the matched location for this time - startTime: getFirstOccurrence(termStartDate, day, startTime), - endTime: getFirstOccurrence(termStartDate, day, endTime) // Parse the end time - })); - }); - }); + const section = course.courseData.sections[course.sectionId!]; + if (!section) return []; + + const times = section.times.split('\n'); // Split multiple times on newline + const locations = section.locations.split('\n'); // Split multiple locations on newline + + // Zip times and locations together + return times.flatMap((time, index) => { + const location = locations[index] || 'Unknown'; // Match time with corresponding location + const [days, startTime, , endTime] = time.split(' '); // Separate days and time range + if (days === 'A') return []; // skip to-be-announced times + + return days.split('').map(day => ({ + name: course.courseData.number, // Use course number for the title + location, // Set the matched location for this time + startTime: getFirstOccurrence(termStartDate, day, startTime), + endTime: getFirstOccurrence(termStartDate, day, endTime) // Parse the end time + })); + }); }); // Create a basic ICS header using stable floating local times.