@playpilot/tpi 7.1.0 → 8.0.0-beta.explore-home.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +1 -0
- package/dist/editorial.mount.js +9 -9
- package/dist/link-injections.js +1 -1
- package/dist/mount.js +7 -7
- package/events.md +1 -0
- package/package.json +1 -1
- package/src/lib/api/youtubeAvailability.ts +26 -0
- package/src/lib/enums/TrackingEvent.ts +1 -0
- package/src/lib/explore.ts +2 -2
- package/src/lib/trailer.ts +31 -0
- package/src/lib/types/explore.d.ts +5 -0
- package/src/routes/components/Button.svelte +2 -1
- package/src/routes/components/Explore/ExploreLayout.svelte +102 -0
- package/src/routes/components/Explore/ExploreModal.svelte +2 -2
- package/src/routes/components/Explore/ExploreRouter.svelte +35 -0
- package/src/routes/components/Explore/Routes/ExploreHome.svelte +15 -0
- package/src/routes/components/Explore/{Explore.svelte → Routes/ExploreResults.svelte} +46 -115
- package/src/routes/components/Rails/TitlesRail.svelte +61 -9
- package/src/routes/components/YouTubeEmbed.svelte +35 -0
- package/src/routes/components/YouTubeEmbedOverlay.svelte +14 -28
- package/src/routes/elements/+page.svelte +12 -2
- package/src/routes/explore/+page.svelte +1 -1
- package/src/tests/lib/api/youtubeAvailability.test.js +70 -0
- package/src/tests/lib/trailer.test.js +57 -1
- package/src/tests/routes/components/Explore/ExploreLayout.test.js +52 -0
- package/src/tests/routes/components/Explore/ExploreRouter.test.js +20 -0
- package/src/tests/routes/components/Explore/Routes/ExploreHome.test.js +18 -0
- package/src/tests/routes/components/Explore/{Explore.test.js → Routes/ExploreResults.test.js} +29 -22
- package/src/tests/routes/components/Rails/TitlesRail.test.js +51 -0
- package/src/tests/routes/components/YouTubeEmbed.test.js +31 -0
- package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +1 -8
- package/vite.config.js +8 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getVideoId } from '$lib/trailer'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
embeddable_url: string
|
|
6
|
+
controls?: ('play' | 'mute' | 'fullscreen' | 'progress' | 'current-time')[]
|
|
7
|
+
muted?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { embeddable_url = '', controls = [], muted = false }: Props = $props()
|
|
11
|
+
|
|
12
|
+
const videoId = $derived(getVideoId(embeddable_url))
|
|
13
|
+
const color = window?.getComputedStyle(document.body).getPropertyValue('--playpilot-primary')?.replace('#', '') || 'fa548a'
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
{#if videoId}
|
|
17
|
+
<iframe
|
|
18
|
+
width="600"
|
|
19
|
+
height="338"
|
|
20
|
+
src="https://video.playpilot.net/?video_id={videoId}&color={color}&muted={muted}&controls={controls.join(',')}&autoplay=true&playsinline=true"
|
|
21
|
+
title="YouTube video player"
|
|
22
|
+
frameborder="0"
|
|
23
|
+
referrerpolicy="strict-origin-when-cross-origin"
|
|
24
|
+
allowfullscreen>
|
|
25
|
+
</iframe>
|
|
26
|
+
{:else}
|
|
27
|
+
Something went wrong
|
|
28
|
+
{/if}
|
|
29
|
+
|
|
30
|
+
<style lang="scss">
|
|
31
|
+
iframe {
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 100%;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
@@ -1,32 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { fade } from 'svelte/transition'
|
|
3
3
|
import IconClose from './Icons/IconClose.svelte'
|
|
4
|
+
import YouTubeEmbed from './YouTubeEmbed.svelte'
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
|
-
embeddable_url
|
|
7
|
+
embeddable_url: string,
|
|
7
8
|
onclose: () => void
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
const { embeddable_url = '', onclose }: Props = $props()
|
|
11
|
-
|
|
12
|
-
const videoId = $derived(getVideoId(embeddable_url))
|
|
13
|
-
|
|
14
|
-
// Gets the YouTube ID from a url, can be a large number of different formats
|
|
15
|
-
// https://stackoverflow.com/a/54200105/1665157
|
|
16
|
-
function getVideoId(url: string): string | null {
|
|
17
|
-
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/
|
|
18
|
-
const match = url.match(regExp)
|
|
19
|
-
|
|
20
|
-
return match?.[7] || null
|
|
21
|
-
}
|
|
22
12
|
</script>
|
|
23
13
|
|
|
24
14
|
<div class="overlay" transition:fade={{ duration: 100 }}>
|
|
25
|
-
{
|
|
26
|
-
<iframe width="600" height="338" src="https://www.youtube.com/embed/{videoId}?autoplay=true" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|
27
|
-
{:else}
|
|
28
|
-
Something went wrong
|
|
29
|
-
{/if}
|
|
15
|
+
<YouTubeEmbed {embeddable_url} controls={['current-time', 'fullscreen', 'mute', 'play', 'progress']} />
|
|
30
16
|
|
|
31
17
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
32
18
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -38,17 +24,6 @@
|
|
|
38
24
|
</div>
|
|
39
25
|
|
|
40
26
|
<style lang="scss">
|
|
41
|
-
iframe {
|
|
42
|
-
z-index: 1;
|
|
43
|
-
position: relative;
|
|
44
|
-
display: block;
|
|
45
|
-
width: 95vmin;
|
|
46
|
-
height: auto;
|
|
47
|
-
aspect-ratio: 16/9;
|
|
48
|
-
box-shadow: 0 0 margin(4) rgba(255, 255, 255, 0.15);
|
|
49
|
-
background: black;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
27
|
.overlay {
|
|
53
28
|
z-index: 2147483647; // As high as she goes
|
|
54
29
|
box-sizing: border-box;
|
|
@@ -61,6 +36,17 @@
|
|
|
61
36
|
bottom: 0;
|
|
62
37
|
left: 0;
|
|
63
38
|
background: theme(detail-backdrop, rgba(0, 0, 0, 0.95));
|
|
39
|
+
|
|
40
|
+
:global(iframe) {
|
|
41
|
+
z-index: 1;
|
|
42
|
+
position: relative;
|
|
43
|
+
display: block;
|
|
44
|
+
width: 95vmin;
|
|
45
|
+
height: auto;
|
|
46
|
+
aspect-ratio: 16/9;
|
|
47
|
+
box-shadow: 0 0 margin(4) rgba(255, 255, 255, 0.15);
|
|
48
|
+
background: black;
|
|
49
|
+
}
|
|
64
50
|
}
|
|
65
51
|
|
|
66
52
|
.backdrop {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { title, linkInjections, campaign, participants } from '$lib/fakeData'
|
|
4
4
|
import { openModal } from '$lib/modal'
|
|
5
5
|
import { insertExplore, insertExploreIntoNavigation } from '$lib/explore'
|
|
6
|
+
import { fetchSimilarTitles } from '$lib/api/titles'
|
|
6
7
|
import Disclaimer from '../components/Ads/Disclaimer.svelte'
|
|
7
8
|
import Display from '../components/Ads/Display.svelte'
|
|
8
9
|
import TopScroll from '../components/Ads/TopScroll.svelte'
|
|
@@ -17,7 +18,8 @@
|
|
|
17
18
|
import Title from '../components/Title.svelte'
|
|
18
19
|
import TitlePopover from '../components/TitlePopover.svelte'
|
|
19
20
|
import Tooltip from '../components/Tooltip.svelte'
|
|
20
|
-
import
|
|
21
|
+
import ExploreRouter from '../components/Explore/ExploreRouter.svelte'
|
|
22
|
+
import TitlesRail from '../components/Rails/TitlesRail.svelte'
|
|
21
23
|
|
|
22
24
|
if (browser) {
|
|
23
25
|
// @ts-ignore
|
|
@@ -121,6 +123,14 @@
|
|
|
121
123
|
</div>
|
|
122
124
|
</div>
|
|
123
125
|
|
|
126
|
+
<h2>Rails</h2>
|
|
127
|
+
|
|
128
|
+
<button onclick={() => renderKey = Math.random()}>Rerender</button>
|
|
129
|
+
|
|
130
|
+
{#key renderKey}
|
|
131
|
+
<TitlesRail titles={fetchSimilarTitles(title)} expandable />
|
|
132
|
+
{/key}
|
|
133
|
+
|
|
124
134
|
<h2>Ads</h2>
|
|
125
135
|
|
|
126
136
|
<div class="group">
|
|
@@ -174,7 +184,7 @@
|
|
|
174
184
|
<button onclick={() => renderKey = Math.random()}>Rerender</button>
|
|
175
185
|
|
|
176
186
|
{#key renderKey}
|
|
177
|
-
<
|
|
187
|
+
<ExploreRouter />
|
|
178
188
|
{/key}
|
|
179
189
|
</div>
|
|
180
190
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { isYouTubeVideoAvailableInRegion } from '$lib/api/youtubeAvailability'
|
|
3
|
+
import { fakeFetch } from '../../helpers'
|
|
4
|
+
import { track } from '$lib/tracking'
|
|
5
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
6
|
+
|
|
7
|
+
vi.mock('$lib/tracking', () => ({
|
|
8
|
+
track: vi.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
vi.mock('$env/static/public', () => ({
|
|
12
|
+
PUBLIC_YOUTUBE_AVAILABILITY_URL: 'https://some-path.com',
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
describe('youtubeAvailability', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.resetAllMocks()
|
|
18
|
+
fakeFetch()
|
|
19
|
+
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
window.PlayPilotLinkInjections = { region: 'nl' }
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('isYouTubeVideoAvailableInRegion', () => {
|
|
25
|
+
it('Should return true if no region is set', async () => {
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
window.PlayPilotLinkInjections = {}
|
|
28
|
+
|
|
29
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
30
|
+
expect(global.fetch).not.toHaveBeenCalled()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('Should return false if region is in blocked list', async () => {
|
|
34
|
+
fakeFetch({ response: { blocked: { NL: true } } })
|
|
35
|
+
|
|
36
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should return true if region is not in blocked list', async () => {
|
|
40
|
+
fakeFetch({ response: { blocked: { US: true } } })
|
|
41
|
+
|
|
42
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('Should return false if allowed list exists but region is not in it', async () => {
|
|
46
|
+
fakeFetch({ response: { allowed: ['US', 'GB'] } })
|
|
47
|
+
|
|
48
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('Should return true if allowed list exists and region is in it', async () => {
|
|
52
|
+
fakeFetch({ response: { allowed: ['US', 'NL'] } })
|
|
53
|
+
|
|
54
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('Should return true if no restrictions are returned', async () => {
|
|
58
|
+
fakeFetch({ response: {} })
|
|
59
|
+
|
|
60
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(true)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('Should return false and track event if fetch throws', async () => {
|
|
64
|
+
fakeFetch({ ok: false, status: 500 })
|
|
65
|
+
|
|
66
|
+
expect(await isYouTubeVideoAvailableInRegion('video-id')).toBe(false)
|
|
67
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.YouTubeAvailabilityRequestFailed, null, { message: expect.any(String) })
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
})
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
2
|
import { title } from '$lib/fakeData'
|
|
3
3
|
import { mount, unmount } from 'svelte'
|
|
4
|
-
import { closeTrailerOverlay, openTrailerOverlay } from '$lib/trailer'
|
|
4
|
+
import { closeTrailerOverlay, getFirstTitleWithAvailableTrailer, getVideoId, openTrailerOverlay } from '$lib/trailer'
|
|
5
|
+
import { isYouTubeVideoAvailableInRegion } from '$lib/api/youtubeAvailability'
|
|
5
6
|
|
|
6
7
|
vi.mock('svelte', () => ({
|
|
7
8
|
mount: vi.fn(),
|
|
8
9
|
unmount: vi.fn(),
|
|
9
10
|
}))
|
|
10
11
|
|
|
12
|
+
vi.mock('$lib/api/youtubeAvailability', () => ({
|
|
13
|
+
isYouTubeVideoAvailableInRegion: vi.fn(),
|
|
14
|
+
}))
|
|
15
|
+
|
|
11
16
|
const titleWithTrailer = { ...title, embeddable_url: 'abc' }
|
|
12
17
|
|
|
13
18
|
describe('modal.js', () => {
|
|
@@ -50,4 +55,55 @@ describe('modal.js', () => {
|
|
|
50
55
|
expect(unmount).not.toHaveBeenCalled()
|
|
51
56
|
})
|
|
52
57
|
})
|
|
58
|
+
|
|
59
|
+
describe('getVideoId', () => {
|
|
60
|
+
it('Should return video id for various youtube formats', () => {
|
|
61
|
+
expect(getVideoId('youtube.com/watch?v=a')).toBe('a')
|
|
62
|
+
expect(getVideoId('youtu.be/b')).toBe('b')
|
|
63
|
+
expect(getVideoId('youtube.com/embed/c')).toBe('c')
|
|
64
|
+
expect(getVideoId('not')).toBe(null)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('getFirstTitleWithAvailableTrailer', () => {
|
|
69
|
+
it('Should return null if no titles are given', async () => {
|
|
70
|
+
expect(await getFirstTitleWithAvailableTrailer([])).toBe(null)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('Should return first title if it has a valid video id and video is available', async () => {
|
|
74
|
+
vi.mocked(isYouTubeVideoAvailableInRegion).mockResolvedValueOnce(true)
|
|
75
|
+
|
|
76
|
+
const titles = [{ ...title, embeddable_url: 'youtu.be/a' }]
|
|
77
|
+
|
|
78
|
+
expect(await getFirstTitleWithAvailableTrailer(titles)).toBe(titles[0])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('Should not return first title if it has a valid video id and but video is not available', async () => {
|
|
82
|
+
vi.mocked(isYouTubeVideoAvailableInRegion).mockResolvedValueOnce(false)
|
|
83
|
+
|
|
84
|
+
expect(await getFirstTitleWithAvailableTrailer([{ ...title, embeddable_url: 'youtu.be/a' }])).toBe(null)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('Should not return first title if it has no valid video id', async () => {
|
|
88
|
+
vi.mocked(isYouTubeVideoAvailableInRegion).mockResolvedValueOnce(false)
|
|
89
|
+
|
|
90
|
+
expect(await getFirstTitleWithAvailableTrailer([title])).toBe(null)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('Should return second title if first is not valid', async () => {
|
|
94
|
+
vi.mocked(isYouTubeVideoAvailableInRegion).mockResolvedValueOnce(false).mockResolvedValueOnce(true)
|
|
95
|
+
|
|
96
|
+
const titles = [{ ...title, embeddable_url: 'youtu.be/a' }, { ...title, embeddable_url: 'youtu.be/b' }]
|
|
97
|
+
|
|
98
|
+
expect(await getFirstTitleWithAvailableTrailer(titles)).toBe(titles[1])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('Should return second title if first rejects', async () => {
|
|
102
|
+
vi.mocked(isYouTubeVideoAvailableInRegion).mockRejectedValueOnce(null).mockResolvedValueOnce(true)
|
|
103
|
+
|
|
104
|
+
const titles = [{ ...title, embeddable_url: 'youtu.be/a' }, { ...title, embeddable_url: 'youtu.be/b' }]
|
|
105
|
+
|
|
106
|
+
expect(await getFirstTitleWithAvailableTrailer(titles)).toBe(titles[1])
|
|
107
|
+
})
|
|
108
|
+
})
|
|
53
109
|
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { fireEvent, render, waitFor } from '@testing-library/svelte'
|
|
2
|
+
import { expect, describe, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import ExploreLayout from '../../../../routes/components/Explore/ExploreLayout.svelte'
|
|
5
|
+
import { createRawSnippet } from 'svelte'
|
|
6
|
+
import { exploreParentSelector } from '$lib/explore'
|
|
7
|
+
|
|
8
|
+
const children = createRawSnippet(() => ({ render: () => '<div></div>' }))
|
|
9
|
+
|
|
10
|
+
const routes = [
|
|
11
|
+
{
|
|
12
|
+
key: 'route-1',
|
|
13
|
+
label: 'Route 1',
|
|
14
|
+
component: {},
|
|
15
|
+
}, {
|
|
16
|
+
key: 'route-2',
|
|
17
|
+
label: 'Route 2',
|
|
18
|
+
component: {},
|
|
19
|
+
},
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
describe('ExploreLayout.svelte', () => {
|
|
23
|
+
it('Should render each given route', () => {
|
|
24
|
+
const { getByText } = render(ExploreLayout, { routes, currentRoute: routes[0], children })
|
|
25
|
+
|
|
26
|
+
expect(getByText('Route 1')).toBeTruthy()
|
|
27
|
+
expect(getByText('Route 2')).toBeTruthy()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('Should call navigate when route is clicked', async () => {
|
|
31
|
+
const navigate = vi.fn()
|
|
32
|
+
const { getByText } = render(ExploreLayout, { routes, currentRoute: routes[0], navigate, children })
|
|
33
|
+
|
|
34
|
+
await fireEvent.click(getByText('Route 2'))
|
|
35
|
+
|
|
36
|
+
expect(navigate).toHaveBeenCalledWith(routes[1])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should should set its height if parent has height', async () => {
|
|
40
|
+
document.body.innerHTML = '<div data-playpilot-explore style="height: 500px"></div>'
|
|
41
|
+
|
|
42
|
+
const { container } = render(
|
|
43
|
+
ExploreLayout,
|
|
44
|
+
{ routes, currentRoute: routes[0], children },
|
|
45
|
+
{ baseElement: /** @type {HTMLElement} */ (document.querySelector(exploreParentSelector)),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(container.querySelector('.explore')?.getAttribute('style')).toBe('height: 500px;')
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/svelte'
|
|
2
|
+
import { expect, describe, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import ExploreRouter from '../../../../routes/components/Explore/ExploreRouter.svelte'
|
|
5
|
+
|
|
6
|
+
describe('ExploreRouter.svelte', () => {
|
|
7
|
+
it('Should render home by default', () => {
|
|
8
|
+
const { getByTestId } = render(ExploreRouter)
|
|
9
|
+
|
|
10
|
+
expect(getByTestId('explore-home')).toBeTruthy()
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('Should render results on click of button', async () => {
|
|
14
|
+
const { getByText, getByTestId } = render(ExploreRouter)
|
|
15
|
+
|
|
16
|
+
await fireEvent.click(getByText('Explore'))
|
|
17
|
+
|
|
18
|
+
expect(getByTestId('explore-results')).toBeTruthy()
|
|
19
|
+
})
|
|
20
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
2
|
+
import { beforeEach, describe, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import ExploreHome from '../../../../../routes/components/Explore/Routes/ExploreHome.svelte'
|
|
5
|
+
|
|
6
|
+
vi.mock('$lib/api/titles', () => ({
|
|
7
|
+
fetchSimilarTitles: vi.fn(),
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
describe('ExploreResults.svelte', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
vi.resetAllMocks()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('Should do nothing yet, this is a placeholder', () => {
|
|
16
|
+
render(ExploreHome)
|
|
17
|
+
})
|
|
18
|
+
})
|
package/src/tests/routes/components/Explore/{Explore.test.js → Routes/ExploreResults.test.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { render, fireEvent, waitFor } from '@testing-library/svelte'
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import ExploreResults from '../../../../../routes/components/Explore/Routes/ExploreResults.svelte'
|
|
5
5
|
import { fetchTitles } from '$lib/api/titles'
|
|
6
6
|
import { title } from '$lib/fakeData'
|
|
7
7
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
@@ -24,7 +24,7 @@ vi.mock('$lib/api/ads', () => ({
|
|
|
24
24
|
fetchAds: vi.fn(),
|
|
25
25
|
}))
|
|
26
26
|
|
|
27
|
-
describe('
|
|
27
|
+
describe('ExploreResults.svelte', () => {
|
|
28
28
|
beforeEach(() => {
|
|
29
29
|
// @ts-ignore
|
|
30
30
|
window.PlayPilotLinkInjections = {}
|
|
@@ -33,13 +33,13 @@ describe('Explore.svelte', () => {
|
|
|
33
33
|
})
|
|
34
34
|
|
|
35
35
|
it('Should call fetchTitles on mount', () => {
|
|
36
|
-
render(
|
|
36
|
+
render(ExploreResults)
|
|
37
37
|
|
|
38
38
|
expect(fetchTitles).toHaveBeenCalledWith({ page_size: 24, page: 1 })
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
it('Should call tracking event and fetchAds on mount', () => {
|
|
42
|
-
render(
|
|
42
|
+
render(ExploreResults)
|
|
43
43
|
|
|
44
44
|
expect(track).toHaveBeenCalledWith(TrackingEvent.ExplorePageView)
|
|
45
45
|
expect(fetchAds).toHaveBeenCalled()
|
|
@@ -52,7 +52,7 @@ describe('Explore.svelte', () => {
|
|
|
52
52
|
ads: ['a'],
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
render(
|
|
55
|
+
render(ExploreResults)
|
|
56
56
|
|
|
57
57
|
expect(fetchAds).not.toHaveBeenCalled()
|
|
58
58
|
})
|
|
@@ -60,7 +60,7 @@ describe('Explore.svelte', () => {
|
|
|
60
60
|
it('Should render all returned titles', async () => {
|
|
61
61
|
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title, title, title], next: null, previous: null })
|
|
62
62
|
|
|
63
|
-
const { getAllByText } = render(
|
|
63
|
+
const { getAllByText } = render(ExploreResults)
|
|
64
64
|
|
|
65
65
|
await waitFor(() => {
|
|
66
66
|
expect(getAllByText(title.title)).toHaveLength(3)
|
|
@@ -70,7 +70,7 @@ describe('Explore.svelte', () => {
|
|
|
70
70
|
it('Should not render Show more button when no next value is given', async () => {
|
|
71
71
|
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title], next: null, previous: null })
|
|
72
72
|
|
|
73
|
-
const { getByText, queryByText } = render(
|
|
73
|
+
const { getByText, queryByText } = render(ExploreResults)
|
|
74
74
|
|
|
75
75
|
await waitFor(() => getByText(title.title))
|
|
76
76
|
|
|
@@ -80,7 +80,7 @@ describe('Explore.svelte', () => {
|
|
|
80
80
|
it('Should render Show more button when next value is given', async () => {
|
|
81
81
|
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title], next: 'truthy', previous: null })
|
|
82
82
|
|
|
83
|
-
const { getByText } = render(
|
|
83
|
+
const { getByText } = render(ExploreResults)
|
|
84
84
|
|
|
85
85
|
await waitFor(() => {
|
|
86
86
|
expect(getByText('Show more')).toBeTruthy()
|
|
@@ -90,7 +90,7 @@ describe('Explore.svelte', () => {
|
|
|
90
90
|
it('Should fire fetchTitles again with incremented page number when show more is clicked and render the given titles', async () => {
|
|
91
91
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
92
92
|
|
|
93
|
-
const { getByText, getAllByText } = render(
|
|
93
|
+
const { getByText, getAllByText } = render(ExploreResults)
|
|
94
94
|
|
|
95
95
|
await waitFor(() => fireEvent.click(getByText('Show more')))
|
|
96
96
|
|
|
@@ -104,7 +104,7 @@ describe('Explore.svelte', () => {
|
|
|
104
104
|
it('Should fire tracking event when show more is clicked', async () => {
|
|
105
105
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
106
106
|
|
|
107
|
-
const { getByText } = render(
|
|
107
|
+
const { getByText } = render(ExploreResults)
|
|
108
108
|
|
|
109
109
|
await waitFor(() => fireEvent.click(getByText('Show more')))
|
|
110
110
|
|
|
@@ -114,7 +114,7 @@ describe('Explore.svelte', () => {
|
|
|
114
114
|
it('Should render skeletons while loading', async () => {
|
|
115
115
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
116
116
|
|
|
117
|
-
const { getAllByTestId, queryByTestId, getByText } = render(
|
|
117
|
+
const { getAllByTestId, queryByTestId, getByText } = render(ExploreResults)
|
|
118
118
|
|
|
119
119
|
expect(getAllByTestId('skeleton')).toHaveLength(24)
|
|
120
120
|
|
|
@@ -126,7 +126,7 @@ describe('Explore.svelte', () => {
|
|
|
126
126
|
it('Should call fetchTitles with string of comma separated values when filtering with array value', async () => {
|
|
127
127
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
128
128
|
|
|
129
|
-
const { getByText } = render(
|
|
129
|
+
const { getByText } = render(ExploreResults)
|
|
130
130
|
|
|
131
131
|
await fireEvent.click(getByText('Genres'))
|
|
132
132
|
await fireEvent.click(getByText('Action'))
|
|
@@ -137,16 +137,23 @@ describe('Explore.svelte', () => {
|
|
|
137
137
|
})
|
|
138
138
|
|
|
139
139
|
it('Should include search param when query is given', async () => {
|
|
140
|
-
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title
|
|
140
|
+
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title], next: 'truthy', previous: null })
|
|
141
|
+
|
|
142
|
+
const { getAllByTestId } = render(ExploreResults, { searchQuery: 'some query' })
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(fetchTitles).toHaveBeenCalledWith({ page: 1, page_size: 24, search: 'some query' })
|
|
146
|
+
expect(getAllByTestId('title')).toHaveLength(1)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
143
149
|
|
|
150
|
+
it('Should include search param when query is empty', async () => {
|
|
144
151
|
vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title], next: 'truthy', previous: null })
|
|
145
152
|
|
|
146
|
-
|
|
153
|
+
const { getAllByTestId } = render(ExploreResults, { searchQuery: '' })
|
|
147
154
|
|
|
148
155
|
await waitFor(() => {
|
|
149
|
-
expect(fetchTitles).toHaveBeenCalledWith(
|
|
156
|
+
expect(fetchTitles).toHaveBeenCalledWith(expect.not.stringContaining('search'))
|
|
150
157
|
expect(getAllByTestId('title')).toHaveLength(1)
|
|
151
158
|
})
|
|
152
159
|
})
|
|
@@ -154,7 +161,7 @@ describe('Explore.svelte', () => {
|
|
|
154
161
|
it('Should call fetchTitles with min and max values when filtering with range value', async () => {
|
|
155
162
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
156
163
|
|
|
157
|
-
const { container, getByText } = render(
|
|
164
|
+
const { container, getByText } = render(ExploreResults)
|
|
158
165
|
|
|
159
166
|
await fireEvent.click(getByText('IMDb'))
|
|
160
167
|
// @ts-ignore
|
|
@@ -168,7 +175,7 @@ describe('Explore.svelte', () => {
|
|
|
168
175
|
it('Should call fetchTitles with multiple values when given', async () => {
|
|
169
176
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
170
177
|
|
|
171
|
-
const { container, getByText } = render(
|
|
178
|
+
const { container, getByText } = render(ExploreResults)
|
|
172
179
|
|
|
173
180
|
await fireEvent.click(getByText('Genres'))
|
|
174
181
|
await fireEvent.click(getByText('Action'))
|
|
@@ -186,7 +193,7 @@ describe('Explore.svelte', () => {
|
|
|
186
193
|
it('Should call fetchTitles with string for sorting when sorting is used', async () => {
|
|
187
194
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
188
195
|
|
|
189
|
-
const { getByText } = render(
|
|
196
|
+
const { getByText } = render(ExploreResults)
|
|
190
197
|
|
|
191
198
|
await fireEvent.click(getByText('Sort by'))
|
|
192
199
|
await fireEvent.click(getByText('New'))
|
|
@@ -197,7 +204,7 @@ describe('Explore.svelte', () => {
|
|
|
197
204
|
it('Should show empty message if no titles are returned for first page', async () => {
|
|
198
205
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [], next: null, previous: null })
|
|
199
206
|
|
|
200
|
-
const { getByText } = render(
|
|
207
|
+
const { getByText } = render(ExploreResults)
|
|
201
208
|
|
|
202
209
|
await waitFor(() => {
|
|
203
210
|
expect(getByText('No results were found')).toBeTruthy()
|
|
@@ -207,7 +214,7 @@ describe('Explore.svelte', () => {
|
|
|
207
214
|
it('Should not show empty message if no titles are returned for pages past the first page', async () => {
|
|
208
215
|
vi.mocked(fetchTitles).mockResolvedValue({ results: [title], next: 'truthy', previous: null })
|
|
209
216
|
|
|
210
|
-
const { getByText, queryByText } = render(
|
|
217
|
+
const { getByText, queryByText } = render(ExploreResults)
|
|
211
218
|
|
|
212
219
|
await waitFor(() => getByText('Show more'))
|
|
213
220
|
|
|
@@ -223,7 +230,7 @@ describe('Explore.svelte', () => {
|
|
|
223
230
|
it('Should show error message if api responded with error', async () => {
|
|
224
231
|
vi.mocked(fetchTitles).mockRejectedValueOnce(null)
|
|
225
232
|
|
|
226
|
-
const { getByText } = render(
|
|
233
|
+
const { getByText } = render(ExploreResults)
|
|
227
234
|
|
|
228
235
|
await waitFor(() => {
|
|
229
236
|
expect(getByText('Something went wrong')).toBeTruthy()
|
|
@@ -4,11 +4,17 @@ import { describe, expect, it, vi } from 'vitest'
|
|
|
4
4
|
import TitlesRail from '../../../../routes/components/Rails/TitlesRail.svelte'
|
|
5
5
|
import { title } from '$lib/fakeData'
|
|
6
6
|
import { openModal } from '$lib/modal'
|
|
7
|
+
import { getFirstTitleWithAvailableTrailer } from '$lib/trailer'
|
|
7
8
|
|
|
8
9
|
vi.mock('$lib/modal', () => ({
|
|
9
10
|
openModal: vi.fn(),
|
|
10
11
|
}))
|
|
11
12
|
|
|
13
|
+
vi.mock('$lib/trailer', () => ({
|
|
14
|
+
getFirstTitleWithAvailableTrailer: vi.fn(),
|
|
15
|
+
getVideoId: vi.fn(),
|
|
16
|
+
}))
|
|
17
|
+
|
|
12
18
|
describe('TitlesRail.svelte', () => {
|
|
13
19
|
it('Should show loading state while promise is resolving', async () => {
|
|
14
20
|
// @ts-ignore
|
|
@@ -44,4 +50,49 @@ describe('TitlesRail.svelte', () => {
|
|
|
44
50
|
|
|
45
51
|
expect(onclick).toHaveBeenCalledWith(title)
|
|
46
52
|
})
|
|
53
|
+
|
|
54
|
+
it('Should expand first title if expandable is true', async () => {
|
|
55
|
+
vi.mocked(getFirstTitleWithAvailableTrailer).mockResolvedValueOnce(title)
|
|
56
|
+
|
|
57
|
+
const { getByTestId } = render(TitlesRail, { titles: [{ ...title, embeddable_url: 'some-url' }], expandable: true })
|
|
58
|
+
|
|
59
|
+
expect(getByTestId('title').classList).not.toContain('expanded')
|
|
60
|
+
|
|
61
|
+
await new Promise(res => setTimeout(res, 2000))
|
|
62
|
+
|
|
63
|
+
expect(getByTestId('title').classList).toContain('expanded')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('Should not expand first title if expandable is not true', async () => {
|
|
67
|
+
const { getByTestId } = render(TitlesRail, { titles: [{ ...title, embeddable_url: 'some-url' }] })
|
|
68
|
+
|
|
69
|
+
expect(getByTestId('title').classList).not.toContain('expanded')
|
|
70
|
+
|
|
71
|
+
await new Promise(res => setTimeout(res, 2000))
|
|
72
|
+
|
|
73
|
+
expect(getByTestId('title').classList).not.toContain('expanded')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('Should not expand first title if it has no embeddable_url', async () => {
|
|
77
|
+
const { getByTestId } = render(TitlesRail, { titles: [title] })
|
|
78
|
+
|
|
79
|
+
expect(getByTestId('title').classList).not.toContain('expanded')
|
|
80
|
+
|
|
81
|
+
await new Promise(res => setTimeout(res, 2000))
|
|
82
|
+
|
|
83
|
+
expect(getByTestId('title').classList).not.toContain('expanded')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('Should expand second title if expandable is true but first title has no trailer', async () => {
|
|
87
|
+
const titles = [{ ...title }, { ...title, sid: 'some-other-sid', embeddable_url: 'some-url' }]
|
|
88
|
+
vi.mocked(getFirstTitleWithAvailableTrailer).mockResolvedValueOnce(titles[1])
|
|
89
|
+
|
|
90
|
+
const { getAllByTestId } = render(TitlesRail, { titles, expandable: true })
|
|
91
|
+
|
|
92
|
+
expect(getAllByTestId('title')[1].classList).not.toContain('expanded')
|
|
93
|
+
|
|
94
|
+
await new Promise(res => setTimeout(res, 2000))
|
|
95
|
+
|
|
96
|
+
expect(getAllByTestId('title')[1].classList).toContain('expanded')
|
|
97
|
+
})
|
|
47
98
|
})
|