@playpilot/tpi 5.23.4 → 5.24.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 +11 -11
- package/events.md +1 -0
- package/package.json +1 -1
- package/src/lib/api/participants.ts +16 -0
- package/src/lib/api/titles.ts +9 -0
- package/src/lib/data/translations.ts +5 -0
- package/src/lib/enums/TrackingEvent.ts +1 -0
- package/src/lib/modal.ts +20 -7
- package/src/routes/components/ListTitle.svelte +5 -5
- package/src/routes/components/Modal.svelte +1 -1
- package/src/routes/components/Participant.svelte +61 -4
- package/src/routes/components/Rails/ParticipantsRail.svelte +19 -9
- package/src/routes/components/Rails/SimilarRail.svelte +2 -9
- package/src/routes/components/Rails/TitlesRail.svelte +5 -1
- package/src/routes/components/Title.svelte +7 -7
- package/src/routes/components/TitlePoster.svelte +4 -1
- package/src/tests/lib/modal.test.js +7 -0
- package/src/tests/routes/components/Participant.test.js +76 -0
- package/src/tests/routes/components/ParticipantModal.test.js +4 -0
- package/src/tests/routes/components/Rails/ParticipantsRail.test.js +39 -9
- package/src/tests/routes/components/Rails/SimilarRail.test.js +26 -0
- package/src/tests/routes/components/Rails/TitlesRail.test.js +1 -1
- package/src/tests/routes/components/Title.test.js +17 -0
- package/src/tests/routes/components/TitleModal.test.js +15 -9
- package/src/tests/routes/components/TitlePopover.test.js +8 -0
package/events.md
CHANGED
|
@@ -37,6 +37,7 @@ Event | Action | Info | Payload
|
|
|
37
37
|
`ali_participant_modal_view` | _Fires any time a title modal is viewed_ | The title modal opens when viewing a participant modal both on desktop and mobile | `participant` (name of the participant)
|
|
38
38
|
`ali_participant_modal_close` | _Fires any time a title modal is closed_ | | `participant` (name of the participant) `time_spent` (time between modal_view and modal_close milliseconds)
|
|
39
39
|
'ali_similar_title_click' | _Fires any time a similar titles rail item is clicked_ | Title | `title_source` (original name of the title the rail item was clicked in)
|
|
40
|
+
'ali_participant_click' | _Fires any time a participants rail item is clicked_ | null | `title_source` (original name of the title the rail item was clicked in), `participant` (name of the clicked participant)
|
|
40
41
|
|
|
41
42
|
### Popover
|
|
42
43
|
Event | Action | Info | Payload
|
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getApiToken } from '$lib/token'
|
|
2
|
+
import type { ParticipantData } from '$lib/types/participant'
|
|
3
|
+
import type { TitleData } from '../types/title'
|
|
4
|
+
import { api } from './api'
|
|
5
|
+
|
|
6
|
+
export async function fetchTitlesForParticipant(participant: ParticipantData, { page = 1 }: { page?: number } = {}): Promise<TitleData[]> {
|
|
7
|
+
const response = await api<{ results: TitleData[] }>(`/titles/browse?api-token=${getApiToken()}&participant_sid=${participant.sid}&page=${page}`)
|
|
8
|
+
|
|
9
|
+
return response.results
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function fetchParticipantsForTitle(title: TitleData): Promise<ParticipantData[]> {
|
|
13
|
+
const response = await api<{ results: ParticipantData[] }>(`/participants/browse?api-token=${getApiToken()}&title_sid=${title.sid}`)
|
|
14
|
+
|
|
15
|
+
return response.results
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getApiToken } from '$lib/token'
|
|
2
|
+
import type { TitleData } from '../types/title'
|
|
3
|
+
import { api } from './api'
|
|
4
|
+
|
|
5
|
+
export async function fetchSimilarTitles(title: TitleData): Promise<TitleData[]> {
|
|
6
|
+
const response = await api<{ results: TitleData[] }>(`/titles/browse?api-token=${getApiToken()}&related_to_sid=${title.sid}`)
|
|
7
|
+
|
|
8
|
+
return response.results
|
|
9
|
+
}
|
|
@@ -116,6 +116,11 @@ export const translations = {
|
|
|
116
116
|
[Language.Swedish]: 'E-post',
|
|
117
117
|
[Language.Danish]: 'E-mail',
|
|
118
118
|
},
|
|
119
|
+
'Show More': {
|
|
120
|
+
[Language.English]: 'Show more',
|
|
121
|
+
[Language.Swedish]: 'Visa mer',
|
|
122
|
+
[Language.Danish]: 'Vis mere',
|
|
123
|
+
},
|
|
119
124
|
|
|
120
125
|
// Genres
|
|
121
126
|
'All': {
|
package/src/lib/modal.ts
CHANGED
|
@@ -12,7 +12,7 @@ type ModalType = 'title' | 'participant'
|
|
|
12
12
|
|
|
13
13
|
type Modal = {
|
|
14
14
|
injection?: LinkInjection | null
|
|
15
|
-
component
|
|
15
|
+
component?: object
|
|
16
16
|
type: ModalType
|
|
17
17
|
data: TitleData | ParticipantData | null
|
|
18
18
|
scrollPosition?: number
|
|
@@ -25,8 +25,8 @@ const modals: Modal[] = []
|
|
|
25
25
|
* Ignore clicks that used modifier keys or that were not left click.
|
|
26
26
|
*/
|
|
27
27
|
export function openModal(
|
|
28
|
-
{ type = 'title', event = null, injection = null, data = null, scrollPosition = 0 }:
|
|
29
|
-
{ type?: ModalType, event?: MouseEvent | null, injection?: LinkInjection | null, data?: TitleData | ParticipantData | null, scrollPosition?: number } = {}): void {
|
|
28
|
+
{ type = 'title', event = null, injection = null, data = null, scrollPosition = 0, returnToTitle = null }:
|
|
29
|
+
{ type?: ModalType, event?: MouseEvent | null, injection?: LinkInjection | null, data?: TitleData | ParticipantData | null, scrollPosition?: number, returnToTitle?: TitleData | null } = {}): void {
|
|
30
30
|
if (event && isHoldingSpecialKey(event)) return
|
|
31
31
|
|
|
32
32
|
event?.preventDefault()
|
|
@@ -35,10 +35,23 @@ export function openModal(
|
|
|
35
35
|
|
|
36
36
|
const target = getPlayPilotWrapperElement()
|
|
37
37
|
const sharedProps = { initialScrollPosition: scrollPosition }
|
|
38
|
-
const component = type
|
|
39
|
-
mount(TitleModal, { target, props: { title: data as TitleData, ...sharedProps } }) :
|
|
40
|
-
mount(ParticipantModal, { target, props: { participant: data as ParticipantData, ...sharedProps } })
|
|
38
|
+
const component = getModalComponentByType({ type, target, data, props: sharedProps })
|
|
41
39
|
|
|
40
|
+
// When a return title is given it is added to the list of modals but is not actually opened.
|
|
41
|
+
// This is only so that the user can return the title they opened another modal from. This will be from the popover.
|
|
42
|
+
// For example title in popover -> open similar title -> go back to title from popover, but in modal
|
|
43
|
+
if (returnToTitle) addModalToList({ type, injection, data: returnToTitle })
|
|
44
|
+
|
|
45
|
+
addModalToList({ type, injection, data, scrollPosition, component })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getModalComponentByType({ type = 'title', target, data, props = {} }: { type: ModalType, target: Element, data: TitleData | ParticipantData | null, props?: Record<string, any> }) {
|
|
49
|
+
return type === 'title' ?
|
|
50
|
+
mount(TitleModal, { target, props: { title: data as TitleData, ...props } }) :
|
|
51
|
+
mount(ParticipantModal, { target, props: { participant: data as ParticipantData, ...props } })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function addModalToList({ type = 'title', injection = null, data, scrollPosition = 0, component }: Modal) {
|
|
42
55
|
modals.push({ type, injection, data, scrollPosition, component })
|
|
43
56
|
}
|
|
44
57
|
|
|
@@ -48,7 +61,7 @@ export function closeCurrentModal(outro: boolean = true): void {
|
|
|
48
61
|
|
|
49
62
|
saveCurrentModalScrollPosition()
|
|
50
63
|
|
|
51
|
-
unmount(modals[modals.length - 1].component
|
|
64
|
+
unmount(modals[modals.length - 1].component!, { outro })
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
/** Unmount the last modal is the list of modals and remove it from the list */
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
}
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
|
-
<a class="title" href={titleUrl(title)} {onclick}>
|
|
32
|
+
<a class="title" href={titleUrl(title)} {onclick} data-testid="title">
|
|
33
33
|
<div class="poster">
|
|
34
|
-
<TitlePoster {title} width={30} height={43} />
|
|
34
|
+
<TitlePoster {title} width={30} height={43} lazy />
|
|
35
35
|
</div>
|
|
36
36
|
|
|
37
37
|
<div class="content">
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
display: flex;
|
|
90
90
|
align-items: center;
|
|
91
91
|
width: 100%;
|
|
92
|
-
background: var(--playpilot-list-item-background, var(--playpilot-lighter));
|
|
92
|
+
background: var(--playpilot-list-item-background, var(--playpilot-playlink-background, var(--playpilot-lighter)));
|
|
93
93
|
padding: margin(0.5);
|
|
94
94
|
border: 0;
|
|
95
95
|
border-radius: var(--playpilot-list-item-border-radius, margin(0.5));
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
font-style: normal !important;
|
|
98
98
|
|
|
99
99
|
&:hover {
|
|
100
|
-
|
|
100
|
+
filter: var(--playpilot-list-item-hover-filter, var(--playpilot-playlink-hover-filter, brightness(1.1)));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
&:last-child {
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
width: margin(4.125);
|
|
113
113
|
aspect-ratio: 2/3;
|
|
114
114
|
border-radius: var(--playpilot-detail-image-border-radius, margin(0.5));
|
|
115
|
-
background: var(--playpilot-detail-image-background, var(--playpilot-content));
|
|
115
|
+
background: var(--playpilot-detail-image-background, var(--playpilot-genre-background, var(--playpilot-content)));
|
|
116
116
|
overflow: hidden;
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte'
|
|
2
3
|
import { heading } from '$lib/actions/heading'
|
|
4
|
+
import { fetchTitlesForParticipant } from '$lib/api/participants'
|
|
3
5
|
import { SplitTest } from '$lib/enums/SplitTest'
|
|
4
6
|
import { openModal } from '$lib/modal'
|
|
5
7
|
import { trackSplitTestView } from '$lib/splitTest'
|
|
6
8
|
import type { ParticipantData } from '$lib/types/participant'
|
|
9
|
+
import type { TitleData } from '$lib/types/title'
|
|
7
10
|
import ListTitle from './ListTitle.svelte'
|
|
11
|
+
import { t } from '$lib/localization'
|
|
8
12
|
|
|
9
13
|
interface Props {
|
|
10
14
|
participant: ParticipantData
|
|
@@ -14,12 +18,37 @@
|
|
|
14
18
|
|
|
15
19
|
const { name, birth_date, death_date } = $derived(participant)
|
|
16
20
|
|
|
21
|
+
const pageSize = 30
|
|
22
|
+
|
|
17
23
|
trackSplitTestView(SplitTest.ParticipantPlaylinkFormat)
|
|
18
24
|
|
|
25
|
+
let titles: TitleData[] = $state([])
|
|
26
|
+
let page = $state(1)
|
|
27
|
+
let hasMorePages = $state(true)
|
|
28
|
+
let loading = $state(true)
|
|
29
|
+
|
|
30
|
+
onMount(loadMore)
|
|
31
|
+
|
|
19
32
|
function formatDate(dateString: string): string {
|
|
20
33
|
const date = new Date(dateString)
|
|
21
34
|
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' })
|
|
22
35
|
}
|
|
36
|
+
|
|
37
|
+
async function loadMore() {
|
|
38
|
+
loading = true
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const results = await fetchTitlesForParticipant(participant, { page })
|
|
42
|
+
|
|
43
|
+
titles = [...titles, ...results]
|
|
44
|
+
hasMorePages = results?.length >= pageSize
|
|
45
|
+
} catch {
|
|
46
|
+
hasMorePages = false
|
|
47
|
+
} finally {
|
|
48
|
+
loading = false
|
|
49
|
+
page += 1
|
|
50
|
+
}
|
|
51
|
+
}
|
|
23
52
|
</script>
|
|
24
53
|
|
|
25
54
|
<div class="header">
|
|
@@ -36,11 +65,17 @@
|
|
|
36
65
|
<div class="heading small" use:heading={3} id="credits">Credits</div>
|
|
37
66
|
|
|
38
67
|
<div class="list">
|
|
39
|
-
{#each
|
|
40
|
-
{
|
|
41
|
-
<ListTitle {title} onclick={(event) => openModal({ event, data: title })} />
|
|
42
|
-
{/if}
|
|
68
|
+
{#each titles as title}
|
|
69
|
+
<ListTitle {title} onclick={(event) => openModal({ event, data: title })} />
|
|
43
70
|
{/each}
|
|
71
|
+
|
|
72
|
+
{#if loading}
|
|
73
|
+
{#each { length: 6 }}
|
|
74
|
+
<div class="skeleton" data-testid="skeleton"></div>
|
|
75
|
+
{/each}
|
|
76
|
+
{:else if hasMorePages}
|
|
77
|
+
<button class="more" onclick={loadMore}>{t('Show More')}</button>
|
|
78
|
+
{/if}
|
|
44
79
|
</div>
|
|
45
80
|
</div>
|
|
46
81
|
|
|
@@ -89,4 +124,26 @@
|
|
|
89
124
|
flex-direction: column;
|
|
90
125
|
gap: margin(0.5);
|
|
91
126
|
}
|
|
127
|
+
|
|
128
|
+
.skeleton {
|
|
129
|
+
min-height: margin(7.25);
|
|
130
|
+
border-radius: var(--playpilot-playlink-border-radius, margin(0.5));
|
|
131
|
+
background: var(--playpilot-list-item-background, var(--playpilot-lighter));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.more {
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
appearance: none;
|
|
137
|
+
padding: margin(0.5) margin(1);
|
|
138
|
+
border: 0;
|
|
139
|
+
border-radius: var(--playpilot-participants-load-more-border-radius, var(--playpilot-list-item-border-radius, margin(0.5)));
|
|
140
|
+
background: var(--playpilot-participants-load-more-background, var(--playpilot-button-background, var(--playpilot-content)));
|
|
141
|
+
color: inherit;
|
|
142
|
+
font-size: inherit;
|
|
143
|
+
font-family: inherit;
|
|
144
|
+
|
|
145
|
+
&:hover {
|
|
146
|
+
filter: brightness(1.2);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
92
149
|
</style>
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { fetchParticipantsForTitle } from '$lib/api/participants'
|
|
3
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
4
|
import { openModal } from '$lib/modal'
|
|
5
|
+
import { track } from '$lib/tracking'
|
|
4
6
|
import type { ParticipantData } from '$lib/types/participant'
|
|
7
|
+
import type { TitleData } from '$lib/types/title'
|
|
5
8
|
import Rail from './Rail.svelte'
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
interface Props {
|
|
11
|
+
title: TitleData
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { title }: Props = $props()
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
function onclick(event: MouseEvent, participant: ParticipantData): void {
|
|
17
|
+
openModal({ event, type: 'participant', data: participant })
|
|
18
|
+
track(TrackingEvent.ParticipantClick, null, { title_source: title.original_title, participant: participant.name })
|
|
12
19
|
}
|
|
13
20
|
</script>
|
|
14
21
|
|
|
15
|
-
{#await
|
|
22
|
+
{#await fetchParticipantsForTitle(title)}
|
|
16
23
|
<Rail heading="Cast">
|
|
17
24
|
{#each { length: 5 }}
|
|
18
25
|
<div class="participant"></div>
|
|
@@ -22,10 +29,12 @@
|
|
|
22
29
|
{#if participants?.length}
|
|
23
30
|
<Rail heading="Cast">
|
|
24
31
|
{#each participants.slice(0, 15) as participant}
|
|
25
|
-
<button class="participant" data-testid="participant" onclick={event =>
|
|
32
|
+
<button class="participant" data-testid="participant" onclick={event => onclick(event, participant)}>
|
|
26
33
|
<span class="truncate">{participant.name}</span>
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
{#if participant.character}
|
|
36
|
+
<div class="character truncate">{participant.character}</div>
|
|
37
|
+
{/if}
|
|
29
38
|
</button>
|
|
30
39
|
{/each}
|
|
31
40
|
</Rail>
|
|
@@ -34,7 +43,8 @@
|
|
|
34
43
|
|
|
35
44
|
<style lang="scss">
|
|
36
45
|
.participant {
|
|
37
|
-
display:
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
38
48
|
flex: 0 0 10rem;
|
|
39
49
|
width: 10rem;
|
|
40
50
|
min-height: margin(3.375); // Matches 54 pixels, the height of a card with both name and character
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { fetchSimilarTitles } from '$lib/api/titles'
|
|
2
3
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
4
|
import { track } from '$lib/tracking'
|
|
4
5
|
import type { TitleData } from '$lib/types/title'
|
|
@@ -10,15 +11,7 @@
|
|
|
10
11
|
|
|
11
12
|
const { title }: Props = $props()
|
|
12
13
|
|
|
13
|
-
const titles =
|
|
14
|
-
|
|
15
|
-
async function fetchTitles(): Promise<TitleData[]> {
|
|
16
|
-
// This is just a fake loading state for now
|
|
17
|
-
await new Promise(res => setTimeout(res, 500))
|
|
18
|
-
|
|
19
|
-
// Imagine this being a fetch request that returns titles instead.
|
|
20
|
-
return (window.PlayPilotLinkInjections?.evaluated_link_injections?.map(i => i.title_details) || []) as TitleData[]
|
|
21
|
-
}
|
|
14
|
+
const titles = fetchSimilarTitles(title)
|
|
22
15
|
</script>
|
|
23
16
|
|
|
24
17
|
<TitlesRail {titles} heading="Similar movies & shows" onclick={(targetTitle) => track(TrackingEvent.SimilarTitleClick, targetTitle, { title_source: title.original_title })} />
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { TitleData } from '$lib/types/title'
|
|
5
5
|
import { openModal } from '$lib/modal'
|
|
6
6
|
import { titleUrl } from '$lib/routes'
|
|
7
|
+
import { getContext } from 'svelte'
|
|
7
8
|
|
|
8
9
|
interface Props {
|
|
9
10
|
titles: Promise<TitleData[]> | TitleData[]
|
|
@@ -13,8 +14,11 @@
|
|
|
13
14
|
|
|
14
15
|
const { titles, heading = '', onclick = () => null }: Props = $props()
|
|
15
16
|
|
|
17
|
+
const isPopover = getContext('scope') === 'popover'
|
|
18
|
+
const returnToTitle: TitleData | null = isPopover ? getContext('title') : null
|
|
19
|
+
|
|
16
20
|
function openTitle(event: MouseEvent, title: TitleData): void {
|
|
17
|
-
openModal({ event, data: title })
|
|
21
|
+
openModal({ event, data: title, returnToTitle })
|
|
18
22
|
onclick(title)
|
|
19
23
|
}
|
|
20
24
|
</script>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { heading } from '$lib/actions/heading'
|
|
13
13
|
import { removeImageUrlPrefix } from '$lib/image'
|
|
14
14
|
import { titleUrl } from '$lib/routes'
|
|
15
|
+
import { setContext } from 'svelte'
|
|
15
16
|
|
|
16
17
|
interface Props {
|
|
17
18
|
title: TitleData
|
|
@@ -20,15 +21,17 @@
|
|
|
20
21
|
|
|
21
22
|
const { title, small = false }: Props = $props()
|
|
22
23
|
|
|
24
|
+
setContext('title', title)
|
|
25
|
+
|
|
23
26
|
let posterLoaded = $state(false)
|
|
24
27
|
let backgroundLoaded = $state(false)
|
|
25
28
|
let useBackgroundFallback = $state(false)
|
|
26
29
|
</script>
|
|
27
30
|
|
|
28
|
-
<div class="content" class:small
|
|
31
|
+
<div class="content" class:small>
|
|
29
32
|
<div class="header">
|
|
30
33
|
<div class="poster" class:loaded={posterLoaded}>
|
|
31
|
-
<TitlePoster {title} onload={() => posterLoaded = true} />
|
|
34
|
+
<TitlePoster {title} onload={() => posterLoaded = true} lazy={false} />
|
|
32
35
|
</div>
|
|
33
36
|
|
|
34
37
|
<div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
|
|
@@ -63,11 +66,8 @@
|
|
|
63
66
|
<Description text={title.description} blurb={title.blurb} />
|
|
64
67
|
{/if}
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
{
|
|
68
|
-
<ParticipantsRail />
|
|
69
|
-
<SimilarRail {title} />
|
|
70
|
-
{/if}
|
|
69
|
+
<ParticipantsRail {title} />
|
|
70
|
+
<SimilarRail {title} />
|
|
71
71
|
</div>
|
|
72
72
|
</div>
|
|
73
73
|
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
title: TitleData
|
|
8
8
|
width?: number
|
|
9
9
|
height?: number
|
|
10
|
+
lazy?: boolean
|
|
10
11
|
onload?: () => void
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const { title, width = 96, height = 144, onload = () => null }: Props = $props()
|
|
14
|
+
const { title, width = 96, height = 144, lazy = true, onload = () => null }: Props = $props()
|
|
14
15
|
</script>
|
|
15
16
|
|
|
16
17
|
<img
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
{width}
|
|
20
21
|
{height}
|
|
21
22
|
onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl}
|
|
23
|
+
loading={lazy ? 'lazy' : null}
|
|
22
24
|
{onload} />
|
|
23
25
|
|
|
24
26
|
<style lang="scss">
|
|
@@ -26,5 +28,6 @@
|
|
|
26
28
|
display: block;
|
|
27
29
|
width: 100%;
|
|
28
30
|
height: auto;
|
|
31
|
+
color: var(--playpilot-detail-text-color, var(--playpilot-text-color-alt));
|
|
29
32
|
}
|
|
30
33
|
</style>
|
|
@@ -43,6 +43,13 @@ describe('modal.js', () => {
|
|
|
43
43
|
|
|
44
44
|
expect(mount).not.toHaveBeenCalled()
|
|
45
45
|
})
|
|
46
|
+
|
|
47
|
+
it('Should add additional modal to the stack when returnToTitle is given', () => {
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
openModal({ ...modal, returnToTitle: title })
|
|
50
|
+
|
|
51
|
+
expect(getAllModals()).toHaveLength(2)
|
|
52
|
+
})
|
|
46
53
|
})
|
|
47
54
|
|
|
48
55
|
describe('closeCurrentModal', () => {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { fireEvent, render, waitFor } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import Participant from '../../../routes/components/Participant.svelte'
|
|
5
|
+
import { participants, title } from '$lib/fakeData'
|
|
6
|
+
import { fetchTitlesForParticipant } from '$lib/api/participants'
|
|
7
|
+
|
|
8
|
+
vi.mock('$lib/tracking', () => ({
|
|
9
|
+
track: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
vi.mock('$lib/api/participants', () => ({
|
|
13
|
+
fetchTitlesForParticipant: vi.fn(),
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
describe('Participant.svelte', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.resetAllMocks()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Should call fetchTitlesForParticipant on mount', () => {
|
|
22
|
+
render(Participant, { participant: participants[0] })
|
|
23
|
+
|
|
24
|
+
expect(fetchTitlesForParticipant).toHaveBeenCalledWith(participants[0], { page: 1 })
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('Should render fetched titles after showing skeletons', async () => {
|
|
28
|
+
vi.mocked(fetchTitlesForParticipant).mockResolvedValueOnce([title, title])
|
|
29
|
+
|
|
30
|
+
const { getAllByTestId } = render(Participant, { participant: participants[0] })
|
|
31
|
+
|
|
32
|
+
expect(getAllByTestId('skeleton')).toHaveLength(6)
|
|
33
|
+
|
|
34
|
+
await waitFor(() => {
|
|
35
|
+
expect(getAllByTestId('title')).toHaveLength(2)
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('Should not show load more button while loading and when fewer than page size of titles are fetched', async () => {
|
|
40
|
+
vi.mocked(fetchTitlesForParticipant).mockResolvedValueOnce([title, title])
|
|
41
|
+
|
|
42
|
+
const { queryByText, getAllByTestId } = render(Participant, { participant: participants[0] })
|
|
43
|
+
|
|
44
|
+
expect(queryByText('Show More')).not.toBeTruthy()
|
|
45
|
+
|
|
46
|
+
await waitFor(() => {
|
|
47
|
+
expect(getAllByTestId('title')).toHaveLength(2)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expect(queryByText('Show More')).not.toBeTruthy()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('Should show load more button after loading and if more than page size of titles are fetched', async () => {
|
|
54
|
+
const resolved = Array(30).fill(title)
|
|
55
|
+
vi.mocked(fetchTitlesForParticipant).mockResolvedValueOnce(resolved)
|
|
56
|
+
|
|
57
|
+
const { getByText } = render(Participant, { participant: participants[0] })
|
|
58
|
+
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(getByText('Show more')).toBeTruthy()
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('Should fetch more titles when load more button is clicked', async () => {
|
|
65
|
+
const resolved = Array(30).fill(title)
|
|
66
|
+
vi.mocked(fetchTitlesForParticipant).mockResolvedValueOnce(resolved)
|
|
67
|
+
|
|
68
|
+
const { getByText } = render(Participant, { participant: participants[0] })
|
|
69
|
+
|
|
70
|
+
await waitFor(() => getByText('Show more'))
|
|
71
|
+
|
|
72
|
+
await fireEvent.click(getByText('Show more'))
|
|
73
|
+
|
|
74
|
+
expect(fetchTitlesForParticipant).toHaveBeenCalledWith(participants[0], { page: 2 })
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
import { fireEvent, render, waitFor } from '@testing-library/svelte'
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
4
|
import ParticipantsRail from '../../../../routes/components/Rails/ParticipantsRail.svelte'
|
|
5
5
|
import { openModal } from '$lib/modal'
|
|
6
|
-
import { participants } from '$lib/fakeData'
|
|
6
|
+
import { participants, title } from '$lib/fakeData'
|
|
7
|
+
import { track } from '$lib/tracking'
|
|
8
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
9
|
+
import { fetchParticipantsForTitle } from '$lib/api/participants'
|
|
7
10
|
|
|
8
11
|
vi.mock('$lib/modal', () => ({
|
|
9
12
|
openModal: vi.fn(),
|
|
10
13
|
}))
|
|
11
14
|
|
|
15
|
+
vi.mock('$lib/tracking', () => ({
|
|
16
|
+
track: vi.fn(),
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
vi.mock('$lib/api/participants', () => ({
|
|
20
|
+
fetchParticipantsForTitle: vi.fn(),
|
|
21
|
+
}))
|
|
22
|
+
|
|
12
23
|
describe('ParticipantsRail.svelte', () => {
|
|
13
24
|
it('Should render each given participant', async () => {
|
|
25
|
+
vi.mocked(fetchParticipantsForTitle).mockResolvedValueOnce(participants)
|
|
26
|
+
|
|
14
27
|
// @ts-ignore
|
|
15
|
-
const { getByText } = render(ParticipantsRail)
|
|
28
|
+
const { getByText } = render(ParticipantsRail, { title })
|
|
16
29
|
|
|
17
30
|
await waitFor(() => {
|
|
18
31
|
expect(getByText(participants[0].name)).toBeTruthy()
|
|
@@ -20,15 +33,20 @@ describe('ParticipantsRail.svelte', () => {
|
|
|
20
33
|
})
|
|
21
34
|
})
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// const { container } = render(ParticipantsRail)
|
|
36
|
+
it('Should not render when no participants are returned', async () => {
|
|
37
|
+
vi.mocked(fetchParticipantsForTitle).mockResolvedValueOnce([])
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
const { container } = render(ParticipantsRail)
|
|
40
|
+
|
|
41
|
+
await waitFor(() => {
|
|
42
|
+
expect(container.querySelectorAll('.participant').length).toBe(0)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
29
45
|
|
|
30
46
|
it('Should open modal on click of participant', async () => {
|
|
31
|
-
|
|
47
|
+
vi.mocked(fetchParticipantsForTitle).mockResolvedValueOnce(participants)
|
|
48
|
+
|
|
49
|
+
const { getAllByTestId } = render(ParticipantsRail, { title })
|
|
32
50
|
|
|
33
51
|
await waitFor(async () => {
|
|
34
52
|
await fireEvent.click(getAllByTestId('participant')[0])
|
|
@@ -36,4 +54,16 @@ describe('ParticipantsRail.svelte', () => {
|
|
|
36
54
|
|
|
37
55
|
expect(openModal).toHaveBeenCalledWith({ event: expect.any(Object), type: 'participant', data: participants[0] })
|
|
38
56
|
})
|
|
57
|
+
|
|
58
|
+
it('Should fire track event on click of participant', async () => {
|
|
59
|
+
vi.mocked(fetchParticipantsForTitle).mockResolvedValueOnce(participants)
|
|
60
|
+
|
|
61
|
+
const { getAllByTestId } = render(ParticipantsRail, { title })
|
|
62
|
+
|
|
63
|
+
await waitFor(async () => {
|
|
64
|
+
await fireEvent.click(getAllByTestId('participant')[0])
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.ParticipantClick, null, { title_source: title.original_title, participant: participants[0].name })
|
|
68
|
+
})
|
|
39
69
|
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { render } from '@testing-library/svelte'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { title } from '$lib/fakeData'
|
|
5
|
+
import SimilarRail from '../../../../routes/components/Rails/SimilarRail.svelte'
|
|
6
|
+
import { fetchSimilarTitles } from '$lib/api/titles'
|
|
7
|
+
|
|
8
|
+
vi.mock('$lib/modal', () => ({
|
|
9
|
+
openModal: vi.fn(),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
vi.mock('$lib/tracking', () => ({
|
|
13
|
+
track: vi.fn(),
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
vi.mock('$lib/api/titles', () => ({
|
|
17
|
+
fetchSimilarTitles: vi.fn(),
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
describe('ParticipantsRail.svelte', () => {
|
|
21
|
+
it('Should fetch titles', async () => {
|
|
22
|
+
render(SimilarRail, { title })
|
|
23
|
+
|
|
24
|
+
expect(fetchSimilarTitles).toHaveBeenCalledWith(title)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -33,7 +33,7 @@ describe('TitlesRail.svelte', () => {
|
|
|
33
33
|
|
|
34
34
|
await fireEvent.click(getAllByRole('link')[0])
|
|
35
35
|
|
|
36
|
-
expect(openModal).toHaveBeenCalledWith({ event: expect.any(Object), data: title })
|
|
36
|
+
expect(openModal).toHaveBeenCalledWith({ event: expect.any(Object), data: title, returnToTitle: null })
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
it('Should fire given onclick function when title is clicked', async () => {
|