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 @@ -130,11 +130,18 @@ class RequirementTreeBuilder implements V2ProgramRequirement {
return programNode
}

serialize(): V2ProgramRequirement {
#serialize(): V2ProgramRequirement {
const node = { id: this.id, data: this.data }
const children = this.children?.map((child) => child.serialize())
const children = this.children?.map((child) => child.#serialize())
return children ? { ...node, children } : node
}
serialize(): V2ProgramRequirement[] {
invariant(
this.#root === this,
"serialize can only be called on the root node",
)
return this.#serialize().children ?? []
}
}

export { RequirementTreeBuilder }
9 changes: 4 additions & 5 deletions frontends/main/src/app-pages/ProductPages/CoursePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,16 @@ describe("CoursePage", () => {

expect(links[0]).toHaveTextContent("About")
expect(links[0]).toHaveAttribute("href", `#${HeadingIds.About}`)
expect(document.getElementById(HeadingIds.About)).toBeVisible()
expect(links[1]).toHaveTextContent("What you'll learn")
expect(links[1]).toHaveAttribute("href", `#${HeadingIds.What}`)
expect(document.getElementById(HeadingIds.What)).toBeVisible()
expect(links[2]).toHaveTextContent("Prerequisites")
expect(links[2]).toHaveAttribute("href", `#${HeadingIds.Prereqs}`)
expect(document.getElementById(HeadingIds.Prereqs)).toBeVisible()
expect(links[3]).toHaveTextContent("Instructors")
expect(links[3]).toHaveAttribute("href", `#${HeadingIds.Instructors}`)

const headings = screen.getAllByRole("heading")
Object.values(HeadingIds).forEach((id) => {
expect(headings.find((h) => h.id === id)).toBeVisible()
})
expect(document.getElementById(HeadingIds.Instructors)).toBeVisible()
})

// Collasping sections tested in AboutSection.test.tsx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ describe("ProgramSummary", () => {
})

describe("Program RequirementsRow", () => {
test("Renders requirements if present", () => {
test("Renders requirement count with required + elective courses", () => {
const requirements = new RequirementTreeBuilder()
const required = requirements.addOperator({ operator: "all_of" })
required.addCourse()
Expand Down Expand Up @@ -350,7 +350,7 @@ describe("Program RequirementsRow", () => {
},
programs: { required: [], electives: [] },
},
req_tree: [requirements.serialize()],
req_tree: requirements.serialize(),
})

renderWithProviders(
Expand All @@ -361,11 +361,10 @@ describe("Program RequirementsRow", () => {
const reqRow = within(summary).getByTestId(TestIds.RequirementsRow)

// The text is split more nicely on screen, but via html tags not spaces
expect(reqRow).toHaveTextContent("3 Required CoursesComplete All")
expect(reqRow).toHaveTextContent("4 Elective CoursesComplete 2 of 4")
expect(reqRow).toHaveTextContent("5 Courses to complete program")
})

test("Does not show electives if there are none", () => {
test("Renders requirement count correctly if no electives", () => {
const requirements = new RequirementTreeBuilder()
const required = requirements.addOperator({ operator: "all_of" })
required.addCourse()
Expand All @@ -384,7 +383,7 @@ describe("Program RequirementsRow", () => {
},
programs: { required: [], electives: [] },
},
req_tree: [requirements.serialize()],
req_tree: requirements.serialize(),
})

renderWithProviders(
Expand All @@ -394,8 +393,7 @@ describe("Program RequirementsRow", () => {
const summary = screen.getByRole("region", { name: "Program summary" })
const reqRow = within(summary).getByTestId(TestIds.RequirementsRow)

expect(reqRow).toHaveTextContent("3 Required CoursesComplete All")
expect(reqRow).not.toHaveTextContent("Elective")
expect(reqRow).toHaveTextContent("3 Courses to complete program")
})
})

Expand Down
66 changes: 31 additions & 35 deletions frontends/main/src/app-pages/ProductPages/ProductSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ import {
CourseRunV2,
V2Program,
} from "@mitodl/mitxonline-api-axios/v2"
import { getElectiveSubtree, getRequiredSubtree } from "./util"
import { HeadingIds, parseReqTree } from "./util"
import { LearningResource } from "api"

const UnderlinedLink = styled(Link)({
const ResponsiveLink = styled(Link)(({ theme }) => ({
[theme.breakpoints.down("sm")]: {
...theme.typography.body3,
},
}))

const UnderlinedLink = styled(ResponsiveLink)({
textDecoration: "underline",
})

Expand Down Expand Up @@ -53,9 +59,14 @@ const InfoRowInner: React.FC<{ children?: React.ReactNode }> = ({
</Stack>
)

const InfoLabel = styled.span(({ theme }) => ({
fontWeight: theme.typography.fontWeightBold,
}))
const InfoLabel = styled.span<{ underline?: boolean }>(
({ theme, underline }) => [
{
fontWeight: theme.typography.fontWeightBold,
},
underline && { textDecoration: "underline" },
],
)
const InfoValue = styled.span({})
const InfoLabelValue: React.FC<{ label: string; value: React.ReactNode }> = ({
label,
Expand Down Expand Up @@ -433,40 +444,25 @@ const RequirementsRow: React.FC<ProgramInfoRowProps> = ({
program,
...others
}) => {
const requiredSubtree = getRequiredSubtree(program)
const electiveSubtree = getElectiveSubtree(program)
const requiredDisplay = requiredSubtree?.children?.length ? (
<InfoRowInner>
{`${requiredSubtree.children.length} Required ${pluralize(
"Course",
requiredSubtree.children.length,
)}`}
<span>Complete All</span>
</InfoRowInner>
) : null

const electiveDisplay = electiveSubtree?.children?.length ? (
<InfoRowInner>
{`${electiveSubtree.children.length} Elective ${pluralize(
"Course",
electiveSubtree.children.length,
)}`}
<span>
Complete {electiveSubtree.data.operator_value} of{" "}
{electiveSubtree.children.length}
</span>
</InfoRowInner>
) : null

if (!requiredDisplay && !electiveDisplay) return null
const parsedReqs = parseReqTree(program.req_tree)
const totalRequired = parsedReqs.reduce(
(sum, req) => sum + req.requiredCourseCount,
0,
)
if (totalRequired === 0) return null

return (
<InfoRow {...others}>
<RiFileCopy2Line aria-hidden="true" />
<Stack gap="8px" flex={1}>
{requiredDisplay}
{electiveDisplay}
</Stack>

<InfoRowInner>
<ResponsiveLink color="black" href={`#${HeadingIds.Requirements}`}>
<InfoLabel underline>
{`${totalRequired} ${pluralize("Course", totalRequired)}`}
</InfoLabel>{" "}
<InfoValue>to complete program</InfoValue>
</ResponsiveLink>
</InfoRowInner>
</InfoRow>
)
}
Expand Down
Loading
Loading