Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,30 @@ import { formatRunDate } from "ol-utilities"
import invariant from "tiny-invariant"
import user from "@testing-library/user-event"
import { renderWithTheme } from "../../test-utils"
import { AvailabilityEnum } from "api"
import { factories } from "api/test-utils"

// This is a pipe followed by a zero-width space
const SEPARATOR = "|​"

// Helper function to create a date N days from today
const daysFromToday = (days: number): string => {
const date = new Date()
date.setDate(date.getDate() + days)
return date.toISOString()
}
Comment on lines +16 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mentioning... faker.date.soon({ days: <x-number> }) which creates a date 0–x days in the future, iirc.

I don't think it's what you wanted here, but might be useful some other time.


// Helper to format date as "Month DD, YYYY"
const formatTestDate = (isoDate: string): string => {
const date = new Date(isoDate)
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "long",
day: "2-digit",
}
return date.toLocaleDateString("en-US", options)
}

describe("Learning resource info section pricing", () => {
test("Free course, no certificate", () => {
renderWithTheme(<InfoSection resource={courses.free.noCertificate} />)
Expand Down Expand Up @@ -106,7 +126,12 @@ describe("Learning resource info section start date", () => {
const course = courses.free.dated
const run = course.runs?.[0]
invariant(run)
const runDate = formatRunDate(run, false)
const runDate = formatRunDate(
run,
false,
course.availability,
course.best_run_id,
)
invariant(runDate)
renderWithTheme(<InfoSection resource={course} />)

Expand All @@ -115,22 +140,79 @@ describe("Learning resource info section start date", () => {
within(section).getByText(runDate)
})

test("Uses next_start_date when available", () => {
test("Uses best_run_id when available", () => {
const run = courses.free.dated.runs?.[0]
invariant(run)
const startDate = daysFromToday(30)
const enrollmentStart = daysFromToday(15)
const course = {
...courses.free.dated,
availability: AvailabilityEnum.Dated,
best_run_id: 1,
runs: [
{
...run,
id: 1,
start_date: startDate,
enrollment_start: enrollmentStart,
},
],
}
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getByText("Starts:")
within(section).getByText(formatTestDate(startDate))
})

test("Shows run date when best_run_id matches a run", () => {
const startDate = daysFromToday(45)
const run = factories.learningResources.run({
id: 1,
start_date: startDate,
enrollment_start: null,
})
const course = {
...courses.free.dated,
next_start_date: "2024-03-15T00:00:00Z",
best_run_id: 1,
runs: [run],
}
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getByText("Starts:")
within(section).getByText("March 15, 2024")
within(section).getByText(formatTestDate(startDate))
})

test("Falls back to run date when next_start_date is null", () => {
test("Uses enrollment_start when it is later than start_date", () => {
const run = courses.free.dated.runs?.[0]
invariant(run)
const startDate = daysFromToday(30)
const enrollmentStart = daysFromToday(40) // Later than start_date
const course = {
...courses.free.dated,
next_start_date: null,
availability: AvailabilityEnum.Dated,
best_run_id: 1,
runs: [
{
...run,
id: 1,
start_date: startDate,
enrollment_start: enrollmentStart,
},
],
}
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getByText("Starts:")
within(section).getByText(formatTestDate(enrollmentStart))
})

test("Falls back to null when best_run_id does not match any run", () => {
const course = {
...courses.free.dated,
best_run_id: 999,
}
const run = course.runs?.[0]
invariant(run)
Expand All @@ -141,14 +223,88 @@ describe("Learning resource info section start date", () => {
const section = screen.getByTestId("drawer-info-items")
within(section).getByText("Starts:")
within(section).getByText(runDate)
expect(within(section).queryByText("March 15, 2024")).toBeNull()
})

test("Shows today's date when best run start date is in the past", () => {
const run = courses.free.dated.runs?.[0]
invariant(run)
const pastStartDate = daysFromToday(-30) // 30 days ago
const pastEnrollmentStart = daysFromToday(-45) // 45 days ago
const todayDate = new Date().toISOString()
const course = {
...courses.free.dated,
availability: AvailabilityEnum.Dated,
best_run_id: 1,
runs: [
{
...run,
id: 1,
start_date: pastStartDate,
enrollment_start: pastEnrollmentStart,
},
],
}
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getByText("Starts:")
within(section).getByText(formatTestDate(todayDate))
})

test("Shows no start date when best_run_id is null", () => {
const run = courses.free.dated.runs?.[0]
invariant(run)
const course = {
...courses.free.dated,
best_run_id: null,
runs: [
{
...run,
id: 1,
start_date: null,
enrollment_start: null,
},
],
}
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
// Should not show a start date section at all when best run is null and no dates exist
expect(within(section).queryByText("Starts:")).toBeNull()
})

test("Shows no start date when best run has null dates", () => {
const run = courses.free.dated.runs?.[0]
invariant(run)
const course = {
...courses.free.dated,
best_run_id: 1,
runs: [
{
...run,
id: 1,
start_date: null,
end_date: null,
},
],
}
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
// Should not show a start date section when best run has null dates
expect(within(section).queryByText("Starts:")).toBeNull()
})

test("As taught in date(s)", () => {
const course = courses.free.anytime
const run = course.runs?.[0]
invariant(run)
const runDate = formatRunDate(run, true)
const runDate = formatRunDate(
run,
true,
course.availability,
course.best_run_id,
)
invariant(runDate)
renderWithTheme(<InfoSection resource={course} />)

Expand All @@ -168,7 +324,9 @@ describe("Learning resource info section start date", () => {
}
return 0
})
.map((run) => formatRunDate(run, false))
.map((run) =>
formatRunDate(run, false, course.availability, course.best_run_id),
)
.slice(0, 2)
.join(SEPARATOR)}Show more`
invariant(expectedDateText)
Expand All @@ -180,31 +338,6 @@ describe("Learning resource info section start date", () => {
})
})

test("Multiple run dates with next_start_date uses next_start_date as first date", () => {
const course = {
...courses.multipleRuns.sameData,
next_start_date: "2024-01-15T00:00:00Z",
}
const sortedDates = course.runs
?.sort((a, b) => {
if (a?.start_date && b?.start_date) {
return Date.parse(a.start_date) - Date.parse(b.start_date)
}
return 0
})
.map((run) => formatRunDate(run, false))
.filter((date) => date !== null)

// First date should be next_start_date, second should be original second date
const expectedDateText = `January 15, 2024${SEPARATOR}${sortedDates?.[1]}Show more`
renderWithTheme(<InfoSection resource={course} />)

const section = screen.getByTestId("drawer-info-items")
within(section).getAllByText((_content, node) => {
return node?.textContent === expectedDateText || false
})
})

test("If data is different then dates, formats, locations and prices are not shown", () => {
const course = courses.multipleRuns.differentData
renderWithTheme(<InfoSection resource={course} />)
Expand All @@ -227,11 +360,8 @@ describe("Learning resource info section start date", () => {
expect(runDates.children.length).toBe(totalRuns + 1)
})

test("Anytime courses with next_start_date should not replace first date in 'As taught in' section", () => {
const course = {
...courses.free.anytime,
next_start_date: "2024-03-15T00:00:00Z",
}
test("Anytime courses show 'Anytime' and semester/year in 'As taught in' section", () => {
const course = courses.free.anytime

renderWithTheme(<InfoSection resource={course} />)

Expand All @@ -242,14 +372,17 @@ describe("Learning resource info section start date", () => {

within(section).getByText("As taught in:")

expect(within(section).queryByText("March 15, 2024")).toBeNull()

const runDates = within(section).getByTestId("drawer-run-dates")
expect(runDates).toBeInTheDocument()

const firstRun = course.runs?.[0]
invariant(firstRun)
const firstRunDate = formatRunDate(firstRun, true)
const firstRunDate = formatRunDate(
firstRun,
true,
course.availability,
course.best_run_id,
)
invariant(firstRunDate)
expect(within(section).getByText(firstRunDate)).toBeInTheDocument()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
getLearningResourcePrices,
showStartAnytime,
NoSSR,
formatDate,
} from "ol-utilities"
import { theme, Link } from "ol-components"
import DifferingRunsTable from "./DifferingRunsTable"
Expand Down Expand Up @@ -173,32 +172,34 @@ const InfoItemValue: React.FC<InfoItemValueProps> = ({
const totalRunsWithDates = (resource: LearningResource) => {
return (
resource.runs
?.map((run) => formatRunDate(run, showStartAnytime(resource)))
?.map((run) =>
formatRunDate(
run,
showStartAnytime(resource),
resource.availability,
resource.best_run_id,
),
)
.filter((date) => date !== null).length || 0
)
}

const RunDates: React.FC<{ resource: LearningResource }> = ({ resource }) => {
const [showingMore, setShowingMore] = useState(false)
const anytime = showStartAnytime(resource)
let sortedDates = resource.runs

const sortedDates = resource.runs
?.sort((a, b) => {
if (a?.start_date && b?.start_date) {
return Date.parse(a.start_date) - Date.parse(b.start_date)
}
return 0
})
.map((run) => formatRunDate(run, anytime))
.map((run) =>
formatRunDate(run, anytime, resource.availability, resource.best_run_id),
)
.filter((date) => date !== null)

const nextStartDate = resource.next_start_date
? formatDate(resource.next_start_date, "MMMM DD, YYYY")
: null

if (sortedDates && nextStartDate && !anytime) {
// Replace the first date with next_start_date
sortedDates = [nextStartDate, ...sortedDates.slice(1)]
}
if (!sortedDates || sortedDates.length === 0) {
return null
}
Expand Down
Loading
Loading