@playpilot/tpi 8.29.11 → 8.30.0
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/dist/editorial.mount.js +9 -9
- package/dist/link-injections.js +1 -1
- package/dist/mount.js +7 -7
- package/package.json +1 -1
- package/src/lib/injection.ts +479 -479
- package/src/lib/types/global.d.ts +1 -0
- package/src/routes/components/Explore/Routes/ExploreModal.svelte +107 -107
- package/src/routes/components/Modals/TitlesReelModal.svelte +3 -3
- package/src/routes/components/Rails/TitlesRail.svelte +1 -1
- package/src/routes/components/YouTubeEmbed.svelte +61 -20
- package/src/routes/components/YouTubeEmbedBackground.svelte +1 -0
- package/src/routes/components/YouTubeEmbedOverlay.svelte +1 -1
- package/src/routes/elements/+page.svelte +12 -0
- package/src/tests/routes/components/YouTubeEmbed.test.js +109 -33
- package/src/tests/routes/components/YouTubeEmbedBackground.test.js +2 -3
- package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +2 -3
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { fetchParticipantBySid } from '$lib/api/participants'
|
|
3
|
-
import { fetchSimilarTitles, fetchTitleBySid } from '$lib/api/titles'
|
|
4
|
-
import { mobileBreakpoint } from '$lib/constants'
|
|
5
|
-
import { SplitTest } from '$lib/enums/SplitTest'
|
|
6
|
-
import { t } from '$lib/localization'
|
|
7
|
-
import { openModal, type ModalType } from '$lib/modal'
|
|
8
|
-
import { getSplitTestVariantName, trackSplitTestView } from '$lib/splitTest'
|
|
9
|
-
import type { TitleData } from '$lib/types/title'
|
|
10
|
-
import Modal from '../../Modals/Modal.svelte'
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
navigate?: (key: string, pushState?: boolean) => void
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const { navigate = () => null }: Props = $props()
|
|
17
|
-
|
|
18
|
-
async function openModalViaRoute(): Promise<void> {
|
|
19
|
-
const currentUrl = new URL(document.location.toString())
|
|
20
|
-
const sid = currentUrl.searchParams.get('sid')
|
|
21
|
-
|
|
22
|
-
if (!sid) return
|
|
23
|
-
|
|
24
|
-
let type: ModalType = sid.startsWith('pr') ? 'participant' : 'title' // Sids with with `pr` for participants, `ti` for titles
|
|
25
|
-
let data: any = null
|
|
26
|
-
|
|
27
|
-
if (type === 'title') {
|
|
28
|
-
const isMobile = window.innerWidth < mobileBreakpoint
|
|
29
|
-
const isInReelSplitTest = isMobile && getSplitTestVariantName(SplitTest.Reels) === 'Reel'
|
|
30
|
-
|
|
31
|
-
if (isMobile) trackSplitTestView(SplitTest.Reels)
|
|
32
|
-
|
|
33
|
-
type = isMobile && isInReelSplitTest ? 'titles-reel' : 'titles-rail'
|
|
34
|
-
|
|
35
|
-
const [title, railTitles] = (await Promise.allSettled([fetchTitleBySid(sid), fetchSimilarTitles({ sid } as unknown as TitleData)])).map((promise) =>
|
|
36
|
-
promise.status === 'fulfilled' ? promise.value : null,
|
|
37
|
-
)
|
|
38
|
-
data = [title as TitleData, ...(railTitles as TitleData[])]
|
|
39
|
-
} else if (type === 'participant') {
|
|
40
|
-
data = await fetchParticipantBySid(sid)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
openModal({
|
|
44
|
-
type,
|
|
45
|
-
data,
|
|
46
|
-
props: {
|
|
47
|
-
onclose: () => navigate('home'),
|
|
48
|
-
pushState: false,
|
|
49
|
-
},
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
</script>
|
|
53
|
-
|
|
54
|
-
{#await openModalViaRoute()}
|
|
55
|
-
<Modal blur pushState={false}>
|
|
56
|
-
{#snippet dialog()}
|
|
57
|
-
<div class="loading">
|
|
58
|
-
<div class="bar"></div>
|
|
59
|
-
<div class="bar"></div>
|
|
60
|
-
<div class="bar"></div>
|
|
61
|
-
</div>
|
|
62
|
-
{/snippet}
|
|
63
|
-
</Modal>
|
|
64
|
-
{:catch}
|
|
65
|
-
<Modal blur onclose={() => navigate('home')} pushState={false}>
|
|
66
|
-
<div class="error">
|
|
67
|
-
{t('An Error Occurred')}
|
|
68
|
-
</div>
|
|
69
|
-
</Modal>
|
|
70
|
-
{/await}
|
|
71
|
-
|
|
72
|
-
<style lang="scss">
|
|
73
|
-
.loading {
|
|
74
|
-
display: flex;
|
|
75
|
-
align-items: center;
|
|
76
|
-
justify-content: center;
|
|
77
|
-
height: 100%;
|
|
78
|
-
gap: margin(0.5);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
@keyframes animate-bar {
|
|
82
|
-
to {
|
|
83
|
-
height: margin(3);
|
|
84
|
-
opacity: 0.5;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.bar {
|
|
89
|
-
height: margin(4);
|
|
90
|
-
width: margin(1);
|
|
91
|
-
border-radius: margin(0.25);
|
|
92
|
-
background: white;
|
|
93
|
-
animation: animate-bar 750ms infinite;
|
|
94
|
-
|
|
95
|
-
@for $i from 0 through 2 {
|
|
96
|
-
&:nth-child(#{$i}) {
|
|
97
|
-
animation-delay: $i * 250ms;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.error {
|
|
103
|
-
padding: margin(2);
|
|
104
|
-
font-size: 1.25em;
|
|
105
|
-
color: theme(text-color);
|
|
106
|
-
}
|
|
107
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fetchParticipantBySid } from '$lib/api/participants'
|
|
3
|
+
import { fetchSimilarTitles, fetchTitleBySid } from '$lib/api/titles'
|
|
4
|
+
import { mobileBreakpoint } from '$lib/constants'
|
|
5
|
+
import { SplitTest } from '$lib/enums/SplitTest'
|
|
6
|
+
import { t } from '$lib/localization'
|
|
7
|
+
import { openModal, type ModalType } from '$lib/modal'
|
|
8
|
+
import { getSplitTestVariantName, trackSplitTestView } from '$lib/splitTest'
|
|
9
|
+
import type { TitleData } from '$lib/types/title'
|
|
10
|
+
import Modal from '../../Modals/Modal.svelte'
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
navigate?: (key: string, pushState?: boolean) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { navigate = () => null }: Props = $props()
|
|
17
|
+
|
|
18
|
+
async function openModalViaRoute(): Promise<void> {
|
|
19
|
+
const currentUrl = new URL(document.location.toString())
|
|
20
|
+
const sid = currentUrl.searchParams.get('sid')
|
|
21
|
+
|
|
22
|
+
if (!sid) return
|
|
23
|
+
|
|
24
|
+
let type: ModalType = sid.startsWith('pr') ? 'participant' : 'title' // Sids with with `pr` for participants, `ti` for titles
|
|
25
|
+
let data: any = null
|
|
26
|
+
|
|
27
|
+
if (type === 'title') {
|
|
28
|
+
const isMobile = window.innerWidth < mobileBreakpoint
|
|
29
|
+
const isInReelSplitTest = isMobile && getSplitTestVariantName(SplitTest.Reels) === 'Reel'
|
|
30
|
+
|
|
31
|
+
if (isMobile) trackSplitTestView(SplitTest.Reels)
|
|
32
|
+
|
|
33
|
+
type = isMobile && isInReelSplitTest ? 'titles-reel' : 'titles-rail'
|
|
34
|
+
|
|
35
|
+
const [title, railTitles] = (await Promise.allSettled([fetchTitleBySid(sid), fetchSimilarTitles({ sid } as unknown as TitleData)])).map((promise) =>
|
|
36
|
+
promise.status === 'fulfilled' ? promise.value : null,
|
|
37
|
+
)
|
|
38
|
+
data = [title as TitleData, ...(railTitles as TitleData[])]
|
|
39
|
+
} else if (type === 'participant') {
|
|
40
|
+
data = await fetchParticipantBySid(sid)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
openModal({
|
|
44
|
+
type,
|
|
45
|
+
data,
|
|
46
|
+
props: {
|
|
47
|
+
onclose: () => navigate('home'),
|
|
48
|
+
pushState: false,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
{#await openModalViaRoute()}
|
|
55
|
+
<Modal blur pushState={false}>
|
|
56
|
+
{#snippet dialog()}
|
|
57
|
+
<div class="loading">
|
|
58
|
+
<div class="bar"></div>
|
|
59
|
+
<div class="bar"></div>
|
|
60
|
+
<div class="bar"></div>
|
|
61
|
+
</div>
|
|
62
|
+
{/snippet}
|
|
63
|
+
</Modal>
|
|
64
|
+
{:catch}
|
|
65
|
+
<Modal blur onclose={() => navigate('home')} pushState={false}>
|
|
66
|
+
<div class="error">
|
|
67
|
+
{t('An Error Occurred')}
|
|
68
|
+
</div>
|
|
69
|
+
</Modal>
|
|
70
|
+
{/await}
|
|
71
|
+
|
|
72
|
+
<style lang="scss">
|
|
73
|
+
.loading {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: center;
|
|
77
|
+
height: 100%;
|
|
78
|
+
gap: margin(0.5);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@keyframes animate-bar {
|
|
82
|
+
to {
|
|
83
|
+
height: margin(3);
|
|
84
|
+
opacity: 0.5;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.bar {
|
|
89
|
+
height: margin(4);
|
|
90
|
+
width: margin(1);
|
|
91
|
+
border-radius: margin(0.25);
|
|
92
|
+
background: white;
|
|
93
|
+
animation: animate-bar 750ms infinite;
|
|
94
|
+
|
|
95
|
+
@for $i from 0 through 2 {
|
|
96
|
+
&:nth-child(#{$i}) {
|
|
97
|
+
animation-delay: $i * 250ms;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.error {
|
|
103
|
+
padding: margin(2);
|
|
104
|
+
font-size: 1.25em;
|
|
105
|
+
color: theme(text-color);
|
|
106
|
+
}
|
|
107
|
+
</style>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
onclose?: () => void
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const { titles, onclose = () => null }: Props = $props()
|
|
25
|
+
const { titles, onclose = () => null, ...restProps }: Props = $props()
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
let interval: ReturnType<typeof setInterval> | null = null
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
{/snippet}
|
|
75
75
|
|
|
76
76
|
<div class="reel" style:--available-height="{availableHeight}px">
|
|
77
|
-
<Modal {prepend}>
|
|
77
|
+
<Modal {prepend} {onclose} {...restProps}>
|
|
78
78
|
{#snippet dialog()}
|
|
79
79
|
<div class="slider" bind:this={element}>
|
|
80
80
|
<TinySlider vertical threshold={50} moveThreshold={10} {onchange}>
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
overscroll-behavior: contain;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
:global(
|
|
145
|
+
:global(.plyr) {
|
|
146
146
|
pointer-events: none;
|
|
147
147
|
z-index: 0;
|
|
148
148
|
}
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
fallbackStartTime?: number
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
let { embeddable_url = '', controls = [], muted = false, loop = false, captions = false, autoplay = true, showMuteControls = false, fallbackStartTime = 0 }: Props = $props()
|
|
18
18
|
|
|
19
19
|
const videoId = $derived(getVideoId(embeddable_url))
|
|
20
20
|
const startTime = $derived((window?.PlayPilotLinkInjections?.video_playtimes?.[videoId || ''] || (fallbackStartTime + 1)) - 1)
|
|
21
21
|
const color = window?.getComputedStyle(document.body).getPropertyValue('--playpilot-primary')?.replace('#', '') || 'fa548a'
|
|
22
22
|
|
|
23
|
-
let
|
|
24
|
-
let
|
|
23
|
+
let element: HTMLElement | null = $state(null)
|
|
24
|
+
let player: any
|
|
25
25
|
|
|
26
26
|
onMount(() => {
|
|
27
27
|
if (!videoId) return
|
|
@@ -37,37 +37,74 @@
|
|
|
37
37
|
return () => clearInterval(interval)
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
40
|
+
function initialize(): void {
|
|
41
|
+
if (typeof window.Plyr === 'undefined') return
|
|
42
|
+
if (!element) return
|
|
43
|
+
|
|
44
|
+
element.dataset.plyrEmbedId = videoId!
|
|
45
|
+
element.style.setProperty('--plyr-color-main', '#' + color)
|
|
46
|
+
|
|
47
|
+
player = new window.Plyr(element, {
|
|
48
|
+
controls,
|
|
49
|
+
playsinline: true,
|
|
50
|
+
autoplay,
|
|
51
|
+
muted,
|
|
52
|
+
clickToPlay: false,
|
|
53
|
+
youtube: {
|
|
54
|
+
noCookie: true,
|
|
55
|
+
cc_load_policy: captions ? 1 : 0,
|
|
56
|
+
cc_lang_pref: 'auto',
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
player.on('ready', () => {
|
|
61
|
+
player.currentTime = startTime
|
|
62
|
+
toggleMute(muted)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if (!loop) return
|
|
66
|
+
|
|
67
|
+
player.on('statechange', (event: { detail: { code: number } }) => {
|
|
68
|
+
if (event.detail.code !== 0) return
|
|
69
|
+
|
|
70
|
+
requestAnimationFrame(() => {
|
|
71
|
+
player.play()
|
|
72
|
+
toggleMute(muted)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function toggleMute(state = !muted): void {
|
|
78
|
+
player.muted = state
|
|
79
|
+
player.volume = muted ? 0 : 1
|
|
80
|
+
|
|
81
|
+
muted = state
|
|
43
82
|
}
|
|
44
83
|
|
|
45
84
|
export function play(event: CustomEvent): void {
|
|
46
|
-
if (event.detail === embeddable_url)
|
|
85
|
+
if (event.detail === embeddable_url) player?.play()
|
|
47
86
|
}
|
|
48
87
|
|
|
49
88
|
export function pause(event: CustomEvent): void {
|
|
50
|
-
if (event.detail === embeddable_url)
|
|
89
|
+
if (event.detail === embeddable_url) player?.pause()
|
|
51
90
|
}
|
|
52
91
|
</script>
|
|
53
92
|
|
|
54
93
|
<svelte:window onplayembed={play} onpauseembed={pause} />
|
|
55
94
|
|
|
95
|
+
<svelte:head>
|
|
96
|
+
{#if videoId}
|
|
97
|
+
<script src="https://unpkg.com/plyr@3" onload={initialize}></script>
|
|
98
|
+
<link rel="stylesheet" href="https://unpkg.com/plyr@3/dist/plyr.css" />
|
|
99
|
+
{/if}
|
|
100
|
+
</svelte:head>
|
|
101
|
+
|
|
56
102
|
{#if videoId}
|
|
57
|
-
<
|
|
58
|
-
bind:this={iframe}
|
|
59
|
-
width="600"
|
|
60
|
-
height="338"
|
|
61
|
-
src="https://video.playpilot.net/?video_id={videoId}&color={color}&muted={muted}&loop={loop}&captions={captions}&controls={controls.join(',')}&start_time={startTime}&autoplay={autoplay}&playsinline=true"
|
|
62
|
-
title="YouTube video player"
|
|
63
|
-
frameborder="0"
|
|
64
|
-
referrerpolicy="strict-origin-when-cross-origin"
|
|
65
|
-
allowfullscreen>
|
|
66
|
-
</iframe>
|
|
103
|
+
<div bind:this={element} class="player" data-plyr-provider="youtube" data-plyr-embed-id data-testid={embeddable_url}></div>
|
|
67
104
|
|
|
68
105
|
{#if showMuteControls}
|
|
69
106
|
<button class="mute" onclick={() => toggleMute()} aria-label="Mute">
|
|
70
|
-
<IconMute muted
|
|
107
|
+
<IconMute {muted} />
|
|
71
108
|
</button>
|
|
72
109
|
{/if}
|
|
73
110
|
{:else}
|
|
@@ -75,7 +112,11 @@
|
|
|
75
112
|
{/if}
|
|
76
113
|
|
|
77
114
|
<style lang="scss">
|
|
78
|
-
|
|
115
|
+
.player {
|
|
116
|
+
--plyr-control-icon-size: 24px;
|
|
117
|
+
--plyr-video-controls-background: linear-gradient(rgba(0, 0, 0, 0), black);
|
|
118
|
+
--plyr-font-size-time: 16px;
|
|
119
|
+
--plyr-font-family: 'Consolas', monospace;
|
|
79
120
|
z-index: -1;
|
|
80
121
|
position: relative;
|
|
81
122
|
width: 100%;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import TitlesRail from '../components/Rails/TitlesRail.svelte'
|
|
23
23
|
import ParticipantsRail from '../components/Rails/ParticipantsRail.svelte'
|
|
24
24
|
import InfiniteTitlesRail from '../components/Rails/InfiniteTitlesRail.svelte'
|
|
25
|
+
import YouTubeEmbed from '../components/YouTubeEmbed.svelte'
|
|
25
26
|
|
|
26
27
|
if (browser) {
|
|
27
28
|
// @ts-ignore
|
|
@@ -43,6 +44,12 @@
|
|
|
43
44
|
|
|
44
45
|
<p>This page lays out various elements used in TPI. It does not currently include editor elements.</p>
|
|
45
46
|
|
|
47
|
+
<h2>Youtube Embed</h2>
|
|
48
|
+
|
|
49
|
+
<div class="video">
|
|
50
|
+
<YouTubeEmbed embeddable_url="https://www.youtube.com/watch?v=bBMajXwi6Cs" controls={['progress', 'play']} loop showMuteControls muted />
|
|
51
|
+
</div>
|
|
52
|
+
|
|
46
53
|
<h2>Title</h2>
|
|
47
54
|
|
|
48
55
|
<div class="group">
|
|
@@ -267,4 +274,9 @@
|
|
|
267
274
|
padding: margin(2);
|
|
268
275
|
margin-bottom: margin(1);
|
|
269
276
|
}
|
|
277
|
+
|
|
278
|
+
.video {
|
|
279
|
+
position: relative;
|
|
280
|
+
max-width: 400px;
|
|
281
|
+
}
|
|
270
282
|
</style>
|
|
@@ -1,72 +1,148 @@
|
|
|
1
1
|
import { render } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
2
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
|
4
3
|
import YouTubeEmbed from '../../../routes/components/YouTubeEmbed.svelte'
|
|
5
|
-
import { beforeEach } from 'node:test'
|
|
6
4
|
import { fakeFetch } from '../../helpers'
|
|
7
5
|
|
|
6
|
+
function loadPlyr() {
|
|
7
|
+
const script = document.head.querySelector('script[src*="plyr"]')
|
|
8
|
+
script?.dispatchEvent(new Event('load'))
|
|
9
|
+
}
|
|
10
|
+
|
|
8
11
|
describe('YouTubeEmbed.svelte', () => {
|
|
12
|
+
/** @type {any} */ let Plyr
|
|
13
|
+
/** @type {any} */ let player
|
|
14
|
+
/** @type {any} */ let onready
|
|
15
|
+
/** @type {any} */ let statechange
|
|
16
|
+
|
|
9
17
|
beforeEach(() => {
|
|
10
18
|
fakeFetch()
|
|
19
|
+
onready = undefined
|
|
20
|
+
statechange = undefined
|
|
21
|
+
|
|
22
|
+
player = {
|
|
23
|
+
on: vi.fn((eventName, callback) => {
|
|
24
|
+
if (eventName === 'ready') onready = callback
|
|
25
|
+
if (eventName === 'statechange') statechange = callback
|
|
26
|
+
}),
|
|
27
|
+
play: vi.fn(),
|
|
28
|
+
pause: vi.fn(),
|
|
29
|
+
muted: false,
|
|
30
|
+
volume: 1,
|
|
31
|
+
currentTime: 0,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Plyr = vi.fn(() => player)
|
|
35
|
+
window.Plyr = Plyr
|
|
11
36
|
})
|
|
12
37
|
|
|
13
|
-
|
|
14
|
-
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
delete window.Plyr
|
|
40
|
+
document.head.innerHTML = ''
|
|
41
|
+
})
|
|
15
42
|
|
|
16
|
-
|
|
43
|
+
it('Should render player element and initialize Plyr with default options', () => {
|
|
44
|
+
const { container } = render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc' })
|
|
45
|
+
loadPlyr()
|
|
46
|
+
|
|
47
|
+
/** @type {HTMLElement | null} */
|
|
48
|
+
const playerElement = container.querySelector('.player')
|
|
49
|
+
|
|
50
|
+
expect(playerElement).toBeTruthy()
|
|
51
|
+
expect(playerElement?.dataset.plyrEmbedId).toBe('abc')
|
|
52
|
+
expect(playerElement?.dataset.plyrProvider).toBe('youtube')
|
|
53
|
+
|
|
54
|
+
expect(Plyr).toHaveBeenCalledWith(
|
|
55
|
+
playerElement,
|
|
56
|
+
expect.objectContaining({
|
|
57
|
+
controls: [],
|
|
58
|
+
autoplay: true,
|
|
59
|
+
muted: false,
|
|
60
|
+
clickToPlay: false,
|
|
61
|
+
playsinline: true,
|
|
62
|
+
youtube: expect.objectContaining({ noCookie: true, cc_load_policy: 0, cc_lang_pref: 'auto' }),
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
17
65
|
})
|
|
18
66
|
|
|
19
|
-
it('Should
|
|
20
|
-
const { container } = render(YouTubeEmbed, {
|
|
67
|
+
it('Should initialize Plyr with given controls', () => {
|
|
68
|
+
const { container } = render(YouTubeEmbed, {
|
|
69
|
+
embeddable_url: 'youtube.com/watch?v=abc',
|
|
70
|
+
controls: ['fullscreen', 'mute'],
|
|
71
|
+
})
|
|
21
72
|
|
|
22
|
-
|
|
73
|
+
loadPlyr()
|
|
74
|
+
|
|
75
|
+
expect(Plyr).toHaveBeenCalledWith(
|
|
76
|
+
container.querySelector('.player'),
|
|
77
|
+
expect.objectContaining({ controls: ['fullscreen', 'mute'] }),
|
|
78
|
+
)
|
|
23
79
|
})
|
|
24
80
|
|
|
25
|
-
it('Should
|
|
81
|
+
it('Should initialize Plyr muted if given', () => {
|
|
26
82
|
const { container } = render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc', muted: true })
|
|
27
83
|
|
|
28
|
-
|
|
84
|
+
loadPlyr()
|
|
85
|
+
|
|
86
|
+
expect(Plyr).toHaveBeenCalledWith(
|
|
87
|
+
container.querySelector('.player'),
|
|
88
|
+
expect.objectContaining({ muted: true }),
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('Should set current time on ready to given fallbackStartTime', () => {
|
|
93
|
+
render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc', fallbackStartTime: 8 })
|
|
94
|
+
|
|
95
|
+
loadPlyr()
|
|
96
|
+
onready?.()
|
|
97
|
+
|
|
98
|
+
expect(player.currentTime).toBe(8)
|
|
29
99
|
})
|
|
30
100
|
|
|
31
|
-
it('Should
|
|
32
|
-
|
|
101
|
+
it('Should use stored playtime for video id, minus 1 second', () => {
|
|
102
|
+
// @ts-ignore
|
|
103
|
+
window.PlayPilotLinkInjections = {
|
|
104
|
+
video_playtimes: { abc: 5 },
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc' })
|
|
108
|
+
|
|
109
|
+
loadPlyr()
|
|
110
|
+
onready?.()
|
|
33
111
|
|
|
34
|
-
expect(
|
|
112
|
+
expect(player.currentTime).toBe(4)
|
|
35
113
|
})
|
|
36
114
|
|
|
37
|
-
it('Should
|
|
115
|
+
it('Should enable captions in youtube options if given', () => {
|
|
38
116
|
const { container } = render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc', captions: true })
|
|
117
|
+
loadPlyr()
|
|
39
118
|
|
|
40
|
-
expect(
|
|
119
|
+
expect(Plyr).toHaveBeenCalledWith(container.querySelector('.player'), expect.objectContaining({ youtube: expect.objectContaining({ cc_load_policy: 1 }) }))
|
|
41
120
|
})
|
|
42
121
|
|
|
43
|
-
it('Should
|
|
122
|
+
it('Should initialize Plyr with autoplay set to false if given', () => {
|
|
44
123
|
const { container } = render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc', autoplay: false })
|
|
45
124
|
|
|
46
|
-
|
|
125
|
+
loadPlyr()
|
|
126
|
+
|
|
127
|
+
expect(Plyr).toHaveBeenCalledWith(container.querySelector('.player'), expect.objectContaining({ autoplay: false }))
|
|
47
128
|
})
|
|
48
129
|
|
|
49
|
-
it('Should
|
|
50
|
-
|
|
130
|
+
it('Should replay and re-mute on loop when video ends', () => {
|
|
131
|
+
render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc', loop: true, muted: true })
|
|
132
|
+
|
|
133
|
+
loadPlyr()
|
|
134
|
+
global.requestAnimationFrame = (callback) => (callback(0), 0)
|
|
51
135
|
|
|
52
|
-
|
|
136
|
+
statechange?.({ detail: { code: 0 } })
|
|
137
|
+
|
|
138
|
+
expect(player.play).toHaveBeenCalled()
|
|
139
|
+
expect(player.muted).toBe(true)
|
|
53
140
|
})
|
|
54
141
|
|
|
55
142
|
it('Should render error message if embeddable_url is invalid', () => {
|
|
56
143
|
const { container, getByText } = render(YouTubeEmbed, { embeddable_url: '-' })
|
|
57
144
|
|
|
58
|
-
expect(container.querySelector('
|
|
145
|
+
expect(container.querySelector('.player')).not.toBeTruthy()
|
|
59
146
|
expect(getByText('Something went wrong')).toBeTruthy()
|
|
60
147
|
})
|
|
61
|
-
|
|
62
|
-
it('Should include start_time for given video id if present, minus 1 second', () => {
|
|
63
|
-
// @ts-ignore
|
|
64
|
-
window.PlayPilotLinkInjections = {
|
|
65
|
-
video_playtimes: { abc: 5 },
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const { container } = render(YouTubeEmbed, { embeddable_url: 'youtube.com/watch?v=abc', captions: true })
|
|
69
|
-
|
|
70
|
-
expect(/** @type {HTMLIFrameElement} */ (container.querySelector('iframe')).src).toContain('&start_time=4')
|
|
71
|
-
})
|
|
72
148
|
})
|
|
@@ -5,9 +5,8 @@ import YouTubeEmbedBackground from '../../../routes/components/YouTubeEmbedBackg
|
|
|
5
5
|
|
|
6
6
|
describe('YouTubeEmbedBackground.svelte', () => {
|
|
7
7
|
it('Should render embed iframe with given video url', () => {
|
|
8
|
-
const {
|
|
8
|
+
const { getByTestId } = render(YouTubeEmbedBackground, { embeddable_url: 'youtube.com/watch?v=abc' })
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
expect(container.querySelector('iframe').src).toBe('https://video.playpilot.net/?video_id=abc&color=fa548a&muted=true&loop=true&captions=false&controls=&start_time=5&autoplay=true&playsinline=true')
|
|
10
|
+
expect(getByTestId('youtube.com/watch?v=abc')).toBeTruthy()
|
|
12
11
|
})
|
|
13
12
|
})
|
|
@@ -5,10 +5,9 @@ import YouTubeEmbedOverlay from '../../../routes/components/YouTubeEmbedOverlay.
|
|
|
5
5
|
|
|
6
6
|
describe('YouTubeEmbedOverlay.svelte', () => {
|
|
7
7
|
it('Should render embed iframe with given video url', () => {
|
|
8
|
-
const {
|
|
8
|
+
const { getByTestId } = render(YouTubeEmbedOverlay, { embeddable_url: 'youtube.com/watch?v=abc', onclose: () => null })
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
expect(container.querySelector('iframe').src).toBe('https://video.playpilot.net/?video_id=abc&color=fa548a&muted=false&loop=false&captions=false&controls=current-time,fullscreen,mute,play,progress&start_time=0&autoplay=true&playsinline=true')
|
|
10
|
+
expect(getByTestId('youtube.com/watch?v=abc')).toBeTruthy()
|
|
12
11
|
})
|
|
13
12
|
|
|
14
13
|
it('Should fire given onclose function on click of close button and backdrop', async () => {
|