@playpilot/tpi 5.34.3 → 5.35.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/link-injections.js +8 -8
- package/package.json +1 -1
- package/src/lib/data/translations.ts +5 -0
- package/src/lib/enums/TrackingEvent.ts +3 -2
- package/src/lib/fakeData.ts +1 -0
- package/src/lib/trailer.ts +21 -0
- package/src/lib/types/title.d.ts +1 -0
- package/src/routes/components/Button.svelte +62 -0
- package/src/routes/components/Icons/IconClose.svelte +9 -1
- package/src/routes/components/Icons/IconPlay.svelte +3 -0
- package/src/routes/components/Share.svelte +5 -23
- package/src/routes/components/Title.svelte +21 -22
- package/src/routes/components/Trailer.svelte +25 -0
- package/src/routes/components/YouTubeEmbedOverlay.svelte +96 -0
- package/src/tests/lib/trailer.test.js +56 -0
- package/src/tests/routes/components/Button.test.js +28 -0
- package/src/tests/routes/components/Share.test.js +12 -12
- package/src/tests/routes/components/Title.test.js +12 -0
- package/src/tests/routes/components/Trailer.test.js +34 -0
- package/src/tests/routes/components/YouTubeEmbedOverlay.test.js +31 -0
package/package.json
CHANGED
|
@@ -126,6 +126,11 @@ export const translations = {
|
|
|
126
126
|
[Language.Swedish]: 'Liknande filmer och serier',
|
|
127
127
|
[Language.Danish]: 'Lignende film og serier',
|
|
128
128
|
},
|
|
129
|
+
'Watch Trailer': {
|
|
130
|
+
[Language.English]: 'Watch trailer',
|
|
131
|
+
[Language.Swedish]: 'Se trailer',
|
|
132
|
+
[Language.Danish]: 'Se trailer',
|
|
133
|
+
},
|
|
129
134
|
|
|
130
135
|
// Genres
|
|
131
136
|
'All': {
|
|
@@ -53,6 +53,7 @@ export const TrackingEvent = Object.freeze({
|
|
|
53
53
|
SplitTestView: 'ali_split_test_view',
|
|
54
54
|
SplitTestAction: 'ali_split_test_action',
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
ShareTitle: 'ali_share_title'
|
|
56
|
+
// Various
|
|
57
|
+
ShareTitle: 'ali_share_title',
|
|
58
|
+
TrailerClick: 'ali_trailer_button_click'
|
|
58
59
|
})
|
package/src/lib/fakeData.ts
CHANGED
|
@@ -38,6 +38,7 @@ export const title: TitleData = {
|
|
|
38
38
|
standing_poster: 'https://img.playpilot.tech/6239ee86a58f11efb0b50a58a9feac02/src/img?optimizer=image&class=2by3x18',
|
|
39
39
|
title: 'Dune: Prophecy',
|
|
40
40
|
original_title: 'Dune: Prophecy',
|
|
41
|
+
embeddable_url: null,
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export const linkInjections: LinkInjection[] = [{
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mount, unmount } from "svelte"
|
|
2
|
+
import { getPlayPilotWrapperElement } from "./injection"
|
|
3
|
+
import type { TitleData } from "./types/title"
|
|
4
|
+
import YouTubeEmbedOverlay from "../routes/components/YouTubeEmbedOverlay.svelte"
|
|
5
|
+
|
|
6
|
+
let currentTrailerComponent: object | null = {}
|
|
7
|
+
|
|
8
|
+
export function openTrailerOverlay(title: TitleData) {
|
|
9
|
+
const target = getPlayPilotWrapperElement()
|
|
10
|
+
const props = { onclose: closeTrailerOverlay, embeddable_url: title.embeddable_url || '' }
|
|
11
|
+
|
|
12
|
+
currentTrailerComponent = mount(YouTubeEmbedOverlay, { target, props })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function closeTrailerOverlay(): void {
|
|
16
|
+
if (!currentTrailerComponent) return
|
|
17
|
+
|
|
18
|
+
unmount(currentTrailerComponent, { outro: true })
|
|
19
|
+
|
|
20
|
+
currentTrailerComponent = null
|
|
21
|
+
}
|
package/src/lib/types/title.d.ts
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'filled' | 'border'
|
|
6
|
+
// eslint-disable-next-line no-unused-vars
|
|
7
|
+
onclick?: (event: MouseEvent) => void
|
|
8
|
+
children?: Snippet
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { variant = 'filled', onclick, children }: Props = $props()
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<button class="button {variant}" {onclick}>
|
|
15
|
+
{@render children?.()}
|
|
16
|
+
</button>
|
|
17
|
+
|
|
18
|
+
<style lang="scss">
|
|
19
|
+
.button {
|
|
20
|
+
appearance: none;
|
|
21
|
+
display: flex;
|
|
22
|
+
height: 100%;
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: margin(0.25);
|
|
25
|
+
border: 0;
|
|
26
|
+
padding: 0.25em 0.5em;
|
|
27
|
+
background: transparent;
|
|
28
|
+
border-radius: theme(button-border-radius, border-radius);
|
|
29
|
+
color: theme(button-text-color, text-color-alt);
|
|
30
|
+
font-size: inherit;
|
|
31
|
+
font-family: inherit;
|
|
32
|
+
font-weight: theme(button-font-weight, normal);
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
|
|
35
|
+
:global(svg) {
|
|
36
|
+
width: 1.5em;
|
|
37
|
+
height: 1.5em;
|
|
38
|
+
opacity: 0.75;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.filled {
|
|
43
|
+
background: theme(button-filled-background, content);
|
|
44
|
+
|
|
45
|
+
&:hover,
|
|
46
|
+
&:active {
|
|
47
|
+
background: theme(button-filled-hover-background, content-light);
|
|
48
|
+
color: theme(button-filled-hover-text-color, text-color);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.border {
|
|
53
|
+
box-shadow: inset 0 0 0 1px theme(button-border-color, content-light);
|
|
54
|
+
|
|
55
|
+
&:hover,
|
|
56
|
+
&:active {
|
|
57
|
+
background: theme(button-border-color, content);
|
|
58
|
+
color: theme(button-hover-text-color, text-color);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
62
|
+
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
<
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
size?: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { size = 16 }: Props = $props()
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
|
|
2
10
|
<path d="M14 2L2 14M2 2L14 14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
11
|
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="24px" height="24px" viewBox="0 -960 960 960">
|
|
2
|
+
<path fill="currentColor" d="m380-300 280-180-280-180v360ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
|
|
3
|
+
</svg>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import IconShare from './Icons/IconShare.svelte'
|
|
10
10
|
import IconLink from './Icons/IconLink.svelte'
|
|
11
11
|
import IconEmail from './Icons/IconEmail.svelte'
|
|
12
|
+
import Button from './Button.svelte'
|
|
12
13
|
import { onMount } from 'svelte'
|
|
13
14
|
|
|
14
15
|
interface Props {
|
|
@@ -55,9 +56,9 @@
|
|
|
55
56
|
<svelte:window onclick={() => showContextMenu = false} />
|
|
56
57
|
|
|
57
58
|
<div class="share">
|
|
58
|
-
<
|
|
59
|
-
<IconShare />
|
|
60
|
-
</
|
|
59
|
+
<Button onclick={toggle}>
|
|
60
|
+
<IconShare /> Share
|
|
61
|
+
</Button>
|
|
61
62
|
|
|
62
63
|
{#if showContextMenu}
|
|
63
64
|
<div class="context-menu" transition:scale={{ duration: 50, start: 0.85 }}>
|
|
@@ -79,30 +80,11 @@
|
|
|
79
80
|
position: relative;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
.button {
|
|
83
|
-
display: flex;
|
|
84
|
-
align-items: center;
|
|
85
|
-
justify-content: center;
|
|
86
|
-
cursor: pointer;
|
|
87
|
-
appearance: none;
|
|
88
|
-
border: 0;
|
|
89
|
-
border-radius: margin(3);
|
|
90
|
-
aspect-ratio: 1 / 1;
|
|
91
|
-
background: transparent;
|
|
92
|
-
color: theme(detail-text-color-alt, text-color-alt);
|
|
93
|
-
|
|
94
|
-
&:hover {
|
|
95
|
-
color: theme(detail-text-color, text-color);
|
|
96
|
-
background: theme(share-button-hover-background, content);
|
|
97
|
-
box-shadow: 0 0 0 2px theme(share-button-hover-background, content);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
83
|
.context-menu {
|
|
102
84
|
z-index: 10;
|
|
103
85
|
position: absolute;
|
|
104
86
|
bottom: calc(100% + margin(0.5));
|
|
105
|
-
|
|
87
|
+
left: 0;
|
|
106
88
|
max-width: margin(15);
|
|
107
89
|
border-radius: $border-radius;
|
|
108
90
|
background: theme(detail-background, lighter);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import SimilarRail from './Rails/SimilarRail.svelte'
|
|
8
8
|
import TitlePoster from './TitlePoster.svelte'
|
|
9
9
|
import Share from './Share.svelte'
|
|
10
|
+
import Trailer from './Trailer.svelte'
|
|
10
11
|
import { t } from '$lib/localization'
|
|
11
12
|
import type { TitleData } from '$lib/types/title'
|
|
12
13
|
import { heading } from '$lib/actions/heading'
|
|
@@ -36,26 +37,27 @@
|
|
|
36
37
|
|
|
37
38
|
<div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
|
|
38
39
|
|
|
39
|
-
<div class="
|
|
40
|
-
<div class="
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</div>
|
|
40
|
+
<div class="info">
|
|
41
|
+
<div class="imdb">
|
|
42
|
+
<IconIMDb />
|
|
43
|
+
{title.imdb_score}
|
|
44
|
+
</div>
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
<Genres genres={title.genres} />
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
<div>{title.year}</div>
|
|
49
|
+
<div class="capitalize">{t(`Type: ${title.type}`)}</div>
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
{#if !small && title.length}
|
|
52
|
+
<div>{title.length} {t('Minutes')}</div>
|
|
53
|
+
{/if}
|
|
54
|
+
</div>
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
<div class="actions">
|
|
57
|
+
{#if title.embeddable_url}
|
|
58
|
+
<Trailer title={title} />
|
|
57
59
|
<Share title={title.title} url={titleUrl(title)} />
|
|
58
|
-
|
|
60
|
+
{/if}
|
|
59
61
|
</div>
|
|
60
62
|
</div>
|
|
61
63
|
|
|
@@ -146,11 +148,6 @@
|
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
.row {
|
|
150
|
-
display: flex;
|
|
151
|
-
align-items: flex-start;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
151
|
.info {
|
|
155
152
|
display: flex;
|
|
156
153
|
flex-wrap: wrap;
|
|
@@ -182,8 +179,10 @@
|
|
|
182
179
|
}
|
|
183
180
|
}
|
|
184
181
|
|
|
185
|
-
.
|
|
186
|
-
|
|
182
|
+
.actions {
|
|
183
|
+
display: flex;
|
|
184
|
+
gap: margin(0.5);
|
|
185
|
+
margin-top: margin(0.5);
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
.background {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
|
+
import { t } from '$lib/localization'
|
|
4
|
+
import { track } from '$lib/tracking'
|
|
5
|
+
import { openTrailerOverlay } from '$lib/trailer'
|
|
6
|
+
import type { TitleData } from '$lib/types/title'
|
|
7
|
+
import Button from './Button.svelte'
|
|
8
|
+
import IconPlay from './Icons/IconPlay.svelte'
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
title: TitleData
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { title }: Props = $props()
|
|
15
|
+
|
|
16
|
+
function onclick() {
|
|
17
|
+
openTrailerOverlay(title)
|
|
18
|
+
track(TrackingEvent.TrailerClick, title)
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<Button {onclick}>
|
|
23
|
+
<IconPlay />
|
|
24
|
+
{t('Watch Trailer')}
|
|
25
|
+
</Button>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade } from 'svelte/transition'
|
|
3
|
+
import IconClose from './Icons/IconClose.svelte'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
embeddable_url?: string,
|
|
7
|
+
onclose: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
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
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<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}
|
|
30
|
+
|
|
31
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
32
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
33
|
+
<div class="backdrop" onclick={onclose} data-testid="backdrop"></div>
|
|
34
|
+
|
|
35
|
+
<button class="close" onclick={onclose} aria-label="Close">
|
|
36
|
+
<IconClose size={24} />
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<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
|
+
.overlay {
|
|
53
|
+
z-index: 2147483647; // As high as she goes
|
|
54
|
+
box-sizing: border-box;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
position: fixed;
|
|
59
|
+
top: 0;
|
|
60
|
+
right: 0;
|
|
61
|
+
bottom: 0;
|
|
62
|
+
left: 0;
|
|
63
|
+
background: theme(detail-backdrop, rgba(0, 0, 0, 0.95));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.backdrop {
|
|
67
|
+
z-index: 0;
|
|
68
|
+
position: fixed;
|
|
69
|
+
top: 0;
|
|
70
|
+
right: 0;
|
|
71
|
+
bottom: 0;
|
|
72
|
+
left: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.close {
|
|
76
|
+
appearance: none;
|
|
77
|
+
z-index: 1;
|
|
78
|
+
position: fixed;
|
|
79
|
+
top: margin(2);
|
|
80
|
+
right: margin(2);
|
|
81
|
+
padding: 0;
|
|
82
|
+
margin: 0;
|
|
83
|
+
border: 0;
|
|
84
|
+
background: transparent;
|
|
85
|
+
color: white;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
|
|
88
|
+
&:hover {
|
|
89
|
+
transform: scale(1.1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&:active {
|
|
93
|
+
transform: scale(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
</style>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
2
|
+
import { closeCurrentModal, destroyAllModals, destroyCurrentModal, getAllModals, getPreviousModal, goBackToPreviousModal, openModal } from '$lib/modal'
|
|
3
|
+
import { linkInjections, title } from '$lib/fakeData'
|
|
4
|
+
import { mount, unmount } from 'svelte'
|
|
5
|
+
import ParticipantModal from '../../routes/components/ParticipantModal.svelte'
|
|
6
|
+
import TitleModal from '../../routes/components/TitleModal.svelte'
|
|
7
|
+
import { closeTrailerOverlay, openTrailerOverlay } from '$lib/trailer'
|
|
8
|
+
|
|
9
|
+
vi.mock('svelte', () => ({
|
|
10
|
+
mount: vi.fn(),
|
|
11
|
+
unmount: vi.fn(),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
const titleWithTrailer = { ...title, embeddable_url: 'abc' }
|
|
15
|
+
|
|
16
|
+
describe('modal.js', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.resetAllMocks()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('openTrailerOverlay', () => {
|
|
22
|
+
it('Should call mount with given title embeddable_url', () => {
|
|
23
|
+
openTrailerOverlay(titleWithTrailer)
|
|
24
|
+
|
|
25
|
+
expect(mount).toHaveBeenCalledWith(
|
|
26
|
+
expect.any(Function),
|
|
27
|
+
expect.objectContaining({
|
|
28
|
+
target: expect.anything(),
|
|
29
|
+
props: expect.objectContaining({
|
|
30
|
+
onclose: expect.any(Function),
|
|
31
|
+
embeddable_url: titleWithTrailer.embeddable_url,
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('closeTrailerOverlay', () => {
|
|
39
|
+
it('Should call unmount if component was previously mounted', () => {
|
|
40
|
+
vi.mocked(mount).mockReturnValueOnce({})
|
|
41
|
+
|
|
42
|
+
openTrailerOverlay(titleWithTrailer)
|
|
43
|
+
closeTrailerOverlay()
|
|
44
|
+
|
|
45
|
+
expect(unmount).toHaveBeenCalled()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('Should not call unmount if component was not previously mounted', () => {
|
|
49
|
+
vi.mocked(mount).mockReturnValueOnce({})
|
|
50
|
+
|
|
51
|
+
closeTrailerOverlay()
|
|
52
|
+
|
|
53
|
+
expect(unmount).not.toHaveBeenCalled()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { render, fireEvent } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import Button from '../../../routes/components/Button.svelte'
|
|
5
|
+
|
|
6
|
+
describe('Button.svelte', () => {
|
|
7
|
+
it('Should use filled class by default', () => {
|
|
8
|
+
const { getByRole } = render(Button)
|
|
9
|
+
|
|
10
|
+
expect(getByRole('button').classList).toContain('filled')
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('Should use border class when border variant is given', () => {
|
|
14
|
+
const { getByRole } = render(Button, { variant: 'border' })
|
|
15
|
+
|
|
16
|
+
expect(getByRole('button').classList).not.toContain('filled')
|
|
17
|
+
expect(getByRole('button').classList).toContain('border')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('Should fire given onclick function on click', async () => {
|
|
21
|
+
const onclick = vi.fn()
|
|
22
|
+
const { getByRole } = render(Button, { onclick })
|
|
23
|
+
|
|
24
|
+
await fireEvent.click(getByRole('button'))
|
|
25
|
+
|
|
26
|
+
expect(onclick).toHaveBeenCalled()
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -16,57 +16,57 @@ vi.mock('$lib/tracking', () => ({
|
|
|
16
16
|
|
|
17
17
|
describe('Share.svelte', () => {
|
|
18
18
|
it('Should open context menu on click', async () => {
|
|
19
|
-
const {
|
|
19
|
+
const { getByText, queryByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
20
20
|
|
|
21
21
|
expect(queryByText('Copy URL')).not.toBeTruthy()
|
|
22
22
|
expect(queryByText('Email')).not.toBeTruthy()
|
|
23
23
|
|
|
24
|
-
await fireEvent.click(
|
|
24
|
+
await fireEvent.click(getByText('Share'))
|
|
25
25
|
|
|
26
26
|
expect(queryByText('Copy URL')).toBeTruthy()
|
|
27
27
|
expect(queryByText('Email')).toBeTruthy()
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
it('Should close context menu on click of items', async () => {
|
|
31
|
-
const {
|
|
31
|
+
const { getByText, queryByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
32
32
|
|
|
33
|
-
await fireEvent.click(
|
|
33
|
+
await fireEvent.click(getByText('Share'))
|
|
34
34
|
await fireEvent.click(getByText('Copy URL'))
|
|
35
35
|
|
|
36
36
|
expect(queryByText('Copy URL')).not.toBeTruthy()
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
it('Should close context menu on click of body', async () => {
|
|
40
|
-
const {
|
|
40
|
+
const { getByText, queryByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
41
41
|
|
|
42
|
-
await fireEvent.click(
|
|
42
|
+
await fireEvent.click(getByText('Share'))
|
|
43
43
|
await fireEvent.click(document.body)
|
|
44
44
|
|
|
45
45
|
expect(queryByText('Copy URL')).not.toBeTruthy()
|
|
46
46
|
})
|
|
47
47
|
|
|
48
48
|
it('Should fire copyToClipboard on click of button', async () => {
|
|
49
|
-
const {
|
|
49
|
+
const { getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
50
50
|
|
|
51
|
-
await fireEvent.click(
|
|
51
|
+
await fireEvent.click(getByText('Share'))
|
|
52
52
|
await fireEvent.click(getByText('Copy URL'))
|
|
53
53
|
|
|
54
54
|
expect(copyToClipboard).toHaveBeenCalledWith('some-url?utm_source=tpi')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
it('Should fire track function on click of copy URL button', async () => {
|
|
58
|
-
const {
|
|
58
|
+
const { getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
59
59
|
|
|
60
|
-
await fireEvent.click(
|
|
60
|
+
await fireEvent.click(getByText('Share'))
|
|
61
61
|
await fireEvent.click(getByText('Copy URL'))
|
|
62
62
|
|
|
63
63
|
expect(track).toHaveBeenCalledWith(TrackingEvent.ShareTitle, null, { title: 'Some title', url: 'http://localhost:3000/', method: 'copy' })
|
|
64
64
|
})
|
|
65
65
|
|
|
66
66
|
it('Should fire track function on click of email button', async () => {
|
|
67
|
-
const {
|
|
67
|
+
const { getByText } = render(Share, { title: 'Some title', url: 'some-url' })
|
|
68
68
|
|
|
69
|
-
await fireEvent.click(
|
|
69
|
+
await fireEvent.click(getByText('Share'))
|
|
70
70
|
await fireEvent.click(getByText('Email'))
|
|
71
71
|
|
|
72
72
|
expect(track).toHaveBeenCalledWith(TrackingEvent.ShareTitle, null, { title: 'Some title', url: 'http://localhost:3000/', method: 'email' })
|
|
@@ -91,4 +91,16 @@ describe('Title.svelte', () => {
|
|
|
91
91
|
expect(fetchParticipantsForTitle).toHaveBeenCalled()
|
|
92
92
|
expect(fetchSimilarTitles).toHaveBeenCalled()
|
|
93
93
|
})
|
|
94
|
+
|
|
95
|
+
it('Should show trailer button when embeddable_url is given', () => {
|
|
96
|
+
const { getByText } = render(Title, { title: { ...title, embeddable_url: 'some-url' } })
|
|
97
|
+
|
|
98
|
+
expect(getByText('Watch trailer')).toBeTruthy()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('Should not show trailer button when embeddable_url is not given', () => {
|
|
102
|
+
const { queryByText } = render(Title, { title })
|
|
103
|
+
|
|
104
|
+
expect(queryByText('Watch Trailer')).not.toBeTruthy()
|
|
105
|
+
})
|
|
94
106
|
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { render, fireEvent } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import Trailer from '../../../routes/components/Trailer.svelte'
|
|
5
|
+
import { title } from '$lib/fakeData'
|
|
6
|
+
import { openTrailerOverlay } from '$lib/trailer'
|
|
7
|
+
import { track } from '$lib/tracking'
|
|
8
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
9
|
+
|
|
10
|
+
vi.mock('$lib/trailer', () => ({
|
|
11
|
+
openTrailerOverlay: vi.fn(),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
vi.mock('$lib/tracking', () => ({
|
|
15
|
+
track: vi.fn(),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
describe('Trailer.svelte', () => {
|
|
19
|
+
it('Should fire openTrailerOverlay with given title on click', async () => {
|
|
20
|
+
const { getByRole } = render(Trailer, { title })
|
|
21
|
+
|
|
22
|
+
await fireEvent.click(getByRole('button'))
|
|
23
|
+
|
|
24
|
+
expect(openTrailerOverlay).toHaveBeenCalledWith(title)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('Should fire tracking event on click', async () => {
|
|
28
|
+
const { getByRole } = render(Trailer, { title })
|
|
29
|
+
|
|
30
|
+
await fireEvent.click(getByRole('button'))
|
|
31
|
+
|
|
32
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.TrailerClick, title)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { fireEvent, render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import YouTubeEmbedOverlay from '../../../routes/components/YouTubeEmbedOverlay.svelte'
|
|
5
|
+
|
|
6
|
+
describe('YouTubeEmbedOverlay.svelte', () => {
|
|
7
|
+
it('Should render embed iframe with given video url', () => {
|
|
8
|
+
const { container } = render(YouTubeEmbedOverlay, { embeddable_url: 'youtube.com/watch?v=abc', onclose: () => null })
|
|
9
|
+
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
expect(container.querySelector('iframe').src).toBe('https://www.youtube.com/embed/abc?autoplay=true')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('Should render error message if embeddable_url is invalid', () => {
|
|
15
|
+
const { container, getByText } = render(YouTubeEmbedOverlay, { embeddable_url: '-', onclose: () => null })
|
|
16
|
+
|
|
17
|
+
expect(container.querySelector('iframe')).not.toBeTruthy()
|
|
18
|
+
expect(getByText('Something went wrong')).toBeTruthy()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Should fire given onclose function on click of close button and backdrop', async () => {
|
|
22
|
+
const onclose = vi.fn()
|
|
23
|
+
const { getByLabelText, getByTestId } = render(YouTubeEmbedOverlay, { embeddable_url: '-', onclose })
|
|
24
|
+
|
|
25
|
+
await fireEvent.click(getByLabelText('Close'))
|
|
26
|
+
expect(onclose).toHaveBeenCalledTimes(1)
|
|
27
|
+
|
|
28
|
+
await fireEvent.click(getByTestId('backdrop'))
|
|
29
|
+
expect(onclose).toHaveBeenCalledTimes(2)
|
|
30
|
+
})
|
|
31
|
+
})
|