Skip to content
Merged
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
8 changes: 8 additions & 0 deletions frontends/api/src/mitxonline/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ProgramsApi,
ProgramCertificatesApi,
UsersApi,
ProgramEnrollmentsApi,
} from "@mitodl/mitxonline-api-axios/v2"
import axios from "axios"

Expand Down Expand Up @@ -51,10 +52,17 @@ const courseRunEnrollmentsApi = new EnrollmentsApi(
axiosInstance,
)

const programEnrollmentsApi = new ProgramEnrollmentsApi(
undefined,
BASE_PATH,
axiosInstance,
)

export {
usersApi,
b2bApi,
courseRunEnrollmentsApi,
programEnrollmentsApi,
programsApi,
programCollectionsApi,
coursesApi,
Expand Down
20 changes: 17 additions & 3 deletions frontends/api/src/mitxonline/hooks/enrollment/queries.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { queryOptions } from "@tanstack/react-query"
import type { CourseRunEnrollment } from "@mitodl/mitxonline-api-axios/v2"
import type {
CourseRunEnrollment,
UserProgramEnrollmentDetail,
} from "@mitodl/mitxonline-api-axios/v2"

import { courseRunEnrollmentsApi } from "../../clients"
import { courseRunEnrollmentsApi, programEnrollmentsApi } from "../../clients"
import { RawAxiosRequestConfig } from "axios"

const enrollmentKeys = {
root: ["mitxonline", "enrollments"],
Expand All @@ -10,10 +14,11 @@ const enrollmentKeys = {
"courseRunEnrollments",
"list",
],
programEnrollmentsList: () => [
programEnrollmentsList: (opts?: RawAxiosRequestConfig) => [
...enrollmentKeys.root,
"programEnrollments",
"list",
opts,
],
}

Expand All @@ -25,6 +30,15 @@ const enrollmentQueries = {
return courseRunEnrollmentsApi.enrollmentsList().then((res) => res.data)
},
}),
programEnrollmentsList: (opts?: RawAxiosRequestConfig) =>
queryOptions({
queryKey: enrollmentKeys.programEnrollmentsList(opts),
queryFn: async (): Promise<UserProgramEnrollmentDetail[]> => {
return programEnrollmentsApi
.programEnrollmentsList(opts)
.then((res) => res.data)
},
}),
}

export { enrollmentQueries, enrollmentKeys }
73 changes: 71 additions & 2 deletions frontends/api/src/mitxonline/test-utils/factories/courses.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,80 @@
import { mergeOverrides, makePaginatedFactory } from "ol-test-utilities"
import type { PartialFactory } from "ol-test-utilities"
import type { CourseWithCourseRunsSerializerV2 } from "@mitodl/mitxonline-api-axios/v2"
import type {
CourseWithCourseRunsSerializerV2,
V1CourseWithCourseRuns,
} from "@mitodl/mitxonline-api-axios/v2"
import { faker } from "@faker-js/faker/locale/en"
import { UniqueEnforcer } from "enforce-unique"

const uniqueCourseId = new UniqueEnforcer()
const uniqueCourseRunId = new UniqueEnforcer()

const v1Course: PartialFactory<V1CourseWithCourseRuns> = (overrides = {}) => {
const defaults: V1CourseWithCourseRuns = {
id: uniqueCourseId.enforce(() => faker.number.int()),
title: faker.lorem.words(3),
readable_id: faker.lorem.slug(),
next_run_id: faker.number.int(),
departments: [
{
name: faker.company.name(),
},
],
page: {
feature_image_src: faker.image.url(),
page_url: faker.internet.url(),
description: faker.lorem.paragraph(),
live: faker.datatype.boolean(),
length: `${faker.number.int({ min: 1, max: 12 })} weeks`,
effort: `${faker.number.int({ min: 1, max: 10 })} hours/week`,
financial_assistance_form_url: faker.internet.url(),
current_price: faker.number.int({ min: 0, max: 1000 }),
instructors: [
{
name: faker.person.fullName(),
bio: faker.lorem.paragraph(),
},
],
},
programs: null,
courseruns: [
{
id: uniqueCourseRunId.enforce(() => faker.number.int()),
title: faker.lorem.words(3),
start_date: faker.date.future().toISOString(),
end_date: faker.date.future().toISOString(),
enrollment_start: faker.date.past().toISOString(),
enrollment_end: faker.date.future().toISOString(),
expiration_date: faker.date.future().toISOString(),
courseware_url: faker.internet.url(),
courseware_id: faker.string.uuid(),
certificate_available_date: faker.date.future().toISOString(),
upgrade_deadline: faker.date.future().toISOString(),
is_upgradable: faker.datatype.boolean(),
is_enrollable: faker.datatype.boolean(),
is_archived: faker.datatype.boolean(),
is_self_paced: faker.datatype.boolean(),
run_tag: faker.lorem.word(),
live: faker.datatype.boolean(),
course_number: faker.lorem.word(),
products: [
{
id: faker.number.int(),
price: faker.commerce.price(),
description: faker.lorem.sentence(),
is_active: faker.datatype.boolean(),
product_flexible_price: null,
},
],
approved_flexible_price_exists: faker.datatype.boolean(),
},
],
}

return mergeOverrides<V1CourseWithCourseRuns>(defaults, overrides)
}

const course: PartialFactory<CourseWithCourseRunsSerializerV2> = (
overrides = {},
) => {
Expand Down Expand Up @@ -91,6 +159,7 @@ const course: PartialFactory<CourseWithCourseRunsSerializerV2> = (
return mergeOverrides<CourseWithCourseRunsSerializerV2>(defaults, overrides)
}

const v1Courses = makePaginatedFactory(v1Course)
const courses = makePaginatedFactory(course)

export { course, courses }
export { v1Course, v1Courses, course, courses }
51 changes: 50 additions & 1 deletion frontends/api/src/mitxonline/test-utils/factories/enrollment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import type { PartialFactory } from "ol-test-utilities"
import type {
CourseRunEnrollment,
CourseRunGrade,
UserProgramEnrollmentDetail,
} from "@mitodl/mitxonline-api-axios/v2"
import { UniqueEnforcer } from "enforce-unique"
import { factories } from ".."

const uniqueEnrollmentId = new UniqueEnforcer()
const uniqueRunId = new UniqueEnforcer()
Expand Down Expand Up @@ -99,9 +101,56 @@ const courseEnrollment: PartialFactory<CourseRunEnrollment> = (
return mergeOverrides<CourseRunEnrollment>(defaults, overrides)
}

const programEnrollment: PartialFactory<UserProgramEnrollmentDetail> = (
overrides = {},
): UserProgramEnrollmentDetail => {
const defaults: UserProgramEnrollmentDetail = {
certificate: faker.datatype.boolean()
? {
uuid: faker.string.uuid(),
link: faker.internet.url(),
}
: null,
program: {
id: faker.number.int(),
title: faker.lorem.words(3),
readable_id: faker.lorem.slug(),
courses: factories.courses.v1Course(),
requirements: {
required: [faker.number.int()],
electives: [faker.number.int()],
},
req_tree: [],
page: {
feature_image_src: faker.image.url(),
page_url: faker.internet.url(),
financial_assistance_form_url: faker.internet.url(),
description: faker.lorem.paragraph(),
live: faker.datatype.boolean(),
length: `${faker.number.int({ min: 1, max: 12 })} weeks`,
effort: `${faker.number.int({ min: 1, max: 10 })} hours/week`,
price: faker.commerce.price(),
},
program_type: faker.helpers.arrayElement([
"certificate",
"degree",
"diploma",
]),
departments: [
{
name: faker.company.name(),
},
],
live: faker.datatype.boolean(),
},
enrollments: [courseEnrollment()],
}
return mergeOverrides<UserProgramEnrollmentDetail>(defaults, overrides)
}

// Not paginated
const courseEnrollments = (count: number): CourseRunEnrollment[] => {
return new Array(count).fill(null).map(() => courseEnrollment())
}

export { courseEnrollment, courseEnrollments, grade }
export { courseEnrollment, courseEnrollments, grade, programEnrollment }
5 changes: 5 additions & 0 deletions frontends/api/src/mitxonline/test-utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const enrollment = {
`${API_BASE_URL}/api/v1/enrollments/${id ? `${id}/` : ""}`,
}

const programEnrollments = {
enrollmentsList: () => `${API_BASE_URL}/api/v1/program_enrollments/`,
}

const b2b = {
courseEnrollment: (readableId?: string) =>
`${API_BASE_URL}/api/v0/b2b/enroll/${readableId}/`,
Expand Down Expand Up @@ -59,4 +63,5 @@ export {
programCollections,
courses,
organization,
programEnrollments,
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import moment from "moment"
const makeCourses = factories.courses.courses
const makeProgram = factories.programs.program
const makeProgramCollection = factories.programs.programCollection
const makeEnrollment = factories.enrollment.courseEnrollment
const makeCourseEnrollment = factories.enrollment.courseEnrollment
const makeGrade = factories.enrollment.grade

const dashboardCourse: PartialFactory<DashboardCourse> = (...overrides) => {
Expand Down Expand Up @@ -45,20 +45,20 @@ const dashboardCourse: PartialFactory<DashboardCourse> = (...overrides) => {

const setupEnrollments = (includeExpired: boolean) => {
const completed = [
makeEnrollment({
makeCourseEnrollment({
run: { title: "C Course Ended" },
grades: [makeGrade({ passed: true })],
}),
]
const expired = includeExpired
? [
makeEnrollment({
makeCourseEnrollment({
run: {
title: "A Course Ended",
end_date: faker.date.past().toISOString(),
},
}),
makeEnrollment({
makeCourseEnrollment({
run: {
title: "B Course Ended",
end_date: faker.date.past().toISOString(),
Expand All @@ -67,26 +67,26 @@ const setupEnrollments = (includeExpired: boolean) => {
]
: []
const started = [
makeEnrollment({
makeCourseEnrollment({
run: {
title: "A Course Started",
start_date: faker.date.past().toISOString(),
},
}),
makeEnrollment({
makeCourseEnrollment({
run: {
title: "B Course Started",
start_date: faker.date.past().toISOString(),
},
}),
]
const notStarted = [
makeEnrollment({
makeCourseEnrollment({
run: {
start_date: moment().add(1, "day").toISOString(), // Sooner first
},
}),
makeEnrollment({
makeCourseEnrollment({
run: {
start_date: moment().add(5, "day").toISOString(), // Later second
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ describe("OrganizationContent", () => {
mockedUseFeatureFlagEnabled.mockReturnValue(true)
// Set default empty enrollments for all tests
setMockResponse.get(urls.enrollment.enrollmentsList(), [])
// Add missing program enrollments mock
setMockResponse.get(urls.programEnrollments.enrollmentsList(), [])
})

it("displays a header for each program returned and cards for courses in program", async () => {
Expand Down Expand Up @@ -300,4 +302,41 @@ describe("OrganizationContent", () => {
)
})
})

test("Shows the program certificate link button if the program has a certificate", async () => {
const { orgX, programA } = setupProgramsAndCourses()

// Mock the program to have a certificate
const programWithCertificate = {
...programA,
program_type: "Program", // Set specific program type
certificate: {
uuid: "cert-123",
url: "/certificates/program/1",
},
}
const programEnrollment = factories.enrollment.programEnrollment({
program: { id: programWithCertificate.id },
certificate: {
link: programWithCertificate.certificate.url,
},
})
setMockResponse.get(urls.programs.programsList({ org_id: orgX.id }), {
results: [programWithCertificate],
})
setMockResponse.get(urls.programEnrollments.enrollmentsList(), [
programEnrollment,
])

renderWithProviders(<OrganizationContent orgSlug={orgX.slug} />)

const programRoot = await screen.findByTestId("org-program-root")
const certificateButton = within(programRoot).getByRole("link", {
name: "View Program Certificate",
})
expect(certificateButton).toHaveAttribute(
"href",
programWithCertificate.certificate.url,
)
})
})
Loading
Loading