Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: "^22.0.0"
node-version: "^24.0.0"
cache: yarn
cache-dependency-path: yarn.lock

Expand Down Expand Up @@ -204,7 +204,7 @@ jobs:

- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: "^22"
node-version: "^24.0.0"
cache: yarn
cache-dependency-path: yarn.lock

Expand All @@ -227,7 +227,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: "^22.0.0"
node-version: "^24.0.0"
cache: yarn
cache-dependency-path: yarn.lock

Expand Down Expand Up @@ -266,7 +266,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: "^22.0.0"
node-version: "^24.0.0"
cache: yarn
cache-dependency-path: yarn.lock

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:

- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with:
node-version: "^22.0.0"
node-version: "^24.0.0"
cache: yarn
cache-dependency-path: yarn.lock

Expand Down
382 changes: 191 additions & 191 deletions .yarn/releases/yarn-4.11.0.cjs → .yarn/releases/yarn-4.12.0.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ enableGlobalCache: false

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.11.0.cjs
yarnPath: .yarn/releases/yarn-4.12.0.cjs

# https://github.com/vitejs/vite-plugin-react-swc/issues/74#issuecomment-1520484130
# https://github.com/swc-project/swc/issues/5616#issuecomment-1265639797
Expand Down
11 changes: 11 additions & 0 deletions RELEASE.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
Release Notes
=============

Version 0.53.5
--------------

- Product Page Improvements (course in programs, display multiple dates) (#2930)
- fix(deps): update dependency django to v4.2.28 [security] (#2917)
- fix: show placeholder for media and course control click (#2914)
- fix(deps): update dependency react-slick to ^0.31.0 (#2743)
- chore(deps): update node.js to v24 (#2755)
- chore(deps): update yarn to v4.12.0 (#2740)
- fix an easily broken test (#2927)

Version 0.53.4 (Released February 05, 2026)
--------------

Expand Down
2 changes: 1 addition & 1 deletion articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def get_url(self):
Return the relative URL for this article.
"""
if self.slug:
return f"/api/v1/articles/{self.slug}/"
return f"/articles/{self.slug}"
return None


Expand Down
8 changes: 4 additions & 4 deletions articles/models_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_get_url_with_slug(self, _mock_queue_purge, _mock_queue_list): # noqa:
user=user,
)

assert article.get_url() == f"/api/v1/articles/{article.slug}/"
assert article.get_url() == f"/articles/{article.slug}"

def test_get_url_without_slug(self, _mock_queue_purge, _mock_queue_list): # noqa: PT019
"""Test that get_url returns None for an article without a slug"""
Expand Down Expand Up @@ -56,8 +56,8 @@ def test_get_url_with_different_slugs(self, _mock_queue_purge, _mock_queue_list)
user=user,
)

assert article1.get_url() == f"/api/v1/articles/{article1.slug}/"
assert article2.get_url() == f"/api/v1/articles/{article2.slug}/"
assert article1.get_url() == f"/articles/{article1.slug}"
assert article2.get_url() == f"/articles/{article2.slug}"
assert article1.get_url() != article2.get_url()

def test_slug_generation_on_publish(self, _mock_queue_purge, _mock_queue_list): # noqa: PT019
Expand All @@ -80,4 +80,4 @@ def test_slug_generation_on_publish(self, _mock_queue_purge, _mock_queue_list):
# Now should have a slug
assert article.slug is not None
assert article.slug == "test-article-title"
assert article.get_url() == "/api/v1/articles/test-article-title/"
assert article.get_url() == "/articles/test-article-title"
2 changes: 1 addition & 1 deletion articles/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def queue_fastly_purge_articles_list():
log.info("Purging articles list page from the Fastly cache...")

# Purge the articles API endpoint
articles_url = "/api/v1/articles/"
articles_url = "/articles"

resp = call_fastly_purge_api(articles_url)

Expand Down
1 change: 0 additions & 1 deletion articles/tasks_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def test_call_fastly_purge_api_success(self, mock_request, mock_fastly_response)

# Verify headers were set correctly
call_kwargs = mock_request.call_args.kwargs
assert call_kwargs["headers"]["host"] == "learn.mit.edu"
assert call_kwargs["headers"]["fastly-key"] == "test-token"
assert call_kwargs["headers"]["fastly-soft-purge"] == "1"
assert call_kwargs["timeout"] == 30
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ services:
profiles:
- frontend
working_dir: /src
image: node:22.21
image: node:24.13
entrypoint: ["/bin/sh", "-c"]
command:
- |
Expand Down
2 changes: 1 addition & 1 deletion frontends/main/Dockerfile.web
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# heroku container:release --app mitopen-rc-nextjs frontend


FROM node:22-alpine AS base
FROM node:24-alpine AS base

RUN apk update
RUN apk add --no-cache libc6-compat && \
Expand Down
4 changes: 2 additions & 2 deletions frontends/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"react-dom": "^19.2.1",
"react-hotkeys-hook": "^5.2.1",
"react-markdown": "^10.0.0",
"react-slick": "^0.30.2",
"react-slick": "^0.31.0",
"sharp": "0.34.4",
"slick-carousel": "^1.8.1",
"tiny-invariant": "^1.3.3",
Expand All @@ -72,7 +72,7 @@
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.7",
"@types/node": "^22.0.0",
"@types/node": "^24.0.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/react-slick": "^0.23.13",
Expand Down
27 changes: 11 additions & 16 deletions frontends/main/src/app-pages/HomePage/HomePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,17 @@ const setupAPIs = () => {
setMockResponse.get(urls.userLists.membershipList(), [])
setMockResponse.get(urls.learningPaths.membershipList(), [])

const resources = learningResources.resources({ count: 4 })
const featured = learningResources.resources({ count: 2 })
const media = learningResources.resources({ count: 2 })
const attestations = testimonials.testimonials({ count: 3 })

setMockResponse.get(
expect.stringContaining(urls.learningResources.list()),
resources,
expect.stringContaining(urls.learningResources.featured()),
featured,
)

setMockResponse.get(
expect.stringContaining(urls.learningResources.featured()),
resources,
expect.stringContaining(urls.learningResources.list()),
media,
)

setMockResponse.get(
Expand Down Expand Up @@ -90,6 +90,7 @@ const setupAPIs = () => {
)

mockedUseFeatureFlagEnabled.mockReturnValue(false)
return { featured, media }
}

describe("Home Page Hero", () => {
Expand Down Expand Up @@ -341,24 +342,18 @@ describe("Home Page Carousel", () => {
})

test("Headings", async () => {
setupAPIs()

setMockResponse.get(
expect.stringContaining(urls.learningResources.list()),
[],
)
setMockResponse.get(
expect.stringContaining(urls.learningResources.featured()),
[],
)
const { featured, media } = setupAPIs()

renderWithProviders(<HomePage heroImageIndex={1} />)
await waitFor(() => {
assertHeadings([
{ level: 1, name: "Learn with MIT" },
{ level: 2, name: "Featured Courses" },
// Featured course order is randomized on frontend, so just check for presence
...featured.results.map(() => ({ level: 3, name: expect.any(String) })),
{ level: 2, name: "Continue Your Journey" },
{ level: 2, name: "Media" },
...media.results.map((result) => ({ level: 3, name: result.title })),
{ level: 2, name: "Browse by Topic" },
{ level: 2, name: "From Our Community" },
{ level: 2, name: "MIT Stories & Events" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe("CourseEnrollmentButton", () => {
screen.getByTestId("signup-popover")
})

test("Returns null if no next run available", async () => {
test("Displays disabled button with 'Access Course Materials' text if no next run available", async () => {
const course = makeCourse({
next_run_id: null,
courseruns: [],
Expand All @@ -72,10 +72,12 @@ describe("CourseEnrollmentButton", () => {
makeUser({ is_authenticated: false }),
)

const { view } = renderWithProviders(
<CourseEnrollmentButton course={course} />,
)
renderWithProviders(<CourseEnrollmentButton course={course} />)

expect(view.container).toBeEmptyDOMElement()
const button = await screen.findByRole("button", {
name: ACCESS_MATERIALS,
})
expect(button).toBeInTheDocument()
expect(button).toBeDisabled()
})
})
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from "react"
import { styled } from "ol-components"
import { useQuery } from "@tanstack/react-query"
import { CourseWithCourseRunsSerializerV2 } from "@mitodl/mitxonline-api-axios/v2"
import {
CourseRunV2,
CourseWithCourseRunsSerializerV2,
} from "@mitodl/mitxonline-api-axios/v2"
import { Button } from "@mitodl/smoot-design"
import CourseEnrollmentDialog from "@/page-components/EnrollmentDialogs/CourseEnrollmentDialog"
import NiceModal from "@ebay/nice-modal-react"
Expand All @@ -12,6 +15,13 @@ const WideButton = styled(Button)({
width: "100%",
})

const getButtonText = (nextRun?: CourseRunV2) => {
if (!nextRun || nextRun.is_archived) {
return "Access Course Materials"
}
return "Enroll for Free"
}

type CourseEnrollmentButtonProps = {
course: CourseWithCourseRunsSerializerV2
}
Expand All @@ -23,10 +33,6 @@ const CourseEnrollmentButton: React.FC<CourseEnrollmentButtonProps> = ({
const nextRunId = course.next_run_id
const nextRun = course.courseruns.find((run) => run.id === nextRunId)

if (!nextRun) {
return null
}

const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
if (me.isLoading) {
return
Expand All @@ -39,8 +45,13 @@ const CourseEnrollmentButton: React.FC<CourseEnrollmentButtonProps> = ({

return (
<>
<WideButton onClick={handleClick} variant="primary" size="large">
{nextRun.is_archived ? "Access Course Materials" : "Enroll for Free"}
<WideButton
disabled={!nextRun}
onClick={handleClick}
variant="primary"
size="large"
>
{getButtonText(nextRun)}
</WideButton>
<SignupPopover anchorEl={anchor} onClose={() => setAnchor(null)} />
</>
Expand Down
Loading
Loading