@@ -215,6 +283,14 @@ const EngagementTrendChart: React.FC<{
$numeric
>
{series.label}
+ {series.description ? (
+
+ {/* eslint-disable-next-line styled-components-a11y/no-noninteractive-tabindex */}
+
+
+
+
+ ) : null}
))}
diff --git a/frontends/main/src/app-pages/DashboardPage/Analytics/charts.test.tsx b/frontends/main/src/app-pages/DashboardPage/Analytics/charts.test.tsx
index a394a42376..d315010527 100644
--- a/frontends/main/src/app-pages/DashboardPage/Analytics/charts.test.tsx
+++ b/frontends/main/src/app-pages/DashboardPage/Analytics/charts.test.tsx
@@ -1,5 +1,11 @@
import React from "react"
-import { render, screen, within } from "@testing-library/react"
+import {
+ render,
+ screen,
+ waitForElementToBeRemoved,
+ within,
+} from "@testing-library/react"
+import userEvent from "@testing-library/user-event"
import { ThemeProvider, useTheme } from "ol-components"
import type { Theme } from "ol-components"
import { factories } from "api/analytics-test-utils"
@@ -144,6 +150,70 @@ describe("EngagementTrendChart", () => {
).toBeInTheDocument()
})
+ /**
+ * "Active learners" alone doesn't say what counts as active — the hover
+ * icon's accessible label carries the definition for keyboard and screen
+ * reader users, not just pointer hover. Scoped to the table: `SERIES` with
+ * the trigger's description also drives a second, mobile-only copy of this
+ * same trigger outside the table (see the test below), so an unscoped query
+ * would match both.
+ */
+ test("explains what counts as an active learner from the column header", () => {
+ renderWithTheme(
)
+
+ const table = screen.getByRole("table", { name: "Monthly engagement" })
+ expect(
+ within(table).getByLabelText(
+ /Learners who did anything in a course this month/,
+ ),
+ ).toBeInTheDocument()
+ })
+
+ /**
+ * The column header carrying that trigger is hidden below the `md`
+ * breakpoint (`TableHeaderRow`), so mobile/tablet users need an equivalent
+ * — once, outside the table, not repeated for every month's row.
+ */
+ test("repeats the active-learner definition once for mobile, outside the table", () => {
+ renderWithTheme(
)
+
+ const table = screen.getByRole("table", { name: "Monthly engagement" })
+ const triggers = screen.getAllByLabelText(
+ /Learners who did anything in a course this month/,
+ )
+ expect(triggers).toHaveLength(2)
+ expect(triggers.some((trigger) => !table.contains(trigger))).toBe(true)
+ })
+
+ /**
+ * A static aria-label proves nothing about whether the `Tooltip` itself
+ * works — this test would still pass if `Tooltip` were deleted entirely.
+ * Driving real hover and asserting the rendered popper text catches that;
+ * keyboard focus isn't asserted here because MUI only opens on focus when
+ * `:focus-visible` matches (`isFocusVisible`), which this test environment
+ * doesn't set for a programmatic `.focus()` call — see the same caveat in
+ * ContractAdminPage.test.tsx's tooltip test.
+ */
+ test("shows the active-learner definition on hover", async () => {
+ const user = userEvent.setup()
+ renderWithTheme(
)
+
+ const table = screen.getByRole("table", { name: "Monthly engagement" })
+ const trigger = within(table).getByLabelText(
+ /Learners who did anything in a course this month/,
+ )
+
+ await user.hover(trigger)
+ expect(
+ await screen.findByRole("tooltip", {
+ name: /Learners who did anything in a course this month/,
+ }),
+ ).toBeInTheDocument()
+
+ await user.unhover(trigger)
+ await waitForElementToBeRemoved(() => screen.queryByRole("tooltip"))
+ })
+
test("shows an empty state rather than an empty chart", () => {
renderWithTheme(
)
expect(
diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/UnenrolledCourseCard.test.tsx b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/UnenrolledCourseCard.test.tsx
index 8b23ff115b..c18b921b84 100644
--- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/UnenrolledCourseCard.test.tsx
+++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/UnenrolledCourseCard.test.tsx
@@ -16,11 +16,12 @@ import { faker } from "@faker-js/faker/locale/en"
import moment from "moment"
import { cartesianProduct } from "ol-test-utilities"
import { UnenrolledCourseCard } from "./UnenrolledCourseCard"
-import { trackCourseEnrolled } from "@/common/analytics/gtm"
+import { trackCourseEnrolled, trackBeginCheckout } from "@/common/analytics/gtm"
jest.mock("@/common/analytics/gtm", () => ({
...jest.requireActual("@/common/analytics/gtm"),
trackCourseEnrolled: jest.fn(),
+ trackBeginCheckout: jest.fn(),
}))
/**
@@ -883,6 +884,14 @@ describe.each([
)
})
+ expect(trackBeginCheckout).toHaveBeenCalledWith(
+ expect.objectContaining({
+ courseName: course.title,
+ courseId: course.readable_id,
+ value: parseFloat(product.price),
+ }),
+ )
+
expect(
screen.queryByRole("dialog", { name: course.title }),
).not.toBeInTheDocument()
diff --git a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/hooks/useEnrollmentHandler.ts b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/hooks/useEnrollmentHandler.ts
index 57779b752b..30ef35c792 100644
--- a/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/hooks/useEnrollmentHandler.ts
+++ b/frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/hooks/useEnrollmentHandler.ts
@@ -13,7 +13,7 @@ import NiceModal from "@ebay/nice-modal-react"
import { getCourseEnrollmentAction } from "@/common/mitxonline"
import { useComplianceGate } from "@/common/mitxonline/useComplianceGate"
import CourseEnrollmentDialog from "@/page-components/EnrollmentDialogs/CourseEnrollmentDialog"
-import { trackCourseEnrolled } from "@/common/analytics/gtm"
+import { trackCourseEnrolled, trackBeginCheckout } from "@/common/analytics/gtm"
import { canOpenCourseware } from "../courseDateUtils"
import { mitxUserQueries } from "api/mitxonline-hooks/user"
import { useQuery } from "@tanstack/react-query"
@@ -170,6 +170,13 @@ export const useEnrollmentHandler = () => {
}
if (enrollmentAction.type === "checkout") {
+ trackBeginCheckout({
+ courseName: course.title,
+ courseId: course.readable_id,
+ value: enrollmentAction.product.price
+ ? parseFloat(enrollmentAction.product.price)
+ : 0,
+ })
replaceBasketItem.mutate(enrollmentAction.product.id)
return
}
diff --git a/frontends/main/src/app-pages/OrganizationalLearningPage/CaseStudiesSection.tsx b/frontends/main/src/app-pages/OrganizationalLearningPage/CaseStudiesSection.tsx
index 3a7cc0f473..376a867885 100644
--- a/frontends/main/src/app-pages/OrganizationalLearningPage/CaseStudiesSection.tsx
+++ b/frontends/main/src/app-pages/OrganizationalLearningPage/CaseStudiesSection.tsx
@@ -2,7 +2,7 @@
import React, { useCallback, useRef, useState } from "react"
import Image from "next/image"
-import { styled, pxToRem } from "ol-components"
+import { styled } from "ol-components"
import { CarouselV2 } from "ol-components/CarouselV2"
import { VisuallyHidden } from "@mitodl/smoot-design"
import {
@@ -28,10 +28,10 @@ const Inner = styled(SectionInner)(({ theme }) => ({
display: "flex",
flexDirection: "column",
gap: "48px",
- padding: "96px 24px 40px",
+ padding: "96px 0 40px",
[theme.breakpoints.down("md")]: {
gap: "32px",
- padding: "32px 24px 16px",
+ padding: "32px 0 16px",
},
}))
@@ -225,7 +225,7 @@ const PillarTitle = styled.h4(({ theme }) => ({
}))
const PillarBody = styled.p(({ theme }) => ({
- ...theme.typography.body2,
+ ...theme.typography.body2Loose,
color: theme.custom.colors.darkGray2,
margin: 0,
}))
@@ -251,8 +251,7 @@ const QuoteMark = styled.span(({ theme }) => ({
}))
const QuoteText = styled.p(({ theme }) => ({
- ...theme.typography.body2,
- lineHeight: pxToRem(22),
+ ...theme.typography.body2Loose,
color: theme.custom.colors.darkGray2,
margin: 0,
marginTop: "-16px",
diff --git a/frontends/main/src/app-pages/OrganizationalLearningPage/ClientLogosSection.tsx b/frontends/main/src/app-pages/OrganizationalLearningPage/ClientLogosSection.tsx
index 8c773af743..99dbbcc456 100644
--- a/frontends/main/src/app-pages/OrganizationalLearningPage/ClientLogosSection.tsx
+++ b/frontends/main/src/app-pages/OrganizationalLearningPage/ClientLogosSection.tsx
@@ -15,9 +15,9 @@ const Inner = styled(SectionInner)(({ theme }) => ({
flexDirection: "column",
alignItems: "center",
gap: "40px",
- padding: "40px 24px 96px",
+ padding: "40px 0 96px",
[theme.breakpoints.down("md")]: {
- padding: "16px 24px 32px",
+ padding: "16px 0 32px",
},
}))
diff --git a/frontends/main/src/app-pages/OrganizationalLearningPage/FaqSection.tsx b/frontends/main/src/app-pages/OrganizationalLearningPage/FaqSection.tsx
index 75882fcbc4..bd80485a72 100644
--- a/frontends/main/src/app-pages/OrganizationalLearningPage/FaqSection.tsx
+++ b/frontends/main/src/app-pages/OrganizationalLearningPage/FaqSection.tsx
@@ -89,7 +89,11 @@ const FaqRow: React.FC<{ index: number; question: string; answer: string }> = ({
const panelId = `faq-panel-${index}`
return (
-
setExpanded(!expanded)}>
+ setExpanded(!expanded)}
+ >