diff --git a/frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.test.tsx b/frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.test.tsx deleted file mode 100644 index 682049ab6c..0000000000 --- a/frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react" -import { render, screen } from "@testing-library/react" -import CardTemplate from "./CardTemplate" -import { DEFAULT_RESOURCE_IMG } from "ol-utilities" -import { makeImgConfig } from "ol-utilities/test-utils/factories" - -describe("CardTemplate", () => { - it("renders title and cover image", () => { - const title = "Test Title" - render( - , - ) - const heading = screen.getByRole("heading", { name: title }) - expect(heading).toHaveAccessibleName(title) - }) -}) diff --git a/frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.tsx b/frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.tsx deleted file mode 100644 index d88b45050c..0000000000 --- a/frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import React from "react" -import invariant from "tiny-invariant" -import { - MuiCard, - CardContent, - CardMedia, - styled, - TruncateText, -} from "ol-components" -import { RiDraggable } from "@remixicon/react" -import { - DEFAULT_RESOURCE_IMG, - EmbedlyConfig, - embedlyCroppedImage, -} from "ol-utilities" - -type CardVariant = "column" | "row" | "row-reverse" -type OnActivateCard = () => void -type CardTemplateProps = { - /** - * Whether the course picture and info display as a column or row. - */ - variant: CardVariant - className?: string - handleActivate?: OnActivateCard - extraDetails?: React.ReactNode - imgUrl: string - imgConfig: EmbedlyConfig - title?: string - bodySlot?: React.ReactNode - footerSlot?: React.ReactNode - footerActionSlot?: React.ReactNode - sortable?: boolean - suppressImage?: boolean -} - -const LIGHT_TEXT_COLOR = "#8c8c8c" -const SPACER = 0.75 -const SMALL_FONT_SIZE = 0.75 - -const StyledCard = styled(MuiCard)` - display: flex; - flex-direction: column; - - /* Ensure the resource image borders match card borders */ - .MuiCardMedia-root, - > .MuiCardContent-root { - border-radius: inherit; - } -` - -const Details = styled.div` - /* Make content flexbox so that we can control which child fills remaining space. */ - flex: 1; - display: flex; - flex-direction: column; - - > * { - /* - Flexbox doesn't have collapsing margins, so we need to avoid double spacing. - The column-gap property would be a nicer solution, but it doesn't have the - best browser support yet. - */ - margin-top: ${SPACER / 2}rem; - margin-bottom: ${SPACER / 2}rem; - - &:first-of-type { - margin-top: 0; - } - - &:last-child { - margin-bottom: 0; - } - } -` - -const StyledCardContent = styled(CardContent, { - shouldForwardProp: (prop) => prop !== "sortable", -})<{ - variant: CardVariant - sortable: boolean -}>` - display: flex; - flex-direction: ${({ variant }) => variant}; - ${({ variant }) => (variant === "column" ? "flex: 1;" : "")} - ${({ sortable }) => (sortable ? "padding-left: 4px;" : "")} -` - -/* - Last child of ol-lrc-content will take up any extra space (flex: 1) but - with its contents at the bottom of its box. - The default is stretch, we we do not want. -*/ -const FillSpaceContentEnd = styled.div` - flex: 1; - display: flex; - flex-direction: column; - justify-content: flex-end; - align-items: flex-start; -` - -const FooterRow = styled.div` - min-height: 2.5 * ${SMALL_FONT_SIZE}; /* ensure consistent spacing even if no date */ - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - width: 100%; -` - -const EllipsisTitle = styled(TruncateText)({ - fontWeight: "bold", - margin: 0, -}) - -const TitleButton = styled.button` - border: none; - background-color: white; - color: inherit; - display: block; - text-align: left; - padding: 0; - margin: 0; - - &:hover { - text-decoration: underline; - cursor: pointer; - } -` - -const DragHandle = styled.div` - display: flex; - align-items: center; - font-size: 40px; - align-self: stretch; - color: ${LIGHT_TEXT_COLOR}; - border-right: 1px solid ${LIGHT_TEXT_COLOR}; - margin-right: 16px; -` - -const CardMediaImage = styled(CardMedia)<{ - variant: CardVariant - component: string - alt: string -}>` - ${({ variant }) => - variant === "row" - ? "margin-right: 16px;" - : variant === "row-reverse" - ? "margin-left: 16px;" - : ""} -` - -type ImageProps = Pick< - CardTemplateProps, - "imgUrl" | "imgConfig" | "suppressImage" | "variant" -> -const CardImage: React.FC = ({ - imgUrl, - imgConfig, - suppressImage, - variant, -}) => { - if (suppressImage) return null - const dims = - variant === "column" - ? { height: imgConfig.height } - : { width: imgConfig.width, height: imgConfig.height } - - return ( - - ) -} - -const CardTemplate = ({ - variant, - className, - handleActivate, - extraDetails, - imgUrl, - imgConfig, - title, - bodySlot, - footerSlot, - footerActionSlot, - sortable = false, - suppressImage = false, -}: CardTemplateProps) => { - invariant( - !sortable || variant === "row-reverse", - "sortable only supported for variant='row-reverse'", - ) - - const image = ( - - ) - - return ( - - {variant === "column" ? image : null} - - {variant !== "column" ? image : null} -
- {extraDetails} - {handleActivate ? ( - - - {title} - - - ) : ( - - {title} - - )} - {sortable ? null : ( - <> - {bodySlot} - - -
{footerSlot}
- {footerActionSlot} -
-
- - )} -
- {sortable ? ( - - - - ) : null} -
-
- ) -} - -export default CardTemplate -export type { CardTemplateProps } diff --git a/frontends/mit-learn/src/page-components/ItemsListing/ItemsListing.test.tsx b/frontends/mit-learn/src/page-components/ItemsListing/ItemsListing.test.tsx index e791c0d4a7..10b770818b 100644 --- a/frontends/mit-learn/src/page-components/ItemsListing/ItemsListing.test.tsx +++ b/frontends/mit-learn/src/page-components/ItemsListing/ItemsListing.test.tsx @@ -175,11 +175,14 @@ describe("ItemsListing", () => { />, { user: {} }, ) + const titles = items.map((item) => item.resource.title) - const headings = screen.getAllByRole("heading", { - name: (value) => titles.includes(value), + + const role = sortable ? "button" : "link" + const cards = screen.getAllByRole(role, { + name: (value) => titles.some((title) => value.includes(title)), }) - expect(headings.map((h) => h.textContent)).toEqual(titles) + expect(cards.length).toBe(titles.length) if (sortable) { items.forEach(({ resource }) => { @@ -320,23 +323,24 @@ describe.each([ListType.LearningPath, ListType.UserList])( const patchResponse = new ControlledPromise() setMockResponse.patch(patchUrl(listType, active.id), patchResponse) - const titleEls1 = screen.getAllByRole("heading", { - name: (value) => titles.includes(value), + // dnd-kit draggables have role button (+ a roledescription) + const cards1 = screen.getAllByRole("button", { + name: (value) => titles.some((title) => value.includes(title)), }) - expect(titleEls1.map((el) => el.textContent)).toEqual(titles) + expect(cards1.length).toBe(titles.length) act(() => simulateDrag(from, to)) await waitFor(() => { - const titleEls2 = screen.getAllByRole("heading", { - name: (value) => titles.includes(value), + const cards2 = screen.getAllByRole("button", { + name: (value) => titles.some((title) => value.includes(title)), }) - expect(titleEls2).toEqual([ - titleEls1[0], - titleEls1[2], - titleEls1[3], - titleEls1[1], - titleEls1[4], + expect(cards2).toEqual([ + cards1[0], + cards1[2], + cards1[3], + cards1[1], + cards1[4], ]) }) }) diff --git a/frontends/mit-learn/src/page-components/ItemsListing/ItemsListingComponent.tsx b/frontends/mit-learn/src/page-components/ItemsListing/ItemsListingComponent.tsx index 13627b7bf0..cb8640bd1b 100644 --- a/frontends/mit-learn/src/page-components/ItemsListing/ItemsListingComponent.tsx +++ b/frontends/mit-learn/src/page-components/ItemsListing/ItemsListingComponent.tsx @@ -26,13 +26,12 @@ type ItemsListingComponentProps = { condensed?: boolean } -const HeaderText = styled.div(({ theme }) => ({ - h3: { - ...theme.typography.h3, - [theme.breakpoints.down("sm")]: { - marginBottom: "24px", - ...theme.typography.h5, - }, +const HeaderText = styled.h1(({ theme }) => ({ + margin: 0, + ...theme.typography.h3, + [theme.breakpoints.down("sm")]: { + marginBottom: "24px", + ...theme.typography.h5, }, })) @@ -110,9 +109,7 @@ const ItemsListingComponent: React.FC = ({ marginBottom="24px" > - - {list?.title} - + {list?.title} {list?.description && ( {list.description} )} diff --git a/frontends/mit-learn/src/page-components/ResourceCard/ResourceCard.test.tsx b/frontends/mit-learn/src/page-components/ResourceCard/ResourceCard.test.tsx index eab4c6980c..51c1bde37a 100644 --- a/frontends/mit-learn/src/page-components/ResourceCard/ResourceCard.test.tsx +++ b/frontends/mit-learn/src/page-components/ResourceCard/ResourceCard.test.tsx @@ -186,8 +186,8 @@ describe.each([ user: { is_learning_path_editor: true }, }) invariant(resource) - const cardTitle = screen.getByRole("heading", { name: resource.title }) - await user.click(cardTitle) + const link = screen.getByRole("link", { name: new RegExp(resource.title) }) + await user.click(link) expect( new URLSearchParams(location.current.search).get( RESOURCE_DRAWER_QUERY_PARAM, diff --git a/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx b/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx index ed54fb90f0..6a40d54875 100644 --- a/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx +++ b/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.test.tsx @@ -86,7 +86,11 @@ describe("ResourceCarousel", () => { const { resources } = setupApis({ autoResolve: true }) renderWithProviders( - , + , ) const tabs = await screen.findAllByRole("tab") @@ -123,7 +127,11 @@ describe("ResourceCarousel", () => { const { resources } = setupApis() renderWithProviders( - , + , ) if (expectTabs) { @@ -153,7 +161,11 @@ describe("ResourceCarousel", () => { setMockResponse.get(urls.userMe.get(), {}) setupApis() renderWithProviders( - , + , ) await waitFor(() => { expect(makeRequest).toHaveBeenCalledWith( @@ -172,22 +184,38 @@ describe("ResourceCarousel", () => { expect(urlParams.get("professional")).toEqual("true") }) - it("Shows the correct title", async () => { - const config: ResourceCarouselProps["config"] = [ - { - label: "Resources", - data: { - type: "resources", - params: { resource_type: ["course", "program"], professional: true }, + it.each([ + { titleComponent: "h1", expectedTag: "H1" }, + { titleComponent: "h3", expectedTag: "H3" }, + ] as const)( + "Shows the correct title with correct heading level", + async ({ titleComponent, expectedTag }) => { + const config: ResourceCarouselProps["config"] = [ + { + label: "Resources", + data: { + type: "resources", + params: { + resource_type: ["course", "program"], + professional: true, + }, + }, }, - }, - ] - setMockResponse.get(urls.userMe.get(), {}) - setupApis() - renderWithProviders( - , - ) + ] + setMockResponse.get(urls.userMe.get(), {}) + setupApis() + renderWithProviders( + , + ) - await screen.findByRole("heading", { name: "My Favorite Carousel" }) - }) + const title = await screen.findByRole("heading", { + name: "My Favorite Carousel", + }) + expect(title.tagName).toBe(expectedTag) + }, + ) }) diff --git a/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.tsx b/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.tsx index 86e6022f20..771fb83cdf 100644 --- a/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.tsx +++ b/frontends/mit-learn/src/page-components/ResourceCarousel/ResourceCarousel.tsx @@ -8,6 +8,7 @@ import { TabButtonList, styled, Typography, + TypographyProps, } from "ol-components" import type { TabConfig } from "./types" import { LearningResource, PaginatedLearningResourceList } from "api" @@ -49,13 +50,15 @@ const HeaderRow = styled.div(({ theme }) => ({ }, })) -const HeaderText = styled(Typography)(({ theme }) => ({ - paddingRight: "16px", - [theme.breakpoints.down("sm")]: { - paddingBottom: "16px", - ...theme.typography.h5, - }, -})) +const HeaderText = styled(Typography)>( + ({ theme }) => ({ + paddingRight: "16px", + [theme.breakpoints.down("sm")]: { + paddingBottom: "16px", + ...theme.typography.h5, + }, + }), +) const ControlsContainer = styled.div(({ theme }) => ({ display: "flex", @@ -152,6 +155,10 @@ type ResourceCarouselProps = { className?: string isLoading?: boolean "data-testid"?: string + /** + * Element type for the carousel title + */ + titleComponent: React.ElementType } /** * A tabbed carousel that fetches resources based on the configuration provided. @@ -170,6 +177,7 @@ const ResourceCarousel: React.FC = ({ className, isLoading, "data-testid": dataTestId, + titleComponent, }) => { const [tab, setTab] = React.useState("0") const [ref, setRef] = React.useState(null) @@ -210,15 +218,12 @@ const ResourceCarousel: React.FC = ({ ) return ( - + - {title} + + {title} + {config.length === 1 ? buttonsContainerElement : null} {config.length > 1 ? ( diff --git a/frontends/mit-learn/src/page-components/SearchDisplay/SearchDisplay.tsx b/frontends/mit-learn/src/page-components/SearchDisplay/SearchDisplay.tsx index 5d5426c746..c57463e37d 100644 --- a/frontends/mit-learn/src/page-components/SearchDisplay/SearchDisplay.tsx +++ b/frontends/mit-learn/src/page-components/SearchDisplay/SearchDisplay.tsx @@ -14,6 +14,7 @@ import { css, Drawer, Checkbox, + VisuallyHidden, } from "ol-components" import { @@ -510,6 +511,8 @@ interface SearchDisplayProps { * rather than from a new "instance" of `useSearchParams`. */ setSearchParams: UseResourceSearchParamsProps["setSearchParams"] + resultsHeadingEl: React.ElementType + filterHeadingEl: React.ElementType } const SearchDisplay: React.FC = ({ @@ -525,6 +528,8 @@ const SearchDisplay: React.FC = ({ toggleParamValue, showProfessionalToggle, setSearchParams, + resultsHeadingEl, + filterHeadingEl, }) => { const [searchParams] = useSearchParams() const [expandAdminOptions, setExpandAdminOptions] = useState(false) @@ -713,12 +718,15 @@ const SearchDisplay: React.FC = ({ - Filter + + Filter + {hasFacets ? ( @@ -734,7 +742,10 @@ const SearchDisplay: React.FC = ({ {filterContents} - + + + Search Results + {sortDropdown} = ({
- Filter + + Filter +
({ +const QuoteContainer = styled.div(({ theme }) => ({ backgroundColor: theme.custom.colors.darkGray2, color: theme.custom.colors.white, overflow: "auto", diff --git a/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.test.tsx b/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.test.tsx index 06168e2b31..562600c4c3 100644 --- a/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.test.tsx +++ b/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.test.tsx @@ -8,7 +8,7 @@ import { renderWithProviders } from "@/test-utils" const userListFactory = factories.userLists describe("UserListCard", () => { - it("renders title and cover image", () => { + it("renders title", () => { const userList = userListFactory.userList() renderWithProviders( { userList={userList} />, ) - const heading = screen.getByRole("heading", { name: userList.title }) - expect(heading).toHaveAccessibleName(userList.title) + screen.getByText(userList.title) }) }) diff --git a/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.tsx b/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.tsx index 9fa5debd43..1689b5bd71 100644 --- a/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.tsx +++ b/frontends/mit-learn/src/page-components/UserListCard/UserListCardCondensed.tsx @@ -52,11 +52,7 @@ const UserListCardCondensed = ({ - + {userList.title} diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx index e6b9fcc9e1..3654e4140a 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.test.tsx @@ -10,6 +10,7 @@ import { assertPartialMetas, } from "../../test-utils" import ChannelSearch from "./ChannelSearch" +import { assertHeadings } from "ol-test-utilities" jest.mock("./ChannelSearch", () => { const actual = jest.requireActual("./ChannelSearch") @@ -214,6 +215,22 @@ describe("ChannelPage", () => { expect(subscribedButton).toBeVisible() }, ) + + test("headings", async () => { + const { channel } = setupApis({ + search_filter: "topic=Physics", + }) + renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + + await waitFor(() => { + assertHeadings([ + { level: 1, name: channel.title }, + { level: 2, name: `Search within ${channel.title}` }, + { level: 3, name: "Filter" }, + { level: 3, name: "Search Results" }, + ]) + }) + }) }) describe("Unit Channel Pages", () => { @@ -302,4 +319,23 @@ describe("Unit Channel Pages", () => { ) }) }) + + test("headings", async () => { + const { channel } = setupApis({ + search_filter: "offered_by=ocw", + channel_type: "unit", + }) + renderTestApp({ url: `/c/${channel.channel_type}/${channel.name}` }) + + await waitFor(() => { + assertHeadings([ + { level: 1, name: channel.title }, + { level: 2, name: "Featured Courses" }, + { level: 2, name: "What Learners Say" }, + { level: 2, name: `Search within ${channel.title}` }, + { level: 3, name: "Filter" }, + { level: 3, name: "Search Results" }, + ]) + }) + }) }) diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx index d791861e10..4e68174a03 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/ChannelPage.tsx @@ -2,7 +2,7 @@ import React from "react" import { useParams } from "react-router" import { ChannelPageTemplate } from "./ChannelPageTemplate" import { useChannelDetail } from "api/hooks/channels" -import FieldSearch from "./ChannelSearch" +import ChannelSearch from "./ChannelSearch" import type { Facets, FacetKey, @@ -39,7 +39,8 @@ const ChannelPage: React.FC = () => {

{channelQuery.data?.public_description}

{channelQuery.data?.search_filter && ( - diff --git a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx b/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx index aba1fdadc2..29a920b54e 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/ChannelSearch.tsx @@ -19,7 +19,7 @@ import { SearchInput } from "@/page-components/SearchDisplay/SearchInput" import { getFacetManifest } from "@/pages/SearchPage/SearchPage" import _ from "lodash" -import { styled } from "ol-components" +import { styled, VisuallyHidden } from "ol-components" const SearchInputContainer = styled.div` padding-bottom: 40px; @@ -106,11 +106,13 @@ const getFacetManifestForChannelType = ( interface ChannelSearchProps { constantSearchParams: Facets & BooleanFacets channelType: ChannelTypeEnum + channelTitle?: string } const ChannelSearch: React.FC = ({ constantSearchParams, channelType, + channelTitle, }) => { const offerorsQuery = useOfferorsList() const offerors = useMemo(() => { @@ -180,7 +182,8 @@ const ChannelSearch: React.FC = ({ const page = +(searchParams.get("page") ?? "1") return ( -
+
+ Search within {channelTitle} = ({ = ({ SHOW_PROFESSIONAL_TOGGLE_BY_CHANNEL_TYPE[channelType] } /> -
+ ) } diff --git a/frontends/mit-learn/src/pages/ChannelPage/UnitChannelTemplate.tsx b/frontends/mit-learn/src/pages/ChannelPage/UnitChannelTemplate.tsx index d4884dc286..eeb699533b 100644 --- a/frontends/mit-learn/src/pages/ChannelPage/UnitChannelTemplate.tsx +++ b/frontends/mit-learn/src/pages/ChannelPage/UnitChannelTemplate.tsx @@ -7,6 +7,7 @@ import { Stack, BannerBackground, Typography, + VisuallyHidden, } from "ol-components" import { SearchSubscriptionToggle } from "@/page-components/SearchSubscriptionToggle/SearchSubscriptionToggle" import { ChannelDetails } from "@/page-components/ChannelDetails/ChannelDetails" @@ -131,7 +132,8 @@ const UnitChannelTemplate: React.FC = ({ /> - + + {channel.data?.title} {channel.data ? ( ) : null} @@ -181,14 +183,18 @@ const UnitChannelTemplate: React.FC = ({ - + - +
+ What Learners Say + +
{children} ) diff --git a/frontends/mit-learn/src/pages/DashboardPage/DashboardPage.tsx b/frontends/mit-learn/src/pages/DashboardPage/DashboardPage.tsx index 392c8eb927..7b0073d72a 100644 --- a/frontends/mit-learn/src/pages/DashboardPage/DashboardPage.tsx +++ b/frontends/mit-learn/src/pages/DashboardPage/DashboardPage.tsx @@ -18,6 +18,7 @@ import { TabPanel, Tabs, Typography, + TypographyProps, styled, } from "ol-components" import { Link } from "react-router-dom" @@ -206,14 +207,16 @@ const TabPanelStyled = styled(TabPanel)({ width: "100%", }) -const TitleText = styled(Typography)(({ theme }) => ({ - color: theme.custom.colors.black, - paddingBottom: "16px", - ...theme.typography.h3, - [theme.breakpoints.down("md")]: { - ...theme.typography.h5, - }, -})) +const TitleText = styled(Typography)>( + ({ theme }) => ({ + color: theme.custom.colors.black, + paddingBottom: "16px", + ...theme.typography.h3, + [theme.breakpoints.down("md")]: { + ...theme.typography.h5, + }, + }), +) const SubTitleText = styled(Typography)(({ theme }) => ({ color: theme.custom.colors.darkGray2, @@ -418,7 +421,7 @@ const DashboardPage: React.FC = () => { - + Your MIT Learning Journey @@ -432,6 +435,7 @@ const DashboardPage: React.FC = () => { { {topics?.map((topic, index) => ( { ))} {certification === true ? ( { /> ) : ( { /> )} { - Profile + Profile {isLoadingProfile || !profile ? ( ) : ( @@ -486,7 +495,7 @@ const DashboardPage: React.FC = () => { )} - Settings + Settings {isLoadingProfile || !profile ? ( ) : ( diff --git a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.test.tsx b/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.test.tsx index b65256f3c1..8138a4bb73 100644 --- a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.test.tsx +++ b/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.test.tsx @@ -5,6 +5,7 @@ import DepartmentListingPage from "./DepartmentListingPage" import { factories, setMockResponse, urls } from "api/test-utils" import invariant from "tiny-invariant" import { faker } from "@faker-js/faker/locale/en" +import { assertHeadings } from "ol-test-utilities" const makeSearchResponse = ( aggregations: Record, @@ -99,12 +100,12 @@ describe("DepartmentListingPage", () => { await screen.findByRole("heading", { name: schools[0].name, }) - ).closest("li") + ).closest("section") const school1 = ( await screen.findByRole("heading", { name: schools[1].name, }) - ).closest("li") + ).closest("section") invariant(school0) invariant(school1) @@ -157,4 +158,16 @@ describe("DepartmentListingPage", () => { }) expect(link).toHaveAttribute("href", dept.channel_url) }) + + test("headings", async () => { + const { schools } = setupApis() + renderWithProviders() + + await waitFor(() => { + assertHeadings([ + { level: 1, name: "Browse by Academic Department" }, + ...schools.map(({ name }) => ({ level: 2, name })), + ]) + }) + }) }) diff --git a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.tsx b/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.tsx index 2479707d8a..b7165eb714 100644 --- a/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.tsx +++ b/frontends/mit-learn/src/pages/DepartmentListingPage/DepartmentListingPage.tsx @@ -3,7 +3,6 @@ import { Container, Typography, styled, - PlainList, List, ListItem, ListItemLink, @@ -130,10 +129,9 @@ const SchoolDepartments: React.FC = ({ courseCounts, programCounts, className, - as: Component = "div", }) => { return ( - +
{SCHOOL_ICONS[school.url] ?? } @@ -172,12 +170,12 @@ const SchoolDepartments: React.FC = ({ ) })} - +
) } -const SchoolList = styled(PlainList)(({ theme }) => ({ - "> li": { +const SchoolList = styled.div(({ theme }) => ({ + "> section": { marginTop: "40px", [theme.breakpoints.down("sm")]: { marginTop: "30px", diff --git a/frontends/mit-learn/src/pages/HomePage/BrowseTopicsSection.tsx b/frontends/mit-learn/src/pages/HomePage/BrowseTopicsSection.tsx index 384672465f..bad7557396 100644 --- a/frontends/mit-learn/src/pages/HomePage/BrowseTopicsSection.tsx +++ b/frontends/mit-learn/src/pages/HomePage/BrowseTopicsSection.tsx @@ -1,5 +1,12 @@ import React from "react" -import { Container, styled, theme, Typography, ButtonLink } from "ol-components" +import { + Container, + styled, + theme, + Typography, + ButtonLink, + TypographyProps, +} from "ol-components" import { Link } from "react-router-dom" import { useLearningResourceTopics } from "api/hooks/learningResources" import { RiArrowRightLine } from "@remixicon/react" @@ -18,7 +25,7 @@ const Section = styled.section` } ` -const Title = styled(Typography)` +const Title = styled(Typography)>` text-align: center; ` @@ -102,7 +109,9 @@ const BrowseTopicsSection: React.FC = () => { return (
- Browse by Topic + + Browse by Topic + {topics?.results.map( ({ id, name, channel_url: channelUrl, icon }) => { diff --git a/frontends/mit-learn/src/pages/HomePage/HeroSearch.tsx b/frontends/mit-learn/src/pages/HomePage/HeroSearch.tsx index fe8314cc41..08827bb975 100644 --- a/frontends/mit-learn/src/pages/HomePage/HeroSearch.tsx +++ b/frontends/mit-learn/src/pages/HomePage/HeroSearch.tsx @@ -185,6 +185,7 @@ const HeroSearch: React.FC = () => { diff --git a/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx b/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx index 1bdfcd9b06..3f1f9eb441 100644 --- a/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx +++ b/frontends/mit-learn/src/pages/HomePage/HomePage.test.tsx @@ -14,9 +14,9 @@ import { within, waitFor, } from "../../test-utils" -import type { FeaturedApiFeaturedListRequest as FeaturedRequest } from "api" import invariant from "tiny-invariant" import * as routes from "@/common/urls" +import { assertHeadings } from "ol-test-utilities" const assertLinksTo = ( el: HTMLElement, @@ -46,27 +46,8 @@ const setupAPIs = () => { resources, ) - setMockResponse.get(urls.learningResources.featured({ limit: 12 }), resources) setMockResponse.get( - urls.learningResources.featured({ - free: true, - limit: 12, - }), - resources, - ) - setMockResponse.get( - urls.learningResources.featured({ - certification: true, - professional: false, - limit: 12, - }), - resources, - ) - setMockResponse.get( - urls.learningResources.featured({ - professional: true, - limit: 12, - }), + expect.stringContaining(urls.learningResources.featured()), resources, ) @@ -315,55 +296,12 @@ describe("Home Page Testimonials", () => { }) describe("Home Page Carousel", () => { - test.each<{ tab: string; params: FeaturedRequest }>([ - { - tab: "All", - params: { limit: 12, resource_type: ["course"] }, - }, - { - tab: "Free", - params: { limit: 12, resource_type: ["course"], free: true }, - }, - { - tab: "With Certificate", - params: { - resource_type: ["course"], - limit: 12, - certification: true, - professional: false, - }, - }, - { - tab: "Professional & Executive Learning", - params: { resource_type: ["course"], limit: 12, professional: true }, - }, - ])("Featured Courses Carousel Tabs", async ({ tab, params }) => { - const resources = learningResources.resources({ count: 12 }) - setupAPIs() - - // The tab buttons eager-load the resources so we need to set them all up. - - // This is for the clicked tab (which might be "All") - // We will check that its response is visible as cards. - setMockResponse.get( - urls.learningResources.featured({ ...params }), - resources, - ) - - renderWithProviders() - screen.findByRole("tab", { name: tab }).then(async (featuredTab) => { - await user.click(within(featuredTab).getByRole("tab", { name: tab })) - const [featuredPanel] = screen.getAllByRole("tabpanel") - await within(featuredPanel).findByText(resources.results[0].title) - }) - }) - test("Tabbed Carousel sanity check", async () => { setupAPIs() renderWithProviders() - screen.findAllByRole("tablist").then(([featured, media]) => { + await screen.findAllByRole("tablist").then(([featured, media]) => { within(featured).getByRole("tab", { name: "All" }) within(featured).getByRole("tab", { name: "Free" }) within(featured).getByRole("tab", { name: "With Certificate" }) @@ -376,3 +314,22 @@ describe("Home Page Carousel", () => { }) }) }) + +test("Headings", async () => { + setupAPIs() + + renderWithProviders() + await waitFor(() => { + assertHeadings([ + { level: 1, name: "Learn with MIT" }, + { level: 2, name: "Featured Courses" }, + { level: 2, name: "Continue Your Journey" }, + { level: 2, name: "Media" }, + { level: 2, name: "Browse by Topic" }, + { level: 2, name: "From Our Community" }, + { level: 2, name: "MIT Stories & Events" }, + { level: 3, name: "Stories" }, + { level: 3, name: "Events" }, + ]) + }) +}) diff --git a/frontends/mit-learn/src/pages/HomePage/HomePage.tsx b/frontends/mit-learn/src/pages/HomePage/HomePage.tsx index ba9ec13d23..d55d25e523 100644 --- a/frontends/mit-learn/src/pages/HomePage/HomePage.tsx +++ b/frontends/mit-learn/src/pages/HomePage/HomePage.tsx @@ -43,15 +43,22 @@ const HomePage: React.FC = () => { - +
+ +
- - + + diff --git a/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx b/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx index cf7ae54175..125ae6aec9 100644 --- a/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx +++ b/frontends/mit-learn/src/pages/HomePage/NewsEventsSection.tsx @@ -7,6 +7,7 @@ import { Grid, useMuiBreakpointAtLeast, Card, + TypographyProps, } from "ol-components" import { useNewsEventsList, @@ -24,7 +25,7 @@ const Section = styled.section` } ` -const Title = styled(Typography)` +const Title = styled(Typography)>` text-align: center; margin-bottom: 8px; ` @@ -231,7 +232,9 @@ const NewsEventsSection: React.FC = () => { return (
- MIT Stories & Events + + MIT Stories & Events + See what's happening in the world of learning with the latest news, insights, and upcoming events at MIT. @@ -239,7 +242,9 @@ const NewsEventsSection: React.FC = () => { {isMobile ? ( - Stories + + Stories + {stories.map((item) => ( { - Events + + Events + {EventCards} @@ -259,7 +266,9 @@ const NewsEventsSection: React.FC = () => { - Stories + + Stories + {stories.map((item) => ( @@ -269,7 +278,9 @@ const NewsEventsSection: React.FC = () => { - Events + + Events + {EventCards} diff --git a/frontends/mit-learn/src/pages/HomePage/TestimonialsSection.tsx b/frontends/mit-learn/src/pages/HomePage/TestimonialsSection.tsx index 3606d50314..f9b58f5d90 100644 --- a/frontends/mit-learn/src/pages/HomePage/TestimonialsSection.tsx +++ b/frontends/mit-learn/src/pages/HomePage/TestimonialsSection.tsx @@ -297,7 +297,9 @@ const TestimonialsSection: React.FC = () => { return (
- From Our Community + + From Our Community + Millions of learners are reaching their goals with MIT's non-degree learning resources. Here's what they're saying. diff --git a/frontends/mit-learn/src/pages/HomePage/UpcomingCoursesSection.tsx b/frontends/mit-learn/src/pages/HomePage/UpcomingCoursesSection.tsx index 5f4b922c76..10b6092146 100644 --- a/frontends/mit-learn/src/pages/HomePage/UpcomingCoursesSection.tsx +++ b/frontends/mit-learn/src/pages/HomePage/UpcomingCoursesSection.tsx @@ -37,6 +37,7 @@ const UpcomingCoursesSection: React.FC = () => { return ( diff --git a/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.test.tsx b/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.test.tsx index cf0a72994d..6ce6a169da 100644 --- a/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.test.tsx +++ b/frontends/mit-learn/src/pages/LearningPathListingPage/LearningPathListingPage.test.tsx @@ -45,13 +45,10 @@ describe("LearningPathListingPage", () => { it("Renders a card for each learning path", async () => { const { paths } = setup() const titles = paths.results.map((resource) => resource.title) - const headings = await screen.findAllByRole("heading", { - name: (value) => titles.includes(value), + const cards = await screen.findAllByRole("link", { + name: (name) => titles.some((title) => name.includes(title)), }) - - // for sanity - expect(headings.length).toBeGreaterThan(0) - expect(titles.length).toBe(headings.length) + expect(cards.length).toBeGreaterThan(0) // sanity }) it.each([ @@ -67,8 +64,8 @@ describe("LearningPathListingPage", () => { // Ensure the lists have loaded const path = paths.results[0] - await screen.findAllByRole("heading", { - name: path.title, + await screen.findAllByRole("link", { + name: new RegExp(path.title), }) const menuButton = screen.queryByRole("button", { name: `Edit list ${path.title}`, @@ -136,7 +133,9 @@ describe("LearningPathListingPage", () => { test("Clicking on list title navigates to list page", async () => { const { location, paths } = setup() const path = faker.helpers.arrayElement(paths.results) - const listTitle = await screen.findByRole("heading", { name: path.title }) + const listTitle = await screen.findByRole("link", { + name: new RegExp(path.title), + }) await user.click(listTitle) expect(location.current).toEqual( expect.objectContaining({ diff --git a/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.tsx b/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.tsx index 8e71dd999e..2528683377 100644 --- a/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.tsx +++ b/frontends/mit-learn/src/pages/PrivacyPage/PrivacyPage.tsx @@ -1,4 +1,10 @@ -import { Breadcrumbs, Container, Typography, styled } from "ol-components" +import { + Breadcrumbs, + Container, + Typography, + TypographyProps, + styled, +} from "ol-components" import MetaTags from "@/page-components/MetaTags/MetaTags" import * as urls from "@/common/urls" import React from "react" @@ -29,10 +35,12 @@ const BannerContainerInner = styled.div({ justifyContent: "center", }) -const Header = styled(Typography)(({ theme }) => ({ - alignSelf: "stretch", - color: theme.custom.colors.black, -})) +const Header = styled(Typography)>( + ({ theme }) => ({ + alignSelf: "stretch", + color: theme.custom.colors.black, + }), +) const BodyContainer = styled.div({ display: "flex", @@ -42,10 +50,12 @@ const BodyContainer = styled.div({ gap: "20px", }) -const BodyText = styled(Typography)(({ theme }) => ({ - alignSelf: "stretch", - color: theme.custom.colors.black, -})) +const BodyText = styled(Typography)>( + ({ theme }) => ({ + alignSelf: "stretch", + color: theme.custom.colors.black, + }), +) const UnorderedList = styled.ul(({ theme }) => ({ width: "100%", @@ -66,18 +76,24 @@ const PrivacyPage: React.FC = () => { ancestors={[{ href: urls.HOME, label: "Home" }]} current="Privacy Policy" /> -
Privacy Policy
+
+ Privacy Policy +
- Introduction + + Introduction + {SITE_NAME} provides information about MIT courses, programs, and learning materials to learners from across the world. This Privacy Statement explains how {SITE_NAME} collects, uses, and processes personal information about our learners. - What personal information we collect + + What personal information we collect + We may collect, use, store, and transfer different kinds of personal information about you, which we have grouped together as follows: @@ -94,7 +110,7 @@ const PrivacyPage: React.FC = () => {
  • IP addresses
  • Course progress and performance
  • - + How we collect personal information about you @@ -130,7 +146,9 @@ const PrivacyPage: React.FC = () => { "Help" section of the toolbar. If you reject our cookies, many functions and conveniences of this Site may not work properly. - How we use your personal information + + How we use your personal information + We collect, use, and process your personal information (1) to process transactions requested by you and meet our contractual @@ -168,7 +186,7 @@ const PrivacyPage: React.FC = () => { will always respect a request by you to stop processing your personal information (subject to our legal obligations). - + When we share your personal information @@ -189,7 +207,7 @@ const PrivacyPage: React.FC = () => { or otherwise address fraud, security or technical issues; or to protect the rights, property or safety of us, our users or others. - + How your information is stored and secured @@ -213,7 +231,7 @@ const PrivacyPage: React.FC = () => { based on verified business use cases and subject to auditing to verify appropriate applications. - + How long we keep your personal information @@ -226,7 +244,7 @@ const PrivacyPage: React.FC = () => { archival, scientific and historical research and for the defense of potential legal claims. - + Rights for Individuals in the European Economic Area (EEA) or United Kingdom (UK) @@ -289,7 +307,9 @@ const PrivacyPage: React.FC = () => {
    Address: 71 Queen Victoria Street, London, EC4V 4BE, United Kingdom
    - Additional Information + + Additional Information + We may change this Privacy Statement from time to time. If we make any significant changes in the way we treat your personal diff --git a/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx b/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx index cb169cdd6d..bac2bf3cb1 100644 --- a/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx +++ b/frontends/mit-learn/src/pages/SearchPage/SearchPage.test.tsx @@ -14,6 +14,7 @@ import type { } from "api" import invariant from "tiny-invariant" import { Permissions } from "@/common/permissions" +import { assertHeadings } from "ol-test-utilities" const setMockApiResponses = ({ search, @@ -21,7 +22,7 @@ const setMockApiResponses = ({ }: { search?: Partial offerors?: PaginatedLearningResourceOfferorDetailList -}) => { +} = {}) => { setMockResponse.get(urls.userMe.get(), { [Permissions.Authenticated]: false, }) @@ -662,4 +663,15 @@ describe("Search Page pagination controls", () => { expect(items.at(-2)?.textContent).toBe("7") // "Last page" expect(items.at(-1)?.textContent).toBe("") // "Next" button }) + + test("headings", () => { + setMockApiResponses() + renderWithProviders() + + assertHeadings([ + { level: 1, name: "Search" }, + { level: 2, name: "Filter" }, + { level: 2, name: "Search Results" }, + ]) + }) }) diff --git a/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx b/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx index a539e17dc7..a3d6d08318 100644 --- a/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx +++ b/frontends/mit-learn/src/pages/SearchPage/SearchPage.tsx @@ -13,7 +13,7 @@ import { SearchInput } from "@/page-components/SearchDisplay/SearchInput" import { GridColumn, GridContainer } from "@/components/GridLayout/GridLayout" import type { LearningResourceOfferor } from "api" import { useOfferorsList } from "api/hooks/learningResources" -import { styled, Container, Grid, theme } from "ol-components" +import { styled, Container, Grid, theme, VisuallyHidden } from "ol-components" import { capitalize } from "ol-utilities" import MetaTags from "@/page-components/MetaTags/MetaTags" @@ -212,6 +212,9 @@ const SearchPage: React.FC = () => { return ( + +

    Search

    +
    @@ -233,6 +236,8 @@ const SearchPage: React.FC = () => {
    ({ - alignSelf: "stretch", - color: theme.custom.colors.black, -})) +const Header = styled(Typography)>( + ({ theme }) => ({ + alignSelf: "stretch", + color: theme.custom.colors.black, + }), +) const BodyContainer = styled.div({ display: "flex", @@ -67,7 +76,9 @@ const TermsPage: React.FC = () => { ancestors={[{ href: urls.HOME, label: "Home" }]} current="Terms of Service" /> -
    Terms of Service
    +
    + Terms of Service +
    diff --git a/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.test.tsx b/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.test.tsx index 9eaa60ca3f..91802d56df 100644 --- a/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.test.tsx +++ b/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.test.tsx @@ -4,6 +4,7 @@ import type { LearningResourcesSearchResponse } from "api" import TopicsListingPage from "./TopicsListingPage" import { factories, setMockResponse, urls } from "api/test-utils" import invariant from "tiny-invariant" +import { assertHeadings } from "ol-test-utilities" const makeSearchResponse = ( aggregations: Record, @@ -25,7 +26,10 @@ const makeSearchResponse = ( } } -describe("DepartmentListingPage", () => { +const sorter = (a: { name: string }, b: { name: string }) => + a.name.localeCompare(b.name) + +describe("TopicsListingPage", () => { const setupApis = () => { const make = factories.learningResources const t1 = make.topic({ parent: null }) @@ -68,8 +72,6 @@ describe("DepartmentListingPage", () => { makeSearchResponse(programCounts), ) - const sorter = (a: { name: string }, b: { name: string }) => - a.name.localeCompare(b.name) const sortedSubtopics1 = [topics.t1a, topics.t1b, topics.t1c].sort(sorter) const sortedSubtopics2 = [topics.t2a, topics.t2b].sort(sorter) @@ -149,4 +151,16 @@ describe("DepartmentListingPage", () => { expect(topic2).toHaveTextContent("Courses: 200") expect(topic2).toHaveTextContent("Programs: 20") }) + + test("headings", async () => { + const { topics } = setupApis() + const sorted = [topics.t1, topics.t2].sort(sorter) + renderWithProviders() + await waitFor(() => { + assertHeadings([ + { level: 1, name: "Browse by Topic" }, + ...sorted.map((t) => ({ level: 2, name: t.name })), + ]) + }) + }) }) diff --git a/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.tsx b/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.tsx index d2296ddffa..9e535856b2 100644 --- a/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.tsx +++ b/frontends/mit-learn/src/pages/TopicListingPage/TopicsListingPage.tsx @@ -42,7 +42,7 @@ type TopicBoxHeaderProps = { const TopicBoxHeader = styled( ({ title, icon, href, className }: TopicBoxHeaderProps) => { return ( - +