@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.
Files changed (32) hide show
  1. package/.env +1 -0
  2. package/dist/editorial.mount.js +9 -9
  3. package/dist/link-injections.js +1 -1
  4. package/dist/mount.js +7 -7
  5. package/events.md +1 -0
  6. package/package.json +1 -1
  7. package/src/lib/api/youtubeAvailability.ts +26 -0
  8. package/src/lib/enums/TrackingEvent.ts +1 -0
  9. package/src/lib/explore.ts +2 -2
  10. package/src/lib/trailer.ts +31 -0
  11. package/src/lib/types/explore.d.ts +5 -0
  12. package/src/routes/components/Button.svelte +2 -1
  13. package/src/routes/components/Explore/ExploreLayout.svelte +102 -0
  14. package/src/routes/components/Explore/ExploreModal.svelte +2 -2
  15. package/src/routes/components/Explore/ExploreRouter.svelte +35 -0
  16. package/src/routes/components/Explore/Routes/ExploreHome.svelte +15 -0
  17. package/src/routes/components/Explore/{Explore.svelte → Routes/ExploreResults.svelte} +46 -115
  18. package/src/routes/components/Rails/TitlesRail.svelte +61 -9
  19. package/src/routes/components/YouTubeEmbed.svelte +35 -0
  20. package/src/routes/components/YouTubeEmbedOverlay.svelte +14 -28
  21. package/src/routes/elements/+page.svelte +12 -2
  22. package/src/routes/explore/+page.svelte +1 -1
  23. package/src/tests/lib/api/youtubeAvailability.test.js +70 -0
  24. package/src/tests/lib/trailer.test.js +57 -1
  25. package/src/tests/routes/components/Explore/ExploreLayout.test.js +52 -0
  26. package/src/tests/routes/components/Explore/ExploreRouter.test.js +20 -0
  27. package/src/tests/routes/components/Explore/Routes/ExploreHome.test.js +18 -0
  28. package/src/tests/routes/components/Explore/{Explore.test.js → Routes/ExploreResults.test.js} +29 -22
  29. package/src/tests/routes/components/Rails/TitlesRail.test.js +51 -0
  30. package/src/tests/routes/components/YouTubeEmbed.test.js +31 -0
  31. package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +1 -8
  32. 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?: string,
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
- {#if videoId}
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 Explore from '../components/Explore/Explore.svelte'
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
- <Explore />
187
+ <ExploreRouter />
178
188
  {/key}
179
189
  </div>
180
190
 
@@ -22,7 +22,7 @@
22
22
 
23
23
  insertExploreIntoNavigation()
24
24
  // Pretend there is some loading time, as there would be on a real page
25
- setTimeout(insertExplore, 1500)
25
+ setTimeout(insertExplore, 100)
26
26
  </script>
27
27
 
28
28
  <main>
@@ -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
+ })
@@ -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 Explore from '../../../../routes/components/Explore/Explore.svelte'
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('Explore.svelte', () => {
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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, title], next: 'truthy', previous: null })
140
+ vi.mocked(fetchTitles).mockResolvedValueOnce({ results: [title], next: 'truthy', previous: null })
141
+
142
+ const { getAllByTestId } = render(ExploreResults, { searchQuery: 'some query' })
141
143
 
142
- const { getByRole, getAllByTestId } = render(Explore)
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
- fireEvent.input(getByRole('searchbox'), { target: { value: 'some query' } })
153
+ const { getAllByTestId } = render(ExploreResults, { searchQuery: '' })
147
154
 
148
155
  await waitFor(() => {
149
- expect(fetchTitles).toHaveBeenCalledWith({ page: 1, page_size: 24, search: 'some query' })
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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(Explore)
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
  })