@playpilot/tpi 8.25.0-beta.1 → 8.25.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 +11 -11
- package/dist/link-injections.js +2 -2
- package/dist/mount.js +9 -9
- package/package.json +1 -1
- package/src/routes/components/Explore/Routes/ExploreHome.svelte +12 -10
- package/src/routes/components/InjectionPopover.svelte +1 -1
- package/src/routes/components/Modals/ParticipantModal.svelte +1 -1
- package/src/routes/components/{Participants/Participant.svelte → Participant.svelte} +9 -27
- package/src/routes/components/Rails/InfiniteTitlesRail.svelte +59 -0
- package/src/routes/components/Rails/ParticipantsRail.svelte +21 -19
- package/src/routes/components/Rails/Rail.svelte +3 -1
- package/src/routes/components/Rails/TitlesRail.svelte +5 -2
- package/src/routes/elements/+page.svelte +6 -1
- package/src/tests/routes/components/{Participants/Participant.test.js → Participant.test.js} +1 -20
- package/src/tests/routes/components/Rails/ParticipantsRail.test.js +0 -13
- package/src/routes/components/Participants/ParticipantImage.svelte +0 -56
package/package.json
CHANGED
|
@@ -9,20 +9,22 @@
|
|
|
9
9
|
import { getLanguage } from '$lib/language'
|
|
10
10
|
import type { LanguageCode } from '$lib/types/language'
|
|
11
11
|
import { api } from '$lib/api/api'
|
|
12
|
+
import InfiniteTitlesRail from '../../Rails/InfiniteTitlesRail.svelte'
|
|
13
|
+
import type { APIPaginatedResult } from '$lib/types/api'
|
|
14
|
+
|
|
15
|
+
type Rail = { headings: Record<LanguageCode, string>, params: Record<string, any>, properties: Record<string, any> }
|
|
12
16
|
|
|
13
17
|
let expandedTitle: TitleData | null = $state(null)
|
|
14
18
|
let expandedRailKey: string | null = $state(null)
|
|
15
19
|
let windowWidth: number = $state(window.innerWidth)
|
|
16
20
|
|
|
17
|
-
type Rail = { headings: Record<LanguageCode, string>, params: Record<string, any>, properties: Record<string, any> }
|
|
18
|
-
|
|
19
21
|
async function fetchRails(): Promise<Rail[]> {
|
|
20
22
|
return await api<Rail[]>('/streaming-guide/rails', { baseUrl: 'https://partner.playpilot.net/api' })
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
async function getListTitles(params: Record<string, any> = {}): Promise<TitleData
|
|
25
|
+
async function getListTitles(params: Record<string, any> = {}): Promise<APIPaginatedResult<TitleData>> {
|
|
24
26
|
try {
|
|
25
|
-
return (await fetchTitles(params))
|
|
27
|
+
return (await fetchTitles({ ...params, page_size: 20 }))
|
|
26
28
|
} catch (error: any) {
|
|
27
29
|
console.log(error)
|
|
28
30
|
track(TrackingEvent.ExploreHomeRailError, null, { message: error.message || error.status.toString() || '', params })
|
|
@@ -38,7 +40,7 @@
|
|
|
38
40
|
{#await fetchRails()}
|
|
39
41
|
{#each { length: 3 }}
|
|
40
42
|
<div class="rail">
|
|
41
|
-
<TitlesRail titles={new Promise((
|
|
43
|
+
<TitlesRail titles={new Promise(() => {})} size={windowWidth >= mobileBreakpoint ? 'flexible' : 'huge'} />
|
|
42
44
|
</div>
|
|
43
45
|
{/each}
|
|
44
46
|
{:then rails}
|
|
@@ -46,16 +48,16 @@
|
|
|
46
48
|
{@const heading = headings[getLanguage()]}
|
|
47
49
|
|
|
48
50
|
<div class="rail">
|
|
49
|
-
<
|
|
51
|
+
<InfiniteTitlesRail
|
|
50
52
|
{heading}
|
|
51
|
-
titles={getListTitles(params)}
|
|
52
|
-
size={windowWidth >= mobileBreakpoint ? (properties.size || 'flexible') : 'huge'}
|
|
53
|
-
minimumLength={7}
|
|
54
53
|
{...properties}
|
|
55
54
|
bind:expandedTitle
|
|
56
55
|
bind:expandedRailKey
|
|
57
56
|
onclick={() => track(TrackingEvent.ExploreTitleRailModalView, null, { rail: headings })}
|
|
58
|
-
onexpand={(title) => track(TrackingEvent.ExploreTitleRailExpandClick, title, { rail: headings })}
|
|
57
|
+
onexpand={(title) => track(TrackingEvent.ExploreTitleRailExpandClick, title, { rail: headings })}
|
|
58
|
+
fetchFunction={(page) => getListTitles({ ...params, page })}
|
|
59
|
+
size={windowWidth >= mobileBreakpoint ? (properties.size || 'flexible') : 'huge'}
|
|
60
|
+
minimumLength={7} />
|
|
59
61
|
</div>
|
|
60
62
|
{/each}
|
|
61
63
|
{:catch}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { trackViaPixel } from '@playpilot/retargeting-tracking'
|
|
11
11
|
import Popover from './Popover.svelte'
|
|
12
12
|
import Title from './Title.svelte'
|
|
13
|
-
import Participant from './
|
|
13
|
+
import Participant from './Participant.svelte'
|
|
14
14
|
import TopScroll from './Ads/TopScroll.svelte'
|
|
15
15
|
import Display from './Ads/Display.svelte'
|
|
16
16
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import Modal from './Modal.svelte'
|
|
4
|
-
import Participant from '../
|
|
4
|
+
import Participant from '../Participant.svelte'
|
|
5
5
|
import type { ParticipantData } from '$lib/types/participant'
|
|
6
6
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
7
7
|
import { track } from '$lib/tracking'
|
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
import { openModal } from '$lib/modal'
|
|
6
6
|
import type { ParticipantData } from '$lib/types/participant'
|
|
7
7
|
import type { TitleData } from '$lib/types/title'
|
|
8
|
+
import ListTitle from './ListTitle.svelte'
|
|
8
9
|
import { t } from '$lib/localization'
|
|
9
10
|
import { hasConsentedTo } from '$lib/consent'
|
|
10
11
|
import { trackViaPixel } from '@playpilot/retargeting-tracking'
|
|
11
12
|
import { MetaEvent } from '$lib/enums/TrackingEvent'
|
|
12
|
-
import ParticipantImage from './ParticipantImage.svelte'
|
|
13
|
-
import ListTitle from '../ListTitle.svelte'
|
|
14
13
|
|
|
15
14
|
interface Props {
|
|
16
15
|
participant: ParticipantData
|
|
@@ -55,17 +54,13 @@
|
|
|
55
54
|
</script>
|
|
56
55
|
|
|
57
56
|
<div class="header" class:small>
|
|
58
|
-
<
|
|
57
|
+
<div class="heading" use:heading={2} id="heading">{name}</div>
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{t('Born On')} <strong>{formatDate(birth_date)}</strong>{#if death_date}, {t('Died On')} <strong>{formatDate(death_date)}</strong>{/if}
|
|
66
|
-
</p>
|
|
67
|
-
{/if}
|
|
68
|
-
</div>
|
|
59
|
+
{#if birth_date}
|
|
60
|
+
<p class="dates">
|
|
61
|
+
{t('Born On')} <strong>{formatDate(birth_date)}</strong>{#if death_date}, {t('Died On')} <strong>{formatDate(death_date)}</strong>{/if}
|
|
62
|
+
</p>
|
|
63
|
+
{/if}
|
|
69
64
|
</div>
|
|
70
65
|
|
|
71
66
|
<div class="content">
|
|
@@ -90,22 +85,13 @@
|
|
|
90
85
|
|
|
91
86
|
<style lang="scss">
|
|
92
87
|
.header {
|
|
93
|
-
|
|
94
|
-
display: grid;
|
|
95
|
-
grid-template-columns: calc(var(--participant-image-size)) auto;
|
|
96
|
-
align-items: center;
|
|
97
|
-
gap: margin(1);
|
|
98
|
-
padding: margin(3) margin(1) margin(2);
|
|
88
|
+
padding: margin(4) margin(1) margin(2);
|
|
99
89
|
font-family: theme(detail-font-family, font-family);
|
|
100
90
|
font-weight: theme(detail-font-weight, normal);
|
|
101
91
|
font-size: theme(detail-font-size, font-size-base);
|
|
102
92
|
line-height: theme(participant-description-line-height, normal);
|
|
103
93
|
color: theme(detail-text-color, text-color);
|
|
104
94
|
|
|
105
|
-
@include desktop {
|
|
106
|
-
--participant-image-size: #{margin(8)};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
95
|
&.small {
|
|
110
96
|
padding: margin(1);
|
|
111
97
|
}
|
|
@@ -115,7 +101,7 @@
|
|
|
115
101
|
margin: 0;
|
|
116
102
|
font-family: theme(detail-title-font-family, font-family);
|
|
117
103
|
font-weight: theme(detail-title-font-weight, lighter);
|
|
118
|
-
font-size:
|
|
104
|
+
font-size: theme(detail-title-font-size, margin(1.5));
|
|
119
105
|
line-height: normal;
|
|
120
106
|
font-style: theme(detail-title-font-style, normal);
|
|
121
107
|
|
|
@@ -123,10 +109,6 @@
|
|
|
123
109
|
margin: 0 0 margin(0.5);
|
|
124
110
|
font-size: theme(detail-title-small-font-size, margin(1.25));
|
|
125
111
|
}
|
|
126
|
-
|
|
127
|
-
.small & {
|
|
128
|
-
font-size: 1em;
|
|
129
|
-
}
|
|
130
112
|
}
|
|
131
113
|
|
|
132
114
|
.dates {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { APIPaginatedResult } from '$lib/types/api'
|
|
3
|
+
import type { TitleData } from '$lib/types/title'
|
|
4
|
+
import TitlesRail from './TitlesRail.svelte'
|
|
5
|
+
import { onMount, type ComponentProps } from 'svelte'
|
|
6
|
+
|
|
7
|
+
interface Props extends Omit<ComponentProps<typeof TitlesRail>, 'titles'> {
|
|
8
|
+
fetchFunction: (page: number) => Promise<APIPaginatedResult<TitleData>>
|
|
9
|
+
maxPage?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
fetchFunction,
|
|
14
|
+
maxPage = 5,
|
|
15
|
+
expandedTitle = $bindable(null),
|
|
16
|
+
expandedRailKey = $bindable(null),
|
|
17
|
+
...rest
|
|
18
|
+
}: Props = $props()
|
|
19
|
+
|
|
20
|
+
const emptyPromise: Promise<TitleData[]> = new Promise(() => {})
|
|
21
|
+
|
|
22
|
+
let titles: TitleData[] = $state([])
|
|
23
|
+
let shown: number[] = $state([])
|
|
24
|
+
let page = $state(1)
|
|
25
|
+
let hasMorePages = $state(false)
|
|
26
|
+
let loading = $state(true)
|
|
27
|
+
|
|
28
|
+
$effect(() => {
|
|
29
|
+
if (loading) return
|
|
30
|
+
if (!hasMorePages) return
|
|
31
|
+
if (page >= maxPage) return
|
|
32
|
+
if (titles.length - shown.length > 5) return
|
|
33
|
+
|
|
34
|
+
page++
|
|
35
|
+
fetchTitles()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
onMount(fetchTitles)
|
|
39
|
+
|
|
40
|
+
async function fetchTitles(): Promise<void> {
|
|
41
|
+
loading = true
|
|
42
|
+
hasMorePages = false
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const { next, results } = await fetchFunction(page)
|
|
46
|
+
|
|
47
|
+
titles = [...titles, ...results]
|
|
48
|
+
hasMorePages = !!next
|
|
49
|
+
} finally {
|
|
50
|
+
loading = false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
{#if !titles.length && loading}
|
|
56
|
+
<TitlesRail titles={emptyPromise} {...rest} />
|
|
57
|
+
{:else}
|
|
58
|
+
<TitlesRail {titles} {...rest} bind:shown />
|
|
59
|
+
{/if}
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
import { track } from '$lib/tracking'
|
|
9
9
|
import type { ParticipantData } from '$lib/types/participant'
|
|
10
10
|
import type { TitleData } from '$lib/types/title'
|
|
11
|
-
import ParticipantImage from '../Participants/ParticipantImage.svelte'
|
|
12
11
|
import Rail from './Rail.svelte'
|
|
13
12
|
|
|
14
13
|
interface Props {
|
|
@@ -36,6 +35,8 @@
|
|
|
36
35
|
participantsByOccurrence[participant.sid] = { participant, count }
|
|
37
36
|
}
|
|
38
37
|
|
|
38
|
+
// Sort all participants by their occurrences. If a participant did not occur their positioned will be retained
|
|
39
|
+
// relative to where it was before
|
|
39
40
|
const participantsSortedByCount = Object.values(participantsByOccurrence).sort((a, b) => b.count - a.count)
|
|
40
41
|
|
|
41
42
|
return participantsSortedByCount.map(({ participant }) => participant)
|
|
@@ -50,10 +51,7 @@
|
|
|
50
51
|
{#await fetchParticipantsForTitle(title)}
|
|
51
52
|
<Rail {heading}>
|
|
52
53
|
{#each { length: 5 }}
|
|
53
|
-
<div class="participant">
|
|
54
|
-
<ParticipantImage />
|
|
55
|
-
<div class="name"></div>
|
|
56
|
-
</div>
|
|
54
|
+
<div class="participant"></div>
|
|
57
55
|
{/each}
|
|
58
56
|
</Rail>
|
|
59
57
|
{:then participants}
|
|
@@ -61,9 +59,11 @@
|
|
|
61
59
|
<Rail {heading}>
|
|
62
60
|
{#each orderParticipantsByPageTextOccurrence(participants).slice(0, 15) as participant}
|
|
63
61
|
<button class="participant" data-testid="participant" onclick={event => onclick(event, participant)}>
|
|
64
|
-
<
|
|
62
|
+
<span class="truncate">{participant.name}</span>
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
{#if participant.character}
|
|
65
|
+
<div class="character truncate">{participant.character}</div>
|
|
66
|
+
{/if}
|
|
67
67
|
</button>
|
|
68
68
|
{/each}
|
|
69
69
|
</Rail>
|
|
@@ -71,25 +71,22 @@
|
|
|
71
71
|
{/await}
|
|
72
72
|
|
|
73
73
|
<style lang="scss">
|
|
74
|
-
$item-width: #{theme(rail-size-default, margin(6))};
|
|
75
|
-
|
|
76
74
|
.participant {
|
|
77
|
-
--participant-image-size: #{$item-width};
|
|
78
75
|
display: flex;
|
|
79
76
|
flex-direction: column;
|
|
80
|
-
flex: 0 0
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
padding: 0;
|
|
77
|
+
flex: 0 0 10rem;
|
|
78
|
+
width: 10rem;
|
|
79
|
+
min-height: margin(3.375); // Matches 54 pixels, the height of a card with both name and character
|
|
80
|
+
padding: margin(0.5);
|
|
81
|
+
border: theme(cast-border, 0);
|
|
84
82
|
border-radius: theme(cast-border-radius, border-radius);
|
|
85
|
-
|
|
86
|
-
background: transparent;
|
|
83
|
+
background: theme(cast-background, lighter);
|
|
87
84
|
cursor: pointer;
|
|
88
|
-
color: inherit;
|
|
89
85
|
font-family: inherit;
|
|
86
|
+
text-align: left;
|
|
87
|
+
color: inherit;
|
|
90
88
|
font-size: theme(cast-font-size, font-size-small);
|
|
91
89
|
line-height: normal;
|
|
92
|
-
text-align: center;
|
|
93
90
|
white-space: nowrap;
|
|
94
91
|
|
|
95
92
|
&:is(button):hover,
|
|
@@ -99,7 +96,12 @@
|
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
98
|
|
|
102
|
-
.
|
|
99
|
+
.character {
|
|
100
|
+
color: theme(cast-character-text-color, text-color-alt);
|
|
101
|
+
font-style: italic;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.truncate {
|
|
103
105
|
overflow: hidden;
|
|
104
106
|
text-overflow: ellipsis;
|
|
105
107
|
white-space: nowrap;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
onchange?: (index: number) => void
|
|
10
10
|
onresize?: () => void
|
|
11
11
|
slider?: ReturnType<typeof TinySlider> | null,
|
|
12
|
+
shown?: number[],
|
|
12
13
|
children: Snippet
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
onchange = () => null,
|
|
18
19
|
onresize = () => null,
|
|
19
20
|
slider = $bindable(),
|
|
21
|
+
shown = $bindable([]),
|
|
20
22
|
children,
|
|
21
23
|
}: Props = $props()
|
|
22
24
|
</script>
|
|
@@ -26,7 +28,7 @@
|
|
|
26
28
|
{/if}
|
|
27
29
|
|
|
28
30
|
<div class="rail">
|
|
29
|
-
<TinySlider bind:this={slider} {onchange} {onresize} allowWheel>
|
|
31
|
+
<TinySlider bind:this={slider} {onchange} {onresize} bind:shown allowWheel>
|
|
30
32
|
{@render children()}
|
|
31
33
|
|
|
32
34
|
{#snippet controls({ setIndex, currentIndex, reachedEnd })}
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
heading?: string,
|
|
19
19
|
size?: 'small' | 'large' | 'huge' | 'flexible'
|
|
20
20
|
aside?: boolean,
|
|
21
|
-
expandable?: boolean,
|
|
22
21
|
navigateToExplore?: boolean,
|
|
22
|
+
expandable?: boolean,
|
|
23
23
|
expandedTitle?: TitleData | null,
|
|
24
24
|
expandedRailKey?: string | null,
|
|
25
|
+
shown?: number[],
|
|
25
26
|
onclick?: (title: TitleData) => void,
|
|
26
27
|
onexpand?: (title: TitleData) => void
|
|
27
28
|
}
|
|
@@ -32,10 +33,11 @@
|
|
|
32
33
|
heading = '',
|
|
33
34
|
size = 'small',
|
|
34
35
|
aside = false,
|
|
35
|
-
expandable = false,
|
|
36
36
|
navigateToExplore = false,
|
|
37
|
+
expandable = false,
|
|
37
38
|
expandedTitle = $bindable(null),
|
|
38
39
|
expandedRailKey = $bindable(null),
|
|
40
|
+
shown = $bindable([]),
|
|
39
41
|
onclick = () => null,
|
|
40
42
|
onexpand = () => null,
|
|
41
43
|
}: Props = $props()
|
|
@@ -169,6 +171,7 @@
|
|
|
169
171
|
{#if titles?.length >= minimumLength}
|
|
170
172
|
<Rail
|
|
171
173
|
bind:slider
|
|
174
|
+
bind:shown
|
|
172
175
|
{heading}
|
|
173
176
|
onchange={(index) => onchange(titles, index)}
|
|
174
177
|
onresize={() => slider?.reposition()}>
|
|
@@ -3,7 +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
|
+
import { fetchSimilarTitles, fetchTitles } from '$lib/api/titles'
|
|
7
7
|
import Disclaimer from '../components/Ads/Disclaimer.svelte'
|
|
8
8
|
import Display from '../components/Ads/Display.svelte'
|
|
9
9
|
import TopScroll from '../components/Ads/TopScroll.svelte'
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import ExploreRouter from '../components/Explore/ExploreRouter.svelte'
|
|
22
22
|
import TitlesRail from '../components/Rails/TitlesRail.svelte'
|
|
23
23
|
import ParticipantsRail from '../components/Rails/ParticipantsRail.svelte'
|
|
24
|
+
import InfiniteTitlesRail from '../components/Rails/InfiniteTitlesRail.svelte'
|
|
24
25
|
|
|
25
26
|
if (browser) {
|
|
26
27
|
// @ts-ignore
|
|
@@ -151,6 +152,10 @@
|
|
|
151
152
|
<TitlesRail titles={fetchSimilarTitles(title)} expandable />
|
|
152
153
|
<br>
|
|
153
154
|
<TitlesRail titles={fetchSimilarTitles(title)} aside size="large" />
|
|
155
|
+
<br>
|
|
156
|
+
Loads more pages:
|
|
157
|
+
<br>
|
|
158
|
+
<InfiniteTitlesRail fetchFunction={(page: number) => fetchTitles({ page })} />
|
|
154
159
|
{/key}
|
|
155
160
|
|
|
156
161
|
<h2>TitlesRailModal</h2>
|
package/src/tests/routes/components/{Participants/Participant.test.js → Participant.test.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fireEvent, render, waitFor } from '@testing-library/svelte'
|
|
2
2
|
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import Participant from '
|
|
4
|
+
import Participant from '../../../routes/components/Participant.svelte'
|
|
5
5
|
import { participants, title } from '$lib/fakeData'
|
|
6
6
|
import { fetchTitlesForParticipant } from '$lib/api/participants'
|
|
7
7
|
|
|
@@ -80,23 +80,4 @@ describe('Participant.svelte', () => {
|
|
|
80
80
|
expect(queryByText('Credits')).not.toBeTruthy()
|
|
81
81
|
expect(container.querySelector('.small')).toBeTruthy()
|
|
82
82
|
})
|
|
83
|
-
|
|
84
|
-
it('Should render image if given', async () => {
|
|
85
|
-
const participantWithImage = { ...participants[0], image: 'some-image.jpg' }
|
|
86
|
-
|
|
87
|
-
const { getByRole } = render(Participant, { participant: participantWithImage })
|
|
88
|
-
|
|
89
|
-
await waitFor(() => {
|
|
90
|
-
expect(getByRole('presentation')).toBeTruthy()
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('Should not render image if not given', async () => {
|
|
95
|
-
|
|
96
|
-
const { queryByRole } = render(Participant, { participant: participants[0] })
|
|
97
|
-
|
|
98
|
-
await waitFor(() => {
|
|
99
|
-
expect(queryByRole('presentation')).not.toBeTruthy()
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
83
|
})
|
|
@@ -36,19 +36,6 @@ describe('ParticipantsRail.svelte', () => {
|
|
|
36
36
|
})
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
it('Should render image only for participants with image', async () => {
|
|
40
|
-
const participantsWithImage = [...participants]
|
|
41
|
-
participantsWithImage[0].image = 'some-image.jpg'
|
|
42
|
-
|
|
43
|
-
vi.mocked(fetchParticipantsForTitle).mockResolvedValueOnce(participantsWithImage)
|
|
44
|
-
|
|
45
|
-
const { getAllByRole } = render(ParticipantsRail, { title })
|
|
46
|
-
|
|
47
|
-
await waitFor(() => {
|
|
48
|
-
expect(getAllByRole('presentation')).toHaveLength(1)
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
39
|
it('Should order participants by their number of occurrences on the page', async () => {
|
|
53
40
|
document.body.innerHTML = `<main>${participants[1].name}. ${participants[2].name}, ${participants[2].name}</main>`
|
|
54
41
|
vi.mocked(fetchParticipantsForTitle).mockResolvedValueOnce(participants)
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { ParticipantData } from '$lib/types/participant'
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
participant?: ParticipantData | null
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const { participant = null }: Props = $props()
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<div class="image">
|
|
12
|
-
{#if participant?.image}
|
|
13
|
-
<img loading="lazy" src={participant.image} alt="" />
|
|
14
|
-
{/if}
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
<style lang="scss">
|
|
18
|
-
$item-width: #{var(--participant-image-size, margin(6))};
|
|
19
|
-
|
|
20
|
-
.image {
|
|
21
|
-
position: relative;
|
|
22
|
-
display: block;
|
|
23
|
-
width: $item-width;
|
|
24
|
-
height: $item-width;
|
|
25
|
-
border-radius: 50%;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
background: theme(cast-background, lighter);
|
|
28
|
-
|
|
29
|
-
&::before,
|
|
30
|
-
&::after {
|
|
31
|
-
content: "";
|
|
32
|
-
display: block;
|
|
33
|
-
position: absolute;
|
|
34
|
-
width: 50%;
|
|
35
|
-
height: 50%;
|
|
36
|
-
top: 50%;
|
|
37
|
-
left: 50%;
|
|
38
|
-
border-radius: 50%;
|
|
39
|
-
transform: translateX(-50%) translateY(-50%);
|
|
40
|
-
background: currentColor;
|
|
41
|
-
opacity: 0.1;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
&::after {
|
|
45
|
-
top: 100%;
|
|
46
|
-
width: 75%;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
img {
|
|
50
|
-
z-index: 1;
|
|
51
|
-
position: relative;
|
|
52
|
-
width: 100%;
|
|
53
|
-
height: auto;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
</style>
|