Skip to content

Commit

Permalink
Condensed list card components for user lists (#1251)
Browse files Browse the repository at this point in the history
* Condensed list card components. Apply to ItemsListing page

* Style lint

* Update test and remove duplication
  • Loading branch information
jonkafton authored and mbertrand committed Jul 12, 2024
1 parent 02297ef commit 63ad3f7
Show file tree
Hide file tree
Showing 10 changed files with 588 additions and 161 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import React, { useCallback, useMemo, useState } from "react"
import { LearningResourceCard, LearningResourceListCard } from "ol-components"
import {
LearningResourceCard,
LearningResourceListCard,
LearningResourceListCardCondensed,
} from "ol-components"
import * as NiceModal from "@ebay/nice-modal-react"
import type {
LearningResourceCardProps,
Expand Down Expand Up @@ -104,7 +108,9 @@ const ResourceCard: React.FC<ResourceCardProps> = ({ resource, ...others }) => {
type ResourceListCardProps = Omit<
LearningResourceListCardProps,
"href" | "onAddToLearningPathClick" | "onAddToUserListClick"
>
> & {
condensed?: boolean
}

/**
* Just like `ol-components/LearningResourceListCard`, but with builtin actions:
Expand All @@ -115,7 +121,8 @@ type ResourceListCardProps = Omit<
*/
const ResourceListCard: React.FC<ResourceListCardProps> = ({
resource,
...others
condensed,
...props
}) => {
const {
getDrawerHref,
Expand All @@ -126,16 +133,20 @@ const ResourceListCard: React.FC<ResourceListCardProps> = ({
inUserList,
inLearningPath,
} = useResourceCard(resource)

const ListCardComponent = condensed
? LearningResourceListCardCondensed
: LearningResourceListCard
return (
<>
<LearningResourceListCard
<ListCardComponent
resource={resource}
href={resource ? getDrawerHref(resource.id) : undefined}
onAddToLearningPathClick={handleAddToLearningPathClick}
onAddToUserListClick={handleAddToUserListClick}
inUserList={inUserList}
inLearningPath={inLearningPath}
{...others}
{...props}
/>
<SignupPopover anchorEl={anchorEl} onClose={handleClosePopover} />
</>
Expand Down
183 changes: 93 additions & 90 deletions frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ jest.mock("ol-components", () => {
}
})

const spyLearningResourceCard = jest.mocked(LearningResourceCard)
const spyLearningResourceCardOld = jest.mocked(LearningResourceCard)

const spySortableList = jest.mocked(SortableList)
const spySortableItem = jest.mocked(SortableItem)

Expand All @@ -57,105 +58,107 @@ const getPaginatedRelationships = (
}
}

describe.each([ListType.LearningPath, ListType.UserList])(
"ItemsListing",
(listType: string) => {
test("Shows loading message while loading", () => {
const emptyMessage = "Empty list"
describe("ItemsListing", () => {
test.each([
{ listType: ListType.LearningPath },
{ listType: ListType.UserList },
])("Shows loading message while loading", ({ listType }) => {
const emptyMessage = "Empty list"

const { view } = renderWithProviders(
<ItemsListing
listType={listType}
emptyMessage={emptyMessage}
isLoading
/>,
)
screen.getByLabelText("Loading")
view.rerender(
<ItemsListing listType={listType} emptyMessage={emptyMessage} />,
)
})

const { view } = renderWithProviders(
test.each([
{ listType: ListType.LearningPath, count: 0, hasEmptyMessage: true },
{
listType: ListType.LearningPath,
count: faker.number.int({ min: 1, max: 5 }),
hasEmptyMessage: false,
},
{ listType: ListType.LearningPath, count: 0, hasEmptyMessage: true },
{
listType: ListType.UserList,
count: faker.number.int({ min: 1, max: 5 }),
hasEmptyMessage: false,
},
])(
"Shows empty message when there are no items",
({ listType, count, hasEmptyMessage }) => {
setMockResponse.get(urls.userMe.get(), {})
const emptyMessage = faker.lorem.sentence()
const paginatedRelationships = getPaginatedRelationships(
listType,
count,
faker.number.int(),
)
renderWithProviders(
<ItemsListing
listType={listType}
emptyMessage={emptyMessage}
isLoading
items={paginatedRelationships.results as LearningResourceListItem[]}
/>,
)
screen.getByLabelText("Loading")
view.rerender(
<ItemsListing listType={listType} emptyMessage={emptyMessage} />,
)
})
const emptyMessageElement = screen.queryByText(emptyMessage)
expect(!!emptyMessageElement).toBe(hasEmptyMessage)
},
)

test.each([
{ listType: ListType.LearningPath, count: 0, hasEmptyMessage: true },
{
listType: ListType.LearningPath,
count: faker.number.int({ min: 1, max: 5 }),
hasEmptyMessage: false,
},
{ listType: ListType.LearningPath, count: 0, hasEmptyMessage: true },
{
listType: ListType.UserList,
count: faker.number.int({ min: 1, max: 5 }),
hasEmptyMessage: false,
},
])(
"Shows empty message when there are no items",
({ listType, count, hasEmptyMessage }) => {
setMockResponse.get(urls.userMe.get(), {})
const emptyMessage = faker.lorem.sentence()
const paginatedRelationships = getPaginatedRelationships(
listType,
count,
faker.number.int(),
)
renderWithProviders(
<ItemsListing
listType={listType}
emptyMessage={emptyMessage}
items={paginatedRelationships.results as LearningResourceListItem[]}
/>,
)
const emptyMessageElement = screen.queryByText(emptyMessage)
expect(!!emptyMessageElement).toBe(hasEmptyMessage)
},
)
test.each([
{ listType: ListType.LearningPath, sortable: false, cardProps: {} },
{
listType: ListType.LearningPath,
sortable: true,
cardProps: { sortable: true },
},
{ listType: ListType.UserList, sortable: false, cardProps: {} },
{
listType: ListType.UserList,
sortable: true,
cardProps: { sortable: true },
},
])(
"Shows a list of $listType LearningResourceCards with sortable=$sortable",
({ listType, sortable, cardProps }) => {
const emptyMessage = faker.lorem.sentence()
const paginatedRelationships = getPaginatedRelationships(
listType,
faker.number.int({ min: 2, max: 4 }),
faker.number.int(),
)
const items = paginatedRelationships.results as LearningResourceListItem[]
renderWithProviders(
<ItemsListing
listType={listType}
emptyMessage={emptyMessage}
items={items}
sortable={sortable}
/>,
{ user: {} },
)
const titles = items.map((item) => item.resource.title)
const headings = screen.getAllByRole("heading", {
name: (value) => titles.includes(value),
})
expect(headings.map((h) => h.textContent)).toEqual(titles)

test.each([
{ listType: ListType.LearningPath, sortable: false, cardProps: {} },
{
listType: ListType.LearningPath,
sortable: true,
cardProps: { sortable: true },
},
{ listType: ListType.UserList, sortable: false, cardProps: {} },
{
listType: ListType.UserList,
sortable: true,
cardProps: { sortable: true },
},
])(
"Shows a list of LearningResourceCards with sortable=$sortable",
({ listType, sortable, cardProps }) => {
const emptyMessage = faker.lorem.sentence()
const paginatedRelationships = getPaginatedRelationships(
listType,
faker.number.int({ min: 2, max: 4 }),
faker.number.int(),
)
const items =
paginatedRelationships.results as LearningResourceListItem[]
renderWithProviders(
<ItemsListing
listType={listType}
emptyMessage={emptyMessage}
items={items}
sortable={sortable}
/>,
{ user: {} },
)
const titles = items.map((item) => item.resource.title)
const headings = screen.getAllByRole("heading", {
name: (value) => titles.includes(value),
})
expect(headings.map((h) => h.textContent)).toEqual(titles)
if (sortable) {
items.forEach(({ resource }) => {
expectProps(spyLearningResourceCard, { resource, ...cardProps })
expectProps(spyLearningResourceCardOld, { resource, ...cardProps })
})
},
)
},
)
}
},
)
})

describe.each([ListType.LearningPath, ListType.UserList])(
"Sorting ItemListing",
Expand Down
8 changes: 3 additions & 5 deletions frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
styled,
PlainList,
} from "ol-components"
import { ResourceListCard } from "@/page-components/ResourceCard/ResourceCard"
import { useListItemMove } from "api/hooks/learningResources"

const EmptyMessage = styled.p({
Expand Down Expand Up @@ -38,14 +39,11 @@ const ItemsListingViewOnly: React.FC<{
items: NonNullable<ItemsListingProps["items"]>
}> = ({ items }) => {
return (
<PlainList itemSpacing={3}>
<PlainList itemSpacing={1}>
{items.map((item) => {
return (
<li key={item.id}>
<LearningResourceCard
variant="row-reverse"
resource={item.resource}
/>
<ResourceListCard resource={item.resource} condensed />
</li>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,6 @@ const EditUserListMenu: React.FC<EditUserListMenuProps> = ({ userList }) => {
)
}

type ListCardProps = {
list: UserList
onActivate: (userList: UserList) => void
canEdit: boolean
}
const ListCard: React.FC<ListCardProps> = ({ list, onActivate }) => {
return (
<UserListCardTemplate
variant="row-reverse"
userList={list}
className="ic-resource-card"
imgConfig={imgConfigs["row-reverse-small"]}
onActivate={onActivate}
footerActionSlot={<EditUserListMenu userList={list} />}
/>
)
}

type UserListListingComponentProps = {
title?: string
onActivate: (userList: UserList) => void
Expand Down Expand Up @@ -132,10 +114,13 @@ const UserListListingComponent: React.FC<UserListListingComponentProps> = (
{listingQuery.data.results?.map((list) => {
return (
<li key={list.id}>
<ListCard
list={list}
<UserListCardTemplate
variant="row-reverse"
userList={list}
className="ic-resource-card"
imgConfig={imgConfigs["row-reverse-small"]}
onActivate={onActivate}
canEdit={true}
footerActionSlot={<EditUserListMenu userList={list} />}
/>
</li>
)
Expand Down
Loading

0 comments on commit 63ad3f7

Please sign in to comment.