@playpilot/tpi 5.12.0-beta.similar.1 → 5.12.1
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 +10 -11
- package/events.md +1 -0
- package/package.json +1 -2
- package/src/lib/enums/SplitTest.ts +1 -2
- package/src/lib/enums/TrackingEvent.ts +2 -1
- package/src/lib/fakeData.ts +0 -70
- package/src/lib/linkInjection.ts +55 -12
- package/src/lib/playlink.ts +1 -4
- package/src/lib/splitTest.ts +9 -2
- package/src/lib/types/title.d.ts +0 -2
- package/src/routes/+page.svelte +0 -1
- package/src/routes/components/Icons/IconClose.svelte +1 -1
- package/src/routes/components/Icons/IconScrollIndicator.svelte +3 -0
- package/src/routes/components/Modal.svelte +7 -41
- package/src/routes/components/Playlinks.svelte +3 -6
- package/src/routes/components/Popover.svelte +111 -12
- package/src/routes/components/Title.svelte +18 -33
- package/src/routes/components/TitleModal.svelte +3 -2
- package/src/routes/components/TitlePopover.svelte +2 -5
- package/src/tests/lib/linkInjection.test.js +35 -9
- package/src/tests/lib/playlink.test.js +10 -25
- package/src/tests/lib/splitTest.test.js +31 -3
- package/src/tests/routes/components/Modal.test.js +19 -51
- package/src/tests/routes/components/Title.test.js +0 -8
- package/src/tests/setup.js +10 -0
- package/src/lib/modal.ts +0 -81
- package/src/lib/types/participant.d.ts +0 -14
- package/src/routes/components/Icons/IconArrow.svelte +0 -17
- package/src/routes/components/ListTitle.svelte +0 -172
- package/src/routes/components/Participant.svelte +0 -88
- package/src/routes/components/ParticipantModal.svelte +0 -30
- package/src/routes/components/PlaylinkIcon.svelte +0 -41
- package/src/routes/components/Rails/ParticipantsRail.svelte +0 -56
- package/src/routes/components/Rails/Rail.svelte +0 -91
- package/src/routes/components/Rails/SimilarRail.svelte +0 -16
- package/src/routes/components/Rails/TitlesRail.svelte +0 -95
- package/src/routes/components/Tabs.svelte +0 -47
- package/src/routes/components/TitlePoster.svelte +0 -30
- package/src/tests/lib/modal.test.js +0 -148
- package/src/tests/routes/components/ListTitle.test.js +0 -82
- package/src/tests/routes/components/PlaylinkIcon.test.js +0 -27
- package/src/tests/routes/components/Rails/ParticipantsRail.test.js +0 -41
- package/src/tests/routes/components/Rails/TitleRail.test.js +0 -38
- package/src/tests/routes/components/TitlePoster.test.js +0 -20
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import IconScrollIndicator from './Icons/IconScrollIndicator.svelte'
|
|
2
3
|
import { SplitTest } from '$lib/enums/SplitTest'
|
|
3
4
|
import { isInSplitTestVariant } from '$lib/splitTest'
|
|
4
5
|
import { onMount, setContext, tick, type Snippet } from 'svelte'
|
|
5
6
|
import { prefersReducedMotion } from 'svelte/motion'
|
|
6
|
-
import { fly } from 'svelte/transition'
|
|
7
|
+
import { fade, fly } from 'svelte/transition'
|
|
7
8
|
|
|
8
9
|
interface Props {
|
|
9
10
|
children: Snippet
|
|
10
11
|
bubble?: Snippet | null
|
|
11
12
|
append?: Snippet | null
|
|
12
|
-
maxHeight?: number
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const { children, bubble, append }: Props = $props()
|
|
16
16
|
|
|
17
17
|
setContext('scope', 'popover')
|
|
18
18
|
|
|
@@ -22,14 +22,30 @@
|
|
|
22
22
|
|
|
23
23
|
let element: HTMLElement | null = $state(null)
|
|
24
24
|
let flip = $state(false)
|
|
25
|
-
let
|
|
25
|
+
let canAppendFit = $state(true)
|
|
26
|
+
let scrollIndicatorVisible = $state(false)
|
|
27
|
+
let fullHeight = $state(0) // Full height of all snippets together, via bind:clientHeight
|
|
28
|
+
let appendHeight = $state(0) // Only the height of the append (in most cases, this will be the display ad), via bind:clientHeight
|
|
29
|
+
let maxHeight = $state(0) // Max height that is computed based on the available screen size, via setMaxHeight()
|
|
26
30
|
|
|
27
31
|
onMount(async () => {
|
|
28
32
|
await tick()
|
|
29
33
|
positionElement()
|
|
30
34
|
|
|
35
|
+
await setMaxHeight()
|
|
36
|
+
|
|
37
|
+
if (append) {
|
|
38
|
+
// When an append is visible it pushes the dialog's height down. We need to recalculate the popover's available height
|
|
39
|
+
// When there is little room for the popover to show, the append will hide itself. We calculate the new height after hiding.
|
|
40
|
+
|
|
41
|
+
requestAnimationFrame(() => {
|
|
42
|
+
setAppendVisibility()
|
|
43
|
+
setMaxHeight()
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
await tick()
|
|
32
|
-
|
|
48
|
+
setScrollIndicatorVisible()
|
|
33
49
|
})
|
|
34
50
|
|
|
35
51
|
/**
|
|
@@ -50,17 +66,25 @@
|
|
|
50
66
|
if (right + offset > window.innerWidth) element.style.left = `${fromRight}px`
|
|
51
67
|
}
|
|
52
68
|
|
|
69
|
+
function setAppendVisibility(): void {
|
|
70
|
+
canAppendFit = fullHeight > 500
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
/**
|
|
54
74
|
* Set the max height of the dialog so that it always fits on screen, even after flipping.
|
|
55
75
|
*/
|
|
56
|
-
function setMaxHeight(): void {
|
|
76
|
+
async function setMaxHeight(): Promise<void> {
|
|
77
|
+
maxHeight = 0
|
|
78
|
+
|
|
79
|
+
await new Promise(res => requestAnimationFrame(res))
|
|
80
|
+
|
|
57
81
|
if (!element) return
|
|
58
82
|
|
|
59
83
|
const { top, height } = element.getBoundingClientRect()
|
|
60
84
|
const offset = getOffset()
|
|
61
85
|
const gap = append ? parseInt(window.getComputedStyle(element).gap) : 0
|
|
62
86
|
|
|
63
|
-
const appendOffset = appendHeight + gap
|
|
87
|
+
const appendOffset = canAppendFit ? appendHeight + gap : 0
|
|
64
88
|
const availableSpace = flip ? (window.innerHeight - top - offset - appendOffset) : (top + height - appendOffset - offset)
|
|
65
89
|
|
|
66
90
|
maxHeight = Math.min(availableSpace, window.innerHeight * 0.8)
|
|
@@ -69,11 +93,41 @@
|
|
|
69
93
|
function getOffset(): number {
|
|
70
94
|
return parseFloat(getComputedStyle(document.documentElement).fontSize) // 1 rem
|
|
71
95
|
}
|
|
96
|
+
|
|
97
|
+
function setScrollIndicatorVisible(): void {
|
|
98
|
+
if (!element?.firstChild) return
|
|
99
|
+
|
|
100
|
+
const dialog = element.firstChild as HTMLElement
|
|
101
|
+
const { offsetHeight, scrollHeight, scrollTop } = dialog
|
|
102
|
+
|
|
103
|
+
if (scrollHeight <= offsetHeight) {
|
|
104
|
+
scrollIndicatorVisible = false
|
|
105
|
+
} else {
|
|
106
|
+
scrollIndicatorVisible = scrollTop + offsetHeight < scrollHeight
|
|
107
|
+
}
|
|
108
|
+
}
|
|
72
109
|
</script>
|
|
73
110
|
|
|
74
|
-
<div
|
|
75
|
-
|
|
111
|
+
<div
|
|
112
|
+
class="popover"
|
|
113
|
+
class:flip
|
|
114
|
+
class:inline={inlineBubble}
|
|
115
|
+
class:has-bubble={!!bubble && inlineBubble}
|
|
116
|
+
style:--max-height={maxHeight ? maxHeight + 'px' : null}
|
|
117
|
+
tabindex="-1"
|
|
118
|
+
aria-hidden="true"
|
|
119
|
+
bind:this={element}
|
|
120
|
+
bind:clientHeight={fullHeight}>
|
|
121
|
+
<div class="dialog" transition:fly|global={transition} onscroll={setScrollIndicatorVisible} data-view-transition-old>
|
|
76
122
|
{@render children()}
|
|
123
|
+
|
|
124
|
+
{#if scrollIndicatorVisible}
|
|
125
|
+
<div class="scroll-indicator" transition:fade={{ duration: 100 }}>
|
|
126
|
+
<div class="scroll-indicator-icon">
|
|
127
|
+
<IconScrollIndicator />
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
{/if}
|
|
77
131
|
</div>
|
|
78
132
|
|
|
79
133
|
{#if bubble}
|
|
@@ -82,7 +136,7 @@
|
|
|
82
136
|
</div>
|
|
83
137
|
{/if}
|
|
84
138
|
|
|
85
|
-
{#if append}
|
|
139
|
+
{#if append && canAppendFit}
|
|
86
140
|
<div class="append" transition:fly|global={transition} bind:clientHeight={appendHeight}>
|
|
87
141
|
{@render append()}
|
|
88
142
|
</div>
|
|
@@ -90,24 +144,36 @@
|
|
|
90
144
|
</div>
|
|
91
145
|
|
|
92
146
|
<style lang="scss">
|
|
147
|
+
@keyframes delay-render {
|
|
148
|
+
from {
|
|
149
|
+
opacity: 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
to {
|
|
153
|
+
opacity: 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
93
157
|
.popover {
|
|
94
158
|
--offset: #{margin(0.5)};
|
|
95
159
|
display: flex;
|
|
96
160
|
flex-direction: column-reverse;
|
|
97
161
|
gap: margin(0.5);
|
|
98
162
|
position: absolute;
|
|
99
|
-
top: calc((var(--offset) - 1px) * -1);
|
|
163
|
+
top: calc((var(--offset) - 1px) * -1); // Add 1 pixel to account for rounding errors
|
|
100
164
|
left: 0;
|
|
101
165
|
width: calc(100vw - margin(2));
|
|
102
166
|
max-width: margin(20);
|
|
103
167
|
padding: var(--offset) 0;
|
|
104
168
|
transform: translateY(calc(-100% + var(--offset)));
|
|
169
|
+
opacity: 0;
|
|
170
|
+
animation: delay-render 0ms 1ms forwards; // Allow height calculations to perform before showing the popover
|
|
105
171
|
z-index: 2147483647; // As high as she goes
|
|
106
172
|
|
|
107
173
|
&.flip {
|
|
108
174
|
flex-direction: column;
|
|
109
175
|
top: auto;
|
|
110
|
-
bottom: calc(var(--offset) + 1px);
|
|
176
|
+
bottom: calc(var(--offset) + 1px); // Add 1 pixel to account for rounding errors
|
|
111
177
|
transform: translateY(calc(100% + var(--offset)));
|
|
112
178
|
|
|
113
179
|
&.inline {
|
|
@@ -138,4 +204,37 @@
|
|
|
138
204
|
border-top-right-radius: 0;
|
|
139
205
|
}
|
|
140
206
|
}
|
|
207
|
+
|
|
208
|
+
.scroll-indicator {
|
|
209
|
+
z-index: 1;
|
|
210
|
+
position: sticky;
|
|
211
|
+
bottom: 0;
|
|
212
|
+
pointer-events: none;
|
|
213
|
+
|
|
214
|
+
&::after {
|
|
215
|
+
content: "";
|
|
216
|
+
display: block;
|
|
217
|
+
position: absolute;
|
|
218
|
+
bottom: 0;
|
|
219
|
+
left: 0;
|
|
220
|
+
height: margin(2);
|
|
221
|
+
width: 100%;
|
|
222
|
+
background: linear-gradient(to bottom, transparent, black);
|
|
223
|
+
opacity: var(--playpilot-popover-scroll-indicator-opacity, 0.5);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.scroll-indicator-icon {
|
|
228
|
+
z-index: 1;
|
|
229
|
+
position: absolute;
|
|
230
|
+
display: flex;
|
|
231
|
+
bottom: margin(0.25);
|
|
232
|
+
left: 50%;
|
|
233
|
+
transform: translateX(-50%);
|
|
234
|
+
opacity: 0.75;
|
|
235
|
+
|
|
236
|
+
:global(svg) {
|
|
237
|
+
fill: var(--playpilot-popover-scroll-indicator-fill, var(--playpilot-detail-text-color, var(--playpilot-text-color))) !important;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
141
240
|
</style>
|
|
@@ -3,35 +3,33 @@
|
|
|
3
3
|
import Playlinks from './Playlinks.svelte'
|
|
4
4
|
import Description from './Description.svelte'
|
|
5
5
|
import IconIMDb from './Icons/IconIMDb.svelte'
|
|
6
|
-
import ParticipantsRail from './Rails/ParticipantsRail.svelte'
|
|
7
|
-
import SimilarRail from './Rails/SimilarRail.svelte'
|
|
8
|
-
import TitlePoster from './TitlePoster.svelte'
|
|
9
6
|
import { t } from '$lib/localization'
|
|
10
7
|
import type { TitleData } from '$lib/types/title'
|
|
11
8
|
import { heading } from '$lib/actions/heading'
|
|
12
9
|
import { removeImageUrlPrefix } from '$lib/image'
|
|
13
|
-
import { participants } from '$lib/fakeData'
|
|
14
10
|
|
|
15
11
|
interface Props {
|
|
16
12
|
title: TitleData
|
|
17
13
|
small?: boolean
|
|
18
|
-
compact?: boolean
|
|
19
14
|
}
|
|
20
15
|
|
|
21
|
-
const { title, small = false
|
|
16
|
+
const { title, small = false }: Props = $props()
|
|
22
17
|
|
|
23
18
|
let posterLoaded = $state(false)
|
|
24
19
|
let backgroundLoaded = $state(false)
|
|
25
20
|
let useBackgroundFallback = $state(false)
|
|
26
21
|
</script>
|
|
27
22
|
|
|
28
|
-
<div class="content" class:small
|
|
23
|
+
<div class="content" class:small data-playpilot-link-injections-title>
|
|
29
24
|
<div class="header">
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
<div class="top">
|
|
26
|
+
<img
|
|
27
|
+
class="poster"
|
|
28
|
+
class:loaded={posterLoaded}
|
|
29
|
+
src={removeImageUrlPrefix(title.standing_poster)}
|
|
30
|
+
alt="Movie poster for '{title.title}'"
|
|
31
|
+
onload={() => posterLoaded = true} />
|
|
32
|
+
</div>
|
|
35
33
|
|
|
36
34
|
<div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
|
|
37
35
|
|
|
@@ -57,17 +55,11 @@
|
|
|
57
55
|
|
|
58
56
|
{#if !small}
|
|
59
57
|
<Description text={title.description} blurb={title.blurb} />
|
|
60
|
-
|
|
61
|
-
{#if true || title.participants?.length}
|
|
62
|
-
<ParticipantsRail participants={participants} />
|
|
63
|
-
{/if}
|
|
64
|
-
|
|
65
|
-
<SimilarRail />
|
|
66
58
|
{/if}
|
|
67
59
|
</div>
|
|
68
60
|
</div>
|
|
69
61
|
|
|
70
|
-
<div class="background"
|
|
62
|
+
<div class="background">
|
|
71
63
|
{#key useBackgroundFallback}
|
|
72
64
|
<img
|
|
73
65
|
src={removeImageUrlPrefix(useBackgroundFallback ? title.standing_poster : title.medium_poster)}
|
|
@@ -92,10 +84,6 @@
|
|
|
92
84
|
.small & {
|
|
93
85
|
font-size: var(--playpilot-detail-title-font-size-small, margin(1.125));
|
|
94
86
|
}
|
|
95
|
-
|
|
96
|
-
.compact & {
|
|
97
|
-
margin-top: 0;
|
|
98
|
-
}
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
.content {
|
|
@@ -115,23 +103,25 @@
|
|
|
115
103
|
}
|
|
116
104
|
|
|
117
105
|
&.small {
|
|
118
|
-
font-size: var(--playpilot-detail-font-size
|
|
106
|
+
font-size: var(--playpilot-detail-font-size, 12px);
|
|
119
107
|
line-height: 1.45;
|
|
120
108
|
padding-bottom: margin(0.5);
|
|
121
109
|
}
|
|
122
110
|
}
|
|
123
111
|
|
|
124
112
|
.header {
|
|
125
|
-
padding: margin(
|
|
113
|
+
padding: margin(2) 0 margin(1);
|
|
126
114
|
background: transparent;
|
|
127
115
|
|
|
128
116
|
.small & {
|
|
129
117
|
padding-top: margin(1);
|
|
130
118
|
}
|
|
119
|
+
}
|
|
131
120
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
121
|
+
.top {
|
|
122
|
+
display: flex;
|
|
123
|
+
justify-content: space-between;
|
|
124
|
+
align-items: flex-end;
|
|
135
125
|
}
|
|
136
126
|
|
|
137
127
|
.poster {
|
|
@@ -142,7 +132,6 @@
|
|
|
142
132
|
border-radius: var(--playpilot-detail-image-border-radius, margin(0.5));
|
|
143
133
|
background: var(--playpilot-detail-image-background, var(--playpilot-content));
|
|
144
134
|
box-shadow: var(--playpilot-detail-image-shadow, var(--playpilot-shadow));
|
|
145
|
-
overflow: hidden;
|
|
146
135
|
opacity: 0;
|
|
147
136
|
|
|
148
137
|
&.loaded {
|
|
@@ -221,10 +210,6 @@
|
|
|
221
210
|
}
|
|
222
211
|
}
|
|
223
212
|
|
|
224
|
-
.faded {
|
|
225
|
-
opacity: 0.15;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
213
|
.capitalize {
|
|
229
214
|
text-transform: capitalize;
|
|
230
215
|
}
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
import { getFirstAdOfType } from '$lib/ads'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
|
+
onclose: () => void,
|
|
13
14
|
title: TitleData
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const { title }: Props = $props()
|
|
17
|
+
const { onclose, title }: Props = $props()
|
|
17
18
|
|
|
18
19
|
const topScrollAd = getFirstAdOfType('top_scroll')
|
|
19
20
|
const displayAd = getFirstAdOfType('card')
|
|
@@ -48,6 +49,6 @@
|
|
|
48
49
|
{/if}
|
|
49
50
|
{/snippet}
|
|
50
51
|
|
|
51
|
-
<Modal {onscroll} prepend={displayAd ? prepend : null} bubble={topScrollAd ? bubble : null} tall>
|
|
52
|
+
<Modal {onclose} {onscroll} prepend={displayAd ? prepend : null} bubble={topScrollAd ? bubble : null} tall>
|
|
52
53
|
<Title {title} />
|
|
53
54
|
</Modal>
|
|
@@ -19,11 +19,8 @@
|
|
|
19
19
|
const topScroll = getFirstAdOfType('top_scroll')
|
|
20
20
|
const displayAd = getFirstAdOfType('card')
|
|
21
21
|
|
|
22
|
-
let maxHeight = $state(0)
|
|
23
22
|
let element: HTMLElement | null = $state(null)
|
|
24
23
|
|
|
25
|
-
const compact = $derived(!!maxHeight && maxHeight < 250)
|
|
26
|
-
|
|
27
24
|
track(TrackingEvent.TitlePopoverView, title)
|
|
28
25
|
|
|
29
26
|
onMount(() => {
|
|
@@ -68,8 +65,8 @@
|
|
|
68
65
|
{/snippet}
|
|
69
66
|
|
|
70
67
|
<div class="title-popover" bind:this={element} data-playpilot-title-popover role="region" aria-labelledby="title">
|
|
71
|
-
<Popover
|
|
72
|
-
<Title {title}
|
|
68
|
+
<Popover append={displayAd ? append : null} bubble={topScroll ? bubble : null}>
|
|
69
|
+
<Title {title} small />
|
|
73
70
|
</Popover>
|
|
74
71
|
</div>
|
|
75
72
|
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { fireEvent } from '@testing-library/svelte'
|
|
2
2
|
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, getLinkInjectionElements, insertAfterArticlePlaylinks, getLinkInjectionsParentElement, isAvailableAsManualInjection, filterRemovedAndInactiveInjections, isEquivalentInjection, filterInvalidInTextInjections, filterInvalidAfterArticleInjections, isValidInjection, isValidPlaylinkType, removePlayPilotTitleLinks } from '$lib/linkInjection'
|
|
4
|
+
import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, getLinkInjectionElements, insertAfterArticlePlaylinks, getLinkInjectionsParentElement, isAvailableAsManualInjection, filterRemovedAndInactiveInjections, isEquivalentInjection, filterInvalidInTextInjections, filterInvalidAfterArticleInjections, isValidInjection, isValidPlaylinkType, removePlayPilotTitleLinks, trackLinkIntersection } from '$lib/linkInjection'
|
|
5
5
|
import { mount, unmount } from 'svelte'
|
|
6
6
|
import { fakeFetch, generateInjection } from '../helpers'
|
|
7
|
-
import {
|
|
7
|
+
import { track } from '$lib/tracking'
|
|
8
8
|
|
|
9
9
|
vi.mock('svelte', () => ({
|
|
10
10
|
mount: vi.fn(),
|
|
11
11
|
unmount: vi.fn(),
|
|
12
12
|
}))
|
|
13
13
|
|
|
14
|
-
vi.mock('$lib/
|
|
15
|
-
|
|
16
|
-
destroyAllModals: vi.fn(),
|
|
17
|
-
getPreviousModal: vi.fn(),
|
|
18
|
-
goBackToPreviousModal: vi.fn(),
|
|
14
|
+
vi.mock('$lib/tracking', () => ({
|
|
15
|
+
track: vi.fn(),
|
|
19
16
|
}))
|
|
20
17
|
|
|
21
18
|
function mockMatchMedia(matches = false) {
|
|
@@ -314,7 +311,7 @@ describe('linkInjection.js', () => {
|
|
|
314
311
|
expect(() => injectLinksInDocument(elements, { aiInjections: linkInjections, manualInjections: [] })).not.toThrow()
|
|
315
312
|
})
|
|
316
313
|
|
|
317
|
-
it('Should
|
|
314
|
+
it('Should mount modal when clicked', async () => {
|
|
318
315
|
document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
|
|
319
316
|
|
|
320
317
|
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
@@ -325,7 +322,27 @@ describe('linkInjection.js', () => {
|
|
|
325
322
|
const link = /** @type {HTMLAnchorElement} */ (document.querySelector('a'))
|
|
326
323
|
await fireEvent.click(link)
|
|
327
324
|
|
|
328
|
-
expect(
|
|
325
|
+
expect(mount).toHaveBeenCalled()
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('Should not mount modal multiple times if modal is already open', async () => {
|
|
329
|
+
const sentence = 'This is a sentence with an injection.'
|
|
330
|
+
document.body.innerHTML = `<p>${sentence}</p>`
|
|
331
|
+
|
|
332
|
+
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
333
|
+
|
|
334
|
+
const linkInjections = [
|
|
335
|
+
generateInjection(sentence, 'a sentence'),
|
|
336
|
+
generateInjection(sentence, 'an injection'),
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
injectLinksInDocument(elements, { aiInjections: linkInjections, manualInjections: [] })
|
|
340
|
+
|
|
341
|
+
const links = document.querySelectorAll('a')
|
|
342
|
+
await fireEvent.click(links[0])
|
|
343
|
+
await fireEvent.click(links[1])
|
|
344
|
+
|
|
345
|
+
expect(mount).toHaveBeenCalledTimes(1)
|
|
329
346
|
})
|
|
330
347
|
|
|
331
348
|
it('Should not fire given onclick function when clicked with modifier keys or not left click', async () => {
|
|
@@ -1225,4 +1242,13 @@ describe('linkInjection.js', () => {
|
|
|
1225
1242
|
expect(document.querySelector('a')).toBeTruthy()
|
|
1226
1243
|
})
|
|
1227
1244
|
})
|
|
1245
|
+
|
|
1246
|
+
describe('trackLinkIntersection', () => {
|
|
1247
|
+
it('Should fire tracking for each intersecting link', () => {
|
|
1248
|
+
// @ts-ignore
|
|
1249
|
+
trackLinkIntersection([{ isIntersecting: true }, { isIntersecting: true }, { isIntersecting: false }])
|
|
1250
|
+
|
|
1251
|
+
expect(track).toHaveBeenCalledTimes(2)
|
|
1252
|
+
})
|
|
1253
|
+
})
|
|
1228
1254
|
})
|
|
@@ -4,12 +4,12 @@ import { mergePlaylinks } from '$lib/playlink'
|
|
|
4
4
|
describe('$lib/playlink', () => {
|
|
5
5
|
describe('mergePlaylinks', () => {
|
|
6
6
|
it('Should return an array with RENT and BUY categories of the same provider as one entry', () => {
|
|
7
|
-
/** @type {
|
|
7
|
+
/** @type {PlaylinkData[]} */
|
|
8
8
|
const playlinks = [{
|
|
9
9
|
sid: '1',
|
|
10
10
|
name: 'Netflix',
|
|
11
11
|
url: '',
|
|
12
|
-
logo_url: '
|
|
12
|
+
logo_url: '',
|
|
13
13
|
extra_info: {
|
|
14
14
|
category: 'RENT',
|
|
15
15
|
},
|
|
@@ -17,7 +17,7 @@ describe('$lib/playlink', () => {
|
|
|
17
17
|
sid: '2',
|
|
18
18
|
name: 'Netflix',
|
|
19
19
|
url: '',
|
|
20
|
-
logo_url: '
|
|
20
|
+
logo_url: '',
|
|
21
21
|
extra_info: {
|
|
22
22
|
category: 'BUY',
|
|
23
23
|
},
|
|
@@ -28,12 +28,12 @@ describe('$lib/playlink', () => {
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
it('Should not merge playlinks if they do not share the same name', () => {
|
|
31
|
-
/** @type {
|
|
31
|
+
/** @type {PlaylinkData[]} */
|
|
32
32
|
const playlinks = [{
|
|
33
33
|
sid: '1',
|
|
34
34
|
name: 'Netflix',
|
|
35
35
|
url: '',
|
|
36
|
-
logo_url: '
|
|
36
|
+
logo_url: '',
|
|
37
37
|
extra_info: {
|
|
38
38
|
category: 'RENT',
|
|
39
39
|
},
|
|
@@ -41,7 +41,7 @@ describe('$lib/playlink', () => {
|
|
|
41
41
|
sid: '2',
|
|
42
42
|
name: 'Apple',
|
|
43
43
|
url: '',
|
|
44
|
-
logo_url: '
|
|
44
|
+
logo_url: '',
|
|
45
45
|
extra_info: {
|
|
46
46
|
category: 'BUY',
|
|
47
47
|
},
|
|
@@ -51,12 +51,12 @@ describe('$lib/playlink', () => {
|
|
|
51
51
|
})
|
|
52
52
|
|
|
53
53
|
it('Should not merge playlinks if one of the categories is not RENT or BUY', () => {
|
|
54
|
-
/** @type {
|
|
54
|
+
/** @type {PlaylinkData[]} */
|
|
55
55
|
const playlinks = [{
|
|
56
56
|
sid: '1',
|
|
57
57
|
name: 'Netflix',
|
|
58
58
|
url: '',
|
|
59
|
-
logo_url: '
|
|
59
|
+
logo_url: '',
|
|
60
60
|
extra_info: {
|
|
61
61
|
category: 'RENT',
|
|
62
62
|
},
|
|
@@ -64,7 +64,7 @@ describe('$lib/playlink', () => {
|
|
|
64
64
|
sid: '2',
|
|
65
65
|
name: 'Netflix',
|
|
66
66
|
url: '',
|
|
67
|
-
logo_url: '
|
|
67
|
+
logo_url: '',
|
|
68
68
|
extra_info: {
|
|
69
69
|
category: 'SVOD',
|
|
70
70
|
},
|
|
@@ -73,23 +73,8 @@ describe('$lib/playlink', () => {
|
|
|
73
73
|
expect(mergePlaylinks(playlinks)).toHaveLength(2)
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
it('Should filter out playlinks without logos', () => {
|
|
77
|
-
/** @type {import('$lib/types/playlink').PlaylinkData[]} */
|
|
78
|
-
const playlinks = [{
|
|
79
|
-
sid: '1',
|
|
80
|
-
name: 'Netflix',
|
|
81
|
-
url: '',
|
|
82
|
-
logo_url: '',
|
|
83
|
-
extra_info: {
|
|
84
|
-
category: 'RENT',
|
|
85
|
-
},
|
|
86
|
-
}]
|
|
87
|
-
|
|
88
|
-
expect(mergePlaylinks(playlinks)).toHaveLength(0)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
76
|
it('Should return an empty array if given', () => {
|
|
92
|
-
/** @type {
|
|
77
|
+
/** @type {PlaylinkData[]} */
|
|
93
78
|
const playlinks = []
|
|
94
79
|
expect(mergePlaylinks(playlinks)).toEqual([])
|
|
95
80
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
2
|
-
import { getSplitTestIdentifier, getSplitTestVariantIndex, isInSplitTestVariant, trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
|
|
2
|
+
import { getSplitTestIdentifier, getSplitTestVariantIndex, getSplitTestVariantName, isInSplitTestVariant, trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
|
|
3
3
|
import { track } from '$lib/tracking'
|
|
4
4
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
5
5
|
|
|
@@ -99,19 +99,47 @@ describe('$lib/splitTest', () => {
|
|
|
99
99
|
it('Should track view event with the given key', () => {
|
|
100
100
|
trackSplitTestView(splitTest)
|
|
101
101
|
|
|
102
|
-
const variant =
|
|
102
|
+
const variant = getSplitTestVariantName(splitTest)
|
|
103
103
|
|
|
104
104
|
expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestView, null, { key: 'Some key', variant })
|
|
105
105
|
})
|
|
106
|
+
|
|
107
|
+
it('Should track view event with the given key and variant name if given', () => {
|
|
108
|
+
// @ts-ignore
|
|
109
|
+
window.PlayPilotLinkInjections.split_test_identifiers = { [splitTest.key]: 0.25 }
|
|
110
|
+
|
|
111
|
+
trackSplitTestView({ ...splitTest, variantNames: ['A', 'B'] })
|
|
112
|
+
|
|
113
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestView, null, { key: 'Some key', variant: 'A' })
|
|
114
|
+
})
|
|
106
115
|
})
|
|
107
116
|
|
|
108
117
|
describe('trackSplitTestAction', () => {
|
|
109
118
|
it('Should track action event with the given key and action', () => {
|
|
110
119
|
trackSplitTestAction(splitTest, 'Some action')
|
|
111
|
-
const variant =
|
|
120
|
+
const variant = getSplitTestVariantName(splitTest)
|
|
112
121
|
|
|
113
122
|
expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestAction, null, { key: splitTest.key, variant, action: 'Some action' })
|
|
114
123
|
})
|
|
124
|
+
|
|
125
|
+
it('Should track action event with the given key and action when consisting of multiple variants', () => {
|
|
126
|
+
const multiple = { key: 'multiple', numberOfVariants: 3 }
|
|
127
|
+
|
|
128
|
+
window.PlayPilotLinkInjections.split_test_identifiers = { [multiple.key]: 0.75 }
|
|
129
|
+
|
|
130
|
+
trackSplitTestAction(multiple, 'Some action')
|
|
131
|
+
|
|
132
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestAction, null, { key: multiple.key, variant: '2', action: 'Some action' })
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('Should track action event with the given key and variant name if given', () => {
|
|
136
|
+
// @ts-ignore
|
|
137
|
+
window.PlayPilotLinkInjections.split_test_identifiers = { [splitTest.key]: 0.5 }
|
|
138
|
+
|
|
139
|
+
trackSplitTestView({ ...splitTest, variantNames: ['A', 'B'] })
|
|
140
|
+
|
|
141
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestView, null, { key: 'Some key', variant: 'B' })
|
|
142
|
+
})
|
|
115
143
|
})
|
|
116
144
|
|
|
117
145
|
describe('getSplitTestVariantIndex', () => {
|