Skip to content

Commit 019a255

Browse files
authored
adds a "Receipt" menu item (#3260)
* Add receipt link to dashboard menu for verified enrollments.
1 parent f9e95ef commit 019a255

6 files changed

Lines changed: 321 additions & 1 deletion

File tree

frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.test.tsx

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2138,5 +2138,202 @@ describe.each([
21382138
screen.getByRole("menuitem", { name: "Unenroll" }),
21392139
).toBeInTheDocument()
21402140
})
2141+
2142+
test("Receipt menu item appears for verified course run enrollment", async () => {
2143+
setupUserApis()
2144+
const course = mitxOnlineCourse()
2145+
const run = course.courseruns[0]
2146+
const enrollment = mitxonline.factories.enrollment.courseEnrollment({
2147+
grades: [mitxonline.factories.enrollment.grade({ passed: true })],
2148+
enrollment_mode: EnrollmentMode.Verified,
2149+
run: { ...run, course },
2150+
})
2151+
2152+
renderWithProviders(
2153+
<DashboardCard
2154+
resource={{
2155+
type: DashboardType.CourseRunEnrollment,
2156+
data: enrollment,
2157+
}}
2158+
/>,
2159+
)
2160+
2161+
const card = getCard()
2162+
const contextMenuButton = within(card).getByRole("button", {
2163+
name: "More options",
2164+
})
2165+
await user.click(contextMenuButton)
2166+
2167+
expect(
2168+
screen.getByRole("menuitem", { name: "Receipt" }),
2169+
).toBeInTheDocument()
2170+
})
2171+
2172+
test("Receipt menu item does not appear for audit course run enrollment", async () => {
2173+
setupUserApis()
2174+
const course = mitxOnlineCourse()
2175+
const run = course.courseruns[0]
2176+
const enrollment = mitxonline.factories.enrollment.courseEnrollment({
2177+
grades: [],
2178+
enrollment_mode: EnrollmentMode.Audit,
2179+
run: { ...run, course },
2180+
})
2181+
2182+
renderWithProviders(
2183+
<DashboardCard
2184+
resource={{
2185+
type: DashboardType.CourseRunEnrollment,
2186+
data: enrollment,
2187+
}}
2188+
/>,
2189+
)
2190+
2191+
const card = getCard()
2192+
const contextMenuButton = within(card).getByRole("button", {
2193+
name: "More options",
2194+
})
2195+
await user.click(contextMenuButton)
2196+
2197+
expect(
2198+
screen.queryByRole("menuitem", { name: "Receipt" }),
2199+
).not.toBeInTheDocument()
2200+
})
2201+
2202+
test("Receipt menu item links to correct MITx Online URL for verified course run enrollment", async () => {
2203+
setupUserApis()
2204+
const course = mitxOnlineCourse()
2205+
const run = mitxonline.factories.courses.courseRun({ id: 42 })
2206+
const enrollment = mitxonline.factories.enrollment.courseEnrollment({
2207+
grades: [mitxonline.factories.enrollment.grade({ passed: true })],
2208+
enrollment_mode: EnrollmentMode.Verified,
2209+
run: { ...run, course },
2210+
})
2211+
2212+
const windowOpenSpy = jest
2213+
.spyOn(window, "open")
2214+
.mockImplementation(() => null)
2215+
2216+
renderWithProviders(
2217+
<DashboardCard
2218+
resource={{
2219+
type: DashboardType.CourseRunEnrollment,
2220+
data: enrollment,
2221+
}}
2222+
/>,
2223+
)
2224+
2225+
const card = getCard()
2226+
const contextMenuButton = within(card).getByRole("button", {
2227+
name: "More options",
2228+
})
2229+
await user.click(contextMenuButton)
2230+
2231+
const receiptItem = screen.getByRole("menuitem", { name: "Receipt" })
2232+
await user.click(receiptItem)
2233+
2234+
expect(windowOpenSpy).toHaveBeenCalledWith(
2235+
mitxonlineLegacyUrl("/orders/receipt/by-run/42/"),
2236+
"_blank",
2237+
"noopener,noreferrer",
2238+
)
2239+
windowOpenSpy.mockRestore()
2240+
})
2241+
2242+
test("Receipt menu item appears for verified program enrollment", async () => {
2243+
setupUserApis()
2244+
const program = mitxonline.factories.programs.simpleProgram()
2245+
const programEnrollment =
2246+
mitxonline.factories.enrollment.programEnrollmentV3({
2247+
program,
2248+
enrollment_mode: EnrollmentMode.Verified,
2249+
})
2250+
2251+
renderWithProviders(
2252+
<DashboardCard
2253+
resource={{
2254+
type: DashboardType.ProgramEnrollment,
2255+
data: programEnrollment,
2256+
}}
2257+
/>,
2258+
)
2259+
2260+
const card = getCard()
2261+
const contextMenuButton = within(card).getByRole("button", {
2262+
name: "More options",
2263+
})
2264+
await user.click(contextMenuButton)
2265+
2266+
expect(
2267+
screen.getByRole("menuitem", { name: "Receipt" }),
2268+
).toBeInTheDocument()
2269+
})
2270+
2271+
test("Receipt menu item does not appear for audit program enrollment", async () => {
2272+
setupUserApis()
2273+
const program = mitxonline.factories.programs.simpleProgram()
2274+
const programEnrollment =
2275+
mitxonline.factories.enrollment.programEnrollmentV3({
2276+
program,
2277+
enrollment_mode: EnrollmentMode.Audit,
2278+
})
2279+
2280+
renderWithProviders(
2281+
<DashboardCard
2282+
resource={{
2283+
type: DashboardType.ProgramEnrollment,
2284+
data: programEnrollment,
2285+
}}
2286+
/>,
2287+
)
2288+
2289+
const card = getCard()
2290+
const contextMenuButton = within(card).getByRole("button", {
2291+
name: "More options",
2292+
})
2293+
await user.click(contextMenuButton)
2294+
2295+
expect(
2296+
screen.queryByRole("menuitem", { name: "Receipt" }),
2297+
).not.toBeInTheDocument()
2298+
})
2299+
2300+
test("Receipt menu item links to correct MITx Online URL for verified program enrollment", async () => {
2301+
setupUserApis()
2302+
const program = mitxonline.factories.programs.simpleProgram({ id: 99 })
2303+
const programEnrollment =
2304+
mitxonline.factories.enrollment.programEnrollmentV3({
2305+
program,
2306+
enrollment_mode: EnrollmentMode.Verified,
2307+
})
2308+
2309+
const windowOpenSpy = jest
2310+
.spyOn(window, "open")
2311+
.mockImplementation(() => null)
2312+
2313+
renderWithProviders(
2314+
<DashboardCard
2315+
resource={{
2316+
type: DashboardType.ProgramEnrollment,
2317+
data: programEnrollment,
2318+
}}
2319+
/>,
2320+
)
2321+
2322+
const card = getCard()
2323+
const contextMenuButton = within(card).getByRole("button", {
2324+
name: "More options",
2325+
})
2326+
await user.click(contextMenuButton)
2327+
2328+
const receiptItem = screen.getByRole("menuitem", { name: "Receipt" })
2329+
await user.click(receiptItem)
2330+
2331+
expect(windowOpenSpy).toHaveBeenCalledWith(
2332+
mitxonlineLegacyUrl("/orders/receipt/by-program/99/"),
2333+
"_blank",
2334+
"noopener,noreferrer",
2335+
)
2336+
windowOpenSpy.mockRestore()
2337+
})
21412338
})
21422339
})

frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { useFeatureFlagEnabled } from "posthog-js/react"
2121
import { FeatureFlags } from "@/common/feature_flags"
2222

2323
import { EnrollmentStatusIndicator } from "./EnrollmentStatusIndicator"
24+
import { getReceiptMenuItem } from "./receiptMenuItem"
2425
import {
2526
EmailSettingsDialog,
2627
JustInTimeDialog,
@@ -37,10 +38,10 @@ import { mitxUserQueries } from "api/mitxonline-hooks/user"
3738
import { useQuery } from "@tanstack/react-query"
3839
import { coursePageView, programPageView, programView } from "@/common/urls"
3940
import {
40-
mitxonlineLegacyUrl,
4141
getCourseEnrollmentAction,
4242
getEnrollmentType,
4343
isVerifiedEnrollmentMode,
44+
mitxonlineLegacyUrl,
4445
} from "@/common/mitxonline"
4546
import { useReplaceBasketItem } from "api/mitxonline-hooks/baskets"
4647
import { EnrollmentStatus, getBestRun, getEnrollmentStatus } from "./helpers"
@@ -220,6 +221,12 @@ const getContextMenuItems = (
220221
},
221222
})
222223
}
224+
225+
const receiptMenuItem = getReceiptMenuItem(
226+
resource.data.enrollment_mode,
227+
`/orders/receipt/by-program/${program.id}/`,
228+
)
229+
if (receiptMenuItem) menuItems.push(receiptMenuItem)
223230
}
224231
if (resource.type === DashboardType.CourseRunEnrollment) {
225232
const detailsUrl = useProductPages
@@ -259,6 +266,12 @@ const getContextMenuItems = (
259266
},
260267
)
261268

269+
const receiptMenuItem = getReceiptMenuItem(
270+
resource.data.enrollment_mode,
271+
`/orders/receipt/by-run/${resource.data.run.id}/`,
272+
)
273+
if (receiptMenuItem) courseMenuItems.push(receiptMenuItem)
274+
262275
menuItems.push(...courseMenuItems)
263276
}
264277
return [...menuItems, ...additionalItems]
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as mitxonline from "api/mitxonline-test-utils"
2+
import { EnrollmentModeEnum } from "@mitodl/mitxonline-api-axios/v2"
3+
import { mitxonlineLegacyUrl } from "@/common/mitxonline"
4+
import { DashboardType, getContextMenuItems } from "./ModuleCard"
5+
6+
describe("ModuleCard context menu receipt item", () => {
7+
test("shows Receipt item for verified enrollment", () => {
8+
const course = mitxonline.factories.courses.course()
9+
const run = mitxonline.factories.courses.courseRun({ id: 42 })
10+
const enrollment = mitxonline.factories.enrollment.courseEnrollment({
11+
enrollment_mode: EnrollmentModeEnum.Verified,
12+
run: { ...run, course },
13+
})
14+
15+
const items = getContextMenuItems("Test Course", {
16+
type: DashboardType.CourseRunEnrollment,
17+
data: enrollment,
18+
})
19+
20+
expect(items.some((item) => item.label === "Receipt")).toBe(true)
21+
})
22+
23+
test("does not show Receipt item for audit enrollment", () => {
24+
const course = mitxonline.factories.courses.course()
25+
const run = mitxonline.factories.courses.courseRun({ id: 42 })
26+
const enrollment = mitxonline.factories.enrollment.courseEnrollment({
27+
enrollment_mode: EnrollmentModeEnum.Audit,
28+
run: { ...run, course },
29+
})
30+
31+
const items = getContextMenuItems("Test Course", {
32+
type: DashboardType.CourseRunEnrollment,
33+
data: enrollment,
34+
})
35+
36+
expect(items.some((item) => item.label === "Receipt")).toBe(false)
37+
})
38+
39+
test("Receipt item opens the expected MITx Online URL", () => {
40+
const course = mitxonline.factories.courses.course()
41+
const run = mitxonline.factories.courses.courseRun({ id: 42 })
42+
const enrollment = mitxonline.factories.enrollment.courseEnrollment({
43+
enrollment_mode: EnrollmentModeEnum.Verified,
44+
run: { ...run, course },
45+
})
46+
const windowOpenSpy = jest
47+
.spyOn(window, "open")
48+
.mockImplementation(() => null)
49+
50+
const items = getContextMenuItems("Test Course", {
51+
type: DashboardType.CourseRunEnrollment,
52+
data: enrollment,
53+
})
54+
const receiptItem = items.find((item) => item.label === "Receipt")
55+
56+
receiptItem?.onClick?.()
57+
58+
expect(windowOpenSpy).toHaveBeenCalledWith(
59+
mitxonlineLegacyUrl("/orders/receipt/by-run/42/"),
60+
"_blank",
61+
"noopener,noreferrer",
62+
)
63+
windowOpenSpy.mockRestore()
64+
})
65+
})

frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/ModuleCard.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from "@/common/mitxonline"
3232
import { useReplaceBasketItem } from "api/mitxonline-hooks/baskets"
3333
import { EnrollmentStatus, getBestRun, getEnrollmentStatus } from "./helpers"
34+
import { getReceiptMenuItem } from "./receiptMenuItem"
3435
import {
3536
CourseWithCourseRunsSerializerV2,
3637
CourseRunEnrollmentV3,
@@ -230,6 +231,12 @@ const getContextMenuItems = (
230231
},
231232
)
232233

234+
const receiptMenuItem = getReceiptMenuItem(
235+
resource.data.enrollment_mode,
236+
`/orders/receipt/by-run/${resource.data.run.id}/`,
237+
)
238+
if (receiptMenuItem) courseMenuItems.push(receiptMenuItem)
239+
233240
menuItems.push(...courseMenuItems)
234241
}
235242
return [...menuItems, ...additionalItems]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { getReceiptMenuItem } from "./receiptMenuItem"
2+
3+
describe("getReceiptMenuItem", () => {
4+
test("returns null when enrollment mode is undefined", () => {
5+
const menuItem = getReceiptMenuItem(
6+
undefined,
7+
"/orders/receipt/by-program/99/",
8+
)
9+
expect(menuItem).toBeNull()
10+
})
11+
})
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { SimpleMenuItem } from "ol-components"
2+
import {
3+
isVerifiedEnrollmentMode,
4+
mitxonlineLegacyUrl,
5+
} from "@/common/mitxonline"
6+
7+
const getReceiptMenuItem = (
8+
enrollmentMode: string | null | undefined,
9+
receiptPath: string,
10+
): SimpleMenuItem | null => {
11+
if (!enrollmentMode || !isVerifiedEnrollmentMode(enrollmentMode)) return null
12+
13+
return {
14+
className: "dashboard-card-menu-item",
15+
key: "receipt",
16+
label: "Receipt",
17+
onClick: () => {
18+
window.open(
19+
mitxonlineLegacyUrl(receiptPath),
20+
"_blank",
21+
"noopener,noreferrer",
22+
)
23+
},
24+
}
25+
}
26+
27+
export { getReceiptMenuItem }

0 commit comments

Comments
 (0)