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
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,67 @@
expect(within(row).getByText("Certificate")).toBeInTheDocument()
})

test("a learner row shows the email alongside the name", async () => {
const { org, contract, orgSlug } = setup()
const contractId = String(contract.id)
setMockResponse.get(
mitxUrls.organization.managerOrganizationsList(),
paginate([org]),
)
mockTotal(contractId, 1)
mockList(contractId, [
analyticsFactories.learnerProgress({
full_name: "Anton Petrov",
email: "anton@example.com",
courserun_title: "Module 5",
}),
])

renderWithProviders(
<ContractLearnersPage orgSlug={orgSlug} contractSlug={contract.slug} />,
)

const name = await screen.findByText("Anton Petrov")
const row = rowOf(name)
expect(within(row).getByText("anton@example.com")).toBeInTheDocument()
expect(within(row).getByText("AP")).toBeInTheDocument()
})

test.each([
{ fullName: null, label: "null" },
{ fullName: "", label: "empty" },
{ fullName: " ", label: "whitespace-only" },
])(
"a learner with a $label name shows only the email and an icon avatar",
async ({ fullName }) => {
const { org, contract, orgSlug } = setup()
const contractId = String(contract.id)
setMockResponse.get(
mitxUrls.organization.managerOrganizationsList(),
paginate([org]),
)
mockTotal(contractId, 1)
mockList(contractId, [
analyticsFactories.learnerProgress({
full_name: fullName,
email: "x7k2m@example.com",
courserun_title: "Module 5",
}),
])

renderWithProviders(
<ContractLearnersPage orgSlug={orgSlug} contractSlug={contract.slug} />,
)

const email = await screen.findByText("x7k2m@example.com")
const row = rowOf(email)
expect(within(row).getByText("Module 5")).toBeInTheDocument()
expect(within(row).queryByText("?")).not.toBeInTheDocument()
expect(within(row).queryByText("Unknown learner")).not.toBeInTheDocument()
expect(row.querySelector("[aria-hidden='true'] svg")).not.toBeNull()
},
)

test("a learner who withheld consent shows No consent given", async () => {
const { org, contract, orgSlug } = setup()
const contractId = String(contract.id)
Expand Down Expand Up @@ -451,7 +512,7 @@
* type-checked code and re-enable by dropping `.skip` once the block they
* cover is restored.
*/
test.skip("the module dropdown lists every course run on the contract", async () => {

Check warning on line 515 in frontends/main/src/app-pages/ContractLearnersPage/ContractLearnersPage.test.tsx

View workflow job for this annotation

GitHub Actions / javascript-tests

Tests should not be skipped
const { org, contract, orgSlug } = setup()
const contractId = String(contract.id)
setMockResponse.get(
Expand Down Expand Up @@ -479,7 +540,7 @@
expect(within(listbox).getByText("Module 6")).toBeInTheDocument()
})

test.skip("selecting a module sends courserun_readable_id", async () => {

Check warning on line 543 in frontends/main/src/app-pages/ContractLearnersPage/ContractLearnersPage.test.tsx

View workflow job for this annotation

GitHub Actions / javascript-tests

Tests should not be skipped
const { org, contract, orgSlug } = setup()
const contractId = String(contract.id)
setMockResponse.get(
Expand Down Expand Up @@ -622,7 +683,7 @@
* deleting; re-enable by dropping `.skip` once that block and the matching
* one in LearnerRow.tsx are restored.
*/
test.skip("row checkboxes are individually named for screen readers", async () => {

Check warning on line 686 in frontends/main/src/app-pages/ContractLearnersPage/ContractLearnersPage.test.tsx

View workflow job for this annotation

GitHub Actions / javascript-tests

Tests should not be skipped
const { org, contract, orgSlug } = setup()
const contractId = String(contract.id)
setMockResponse.get(
Expand Down Expand Up @@ -651,7 +712,7 @@
})
})

test.skip("bulk reminder is disabled until a row is selected", async () => {

Check warning on line 715 in frontends/main/src/app-pages/ContractLearnersPage/ContractLearnersPage.test.tsx

View workflow job for this annotation

GitHub Actions / javascript-tests

Tests should not be skipped
const { org, contract, orgSlug } = setup()
const contractId = String(contract.id)
setMockResponse.get(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client"

import React from "react"
import { RiUserLine } from "@remixicon/react"
import { styled, Typography } from "ol-components"
import { initials } from "ol-utilities"
import type { LearnerProgress } from "api/analytics-hooks/organizations"
Expand Down Expand Up @@ -68,8 +69,9 @@ import { COLUMN_FLEX } from "./columns"
// --------------------------------------------------------------------------

/**
* Initials, not a photo: the analytics API returns no avatar image, and a name
* is the only identity it carries.
* Initials, not a photo: the analytics API returns no avatar image. Without a
* name, a generic person icon — email-derived initials are wrong for addresses
* like `jdoe@` or `x7k2m@`.
*/
const Avatar = styled.div(({ theme }) => ({
display: "flex",
Expand Down Expand Up @@ -97,6 +99,13 @@ const LearnerName = styled(Typography)(({ theme }) => ({
textOverflow: "ellipsis",
})) as typeof Typography

const LearnerEmail = styled(Typography)(({ theme }) => ({
...theme.typography.body3,
color: theme.custom.colors.darkGray2,
overflow: "hidden",
textOverflow: "ellipsis",
})) as typeof Typography

const CourseTitle = styled(Typography)(({ theme }) => ({
...theme.typography.body3,
color: theme.custom.colors.silverGrayDark,
Expand Down Expand Up @@ -184,7 +193,7 @@ const LearnerRow: React.FC<LearnerRowProps> = ({ row }) => {
const status = getDisplayStatus(row)
const statusLabel = DISPLAY_STATUS_LABEL[status]
const isWithheld = status === "not-shared"
const name = row.full_name ?? row.email ?? "Unknown learner"
const name = row.full_name?.trim() || null

return (
<TableRow role="row">
Expand All @@ -195,18 +204,21 @@ const LearnerRow: React.FC<LearnerRowProps> = ({ row }) => {
checked={selected}
onChange={() => onToggleSelect(rowId)}
inputProps={{
"aria-label": `Select ${name}, ${row.courserun_title}`,
"aria-label": `Select ${name ?? row.email ?? "learner"}, ${row.courserun_title}`,
}}
/>
</SelectCell>
*/}

<LearnerCell role="cell" $flex={COLUMN_FLEX.learner} $primary>
<Avatar aria-hidden="true">
{row.full_name ? initials(row.full_name) : "?"}
{name ? initials(name) : <RiUserLine size={16} />}
</Avatar>
<span>
<LearnerName component="div">{name}</LearnerName>
{name && <LearnerName component="div">{name}</LearnerName>}
{row.email && (
<LearnerEmail component="div">{row.email}</LearnerEmail>
)}
<CourseTitle component="div">{row.courserun_title}</CourseTitle>
</span>
</LearnerCell>
Expand Down
Loading