diff --git a/web/src/lib/components/CourseTable.svelte b/web/src/lib/components/CourseTable.svelte index 4f6571f..c444fcc 100644 --- a/web/src/lib/components/CourseTable.svelte +++ b/web/src/lib/components/CourseTable.svelte @@ -14,6 +14,7 @@ import { formatTimeRange, getDeliveryConcern, getPrimaryInstructor, + isAsyncOnline, isMeetingTimeTBA, isTimeTBA, openSeats, @@ -611,7 +612,12 @@ const table = createSvelteTable({ )} passthrough > - {#if timeIsTBA(course)} + {#if isAsyncOnline(course)} + Async + {:else if timeIsTBA(course)} TBA { expect(result).toContain("\n\n"); }); }); + +describe("isAsyncOnline", () => { + it("returns true for INT building with no times", () => { + const course = { + meetingTimes: [ + makeMeetingTime({ + building: "INT", + building_description: "Internet Class", + begin_time: null, + end_time: null, + }), + ], + } as CourseResponse; + expect(isAsyncOnline(course)).toBe(true); + }); + it("returns false for INT building with meeting times", () => { + const course = { + meetingTimes: [ + makeMeetingTime({ + building: "INT", + building_description: "Internet Class", + tuesday: true, + thursday: true, + begin_time: "1000", + end_time: "1115", + }), + ], + } as CourseResponse; + expect(isAsyncOnline(course)).toBe(false); + }); + it("returns false for non-INT building", () => { + const course = { + meetingTimes: [ + makeMeetingTime({ + building: "MH", + building_description: "Main Hall", + begin_time: null, + end_time: null, + }), + ], + } as CourseResponse; + expect(isAsyncOnline(course)).toBe(false); + }); + it("returns false for empty meeting times", () => { + const course = { meetingTimes: [] } as unknown as CourseResponse; + expect(isAsyncOnline(course)).toBe(false); + }); +}); + +describe("formatLocationDisplay", () => { + it("returns 'Online' for INT building", () => { + const course = { + meetingTimes: [ + makeMeetingTime({ + building: "INT", + building_description: "Internet Class", + }), + ], + campus: "9", + } as CourseResponse; + expect(formatLocationDisplay(course)).toBe("Online"); + }); + it("returns building and room for physical location", () => { + const course = { + meetingTimes: [ + makeMeetingTime({ + building: "MH", + building_description: "Main Hall", + room: "2.206", + }), + ], + campus: "11", + } as CourseResponse; + expect(formatLocationDisplay(course)).toBe("MH 2.206"); + }); + it("returns building only when no room", () => { + const course = { + meetingTimes: [ + makeMeetingTime({ + building: "MH", + building_description: "Main Hall", + room: null, + }), + ], + campus: "11", + } as CourseResponse; + expect(formatLocationDisplay(course)).toBe("MH"); + }); +}); diff --git a/web/src/lib/course.ts b/web/src/lib/course.ts index a4397de..de966ca 100644 --- a/web/src/lib/course.ts +++ b/web/src/lib/course.ts @@ -152,6 +152,13 @@ export function isTimeTBA(mt: DbMeetingTime): boolean { return !mt.begin_time || mt.begin_time.length !== 4; } +/** Check if course is asynchronous online (INT building with no meeting times) */ +export function isAsyncOnline(course: CourseResponse): boolean { + if (course.meetingTimes.length === 0) return false; + const mt = course.meetingTimes[0]; + return mt.building === "INT" && isTimeTBA(mt); +} + /** Format a date string to "January 20, 2026". Accepts YYYY-MM-DD or MM/DD/YYYY. */ export function formatDate(dateStr: string): string { let year: number, month: number, day: number; @@ -170,6 +177,8 @@ export function formatDate(dateStr: string): string { /** Short location string from first meeting time: "MH 2.206" or campus fallback */ export function formatLocation(course: CourseResponse): string | null { for (const mt of course.meetingTimes) { + // Skip INT building - handled by formatLocationDisplay + if (mt.building === "INT") continue; if (mt.building && mt.room) return `${mt.building} ${mt.room}`; if (mt.building) return mt.building; } @@ -307,13 +316,19 @@ export function concernAccentColor(concern: DeliveryConcern): string | null { /** * Location display text for the table cell. - * Falls back to "Online" for online courses instead of showing a dash. + * Shows "Online" for internet class (INT building) or other online courses. */ export function formatLocationDisplay(course: CourseResponse): string | null { + // Check for Internet Class building first + const hasIntBuilding = course.meetingTimes.some((mt) => mt.building === "INT"); + if (hasIntBuilding) return "Online"; + const loc = formatLocation(course); if (loc) return loc; + const concern = getDeliveryConcern(course); - if (concern === "online") return "Online"; + if (concern === "online" || concern === "internet") return "Online"; + return null; }