From fcdb3846b9408f7da777031027e582923d8f4381 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Thu, 29 Aug 2024 14:07:32 -0400 Subject: [PATCH 1/5] Fix dialog spacing + reset AddToListDialog (#1484) * enable emotion better classnames during dev * rename some mui dialog exports * no bottom margin on last formfield child * fix dialog spacing and reset * fix a prop ts error --- .gitignore | 1 + frontends/mit-learn/package.json | 1 + .../Dialogs/AddToListDialog.tsx | 104 +++++++++--------- .../ManageListDialogs/ManageListDialogs.tsx | 26 ++--- frontends/mit-learn/webpack.config.js | 11 +- .../src/components/Dialog/Dialog.tsx | 19 ++-- .../FormDialog/FormDialog.stories.tsx | 1 - .../src/components/FormDialog/FormDialog.tsx | 20 +--- .../components/FormHelpers/FormHelpers.tsx | 2 +- frontends/ol-components/src/index.ts | 6 +- .../components/editing/ManageWidgetDialog.tsx | 32 +++--- yarn.lock | 10 ++ 12 files changed, 116 insertions(+), 117 deletions(-) diff --git a/.gitignore b/.gitignore index 828d116cc9..3ea33029f8 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,4 @@ storybook-static/ !/e2e_testing/.env e2e_testing/.yarn/cache +.swc diff --git a/frontends/mit-learn/package.json b/frontends/mit-learn/package.json index a0891233a0..236f204e06 100644 --- a/frontends/mit-learn/package.json +++ b/frontends/mit-learn/package.json @@ -29,6 +29,7 @@ "@storybook/react-webpack5": "^8.0.9", "@storybook/test": "^8.0.9", "@swc/core": "^1.4.11", + "@swc/plugin-emotion": "^4.0.0", "@testing-library/react": "14.3.1", "@testing-library/user-event": "14.5.2", "@types/lodash": "^4.14.182", diff --git a/frontends/mit-learn/src/page-components/Dialogs/AddToListDialog.tsx b/frontends/mit-learn/src/page-components/Dialogs/AddToListDialog.tsx index a9642557a7..037a39031e 100644 --- a/frontends/mit-learn/src/page-components/Dialogs/AddToListDialog.tsx +++ b/frontends/mit-learn/src/page-components/Dialogs/AddToListDialog.tsx @@ -1,11 +1,12 @@ -import React, { useCallback, useId } from "react" +import React, { useCallback } from "react" import { - Dialog, LoadingSpinner, Typography, styled, CheckboxChoiceField, Button, + FormDialog, + DialogActions, } from "ol-components" import { RiAddLine } from "@remixicon/react" @@ -33,17 +34,9 @@ const ResourceTitle = styled.span({ fontStyle: "italic", }) -const CheckboxContainer = styled.div({ - padding: "28px 0", -}) - -const ButtonContainer = styled.div({ +const Actions = styled(DialogActions)({ display: "flex", - gap: "12px", - alignItems: "flex-start", - button: { - width: "50%", - }, + "> *": { flex: 1 }, }) type AddToListDialogInnerProps = { @@ -100,7 +93,6 @@ const AddToListDialogInner: React.FC = ({ : null, ) .filter((value) => value !== null) - const formId = useId() const formik = useFormik({ enableReinitialize: true, validateOnChange: false, @@ -130,59 +122,61 @@ const AddToListDialogInner: React.FC = ({ }) return ( - + + + + } > {isReady ? ( -
+ <> Adding {resource?.title} - - {listType === ListType.LearningPath ? ( - - ) : null} - {listType === ListType.UserList ? ( - - ) : null} - - - - - -
+ + {listType === ListType.LearningPath ? ( + + ) : null} + {listType === ListType.UserList ? ( + + ) : null} + ) : ( )} -
+ ) } diff --git a/frontends/mit-learn/src/page-components/ManageListDialogs/ManageListDialogs.tsx b/frontends/mit-learn/src/page-components/ManageListDialogs/ManageListDialogs.tsx index cb221be6f7..7607f6d67c 100644 --- a/frontends/mit-learn/src/page-components/ManageListDialogs/ManageListDialogs.tsx +++ b/frontends/mit-learn/src/page-components/ManageListDialogs/ManageListDialogs.tsx @@ -122,14 +122,6 @@ const UpsertLearningPathDialog = NiceModal.create( onSubmit={formik.handleSubmit} confirmText="Save" noValidate - footerContent={ - mutation.isError && - !formik.isSubmitting && ( - - There was a problem saving your list. Please try again later. - - ) - } > formik.setFieldValue(e.name, e.value)} /> + {mutation.isError && !formik.isSubmitting && ( + + There was a problem saving your list. Please try again later. + + )} ) }, @@ -242,14 +239,6 @@ const UpsertUserListDialog = NiceModal.create( onSubmit={formik.handleSubmit} confirmText={userList ? "Update" : "Create"} noValidate - footerContent={ - mutation.isError && - !formik.isSubmitting && ( - - There was a problem saving your list. Please try again later. - - ) - } > + {mutation.isError && !formik.isSubmitting && ( + + There was a problem saving your list. Please try again later. + + )} {userList && (
@@ -148,11 +149,11 @@ const Dialog: React.FC = ({ > {confirmText} - + )} ) } -export { Dialog } +export { Dialog, DialogActions } export type { DialogProps } diff --git a/frontends/ol-components/src/components/FormDialog/FormDialog.stories.tsx b/frontends/ol-components/src/components/FormDialog/FormDialog.stories.tsx index fbe2a9336b..1c0f3c5ad3 100644 --- a/frontends/ol-components/src/components/FormDialog/FormDialog.stories.tsx +++ b/frontends/ol-components/src/components/FormDialog/FormDialog.stories.tsx @@ -72,6 +72,5 @@ export const Simple: Story = { args: { title: "Form Title", fullWidth: true, - footerContent: "Footer content", }, } diff --git a/frontends/ol-components/src/components/FormDialog/FormDialog.tsx b/frontends/ol-components/src/components/FormDialog/FormDialog.tsx index 7b4ec1e616..85d7cbb281 100644 --- a/frontends/ol-components/src/components/FormDialog/FormDialog.tsx +++ b/frontends/ol-components/src/components/FormDialog/FormDialog.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react" import styled from "@emotion/styled" import { Dialog } from "../Dialog/Dialog" -import type { DialogProps } from "@mui/material/Dialog" +import type { DialogProps } from "../Dialog/Dialog" const FormContent = styled.div` display: flex; @@ -50,20 +50,12 @@ interface FormDialogProps { * The form content. These will be direct children of MUI's [DialogContent](https://mui.com/material-ui/api/dialog-content/) */ children?: React.ReactNode - /** - * Extra content below the cancel/submit buttons. This is useful, e.g., for - * displaying overall form error messages. - */ - footerContent?: React.ReactNode + actions?: DialogProps["actions"] /** * Class applied to the `
` element. */ formClassName?: string - /** - * MUI Dialog's [TransitionProps](https://mui.com/material-ui/api/dialog/#props) - */ - TransitionProps?: DialogProps["TransitionProps"] /** * If `true`, the dialog stretches to its `maxWidth`. * @@ -93,7 +85,7 @@ const FormDialog: React.FC = ({ title, noValidate, children, - footerContent, + actions, confirmText = "Submit", cancelText = "Cancel", className, @@ -139,11 +131,9 @@ const FormDialog: React.FC = ({ isSubmitting={isSubmitting} className={className} PaperProps={paperProps} + actions={actions} > - - {children} - {footerContent} - + {children} ) } diff --git a/frontends/ol-components/src/components/FormHelpers/FormHelpers.tsx b/frontends/ol-components/src/components/FormHelpers/FormHelpers.tsx index 65cc5f2c95..1e67cf3ccf 100644 --- a/frontends/ol-components/src/components/FormHelpers/FormHelpers.tsx +++ b/frontends/ol-components/src/components/FormHelpers/FormHelpers.tsx @@ -35,7 +35,7 @@ const Container = styled.div<{ fullWidth?: boolean }>(({ fullWidth }) => [ display: "inline-flex", flexDirection: "column", alignItems: "start", - "> *": { + "> *:not(:last-child)": { marginBottom: "4px", }, }, diff --git a/frontends/ol-components/src/index.ts b/frontends/ol-components/src/index.ts index 7ee530994a..fda60ca714 100644 --- a/frontends/ol-components/src/index.ts +++ b/frontends/ol-components/src/index.ts @@ -64,11 +64,11 @@ export type { ContainerProps } from "@mui/material/Container" export { default as MuiDialog } from "@mui/material/Dialog" export type { DialogProps as MuiDialogProps } from "@mui/material/Dialog" -export { default as DialogActions } from "@mui/material/DialogActions" +export { default as MuiDialogActions } from "@mui/material/DialogActions" export type { DialogActionsProps } from "@mui/material/DialogActions" -export { default as DialogContent } from "@mui/material/DialogContent" +export { default as MuiDialogContent } from "@mui/material/DialogContent" export type { DialogContentProps } from "@mui/material/DialogContent" -export { default as DialogTitle } from "@mui/material/DialogTitle" +export { default as MuiDialogTitle } from "@mui/material/DialogTitle" export type { DialogTitleProps } from "@mui/material/DialogTitle" export { default as Divider } from "@mui/material/Divider" diff --git a/frontends/ol-widgets/src/components/editing/ManageWidgetDialog.tsx b/frontends/ol-widgets/src/components/editing/ManageWidgetDialog.tsx index 2240a698e0..3e1c0d97a9 100644 --- a/frontends/ol-widgets/src/components/editing/ManageWidgetDialog.tsx +++ b/frontends/ol-widgets/src/components/editing/ManageWidgetDialog.tsx @@ -1,9 +1,9 @@ import React, { useId, useCallback, useEffect, useMemo, useState } from "react" import { - Dialog, - DialogActions, - DialogContent, - DialogTitle, + MuiDialog, + MuiDialogActions, + MuiDialogContent, + MuiDialogTitle, Button, RadioChoiceField, } from "ol-components" @@ -122,7 +122,7 @@ const DialogContentEditing: React.FC = ({ ) return ( <> - {title} + {title} >} validationSchema={validationSchema} @@ -135,7 +135,7 @@ const DialogContentEditing: React.FC = ({ const { htmlFor: labelFor, ...labelAttrs } = titleAttrs.label return ( - +
) })} -
- + + - + ) }} @@ -275,11 +275,11 @@ const DialogContentAdding: React.FC = ({ ) return ( <> - New widget + New widget {({ handleSubmit, values }) => (
- + = ({ /> )} - - + + - +
)}
@@ -345,7 +345,7 @@ const ManageWidgetDialog: React.FC = ({ [], ) return ( - = ({ classes={classes} /> )} - + ) } diff --git a/yarn.lock b/yarn.lock index 13500e370a..783342b21f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4648,6 +4648,15 @@ __metadata: languageName: node linkType: hard +"@swc/plugin-emotion@npm:^4.0.0": + version: 4.0.0 + resolution: "@swc/plugin-emotion@npm:4.0.0" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10/31159f1a510732f48b9e3bdd9929f6f503bb866957d15ebcc8635f273b1aebad23f23b6798e6c0d426d5a015b15403b20fcd3102ac33c7908e614ab480bf92e3 + languageName: node + linkType: hard + "@swc/types@npm:^0.1.12": version: 0.1.12 resolution: "@swc/types@npm:0.1.12" @@ -14769,6 +14778,7 @@ __metadata: "@storybook/react-webpack5": "npm:^8.0.9" "@storybook/test": "npm:^8.0.9" "@swc/core": "npm:^1.4.11" + "@swc/plugin-emotion": "npm:^4.0.0" "@tanstack/react-query": "npm:^4.36.1" "@tanstack/react-query-devtools": "npm:^4.29.6" "@testing-library/react": "npm:14.3.1" From d4a0cc0da27e2651c4543f684abe762c5cbd9210 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Thu, 29 Aug 2024 15:43:51 -0400 Subject: [PATCH 2/5] Better Headings Structure (#1473) * remove default headers from Typography, fix tests * add VisuallyHidden component * put all page content in "main" * fix homepage headers * remove heading element from cards * better header on listing pages * Add headings to channel + search * update privacy page headings * improve dashboard headings * fix a test * fix a ts issue * fix a test * fix accidental testimonial breakage * add some tests * remove some commented out code * add a comment --- .../CardTemplate/CardTemplate.test.tsx | 21 -- .../CardTemplate/CardTemplate.tsx | 250 ------------------ .../ItemsListing/ItemsListing.test.tsx | 32 ++- .../ItemsListing/ItemsListingComponent.tsx | 17 +- .../ResourceCard/ResourceCard.test.tsx | 4 +- .../ResourceCarousel.test.tsx | 66 +++-- .../ResourceCarousel/ResourceCarousel.tsx | 33 ++- .../SearchDisplay/SearchDisplay.tsx | 19 +- .../TestimonialDisplay/TestimonialDisplay.tsx | 2 +- .../UserListCardCondensed.test.tsx | 5 +- .../UserListCard/UserListCardCondensed.tsx | 6 +- .../pages/ChannelPage/ChannelPage.test.tsx | 36 +++ .../src/pages/ChannelPage/ChannelPage.tsx | 5 +- .../src/pages/ChannelPage/ChannelSearch.tsx | 11 +- .../pages/ChannelPage/UnitChannelTemplate.tsx | 12 +- .../src/pages/DashboardPage/DashboardPage.tsx | 31 ++- .../DepartmentListingPage.test.tsx | 17 +- .../DepartmentListingPage.tsx | 10 +- .../pages/HomePage/BrowseTopicsSection.tsx | 15 +- .../src/pages/HomePage/HeroSearch.tsx | 1 + .../src/pages/HomePage/HomePage.test.tsx | 87 ++---- .../mit-learn/src/pages/HomePage/HomePage.tsx | 19 +- .../src/pages/HomePage/NewsEventsSection.tsx | 23 +- .../pages/HomePage/TestimonialsSection.tsx | 4 +- .../pages/HomePage/UpcomingCoursesSection.tsx | 1 + .../LearningPathListingPage.test.tsx | 17 +- .../src/pages/PrivacyPage/PrivacyPage.tsx | 58 ++-- .../src/pages/SearchPage/SearchPage.test.tsx | 14 +- .../src/pages/SearchPage/SearchPage.tsx | 7 +- .../src/pages/TermsPage/TermsPage.tsx | 23 +- .../TopicsListingPage.test.tsx | 20 +- .../TopicListingPage/TopicsListingPage.tsx | 2 +- .../UnitsListingPage.test.tsx | 12 + .../UnitsListingPage/UnitsListingPage.tsx | 5 +- .../UserListListingComponent.test.tsx | 4 +- .../UserListListingComponent.tsx | 7 +- frontends/mit-learn/src/routes.tsx | 2 +- .../src/components/Banner/Banner.tsx | 1 + .../src/components/Card/Card.tsx | 2 +- .../src/components/Card/ListCard.tsx | 2 +- .../src/components/Dialog/Dialog.tsx | 8 +- .../LearningResourceCard.test.tsx | 21 +- .../LearningResourceListCard.test.tsx | 18 +- .../components/ThemeProvider/typography.ts | 5 + .../VisuallyHidden/VisuallyHidden.tsx | 30 +++ frontends/ol-components/src/index.ts | 1 + frontends/ol-test-utilities/package.json | 1 + frontends/ol-test-utilities/src/assertions.ts | 26 ++ frontends/ol-test-utilities/src/index.ts | 1 + yarn.lock | 8 + 50 files changed, 497 insertions(+), 525 deletions(-) delete mode 100644 frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.test.tsx delete mode 100644 frontends/mit-learn/src/page-components/CardTemplate/CardTemplate.tsx create mode 100644 frontends/ol-components/src/components/VisuallyHidden/VisuallyHidden.tsx create mode 100644 frontends/ol-test-utilities/src/assertions.ts 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 ( - +