diff --git a/web/src/components/ContinueWatchingCard.test.tsx b/web/src/components/ContinueWatchingCard.test.tsx index aa081c5b..c465245e 100644 --- a/web/src/components/ContinueWatchingCard.test.tsx +++ b/web/src/components/ContinueWatchingCard.test.tsx @@ -176,6 +176,8 @@ describe("ContinueWatchingCard", () => { expect(markup).toContain('href="/watch/ep-001"'); expect(markup).toContain('href="/item/ep-001"'); + expect(markup).toContain('href="/item/series-1"'); + expect(markup).toContain('aria-label="Play Breaking Bad"'); expect(markup).toContain("Breaking Bad"); expect(markup).toContain("Season 1 Episode 1"); expect(markup).toContain("Pilot"); @@ -183,6 +185,46 @@ describe("ContinueWatchingCard", () => { expect(markup).toContain("More actions"); }); + it("links the poster to the item page and reserves playback for the play button", () => { + const queryClient = new QueryClient(); + const markup = renderToStaticMarkup( + + + + + , + ); + + // The poster link renders first and must navigate to the item page; the + // watch href is reserved for the explicit play button. + const posterLinkIndex = markup.indexOf('href="/item/movie-001"'); + const playLinkIndex = markup.indexOf('href="/watch/movie-001"'); + expect(posterLinkIndex).toBeGreaterThan(-1); + expect(playLinkIndex).toBeGreaterThan(-1); + expect(posterLinkIndex).toBeLessThan(playLinkIndex); + expect(markup).toContain('aria-label="Play Apex"'); + }); + it("routes audiobook continue cards to the audiobook detail player", () => { const queryClient = new QueryClient(); const markup = renderToStaticMarkup( diff --git a/web/src/components/ContinueWatchingCard.tsx b/web/src/components/ContinueWatchingCard.tsx index 02780505..3ab59c86 100644 --- a/web/src/components/ContinueWatchingCard.tsx +++ b/web/src/components/ContinueWatchingCard.tsx @@ -45,6 +45,7 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) { libraryId: props.libraryId, }), title: props.sectionItem.title, + seriesId: props.sectionItem.series_id, seriesTitle: props.sectionItem.series_title, seasonNumber: props.sectionItem.season_number, episodeNumber: props.sectionItem.episode_number, @@ -65,6 +66,7 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) { libraryId: props.libraryId, }), title: props.detail.title, + seriesId: props.detail.series_id, seriesTitle: props.detail.series_title, seasonNumber: props.detail.season_number, episodeNumber: props.detail.episode_number, @@ -105,7 +107,14 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) { card.durationSeconds > 0 ? (card.positionSeconds / card.durationSeconds) * 100 : 0; const hasPartialProgress = progressPercent > 0 && progressPercent < 100; const hasEpisodeMeta = card.seasonNumber != null && card.episodeNumber != null; - const heading = hasEpisodeMeta && card.seriesTitle ? card.seriesTitle : card.title; + const headingIsSeries = hasEpisodeMeta && !!card.seriesTitle; + const heading = headingIsSeries ? card.seriesTitle : card.title; + // The heading shows the series title for episodes, so it should navigate to + // the series page; everything else heads to the item's own page. + const headingHref = + headingIsSeries && card.seriesId + ? buildItemHref({ contentId: card.seriesId, libraryId: props.libraryId }) + : card.itemHref; const episodeLabel = hasEpisodeMeta ? `Season ${card.seasonNumber} Episode ${card.episodeNumber}` : null; @@ -195,18 +204,14 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) { return (
-
- +
+
{imageSrc ? ( {heading} ) : ( @@ -223,12 +228,8 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) { /> )} - {/* Play overlay */} -
-
- -
-
+ {/* Hover dim behind the play button */} +
{/* Progress bar */} {!isNextUp && progressPercent > 0 && ( @@ -244,6 +245,14 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) { )}
+ + + {/* Info */} - -
{heading}
+
+ + {heading} + {episodeMeta &&
{episodeMeta}
} {premiereBadge && (
@@ -279,7 +293,7 @@ export default function ContinueWatchingCard(props: ContinueWatchingCardProps) {
)} {timeLeftLabel &&
{timeLeftLabel}
} - +
); } diff --git a/web/src/components/SectionRow.test.tsx b/web/src/components/SectionRow.test.tsx index 63778238..3380c4d3 100644 --- a/web/src/components/SectionRow.test.tsx +++ b/web/src/components/SectionRow.test.tsx @@ -57,6 +57,10 @@ vi.mock("@/hooks/useViewTransition", () => ({ useViewTransitionNavigate: () => mockNavigate, })); +vi.mock("@/playback/watchPlaybackContext", () => ({ + useWatchPlaybackController: () => ({ startPlayback: () => {} }), +})); + describe("SectionRow", () => { beforeEach(() => { latestCarouselProps = undefined;