Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {
renderWithProviders,
screen,
waitFor,
within,
} from "@/test-utils"
import LearningResourceDrawer, {
useOpenLearningResourceDrawer,
} from "./LearningResourceDrawer"
import { urls, factories, setMockResponse } from "api/test-utils"
import { LearningResourceExpanded } from "ol-components"
import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls"
import { ResourceTypeEnum } from "api"
import invariant from "tiny-invariant"

jest.mock("ol-components", () => {
const actual = jest.requireActual("ol-components")
Expand Down Expand Up @@ -40,6 +43,7 @@ describe("LearningResourceDrawer", () => {
])(
"Renders drawer content when resource=id is in the URL and captures the view if PostHog $descriptor",
async ({ enablePostHog }) => {
setMockResponse.get(urls.userMe.get(), {})
APP_SETTINGS.POSTHOG = {
api_key: enablePostHog ? "test1234" : "", // pragma: allowlist secret
}
Expand Down Expand Up @@ -95,4 +99,84 @@ describe("LearningResourceDrawer", () => {
dog: "woof",
})
})

test.each([
{
isLearningPathEditor: true,
isAuthenticated: true,
expectAddToLearningPathButton: true,
expectAddToUserListButton: true,
},
{
isLearningPathEditor: false,
isAuthenticated: true,
expectAddToLearningPathButton: false,
expectAddToUserListButton: true,
},
{
isLearningPathEditor: false,
isAuthenticated: false,
expectAddToLearningPathButton: false,
expectAddToUserListButton: false,
},
])(
"Renders info section list buttons correctly",
async ({
isLearningPathEditor,
isAuthenticated,
expectAddToLearningPathButton,
expectAddToUserListButton,
}) => {
const resource = factories.learningResources.resource({
resource_type: ResourceTypeEnum.Course,
runs: [
factories.learningResources.run({
languages: ["en-us", "es-es", "fr-fr"],
}),
],
})
setMockResponse.get(
urls.learningResources.details({ id: resource.id }),
resource,
)

renderWithProviders(<LearningResourceDrawer />, {
url: `?resource=${resource.id}`,
user: {
is_learning_path_editor: isLearningPathEditor,
is_authenticated: isAuthenticated,
},
})

expect(LearningResourceExpanded).toHaveBeenCalled()

await waitFor(() => {
expectProps(LearningResourceExpanded, { resource })
})

const section = screen
.getByRole("heading", { name: "Info" })
.closest("section")
invariant(section)

if (!isAuthenticated) {
const buttons = within(section).queryAllByRole("button")
expect(buttons).toHaveLength(0)
return
} else {
const buttons = within(section).getAllByRole("button")
const expectedButtons =
expectAddToLearningPathButton && expectAddToUserListButton ? 2 : 1
expect(buttons).toHaveLength(expectedButtons)
expect(
!!within(section).queryByRole("button", {
name: "Add to Learning Path",
}),
).toBe(expectAddToLearningPathButton)
expect(
!!within(section).queryByRole("button", { name: "Add to User List" }),
).toBe(expectAddToUserListButton)
}
},
)
})
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import React, { useEffect, useCallback } from "react"
import React, { useEffect, useCallback, useMemo } from "react"
import {
RoutedDrawer,
LearningResourceExpanded,
imgConfigs,
} from "ol-components"
import type { RoutedDrawerProps } from "ol-components"
import type {
LearningResourceCardProps,
RoutedDrawerProps,
} from "ol-components"
import { useLearningResourcesDetail } from "api/hooks/learningResources"
import { useSearchParams, useLocation } from "react-router-dom"
import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls"
import { usePostHog } from "posthog-js/react"
import MetaTags from "@/page-components/MetaTags/MetaTags"
import { useUserMe } from "api/hooks/user"
import NiceModal from "@ebay/nice-modal-react"
import {
AddToLearningPathDialog,
AddToUserListDialog,
} from "../Dialogs/AddToListDialog"

const RESOURCE_DRAWER_PARAMS = [RESOURCE_DRAWER_QUERY_PARAM] as const

Expand Down Expand Up @@ -56,6 +65,25 @@ const DrawerContent: React.FC<{
resourceId: number
}> = ({ resourceId }) => {
const resource = useLearningResourcesDetail(Number(resourceId))
const { data: user } = useUserMe()
const handleAddToLearningPathClick: LearningResourceCardProps["onAddToLearningPathClick"] =
useMemo(() => {
if (user?.is_learning_path_editor) {
return (event, resourceId: number) => {
NiceModal.show(AddToLearningPathDialog, { resourceId })
}
}
return null
}, [user])
const handleAddToUserListClick: LearningResourceCardProps["onAddToUserListClick"] =
useMemo(() => {
if (user?.is_authenticated) {
return (event, resourceId: number) => {
NiceModal.show(AddToUserListDialog, { resourceId })
}
}
return null
}, [user])
useCapturePageView(Number(resourceId))

return (
Expand All @@ -70,6 +98,9 @@ const DrawerContent: React.FC<{
<LearningResourceExpanded
imgConfig={imgConfigs.large}
resource={resource.data}
user={user}
onAddToLearningPathClick={handleAddToLearningPathClick}
onAddToUserListClick={handleAddToUserListClick}
/>
</>
)
Expand Down
1 change: 1 addition & 0 deletions frontends/ol-ckeditor/src/types/settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export declare global {
MITOL_API_BASE_URL: string
PUBLIC_URL: string
SITE_NAME: string
CSRF_COOKIE_NAME: string
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
RiTranslate2,
RiAwardLine,
RiPresentationLine,
RiMenuAddLine,
RiBookmarkLine,
} from "@remixicon/react"
import { LearningResource, LearningResourceRun, ResourceTypeEnum } from "api"
import {
Expand All @@ -21,13 +23,27 @@ import {
} from "ol-utilities"
import { theme } from "../ThemeProvider/ThemeProvider"
import Typography from "@mui/material/Typography"
import type { User } from "api/hooks/user"
import { CardActionButton } from "../LearningResourceCard/LearningResourceListCard"
import { LearningResourceCardProps } from "../LearningResourceCard/LearningResourceCard"

const InfoItems = styled.section`
display: flex;
flex-direction: column;
gap: 16px;
`

const InfoHeader = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`

const ListButtonContainer = styled.div`
display: flex;
gap: 8px;
`

const InfoItemContainer = styled.div`
display: flex;
align-items: flex-start;
Expand Down Expand Up @@ -223,14 +239,23 @@ const InfoItem = ({ label, Icon, value }: InfoItemProps) => {
const InfoSection = ({
resource,
run,
user,
onAddToLearningPathClick,
onAddToUserListClick,
}: {
resource?: LearningResource
run?: LearningResourceRun
user?: User
onAddToLearningPathClick?: LearningResourceCardProps["onAddToLearningPathClick"]
onAddToUserListClick?: LearningResourceCardProps["onAddToUserListClick"]
}) => {
if (!resource) {
return null
}

const inUserList = !!resource?.user_list_parents?.length
const inLearningPath = !!resource?.learning_path_parents?.length

const infoItems = INFO_ITEMS.map(({ label, Icon, selector }) => ({
label,
Icon,
Expand All @@ -243,9 +268,39 @@ const InfoSection = ({

return (
<InfoItems>
<Typography variant="subtitle2" component="h3">
Info
</Typography>
<InfoHeader>
<Typography variant="subtitle2" component="h3">
Info
</Typography>
<ListButtonContainer>
{user?.is_learning_path_editor && (
<CardActionButton
filled={inLearningPath}
aria-label="Add to Learning Path"
onClick={(event) =>
onAddToLearningPathClick
? onAddToLearningPathClick(event, resource.id)
: null
}
>
<RiMenuAddLine aria-hidden />
</CardActionButton>
)}
{user?.is_authenticated && (
<CardActionButton
filled={inUserList}
aria-label="Add to User List"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably best dealt with in a separate PR for all the cards at once, but we did have accessibility feedback that this label text is confusing, suggesting "Bookmark Resource(course?video?)" instead, or "Save..."

onClick={(event) =>
onAddToUserListClick
? onAddToUserListClick(event, resource.id)
: null
}
>
<RiBookmarkLine aria-hidden />
</CardActionButton>
)}
</ListButtonContainer>
</InfoHeader>
{infoItems.map((props, index) => (
<InfoItem key={index} {...props} />
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import type { SimpleSelectProps } from "../SimpleSelect/SimpleSelect"
import { EmbedlyCard } from "../EmbedlyCard/EmbedlyCard"
import { PlatformLogo, PLATFORMS } from "../Logo/Logo"
import InfoSection from "./InfoSection"
import type { User } from "api/hooks/user"
import { LearningResourceCardProps } from "../LearningResourceCard/LearningResourceCard"

const Container = styled.div<{ padTop?: boolean }>`
display: flex;
Expand Down Expand Up @@ -138,7 +140,10 @@ const OnPlatform = styled.span`

type LearningResourceExpandedProps = {
resource?: LearningResource
user?: User
imgConfig: EmbedlyConfig
onAddToLearningPathClick?: LearningResourceCardProps["onAddToLearningPathClick"]
onAddToUserListClick?: LearningResourceCardProps["onAddToUserListClick"]
}

const ImageSection: React.FC<{
Expand Down Expand Up @@ -314,7 +319,10 @@ const formatRunDate = (

const LearningResourceExpanded: React.FC<LearningResourceExpandedProps> = ({
resource,
user,
imgConfig,
onAddToLearningPathClick,
onAddToUserListClick,
}) => {
const [selectedRun, setSelectedRun] = useState(resource?.runs?.[0])

Expand Down Expand Up @@ -411,7 +419,13 @@ const LearningResourceExpanded: React.FC<LearningResourceExpandedProps> = ({
<ImageSection resource={resource} config={imgConfig} />
<CallToActionSection resource={resource} hide={isVideo} />
<DetailSection resource={resource} />
<InfoSection resource={resource} run={selectedRun} />
<InfoSection
resource={resource}
run={selectedRun}
user={user}
onAddToLearningPathClick={onAddToLearningPathClick}
onAddToUserListClick={onAddToUserListClick}
/>
</Container>
)
}
Expand Down