diff --git a/src/components/Card/Card.module.scss b/src/components/Card/Card.module.scss index e2ff24fb5..afdd7fcd1 100644 --- a/src/components/Card/Card.module.scss +++ b/src/components/Card/Card.module.scss @@ -7,6 +7,8 @@ display: flex; flex-direction: column; justify-content: flex-start; + color: inherit; + text-decoration: none; cursor: pointer; transition: transform 0.2s ease-out, -webkit-transform 0.2s ease-out; diff --git a/src/components/Card/Card.test.tsx b/src/components/Card/Card.test.tsx index 5c948ea3d..ea646de7c 100644 --- a/src/components/Card/Card.test.tsx +++ b/src/components/Card/Card.test.tsx @@ -1,32 +1,32 @@ import * as React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent } from '@testing-library/react'; import Card from './Card'; import type { PlaylistItem } from '#types/playlist'; +import { renderWithRouter } from '#test/testUtils'; const item = { title: 'aa', duration: 120 } as PlaylistItem; const itemWithImage = { title: 'This is a movie', duration: 120, cardImage: 'http://movie.jpg' } as PlaylistItem; describe('', () => { it('renders card with video title', () => { - const { getByText } = render( ''} />); + const { getByText } = renderWithRouter(); expect(getByText(/aa/i)).toBeTruthy(); }); it('renders tag with correct duration', () => { - const { getByText } = render( ''} />); + const { getByText } = renderWithRouter(); expect(getByText(/2/i)).toBeTruthy(); }); it('renders the image with the image prop when valid', () => { - const { getByAltText } = render( ''} />); - + const { getByAltText } = renderWithRouter(); expect(getByAltText('This is a movie')).toHaveAttribute('src', 'http://movie.jpg?width=320'); }); it('makes the image visible after load', () => { - const { getByAltText } = render( ''} />); + const { getByAltText } = renderWithRouter(); expect(getByAltText('This is a movie')).toHaveAttribute('src', 'http://movie.jpg?width=320'); expect(getByAltText('This is a movie')).toHaveStyle({ opacity: 0 }); @@ -35,4 +35,9 @@ describe('', () => { expect(getByAltText('This is a movie')).toHaveStyle({ opacity: 1 }); }); + + it('should render anchor tag', () => { + const { container } = renderWithRouter(); + expect(container).toMatchSnapshot(); + }); }); diff --git a/src/components/Card/Card.tsx b/src/components/Card/Card.tsx index 43592d783..621112e2f 100644 --- a/src/components/Card/Card.tsx +++ b/src/components/Card/Card.tsx @@ -1,6 +1,7 @@ -import React, { KeyboardEvent, memo, useState } from 'react'; +import React, { memo, useState } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; import styles from './Card.module.scss'; @@ -18,7 +19,6 @@ export type PosterAspectRatio = (typeof cardAspectRatios)[number]; type CardProps = { item: PlaylistItem; - onClick?: () => void; onHover?: () => void; progress?: number; posterAspect?: PosterAspectRatio; @@ -28,10 +28,10 @@ type CardProps = { isCurrent?: boolean; isLocked?: boolean; currentLabel?: string; + url: string; }; function Card({ - onClick, onHover, progress, item, @@ -42,6 +42,7 @@ function Card({ isCurrent = false, isLocked = true, currentLabel, + url, }: CardProps): JSX.Element { const { title, duration, episodeNumber, seasonNumber, cardImage: image, mediaStatus, scheduledStart } = item; const { @@ -86,14 +87,13 @@ function Card({ }; return ( -
e.preventDefault() : undefined} onMouseEnter={onHover} tabIndex={disabled ? -1 : 0} - onKeyDown={(event: KeyboardEvent) => (event.key === 'Enter' || event.key === ' ') && !disabled && onClick && onClick()} - role="button" - aria-label={t('play_item', { title })} + aria-label={title} >
setImageLoaded(true)} alt={title} /> @@ -125,7 +125,7 @@ function Card({
{title}
)} -
+ ); } diff --git a/src/components/Card/__snapshots__/Card.test.tsx.snap b/src/components/Card/__snapshots__/Card.test.tsx.snap new file mode 100644 index 000000000..8c63b5145 --- /dev/null +++ b/src/components/Card/__snapshots__/Card.test.tsx.snap @@ -0,0 +1,62 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` > should render anchor tag 1`] = ` + +`; diff --git a/src/components/CardGrid/CardGrid.test.tsx b/src/components/CardGrid/CardGrid.test.tsx index 9133a954b..0e3f06a16 100644 --- a/src/components/CardGrid/CardGrid.test.tsx +++ b/src/components/CardGrid/CardGrid.test.tsx @@ -1,22 +1,23 @@ import * as React from 'react'; -import { render } from '@testing-library/react'; import CardGrid from './CardGrid'; -import { generatePlaylistPlaceholder } from '#src/utils/collection'; +import playlistFixture from '#test/fixtures/playlist.json'; +import { renderWithRouter } from '#test/testUtils'; +import type { Playlist, PlaylistItem } from '#types/playlist'; describe('', () => { it('renders and matches snapshot', () => { - const placeholderData = generatePlaylistPlaceholder(); - const { container } = render( + const playlist = playlistFixture as Playlist; + const { container } = renderWithRouter( `m/${item.mediaid}`} />, ); diff --git a/src/components/CardGrid/CardGrid.tsx b/src/components/CardGrid/CardGrid.tsx index 5a62a0217..af5b62e72 100644 --- a/src/components/CardGrid/CardGrid.tsx +++ b/src/components/CardGrid/CardGrid.tsx @@ -36,7 +36,7 @@ type CardGridProps = { hasLoadMore?: boolean; loadMore?: () => void; onCardHover?: (item: PlaylistItem) => void; - onCardClick: (item: PlaylistItem, playlistId?: string) => void; + getUrl: (item: PlaylistItem) => string; }; function CardGrid({ @@ -50,8 +50,8 @@ function CardGrid({ isLoggedIn, hasSubscription, hasLoadMore, + getUrl, loadMore, - onCardClick, onCardHover, }: CardGridProps) { const breakpoint: Breakpoint = useBreakpoint(); @@ -75,7 +75,7 @@ function CardGrid({
onCardClick(playlistItem, playlistItem.feedid)} + url={getUrl(playlistItem)} onHover={typeof onCardHover === 'function' ? () => onCardHover(playlistItem) : undefined} loading={isLoading} isCurrent={currentCardItem && currentCardItem.mediaid === mediaid} diff --git a/src/components/CardGrid/__snapshots__/CardGrid.test.tsx.snap b/src/components/CardGrid/__snapshots__/CardGrid.test.tsx.snap index 9f6e8f82a..d03b668ff 100644 --- a/src/components/CardGrid/__snapshots__/CardGrid.test.tsx.snap +++ b/src/components/CardGrid/__snapshots__/CardGrid.test.tsx.snap @@ -14,10 +14,10 @@ exports[` > renders and matches snapshot 1`] = `
-
> renders and matches snapshot 1`] = ` >
+ > +
+ 4 min +
+
> renders and matches snapshot 1`] = ` >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Agent 327
-
-
-
-
+
> renders and matches snapshot 1`] = `
-
> renders and matches snapshot 1`] = ` >
+ > +
+ 10 min +
+
> renders and matches snapshot 1`] = ` >
-
-
-
-
-
-
-
-
-
-
+ Big Buck Bunny
-
-
-
-
+
> renders and matches snapshot 1`] = `
-
> renders and matches snapshot 1`] = ` >
+ > +
+ 11 min +
+
> renders and matches snapshot 1`] = ` >
-
-
-
-
-
-
-
-
-
-
+ Elephants Dream
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/components/Epg/Epg.tsx b/src/components/Epg/Epg.tsx index 16606c7f9..843998cb0 100644 --- a/src/components/Epg/Epg.tsx +++ b/src/components/Epg/Epg.tsx @@ -25,9 +25,10 @@ type Props = { channel: EpgChannel | undefined; program: EpgProgram | undefined; config: Config; + getUrl: (channelId: string) => string; }; -export default function Epg({ channels, onChannelClick, onProgramClick, channel, program, config }: Props) { +export default function Epg({ channels, onChannelClick, onProgramClick, channel, program, config, getUrl }: Props) { const breakpoint = useBreakpoint(); const { t } = useTranslation('common'); @@ -74,6 +75,7 @@ export default function Epg({ channels, onChannelClick, onProgramClick, channel, onScrollToNow(); }} isActive={channel?.id === epgChannel.uuid} + url={getUrl(epgChannel.uuid)} /> )} renderProgram={({ program: programItem, isBaseTimeFormat }) => { @@ -89,6 +91,7 @@ export default function Epg({ channels, onChannelClick, onProgramClick, channel, isActive={program?.id === programItem.data.id} compact={isMobile} isBaseTimeFormat={isBaseTimeFormat} + url={getUrl(programItem.data.channelUuid)} /> ); }} diff --git a/src/components/EpgChannel/EpgChannelItem.module.scss b/src/components/EpgChannel/EpgChannelItem.module.scss index 1105805fe..366e36adb 100644 --- a/src/components/EpgChannel/EpgChannelItem.module.scss +++ b/src/components/EpgChannel/EpgChannelItem.module.scss @@ -5,6 +5,8 @@ .epgChannelBox { position: absolute; padding: 8px 0; + color: inherit; + text-decoration: none; background-color: var(--body-background-color); } diff --git a/src/components/EpgChannel/EpgChannelItem.tsx b/src/components/EpgChannel/EpgChannelItem.tsx index a2a7c606c..4d3f4a79d 100644 --- a/src/components/EpgChannel/EpgChannelItem.tsx +++ b/src/components/EpgChannel/EpgChannelItem.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type { Channel } from 'planby'; import classNames from 'classnames'; +import { Link } from 'react-router-dom'; import styles from './EpgChannelItem.module.scss'; @@ -13,14 +14,15 @@ type Props = { sidebarWidth: number; onClick?: (channel: Channel) => void; isActive: boolean; + url: string; }; -const EpgChannelItem: React.VFC = ({ channel, channelItemWidth, sidebarWidth, onClick, isActive }) => { +const EpgChannelItem: React.VFC = ({ channel, channelItemWidth, sidebarWidth, onClick, isActive, url }) => { const { position, uuid, channelLogoImage } = channel; const style = { top: position.top, height: position.height, width: sidebarWidth }; return ( -
+
= ({ channel, channelItemWidth, sidebarWi > Logo
-
+ ); }; diff --git a/src/components/EpgProgramItem/EpgProgramItem.module.scss b/src/components/EpgProgramItem/EpgProgramItem.module.scss index 4d3c69e40..e4aa62715 100644 --- a/src/components/EpgProgramItem/EpgProgramItem.module.scss +++ b/src/components/EpgProgramItem/EpgProgramItem.module.scss @@ -6,6 +6,8 @@ position: absolute; padding: 8px 4px; overflow: hidden; + color: inherit; + text-decoration: none; } .epgProgram { diff --git a/src/components/EpgProgramItem/EpgProgramItem.tsx b/src/components/EpgProgramItem/EpgProgramItem.tsx index 28efc53d9..6c5a64465 100644 --- a/src/components/EpgProgramItem/EpgProgramItem.tsx +++ b/src/components/EpgProgramItem/EpgProgramItem.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { Program, useProgram } from 'planby'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; import styles from './EpgProgramItem.module.scss'; @@ -14,9 +15,10 @@ type Props = { compact: boolean; disabled: boolean; isBaseTimeFormat: boolean; + url: string; }; -const ProgramItem: React.VFC = ({ program, onClick, isActive, compact, disabled, isBaseTimeFormat }) => { +const ProgramItem: React.VFC = ({ program, onClick, isActive, compact, disabled, isBaseTimeFormat, url }) => { const { styles: { position }, formatTime, @@ -39,7 +41,7 @@ const ProgramItem: React.VFC = ({ program, onClick, isActive, compact, di const showLiveTagInImage = !compact && isMinWidth && isLive; return ( -
onClick && onClick(program)}> + onClick && onClick(program)}>
= ({ program, onClick, isActive, compact, di
-
+ ); }; diff --git a/src/components/Favorites/Favorites.test.tsx b/src/components/Favorites/Favorites.test.tsx index 0f49202c9..3dbc6b0ba 100644 --- a/src/components/Favorites/Favorites.test.tsx +++ b/src/components/Favorites/Favorites.test.tsx @@ -15,7 +15,6 @@ describe('', () => { playlist={playlist} error={error} isLoading={isLoading} - onCardClick={() => null} onCardHover={() => null} onClearFavoritesClick={() => null} hasSubscription={true} diff --git a/src/components/Favorites/Favorites.tsx b/src/components/Favorites/Favorites.tsx index 07c1cb47e..85f3e2df5 100644 --- a/src/components/Favorites/Favorites.tsx +++ b/src/components/Favorites/Favorites.tsx @@ -10,6 +10,7 @@ import ErrorPage from '#components/ErrorPage/ErrorPage'; import { Breakpoint, Breakpoints } from '#src/hooks/useBreakpoint'; import type { AccessModel } from '#types/Config'; import type { Playlist, PlaylistItem } from '#types/playlist'; +import { mediaURL } from '#src/utils/formatting'; type Props = { playlist: Playlist; @@ -17,7 +18,6 @@ type Props = { isLoading: boolean; accessModel: AccessModel; hasSubscription: boolean; - onCardClick: (item: PlaylistItem) => void; onCardHover?: (item: PlaylistItem) => void; onClearFavoritesClick: () => void; }; @@ -30,7 +30,7 @@ const cols: Breakpoints = { [Breakpoint.xl]: 3, }; -const Favorites = ({ playlist, error, isLoading, accessModel, hasSubscription, onCardClick, onCardHover, onClearFavoritesClick }: Props): JSX.Element => { +const Favorites = ({ playlist, error, isLoading, accessModel, hasSubscription, onCardHover, onClearFavoritesClick }: Props): JSX.Element => { const { t } = useTranslation('user'); if (isLoading) return ; @@ -39,6 +39,8 @@ const Favorites = ({ playlist, error, isLoading, accessModel, hasSubscription, o return ; } + const getURL = (playlistItem: PlaylistItem) => mediaURL({ media: playlistItem, playlistId: playlistItem.feedid }); + return (
@@ -47,8 +49,8 @@ const Favorites = ({ playlist, error, isLoading, accessModel, hasSubscription, o
{playlist.playlist.length > 0 ? ( { test('Regular shelf', () => { - const { container } = render( + const { container } = renderWithRouter( { - /**/ - }} playlist={playlist} enableCardTitles enableTitle @@ -79,16 +76,13 @@ describe('Shelf Component tests', () => { }); test('Featured shelf', () => { - const { container } = render( + const { container } = renderWithRouter( { - /**/ - }} playlist={playlist} featured enableCardTitles diff --git a/src/components/Shelf/Shelf.tsx b/src/components/Shelf/Shelf.tsx index 50d23fccf..6f98036d9 100644 --- a/src/components/Shelf/Shelf.tsx +++ b/src/components/Shelf/Shelf.tsx @@ -12,6 +12,8 @@ import { isLocked } from '#src/utils/entitlements'; import TileDock from '#components/TileDock/TileDock'; import Card, { type PosterAspectRatio } from '#components/Card/Card'; import type { Playlist, PlaylistItem } from '#types/playlist'; +import { mediaURL } from '#src/utils/formatting'; +import { PersonalShelf } from '#src/stores/ConfigStore'; export const tileBreakpoints: Breakpoints = { [Breakpoint.xs]: 1, @@ -32,7 +34,6 @@ export const featuredTileBreakpoints: Breakpoints = { export type ShelfProps = { playlist: Playlist; type: ContentType; - onCardClick: (playlistItem: PlaylistItem, playlistId: string | undefined, type: ContentType) => void; onCardHover?: (playlistItem: PlaylistItem) => void; watchHistory?: { [key: string]: number }; enableTitle?: boolean; @@ -51,7 +52,6 @@ export type ShelfProps = { const Shelf = ({ playlist, type, - onCardClick, onCardHover, title, watchHistory, @@ -71,11 +71,11 @@ const Shelf = ({ const renderTile = useCallback( (item: PlaylistItem, isInView: boolean) => { + const url = mediaURL({ media: item, playlistId: playlist.feedid, play: type === PersonalShelf.ContinueWatching }); return ( onCardClick(item, playlist.feedid, type) : undefined} onHover={typeof onCardHover === 'function' ? () => onCardHover(item) : undefined} featured={featured} disabled={!isInView} @@ -83,10 +83,11 @@ const Shelf = ({ isLocked={isLocked(accessModel, isLoggedIn, hasSubscription, item)} posterAspect={posterAspect} item={item} + url={url} /> ); }, - [watchHistory, onCardHover, featured, loading, accessModel, isLoggedIn, hasSubscription, posterAspect, onCardClick, playlist.feedid, type], + [watchHistory, onCardHover, featured, loading, accessModel, isLoggedIn, hasSubscription, posterAspect, playlist.feedid, type], ); const renderRightControl = useCallback( diff --git a/src/components/Shelf/__snapshots__/Shelf.test.tsx.snap b/src/components/Shelf/__snapshots__/Shelf.test.tsx.snap index 8aaf8f22c..949a35623 100644 --- a/src/components/Shelf/__snapshots__/Shelf.test.tsx.snap +++ b/src/components/Shelf/__snapshots__/Shelf.test.tsx.snap @@ -40,10 +40,10 @@ exports[`Shelf Component tests > Featured shelf 1`] = ` class="_notInView_22abb0" style="width: 100%; padding-left: 4px; padding-right: 4px; box-sizing: border-box; transition: opacity .2s ease-in 0s;" > - -
+
  • - -
  • +
  • -
  • -
    +
  • - -
  • +
  • - -
  • +
    Regular shelf 1`] = ` class="" style="width: 20%; padding-left: 4px; padding-right: 4px; box-sizing: border-box;" > -
    Regular shelf 1`] = ` Movie 1
    -
    +
  • -
    Regular shelf 1`] = ` Movie 2
    -
  • +
  • -
    Regular shelf 1`] = ` Third movie
    -
  • +
  • -
    Regular shelf 1`] = ` Last playlist item
    -
  • +
    diff --git a/src/components/VideoDetails/VideoDetails.test.tsx b/src/components/VideoDetails/VideoDetails.test.tsx index 22847cf73..651377082 100644 --- a/src/components/VideoDetails/VideoDetails.test.tsx +++ b/src/components/VideoDetails/VideoDetails.test.tsx @@ -16,7 +16,9 @@ describe('', () => { shareButton={} favoriteButton={} trailerButton={} - />, + > +
    Related Videos
    +
    , ); expect(container).toMatchSnapshot(); @@ -34,7 +36,9 @@ describe('', () => { shareButton={} favoriteButton={} trailerButton={} - />, + > +
    Related Videos
    +
    , ); expect(getByAltText('Test video')).toHaveAttribute('src', 'http://image.jpg?width=1280'); diff --git a/src/components/VideoDetails/VideoDetails.tsx b/src/components/VideoDetails/VideoDetails.tsx index f694d5f1f..e3cc75bab 100644 --- a/src/components/VideoDetails/VideoDetails.tsx +++ b/src/components/VideoDetails/VideoDetails.tsx @@ -16,8 +16,9 @@ type Props = { image?: string; startWatchingButton: React.ReactNode; shareButton: React.ReactNode; - favoriteButton: React.ReactNode; - trailerButton: React.ReactNode; + favoriteButton?: React.ReactNode; + trailerButton?: React.ReactNode; + children: React.ReactNode; }; const VideoDetails: React.VFC = ({ @@ -30,30 +31,34 @@ const VideoDetails: React.VFC = ({ shareButton, favoriteButton, trailerButton, + children, }) => { const breakpoint: Breakpoint = useBreakpoint(); const isMobile = breakpoint === Breakpoint.xs; return ( -
    -
    - {title} -
    -

    {title}

    -
    -
    {primaryMetadata}
    - {secondaryMetadata &&
    {secondaryMetadata}
    } -
    - +
    +
    +
    + {title} +
    +

    {title}

    +
    +
    {primaryMetadata}
    + {secondaryMetadata &&
    {secondaryMetadata}
    } +
    + -
    - {startWatchingButton} - {trailerButton} - {favoriteButton} - {shareButton} +
    + {startWatchingButton} + {trailerButton} + {favoriteButton} + {shareButton} +
    + {children}
    ); }; diff --git a/src/components/VideoDetails/__snapshots__/VideoDetails.test.tsx.snap b/src/components/VideoDetails/__snapshots__/VideoDetails.test.tsx.snap index 52ef7ee6c..2be219b31 100644 --- a/src/components/VideoDetails/__snapshots__/VideoDetails.test.tsx.snap +++ b/src/components/VideoDetails/__snapshots__/VideoDetails.test.tsx.snap @@ -3,69 +3,76 @@ exports[` > renders and matches snapshot 1`] = `
    - Test video
    -

    - Test video -

    + Test video
    +

    + Test video +

    -
    -
    - Video description + + + +
    -
    - - - - -
    +
    + Related Videos +
    `; diff --git a/src/components/VideoLayout/VideoLayout.tsx b/src/components/VideoLayout/VideoLayout.tsx index 776c56898..724904c11 100644 --- a/src/components/VideoLayout/VideoLayout.tsx +++ b/src/components/VideoLayout/VideoLayout.tsx @@ -41,11 +41,11 @@ type VideoDetailsProps = { type VideoListProps = { relatedTitle?: string; - onItemClick?: (item: PlaylistItem) => void; onItemHover?: (item: PlaylistItem) => void; watchHistory?: { [key: string]: number }; activeMediaId?: string; activeLabel?: string; + getURL: (item: PlaylistItem) => string; }; type Props = { @@ -83,7 +83,6 @@ const VideoLayout: React.FC = ({ startWatchingButton, trailerButton, // list - onItemClick, relatedTitle, watchHistory, activeLabel, @@ -97,6 +96,7 @@ const VideoLayout: React.FC = ({ // load more hasLoadMore, loadMore, + getURL, }) => { const breakpoint = useBreakpoint(); const isTablet = breakpoint === Breakpoint.sm || breakpoint === Breakpoint.md; @@ -114,7 +114,7 @@ const VideoLayout: React.FC = ({ ); const renderRelatedVideos = (grid = true) => { - if (!playlist || !onItemClick) return null; + if (!playlist) return null; return grid ? ( <> @@ -124,7 +124,6 @@ const VideoLayout: React.FC = ({
    = ({ hasSubscription={hasSubscription} hasLoadMore={hasLoadMore} loadMore={loadMore} + getUrl={getURL} /> ) : ( @@ -149,7 +149,6 @@ const VideoLayout: React.FC = ({ activeMediaId={item?.mediaid} activeLabel={activeLabel} playlist={playlist} - onListItemClick={onItemClick} watchHistory={watchHistory} isLoading={isLoading} accessModel={accessModel} @@ -157,6 +156,7 @@ const VideoLayout: React.FC = ({ hasSubscription={hasSubscription} hasLoadMore={hasLoadMore} loadMore={loadMore} + getUrl={getURL} />
    ); @@ -184,22 +184,21 @@ const VideoLayout: React.FC = ({ } return ( -
    - - {playlist && onItemClick &&
    {renderRelatedVideos(true)}
    } + + {playlist &&
    {renderRelatedVideos(true)}
    } {children} {player} -
    + ); }; diff --git a/src/components/VideoList/VideoList.tsx b/src/components/VideoList/VideoList.tsx index 37188a9d2..62a265648 100644 --- a/src/components/VideoList/VideoList.tsx +++ b/src/components/VideoList/VideoList.tsx @@ -15,7 +15,6 @@ type Props = { playlist?: Playlist; header?: React.ReactNode; onListItemHover?: (item: PlaylistItem) => void; - onListItemClick?: (item: PlaylistItem, playlistId?: string) => void; watchHistory?: { [key: string]: number }; isLoading: boolean; activeMediaId?: string; @@ -26,6 +25,7 @@ type Props = { hasSubscription: boolean; hasLoadMore?: boolean; loadMore?: () => void; + getUrl: (item: PlaylistItem) => string; }; // eslint-disable-next-line @typescript-eslint/no-empty-function @@ -34,7 +34,6 @@ const defaultLoadMore = () => {}; function VideoList({ playlist, header, - onListItemClick, onListItemHover, isLoading = false, watchHistory, @@ -46,6 +45,7 @@ function VideoList({ hasSubscription, hasLoadMore, loadMore = defaultLoadMore, + getUrl, }: Props) { return (
    @@ -54,9 +54,9 @@ function VideoList({ }> {playlist?.playlist?.map((playlistItem: PlaylistItem) => ( onListItemClick && onListItemClick(playlistItem, playlistItem.feedid)} onHover={() => onListItemHover && onListItemHover(playlistItem)} loading={isLoading} isActive={activeMediaId === playlistItem.mediaid} diff --git a/src/components/VideoListItem/VideoListItem.module.scss b/src/components/VideoListItem/VideoListItem.module.scss index 4b757f1cf..90d76ecc3 100644 --- a/src/components/VideoListItem/VideoListItem.module.scss +++ b/src/components/VideoListItem/VideoListItem.module.scss @@ -9,6 +9,8 @@ display: flex; justify-content: flex-start; margin-bottom: variables.$base-spacing; + color: inherit; + text-decoration: none; cursor: pointer; transition: transform 0.2s ease-out, -webkit-transform 0.2s ease-out; diff --git a/src/components/VideoListItem/VideoListItem.tsx b/src/components/VideoListItem/VideoListItem.tsx index 016130eb1..af5a58c72 100644 --- a/src/components/VideoListItem/VideoListItem.tsx +++ b/src/components/VideoListItem/VideoListItem.tsx @@ -1,6 +1,7 @@ -import React, { KeyboardEvent, memo, useState } from 'react'; +import React, { memo, useState } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; import styles from './VideoListItem.module.scss'; @@ -14,7 +15,6 @@ import { isLiveChannel, isSeries } from '#src/utils/media'; import { MediaStatus } from '#src/utils/liveEvent'; type VideoListItemProps = { - onClick?: () => void; onHover?: () => void; item: PlaylistItem; progress?: number; @@ -22,9 +22,10 @@ type VideoListItemProps = { isActive?: boolean; activeLabel?: string; isLocked?: boolean; + url: string; }; -function VideoListItem({ onClick, onHover, progress, activeLabel, item, loading = false, isActive = false, isLocked = true }: VideoListItemProps): JSX.Element { +function VideoListItem({ onHover, progress, activeLabel, item, url, loading = false, isActive = false, isLocked = true }: VideoListItemProps): JSX.Element { const { title, duration, seasonNumber, episodeNumber, cardImage: image, mediaStatus, scheduledStart } = item; const { @@ -61,18 +62,8 @@ function VideoListItem({ onClick, onHover, progress, activeLabel, item, loading } }; - const handleKeyDown = (event: KeyboardEvent) => (event.key === 'Enter' || event.key === ' ') && onClick && onClick(); - return ( -
    +
    {title} setImageLoaded(true)} width={320} /> {isActive &&
    {activeLabel}
    } @@ -91,7 +82,7 @@ function VideoListItem({ onClick, onHover, progress, activeLabel, item, loading {!!scheduledStart &&
    {formatLocalizedDateTime(scheduledStart, language)}
    }
    {title}
    -
    + ); } diff --git a/src/containers/ShelfList/ShelfList.tsx b/src/containers/ShelfList/ShelfList.tsx index 914a2f801..8d563ca07 100644 --- a/src/containers/ShelfList/ShelfList.tsx +++ b/src/containers/ShelfList/ShelfList.tsx @@ -1,8 +1,7 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import shallow from 'zustand/shallow'; import InfiniteScroll from 'react-infinite-scroller'; -import { useNavigate } from 'react-router'; import styles from './ShelfList.module.scss'; @@ -10,13 +9,12 @@ import PlaylistContainer from '#src/containers/PlaylistContainer/PlaylistContain import { useAccountStore } from '#src/stores/AccountStore'; import { PersonalShelf, useConfigStore } from '#src/stores/ConfigStore'; import ShelfComponent from '#components/Shelf/Shelf'; -import { mediaURL, slugify } from '#src/utils/formatting'; -import type { Content, ContentType } from '#types/Config'; +import { slugify } from '#src/utils/formatting'; +import type { Content } from '#types/Config'; import { useWatchHistoryStore } from '#src/stores/WatchHistoryStore'; import { parseAspectRatio, parseTilesDelta } from '#src/utils/collection'; import InfiniteScrollLoader from '#components/InfiniteScrollLoader/InfiniteScrollLoader'; import { testId } from '#src/utils/common'; -import type { PlaylistItem } from '#types/playlist'; const INITIAL_ROW_COUNT = 6; const LOAD_ROWS_COUNT = 4; @@ -26,7 +24,6 @@ type Props = { }; const ShelfList = ({ rows }: Props) => { - const navigate = useNavigate(); const { accessModel } = useConfigStore(({ accessModel }) => ({ accessModel }), shallow); const [rowCount, setRowCount] = useState(INITIAL_ROW_COUNT); @@ -35,13 +32,6 @@ const ShelfList = ({ rows }: Props) => { // User const { user, subscription } = useAccountStore(({ user, subscription }) => ({ user, subscription }), shallow); - const onCardClick = useCallback( - (playlistItem: PlaylistItem, playlistId: string | undefined, type: ContentType) => { - navigate(mediaURL({ media: playlistItem, playlistId, play: type === PersonalShelf.ContinueWatching })); - }, - [navigate], - ); - useEffect(() => { // reset row count when the page changes return () => setRowCount(INITIAL_ROW_COUNT); @@ -57,44 +47,41 @@ const ShelfList = ({ rows }: Props) => { role="grid" loader={} > - <> - {rows.slice(0, rowCount).map((row, index) => ( - - {({ playlist, error, isLoading, style }) => { - const title = row?.title || playlist.title; - const posterAspect = parseAspectRatio(playlist.cardImageAspectRatio || playlist.shelfImageAspectRatio); - const visibleTilesDelta = parseTilesDelta(posterAspect); + {rows.slice(0, rowCount).map((row, index) => ( + + {({ playlist, error, isLoading, style }) => { + const title = row?.title || playlist.title; + const posterAspect = parseAspectRatio(playlist.cardImageAspectRatio || playlist.shelfImageAspectRatio); + const visibleTilesDelta = parseTilesDelta(posterAspect); - return ( -
    -
    - -
    + return ( +
    +
    +
    - ); - }} - - ))} - +
    + ); + }} + + ))}
    ); diff --git a/src/pages/Home/__snapshots__/Home.test.tsx.snap b/src/pages/Home/__snapshots__/Home.test.tsx.snap index 9d7d9e5d2..2d2914ed7 100644 --- a/src/pages/Home/__snapshots__/Home.test.tsx.snap +++ b/src/pages/Home/__snapshots__/Home.test.tsx.snap @@ -34,10 +34,10 @@ exports[`Home Component tests > Home test 1`] = ` class="" style="width: 20%; padding-left: 4px; padding-right: 4px; box-sizing: border-box;" > -
    Home test 1`] = ` My video
    -
    +
  • -
    Home test 1`] = ` Other Vids
    -
  • +
    @@ -175,10 +175,10 @@ exports[`Home Component tests > Home test 1`] = ` class="" style="width: 20%; padding-left: 4px; padding-right: 4px; box-sizing: border-box;" > -
    Home test 1`] = ` My video
    -
    +
  • -
    Home test 1`] = ` Other Vids
    -
  • +
    diff --git a/src/pages/LegacySeries/LegacySeries.tsx b/src/pages/LegacySeries/LegacySeries.tsx index eb1af80ed..17c25fe0e 100644 --- a/src/pages/LegacySeries/LegacySeries.tsx +++ b/src/pages/LegacySeries/LegacySeries.tsx @@ -24,10 +24,10 @@ import ShareButton from '#components/ShareButton/ShareButton'; import FavoriteButton from '#src/containers/FavoriteButton/FavoriteButton'; import Button from '#components/Button/Button'; import PlayTrailer from '#src/icons/PlayTrailer'; -import type { PlaylistItem } from '#types/playlist'; import useQueryParam from '#src/hooks/useQueryParam'; import Loading from '#src/pages/Loading/Loading'; import usePlaylist from '#src/hooks/usePlaylist'; +import type { PlaylistItem } from '#types/playlist'; const LegacySeries = () => { const breakpoint = useBreakpoint(); @@ -79,8 +79,10 @@ const LegacySeries = () => { // Handlers const goBack = () => episode && navigate(legacySeriesURL({ episodeId: episode.mediaid, seriesId, play: false, playlistId: feedId })); - const onCardClick = (toEpisode: PlaylistItem) => - seriesPlaylist && navigate(legacySeriesURL({ episodeId: toEpisode.mediaid, seriesId, play: false, playlistId: feedId })); + const getUrl = (toEpisode: PlaylistItem) => { + return seriesPlaylist ? legacySeriesURL({ episodeId: toEpisode.mediaid, seriesId, play: false, playlistId: feedId }) : ''; + }; + const handleComplete = useCallback(async () => { navigate(legacySeriesURL({ episodeId: nextItem?.mediaid, seriesId, play: !!nextItem, playlistId: feedId })); }, [navigate, nextItem, seriesId, feedId]); @@ -191,7 +193,6 @@ const LegacySeries = () => { hasSubscription={hasSubscription} playlist={filteredPlaylist} relatedTitle={inlineLayout ? selectedItem.title : t('episodes')} - onItemClick={onCardClick} setFilter={setSeasonFilter} currentFilter={seasonFilter} defaultFilterLabel={t('all_seasons')} @@ -199,6 +200,7 @@ const LegacySeries = () => { watchHistory={watchHistoryDictionary} filterMetadata={filterMetadata} filters={filters} + getURL={getUrl} player={ inlineLayout && (episode || firstEpisode) ? ( = ({ data: media, isLoading }) = // Handlers const goBack = () => media && navigate(mediaURL({ media, playlistId, play: false })); - const onCardClick = (item: PlaylistItem) => navigate(mediaURL({ media: item, playlistId })); + const getUrl = (item: PlaylistItem) => mediaURL({ media: item, playlistId }); const handleComplete = useCallback(() => { if (!id || !playlist) return; @@ -156,7 +156,7 @@ const MediaEvent: ScreenComponent = ({ data: media, isLoading }) = startWatchingButton={startWatchingButton} playlist={playlist} relatedTitle={playlist?.title} - onItemClick={onCardClick} + getURL={getUrl} activeLabel={t('current_video')} player={ inlineLayout ? ( diff --git a/src/pages/ScreenRouting/mediaScreens/MediaMovie/MediaMovie.tsx b/src/pages/ScreenRouting/mediaScreens/MediaMovie/MediaMovie.tsx index 00c803808..67ede7391 100644 --- a/src/pages/ScreenRouting/mediaScreens/MediaMovie/MediaMovie.tsx +++ b/src/pages/ScreenRouting/mediaScreens/MediaMovie/MediaMovie.tsx @@ -57,7 +57,7 @@ const MediaMovie: ScreenComponent = ({ data, isLoading }) => { // Handlers const goBack = () => data && navigate(mediaURL({ media: data, playlistId: feedId, play: false })); - const onCardClick = (item: PlaylistItem) => navigate(mediaURL({ media: item, playlistId: features?.recommendationsPlaylist })); + const getUrl = (item: PlaylistItem) => mediaURL({ media: item, playlistId: features?.recommendationsPlaylist }); const handleComplete = useCallback(() => { if (!id || !playlist) return; @@ -140,7 +140,7 @@ const MediaMovie: ScreenComponent = ({ data, isLoading }) => { startWatchingButton={startWatchingButton} playlist={playlist} relatedTitle={playlist?.title} - onItemClick={onCardClick} + getURL={getUrl} activeLabel={t('current_video')} player={ inlineLayout ? ( diff --git a/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx b/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx index 406a728dc..190dab57d 100644 --- a/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx +++ b/src/pages/ScreenRouting/mediaScreens/MediaSeries/MediaSeries.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Navigate } from 'react-router'; +import { Navigate, useLocation } from 'react-router'; import { Helmet } from 'react-helmet'; import { useTranslation } from 'react-i18next'; import shallow from 'zustand/shallow'; @@ -33,6 +33,7 @@ import type { PlaylistItem } from '#types/playlist'; import Loading from '#src/pages/Loading/Loading'; import type { ScreenComponent } from '#types/screens'; import { VideoProgressMinMax } from '#src/config'; +import { addQueryParam } from '#src/utils/location'; const MediaSeries: ScreenComponent = ({ data: seriesMedia }) => { const breakpoint = useBreakpoint(); @@ -106,18 +107,16 @@ const MediaSeries: ScreenComponent = ({ data: seriesMedia }) => { const isLoggedIn = !!user; const hasSubscription = !!subscription; + const location = useLocation(); + const getURL = (toEpisode: PlaylistItem) => { + return addQueryParam(location, 'e', toEpisode.mediaid); + }; + // Handlers const goBack = useCallback(() => { setSearchParams({ ...searchParams, e: episode?.mediaid, r: feedId || '' }); }, [setSearchParams, searchParams, episode, feedId]); - const onCardClick = useCallback( - (toEpisode: PlaylistItem) => { - setSearchParams({ ...searchParams, e: toEpisode.mediaid }); - }, - [setSearchParams, searchParams], - ); - const handleComplete = useCallback(async () => { setSearchParams({ ...searchParams, e: (nextItem || episode)?.mediaid, r: feedId || '', play: nextItem ? '1' : '0' }); }, [setSearchParams, nextItem, episode, feedId, searchParams]); @@ -254,7 +253,7 @@ const MediaSeries: ScreenComponent = ({ data: seriesMedia }) => { hasSubscription={hasSubscription} playlist={playlist} relatedTitle={inlineLayout ? seriesMedia.title : t('episodes')} - onItemClick={onCardClick} + getURL={getURL} setFilter={setSeasonFilter} currentFilter={seasonFilter} defaultFilterLabel={t('all_seasons')} diff --git a/src/pages/ScreenRouting/playlistScreens/PlaylistGrid/PlaylistGrid.tsx b/src/pages/ScreenRouting/playlistScreens/PlaylistGrid/PlaylistGrid.tsx index 79a3fc84b..93b8ce6f1 100644 --- a/src/pages/ScreenRouting/playlistScreens/PlaylistGrid/PlaylistGrid.tsx +++ b/src/pages/ScreenRouting/playlistScreens/PlaylistGrid/PlaylistGrid.tsx @@ -1,11 +1,9 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import { Helmet } from 'react-helmet'; import shallow from 'zustand/shallow'; import styles from './PlaylistGrid.module.scss'; -import { mediaURL } from '#src/utils/formatting'; import { filterPlaylist, getFiltersFromConfig } from '#src/utils/collection'; import CardGrid from '#components/CardGrid/CardGrid'; import Filter from '#components/Filter/Filter'; @@ -13,9 +11,9 @@ import { useAccountStore } from '#src/stores/AccountStore'; import { useConfigStore } from '#src/stores/ConfigStore'; import type { Playlist, PlaylistItem } from '#types/playlist'; import type { ScreenComponent } from '#types/screens'; +import { mediaURL } from '#src/utils/formatting'; const PlaylistGrid: ScreenComponent = ({ data, isLoading }) => { - const navigate = useNavigate(); const { config, accessModel } = useConfigStore(({ config, accessModel }) => ({ config, accessModel }), shallow); const [filter, setFilter] = useState(''); @@ -32,10 +30,10 @@ const PlaylistGrid: ScreenComponent = ({ data, isLoading }) => { setFilter(''); }, [data.feedid]); - const onCardClick = (playlistItem: PlaylistItem) => navigate(mediaURL({ media: playlistItem, playlistId: data.feedid })); - const pageTitle = `${data.title} - ${config.siteName}`; + const getUrl = (playlistItem: PlaylistItem) => mediaURL({ media: playlistItem, playlistId: playlistItem.feedid }); + return (
    @@ -49,8 +47,8 @@ const PlaylistGrid: ScreenComponent = ({ data, isLoading }) => {
    = ({ data: { feedid, playlist } }) => { const { t } = useTranslation('epg'); // Config - const { config, accessModel } = useConfigStore(({ config, accessModel }) => ({ config, accessModel }), shallow); + const { config } = useConfigStore(({ config }) => ({ config }), shallow); const { siteName } = config; // Routing @@ -124,6 +124,7 @@ const PlaylistLiveChannels: ScreenComponent = ({ data: { feedid, playl } // SEO (for channels) + const getUrl = (id: string) => liveChannelsURL(feedid, id); const canonicalUrl = `${window.location.origin}${liveChannelsURL(feedid, channel.id)}`; const pageTitle = `${channel.title} - ${siteName}`; @@ -185,38 +186,27 @@ const PlaylistLiveChannels: ScreenComponent = ({ data: { feedid, playl ))} {channelMediaItem ? : null} - - ) - } + primaryMetadata={primaryMetadata} > + {channelMediaItem && ( + + )}
    = ({ data: { feedid, playl channel={channel} program={program} config={config} + getUrl={getUrl} />
    -
    + ); }; diff --git a/src/pages/Search/Search.tsx b/src/pages/Search/Search.tsx index 534c0d65c..6cd3130cf 100644 --- a/src/pages/Search/Search.tsx +++ b/src/pages/Search/Search.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router'; +import { useParams } from 'react-router'; import { Helmet } from 'react-helmet'; import { useTranslation } from 'react-i18next'; import shallow from 'zustand/shallow'; @@ -7,15 +7,15 @@ import shallow from 'zustand/shallow'; import styles from './Search.module.scss'; import { useUIStore } from '#src/stores/UIStore'; -import { mediaURL } from '#src/utils/formatting'; import { useAccountStore } from '#src/stores/AccountStore'; import { useConfigStore } from '#src/stores/ConfigStore'; import useFirstRender from '#src/hooks/useFirstRender'; -import type { PlaylistItem } from '#types/playlist'; import useSearchQueryUpdater from '#src/hooks/useSearchQueryUpdater'; import CardGrid from '#components/CardGrid/CardGrid'; import ErrorPage from '#components/ErrorPage/ErrorPage'; import usePlaylist from '#src/hooks/usePlaylist'; +import type { PlaylistItem } from '#types/playlist'; +import { mediaURL } from '#src/utils/formatting'; const Search = () => { const { t } = useTranslation('search'); @@ -25,7 +25,6 @@ const Search = () => { const firstRender = useFirstRender(); const searchQuery = useUIStore((state) => state.searchQuery); const { updateSearchQuery } = useSearchQueryUpdater(); - const navigate = useNavigate(); const params = useParams(); const query = params['*']; const { isFetching, error, data: playlist } = usePlaylist(features?.searchPlaylist || '', { search: query || '' }, true, !!query); @@ -33,6 +32,8 @@ const Search = () => { // User const { user, subscription } = useAccountStore(({ user, subscription }) => ({ user, subscription }), shallow); + const getURL = (playlistItem: PlaylistItem) => mediaURL({ media: playlistItem, playlistId: features?.searchPlaylist }); + // Update the search bar query to match the route param on mount useEffect(() => { if (!firstRender) { @@ -44,14 +45,14 @@ const Search = () => { } }, [firstRender, query, searchQuery, updateSearchQuery]); - const onCardClick = (playlistItem: PlaylistItem) => { - useUIStore.setState({ - searchQuery: '', - searchActive: false, - }); - - navigate(mediaURL({ media: playlistItem, playlistId: features?.searchPlaylist })); - }; + useEffect(() => { + return () => { + useUIStore.setState({ + searchQuery: '', + searchActive: false, + }); + }; + }, []); if ((error || !playlist) && !isFetching) { return ( @@ -90,14 +91,7 @@ const Search = () => {

    {t('heading')}

    - +
    ); diff --git a/src/pages/User/User.tsx b/src/pages/User/User.tsx index fb0d9caab..8f40e5153 100644 --- a/src/pages/User/User.tsx +++ b/src/pages/User/User.tsx @@ -22,8 +22,6 @@ import { useAccountStore } from '#src/stores/AccountStore'; import { getSubscriptionSwitches } from '#src/stores/CheckoutController'; import { PersonalShelf, useConfigStore } from '#src/stores/ConfigStore'; import { clear as clearFavorites } from '#src/stores/FavoritesController'; -import { mediaURL } from '#src/utils/formatting'; -import type { PlaylistItem } from '#types/playlist'; const User = (): JSX.Element => { const { accessModel, favoritesList } = useConfigStore( @@ -41,7 +39,6 @@ const User = (): JSX.Element => { const isLargeScreen = breakpoint > Breakpoint.md; const { user: customer, subscription, loading, canUpdateEmail } = useAccountStore(); - const onCardClick = (playlistItem: PlaylistItem) => navigate(mediaURL({ media: playlistItem })); const onLogout = useCallback(async () => { // Empty customer on a user page leads to navigate (code bellow), so we don't repeat it here await logout(); @@ -110,7 +107,6 @@ const User = (): JSX.Element => { playlist={playlist} error={error} isLoading={isLoading} - onCardClick={onCardClick} onClearFavoritesClick={() => setClearFavoritesOpen(true)} accessModel={accessModel} hasSubscription={!!subscription} diff --git a/src/pages/User/__snapshots__/User.test.tsx.snap b/src/pages/User/__snapshots__/User.test.tsx.snap index ecfd244d0..8bbf909f6 100644 --- a/src/pages/User/__snapshots__/User.test.tsx.snap +++ b/src/pages/User/__snapshots__/User.test.tsx.snap @@ -388,10 +388,10 @@ exports[`User Component tests > Favorites Page 1`] = `
    -
    Favorites Page 1`] = ` Fav 1
    -
    +
    Favorites Page 1`] = `
    -
    Favorites Page 1`] = ` Big Buck Bunny
    -
    +
    Favorites Page 1`] = `
    -
    Favorites Page 1`] = ` My last favorite
    -
    +
    diff --git a/test-e2e/tests/inline_layout_test.ts b/test-e2e/tests/inline_layout_test.ts index 1289f49ff..91d79696d 100644 --- a/test-e2e/tests/inline_layout_test.ts +++ b/test-e2e/tests/inline_layout_test.ts @@ -24,15 +24,15 @@ Scenario('I can see the movie inline player layout', async ({ I }) => { I.see('Favorite'); I.see('Share'); I.seeTextEquals('Related Films', 'h3'); - I.see('Caminandes 1: Llama Drama', locate({ css: 'div[aria-label="Play Caminandes 1: Llama Drama"]' }).inside(videoListLocator)); - I.see('Caminandes 2: Gran Dillama', locate({ css: 'div[aria-label="Play Caminandes 2: Gran Dillama"]' }).inside(videoListLocator)); + I.see('Caminandes 1: Llama Drama', locate({ css: 'a[aria-label="Caminandes 1: Llama Drama"]' }).inside(videoListLocator)); + I.see('Caminandes 2: Gran Dillama', locate({ css: 'a[aria-label="Caminandes 2: Gran Dillama"]' }).inside(videoListLocator)); }); Scenario('I switch to another video in the movie screen', async ({ I }) => { await I.openVideoCard(constants.bigBuckBunnyTitle, ShelfId.allFilms); I.see(constants.bigBuckBunnyTitle); - I.click('Caminandes 1: Llama Drama', locate({ css: 'div[aria-label="Play Caminandes 1: Llama Drama"]' }).inside(videoListLocator)); + I.click('Caminandes 1: Llama Drama', locate({ css: 'a[aria-label="Caminandes 1: Llama Drama"]' }).inside(videoListLocator)); I.see('Caminandes 1: Llama Drama'); }); @@ -51,8 +51,8 @@ Scenario('I can see the series inline player layout', async ({ I }) => { I.see('Share'); I.seeTextEquals('Minecraft Animation Workshop', 'h3'); I.see('Season 1', locate({ css: 'select' }).inside(videoListLocator)); - I.see('S1:E2', locate({ css: 'div[aria-label="Play Basics Of Blender"]' }).inside(videoListLocator)); - I.see('S1:E3', locate({ css: 'div[aria-label="Play Using Mineways"]' }).inside(videoListLocator)); + I.see('S1:E2', locate({ css: 'a[aria-label="Basics Of Blender"]' }).inside(videoListLocator)); + I.see('S1:E3', locate({ css: 'a[aria-label="Using Mineways"]' }).inside(videoListLocator)); }); Scenario('I can start the inline player', async ({ I }) => { @@ -62,21 +62,21 @@ Scenario('I can start the inline player', async ({ I }) => { Scenario('I switch to the episode in the video list', async ({ I }) => { await I.openVideoCard(constants.minecraftAnimationWorkshopTitle, ShelfId.allCourses, true); - I.waitForElement('div[aria-label="Play Welcome"]', 3); - I.click('S1:E1', locate({ css: 'div[aria-label="Play Welcome"]' }).inside(videoListLocator)); + I.waitForElement('a[aria-label="Welcome"]', 3); + I.click('S1:E1', locate({ css: 'a[aria-label="Welcome"]' }).inside(videoListLocator)); I.see('S1:E1 - Welcome'); }); Scenario('I switch to another season in the video list', async ({ I }) => { await I.openVideoCard(constants.minecraftAnimationWorkshopTitle, ShelfId.allCourses, true); - I.waitForElement('div[aria-label="Play Welcome"]', 3); - I.click('S1:E1', locate({ css: 'div[aria-label="Play Welcome"]' }).inside(videoListLocator)); + I.waitForElement('a[aria-label="Welcome"]', 3); + I.click('S1:E1', locate({ css: 'a[aria-label="Welcome"]' }).inside(videoListLocator)); I.see('Season 1/4 - Episode 1/6'); I.selectOption({ css: 'select[name="season"]' }, 'Season 2'); - I.waitForElement('div[aria-label="Play Choosing a skin (Cycles Render)"]', 3); - I.click(locate({ css: 'div[aria-label="Play Choosing a skin (Cycles Render)"]' }).inside(videoListLocator)); + I.waitForElement('a[aria-label="Choosing a skin (Cycles Render)"]', 3); + I.click(locate({ css: 'a[aria-label="Choosing a skin (Cycles Render)"]' }).inside(videoListLocator)); I.dontSee('Season 1/4 - Episode 1/6'); I.see('Season 2/4 - Episode 1/4'); diff --git a/test-e2e/tests/live_channel_test.ts b/test-e2e/tests/live_channel_test.ts index 9c31166cd..b9a878eec 100644 --- a/test-e2e/tests/live_channel_test.ts +++ b/test-e2e/tests/live_channel_test.ts @@ -62,7 +62,7 @@ Scenario('I can navigate to live channels from the live channels shelf', async ( } I.see('Live Channels'); - I.click('Play Channel 1'); + I.click('Channel 1'); waitForEpgAnimation(I); I.see('LIVE'); diff --git a/test-e2e/tests/playlist_test.ts b/test-e2e/tests/playlist_test.ts index ddf6146bb..a45d88a46 100644 --- a/test-e2e/tests/playlist_test.ts +++ b/test-e2e/tests/playlist_test.ts @@ -60,7 +60,7 @@ Scenario('I can filter and click on a card and navigate to the video screen', as }); function canNavigateToBigBuckBunny(I: CodeceptJS.I) { - I.click({ css: 'div[aria-label="Play Big Buck Bunny"]' }); + I.click({ css: 'a[aria-label="Big Buck Bunny"]' }); I.see(constants.bigBuckBunnyDescription); I.see(constants.startWatchingButton); diff --git a/test-e2e/tests/seo_test.ts b/test-e2e/tests/seo_test.ts index 9bf1ba64e..ee1425ed5 100644 --- a/test-e2e/tests/seo_test.ts +++ b/test-e2e/tests/seo_test.ts @@ -72,7 +72,7 @@ Scenario('It renders the correct meta tags for the episode screen', async ({ I } await I.openVideoCard(constants.primitiveAnimalsTitle); I.see('Primitive Animals'); - I.click('div[aria-label="Play Blocking"]'); + I.click('a[aria-label="Blocking"]'); await checkMetaTags(I, 'Blocking', constants.primitiveAnimalsDescription, 'episode'); }); @@ -101,7 +101,7 @@ Scenario('It renders the correct structured metadata for the episode screen', as await I.openVideoCard(constants.primitiveAnimalsTitle); I.see('Primitive Animals'); - I.click('div[aria-label="Play Blocking"]'); + I.click('a[aria-label="Blocking"]'); const rawURL = await I.grabCurrentUrl(); const url = removeQueryParams(rawURL, ['r', 'app-config']); diff --git a/test-e2e/tests/series_test.ts b/test-e2e/tests/series_test.ts index b09c9f1b0..52c805ba5 100644 --- a/test-e2e/tests/series_test.ts +++ b/test-e2e/tests/series_test.ts @@ -24,19 +24,19 @@ Scenario('I can see series without seasons', async ({ I }) => { I.see('Share'); I.seeTextEquals('Episodes', 'h3'); - I.click('div[aria-label="Play Blocking"]'); + I.click('a[aria-label="Blocking"]'); I.scrollTo('text="Episodes"'); I.see('E1 - Blocking'); - I.see('Current episode', { css: 'div[aria-label="Play Blocking"]' }); + I.see('Current episode', { css: 'a[aria-label="Blocking"]' }); I.see('Concept Art'); - I.see('E2', { css: 'div[aria-label="Play Concept Art"]' }); - I.see('E3', { css: 'div[aria-label="Play Modeling Part 1"]' }); - I.see('E4', { css: 'div[aria-label="Play Modeling Part 2"]' }); + I.see('E2', { css: 'a[aria-label="Concept Art"]' }); + I.see('E3', { css: 'a[aria-label="Modeling Part 1"]' }); + I.see('E4', { css: 'a[aria-label="Modeling Part 2"]' }); - I.scrollTo('div[aria-label="Play Modeling Part 2"]'); + I.scrollTo('a[aria-label="Modeling Part 2"]'); - I.see('E5', { css: 'div[aria-label="Play Texturing and Lighting"]' }); + I.see('E5', { css: 'a[aria-label="Texturing and Lighting"]' }); }); Scenario('I can see series with seasons', async ({ I }) => { @@ -52,27 +52,27 @@ Scenario('I can see series with seasons', async ({ I }) => { I.see('Share'); I.seeTextEquals('Episodes', 'h3'); - I.click('div[aria-label="Play Welcome"]'); + I.click('a[aria-label="Welcome"]'); I.scrollTo('text="Episodes"'); I.see('S1:E1 - Welcome'); - I.see('Current episode', { css: 'div[aria-label="Play Welcome"]' }); - I.see('S1:E2', { css: 'div[aria-label="Play Basics Of Blender"]' }); - I.see('S1:E3', { css: 'div[aria-label="Play Using Mineways"]' }); - I.see('S1:E4', { css: 'div[aria-label="Play Texturing your Minecraft World (Blender Render)"]' }); + I.see('Current episode', { css: 'a[aria-label="Welcome"]' }); + I.see('S1:E2', { css: 'a[aria-label="Basics Of Blender"]' }); + I.see('S1:E3', { css: 'a[aria-label="Using Mineways"]' }); + I.see('S1:E4', { css: 'a[aria-label="Texturing your Minecraft World (Blender Render)"]' }); - I.scrollTo('div[aria-label="Play Texturing your Minecraft World (Blender Render)"]'); + I.scrollTo('a[aria-label="Texturing your Minecraft World (Blender Render)"]'); - I.see('S1:E5', { css: 'div[aria-label="Play Texturing your Minecraft World (Cycles)"]' }); - I.see('S1:E6', { css: 'div[aria-label="Play Rig Overview (Boxscape Studios)"]' }); + I.see('S1:E5', { css: 'a[aria-label="Texturing your Minecraft World (Cycles)"]' }); + I.see('S1:E6', { css: 'a[aria-label="Rig Overview (Boxscape Studios)"]' }); }); -Scenario('I can play other episodes from the series', async ({ I }) => { +Scenario('I can other episodes from the series', async ({ I }) => { await I.openVideoCard(constants.fantasyVehicleTitle, ShelfId.allCourses); I.see('Fantasy Vehicle Creation'); I.scrollTo('text="Modeling Part 1"'); - I.click('div[aria-label="Play Modeling Part 1"]'); + I.click('a[aria-label="Modeling Part 1"]'); // Scroll to the top when a new episode is selected (takes a short time) I.wait(2); @@ -83,7 +83,7 @@ Scenario('I can play other episodes from the series', async ({ I }) => { ); I.scrollTo('text="Texturing and Lighting"'); - I.click('div[aria-label="Play Texturing and Lighting"]'); + I.click('a[aria-label="Texturing and Lighting"]'); // Check that scrolled to the top I.wait(2); diff --git a/test-e2e/tests/video_detail_test.ts b/test-e2e/tests/video_detail_test.ts index 7adea6485..e38737472 100644 --- a/test-e2e/tests/video_detail_test.ts +++ b/test-e2e/tests/video_detail_test.ts @@ -31,7 +31,7 @@ function runTestSuite(config: typeof testConfigs.svod, providerName: string) { I.see('Favorite'); I.see('Share'); I.see('Elephants Dream'); - I.see('11 min', { css: 'div[aria-label="Play Elephants Dream"]' }); + I.see('11 min', { css: 'a[aria-label="Elephants Dream"]' }); }); Scenario(`I can expand the description (@mobile-only) - ${providerName}`, async ({ I }) => { diff --git a/test-e2e/tests/watch_history/local_test.ts b/test-e2e/tests/watch_history/local_test.ts index 2450a7035..f9fc3a01d 100644 --- a/test-e2e/tests/watch_history/local_test.ts +++ b/test-e2e/tests/watch_history/local_test.ts @@ -44,7 +44,7 @@ Scenario('I can see my watch history on the Home screen', async ({ I }) => { I.see('10 min'); }); - const selector = `${makeShelfXpath(ShelfId.continueWatching)}//div[@aria-label="Play ${videoTitle}"]`; + const selector = `${makeShelfXpath(ShelfId.continueWatching)}//a[@aria-label="${videoTitle}"]`; await checkProgress(I, selector, (200 / videoLength) * 100); I.click(selector); diff --git a/test-e2e/tests/watch_history/logged_in_test.ts b/test-e2e/tests/watch_history/logged_in_test.ts index 7505cdc58..e9c8f4434 100644 --- a/test-e2e/tests/watch_history/logged_in_test.ts +++ b/test-e2e/tests/watch_history/logged_in_test.ts @@ -71,7 +71,7 @@ function runTestSuite(config: typeof testConfigs.svod, configNoWatchlist: typeof I.see('10 min'); }); - const selector = `${makeShelfXpath(ShelfId.continueWatching)}//div[@aria-label="Play ${videoTitle}"]`; + const selector = `${makeShelfXpath(ShelfId.continueWatching)}//a[@aria-label="${videoTitle}"]`; await checkProgress(I, selector, (80 / videoLength) * 100); I.click(selector); diff --git a/test-e2e/utils/steps_file.ts b/test-e2e/utils/steps_file.ts index 392275cee..844eda47b 100644 --- a/test-e2e/utils/steps_file.ts +++ b/test-e2e/utils/steps_file.ts @@ -378,7 +378,7 @@ const stepsObj = { scrollToTheRight: boolean = true, preOpenCallback?: (locator: string) => void, ) { - const locator = `//div[@aria-label="Play ${name}"]`; + const locator = `//a[@aria-label="${name}"]`; const shelfXpath = shelf ? makeShelfXpath(shelf) : undefined; if (shelfXpath) { @@ -419,7 +419,7 @@ const stepsObj = { if (isMobile) { // This swipes on the current item in the carousel where the card we're trying to click is await this.swipe({ - xpath: shelfXpath ? `${shelfXpath}//*[@tabindex=0]` : `${locator}/ancestor::ul/li/div[@tabindex=0]`, + xpath: shelfXpath ? `${shelfXpath}//*[@tabindex=0]` : `${locator}/ancestor::ul/li/a[@tabindex=0]`, direction: scrollToTheRight ? 'left' : 'right', }); } else {