@playpilot/tpi 6.3.0-beta.5 → 6.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "6.3.0-beta.5",
3
+ "version": "6.3.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -291,7 +291,6 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
291
291
  injectionElement.dataset.playpilotInjectionKey = injection.key
292
292
 
293
293
  const linkElement = document.createElement('a')
294
- linkElement.dataset.playpilotPosterUrl = injection.title_details?.standing_poster
295
294
  linkElement.innerText = injection.title
296
295
  linkElement.href = injection.playpilot_url
297
296
  linkElement.target = '_blank'
@@ -99,39 +99,3 @@ h1, h2, h3, h4, h5 {
99
99
  ::view-transition-new(playpilot-title-content) {
100
100
  height: 100%;
101
101
  }
102
-
103
- @keyframes playpilot-poster-fly-in {
104
- from {
105
- opacity: 0;
106
- transform: translateX(-50%) translateY(1.5rem);
107
- }
108
- }
109
-
110
- @keyframes playpilot-poster-fly-out {
111
- to {
112
- opacity: 0;
113
- transform: translateX(-50%) translateY(0.5rem);
114
- }
115
- }
116
-
117
- .playpilot-floating-poster {
118
- opacity: 0;
119
- position: absolute;
120
- bottom: calc(100% + 0.15em);
121
- left: 50%;
122
- transform: translateX(-50%);
123
- width: 40px;
124
- height: auto;
125
- border-radius: theme(border-radius-small);
126
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
127
- transform-origin: bottom center;
128
-
129
- &.loaded {
130
- animation: playpilot-poster-fly-in 500ms cubic-bezier(0.25, 1.75, 0.5, 1);
131
- opacity: 1;
132
- }
133
-
134
- &.animating-out {
135
- animation: playpilot-poster-fly-out 150ms forwards;
136
- }
137
- }
@@ -10,6 +10,12 @@ declare global {
10
10
  mockAd(override?: Record<any, any> = {}): void
11
11
  }
12
12
  }
13
+
14
+ declare namespace svelteHTML {
15
+ interface HTMLAttributes<T> {
16
+ 'onupdateconsent'?: (event: CustomEvent<{}>) => void
17
+ }
18
+ }
13
19
  }
14
20
 
15
21
  export { }
@@ -8,6 +8,7 @@ export type PlaylinkData = {
8
8
  cta_text?: string | null
9
9
  // This doesn't actually exists on the API objects, it's only present from ads
10
10
  action_text?: string | null,
11
+ pixels?: string[]
11
12
  extra_info: {
12
13
  category: PlaylinkCategory
13
14
  }
@@ -18,7 +18,6 @@
18
18
  import Consent from './components/Consent.svelte'
19
19
  import Debugger from './components/Debugger.svelte'
20
20
  import UserJourney from './components/UserJourney.svelte'
21
- import PostersScrollReveal from './components/PostersScrollReveal.svelte'
22
21
 
23
22
  let parentElement: HTMLElement | null = $state(null)
24
23
  let elements: HTMLElement[] = $state([])
@@ -73,12 +72,12 @@
73
72
 
74
73
  window.PlayPilotLinkInjections.config = config
75
74
 
76
- isUrlExcluded = !!(config?.exclude_urls_pattern && url.match(new RegExp(config.exclude_urls_pattern)))
77
- if (isUrlExcluded) return
78
-
79
75
  if (config?.custom_style) insertCustomStyle(config.custom_style || '')
80
76
  if (config?.explore_navigation_selector) insertExploreIntoNavigation()
81
77
 
78
+ isUrlExcluded = !!(config?.exclude_urls_pattern && url.match(new RegExp(config.exclude_urls_pattern)))
79
+ if (isUrlExcluded) return
80
+
82
81
  setElements(config?.html_selector || '', config?.exclude_elements_selector || '')
83
82
  } catch(error) {
84
83
  // We return if the config did not get fetched properly, as we can't determine what should and should
@@ -234,7 +233,6 @@
234
233
 
235
234
  {#key linkInjections}
236
235
  <UserJourney />
237
- <PostersScrollReveal />
238
236
  {/key}
239
237
 
240
238
  <Consent onchange={afterConsent} />
@@ -17,7 +17,7 @@
17
17
  // If require_consent has been explicitely turned off we return right away and call `onchange`.
18
18
  // We don't need to set consent values as require_consent=false will mean all consent is true.
19
19
  if (window.PlayPilotLinkInjections.require_consent === false) {
20
- onchange()
20
+ update()
21
21
  return
22
22
  }
23
23
 
@@ -66,7 +66,15 @@
66
66
  affiliate: consent(1) && consent(7),
67
67
  })
68
68
 
69
- onchange()
69
+ update()
70
70
  })
71
71
  }
72
+
73
+ function update() {
74
+ window.dispatchEvent(new CustomEvent('updateconsent', {
75
+ bubbles: true,
76
+ }))
77
+
78
+ onchange()
79
+ }
72
80
  </script>
@@ -27,6 +27,8 @@
27
27
  const succesfulInjections = data.evaluated_link_injections?.filter(injection => !injection.failed) || []
28
28
  const failedInjections = data.evaluated_link_injections?.filter(injection => injection.failed) || []
29
29
 
30
+ const visiblePixels = Array.from(document.querySelectorAll<HTMLImageElement>('[data-playpilot-pixel]'))
31
+
30
32
  return {
31
33
  'Config': [
32
34
  { label: 'Domain', data: data.domain_sid },
@@ -38,6 +40,7 @@
38
40
  [`Failed injections (${failedInjections.length})`]: failedInjections.map(injection => ({ label: injection.title, data: `Reason: ${injection.failed_message} | Sentence: ${injection.sentence}` })),
39
41
  [`Fetched ads (${data.ads?.length || 0})`]: data.ads?.map(ad => ({ label: ad.campaign_name, data: ad })),
40
42
  [`Tracking events (${data.tracked_events?.length || 0})`]: data.tracked_events?.map(event => ({ label: event.event, data: event.payload })),
43
+ [`Visible pixels (${visiblePixels.length})`]: visiblePixels.map((pixel, index) => ({ label: index + 1, data: pixel.src })),
41
44
  }
42
45
  }
43
46
 
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { heading } from '$lib/actions/heading'
3
+ import { fetchAds } from '$lib/api/ads'
3
4
  import { fetchTitles } from '$lib/api/titles'
4
5
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
5
6
  import { exploreParentSelector } from '$lib/explore'
@@ -46,7 +47,11 @@
46
47
  height = element?.closest<HTMLElement>(exploreParentSelector)?.style.height || null
47
48
  })
48
49
 
49
- onMount(() => track(TrackingEvent.ExplorePageView))
50
+ onMount(async () => {
51
+ track(TrackingEvent.ExplorePageView)
52
+
53
+ if (!window.PlayPilotLinkInjections.ads?.length) window.PlayPilotLinkInjections.ads = await fetchAds()
54
+ })
50
55
 
51
56
  async function getTitlesForFilter(): Promise<APIPaginatedResult<TitleData>> {
52
57
  latestRequestId += 1
@@ -41,12 +41,14 @@
41
41
  border: theme(explore-search-border, 0);
42
42
  border-radius: theme(exlore-search-border-radius, border-radius);
43
43
  background: theme(explore-search-background, content);
44
+ transition: none;
44
45
  color: theme(text-color-alt);
45
- font-size: theme(font-size-base);
46
+ font-size: theme(font-size-large);
46
47
  font-family: theme(font-family);
47
48
 
48
49
  &:focus {
49
- outline: 1px solid white;
50
+ outline: theme(explore-search-focus-outline, 1px solid white);
51
+ outline-offset: 0;
50
52
  }
51
53
 
52
54
  &::placeholder {
@@ -41,9 +41,9 @@
41
41
  let windowWidth = $state(0)
42
42
  let modalElement: HTMLElement | null = $state(null)
43
43
  let dialogElement: HTMLElement | null = $state(null)
44
+ let closeElement: HTMLElement | null = $state(null)
44
45
  let dialogOffset: number = $state(0)
45
46
  let hasPreviousModal = $state(false)
46
- let isTransitioningIn = $state(false)
47
47
 
48
48
  const isMobile = $derived(windowWidth < mobileBreakpoint)
49
49
 
@@ -79,10 +79,10 @@
79
79
  scrollableElement?.scrollTo(0, initialScrollPosition)
80
80
  }
81
81
 
82
- function scaleOrFly(node: Element, options: { y: number } = { y: 0 }): TransitionConfig {
82
+ function scaleOrFly(node: Element): TransitionConfig {
83
83
  if (prefersReducedMotion.current) return fade(node, { duration: 0 })
84
84
 
85
- if (isMobile) return fly(node, { duration: 250, ...options })
85
+ if (isMobile) return fly(node, { duration: 250, y: window.innerHeight, opacity: 1 })
86
86
  return scale(node, { duration: 150, start: 0.85 })
87
87
  }
88
88
 
@@ -103,6 +103,14 @@
103
103
  onhashchange={close}
104
104
  bind:innerWidth={windowWidth} />
105
105
 
106
+ {#snippet closeButton()}
107
+ <div class="close {closeButtonStyle}" transition:scaleOrFly|global bind:this={closeElement}>
108
+ <RoundButton onclick={close} aria-label="Close">
109
+ <IconClose />
110
+ </RoundButton>
111
+ </div>
112
+ {/snippet}
113
+
106
114
  <!-- svelte-ignore a11y_click_events_have_key_events -->
107
115
  <!-- svelte-ignore a11y_no_static_element_interactions -->
108
116
  <div
@@ -114,38 +122,38 @@
114
122
  style:--dialog-offset="{dialogOffset}px"
115
123
  onclick={closeOnBackdropClick}
116
124
  data-testid="modal"
117
- transition:fade|global={{ duration: 150 }}
125
+ in:fade|global={{ duration: 150 }}
126
+ out:fade|global={{ duration: 150, delay: 100 }}
118
127
  bind:this={modalElement}
119
128
  use:focustrap>
120
129
  {#if prepend}
121
- <div class="prepend" transition:scaleOrFly|global={{ y: window.innerHeight }} data-view-transition-new="playpilot-title-extra">
130
+ <div class="prepend" transition:scaleOrFly|global data-view-transition-new="playpilot-title-extra">
122
131
  {@render prepend()}
123
132
  </div>
124
133
  {/if}
125
134
 
126
135
  {#if bubble}
127
- <div class="bubble" transition:scaleOrFly|global={{ y: window.innerHeight }} data-view-transition-new="playpilot-title-extra">
136
+ <div class="bubble" transition:scaleOrFly|global data-view-transition-new="playpilot-title-extra">
128
137
  {@render bubble()}
129
138
  </div>
130
139
  {/if}
131
140
 
132
141
  {#if isMobile}
133
142
  <div class="swipe-handle" transition:scaleOrFly|global>
134
- <SwipeHandle target={dialogElement!} onpassed={close} />
143
+ {#if dialogElement && closeElement}
144
+ <SwipeHandle targets={[dialogElement, closeElement]} onpassed={close} />
145
+ {/if}
135
146
  </div>
136
147
  {/if}
137
148
 
138
149
  <div
139
150
  class="dialog"
140
- class:transitioning={isTransitioningIn}
141
151
  {onscroll}
142
152
  bind:this={dialogElement}
143
153
  role="dialog"
144
154
  aria-labelledby="title"
145
155
  data-view-transition-new="playpilot-title-content"
146
- onintrostart={() => isTransitioningIn = true}
147
- onintroend={() => isTransitioningIn = false}
148
- transition:scaleOrFly|global={{ y: window.innerHeight }}>
156
+ transition:scaleOrFly|global>
149
157
  {#if hasPreviousModal}
150
158
  <div class="close back {closeButtonStyle}">
151
159
  <RoundButton onclick={() => goBackToPreviousModal()} aria-label="Back">
@@ -154,14 +162,16 @@
154
162
  </div>
155
163
  {/if}
156
164
 
157
- <div class="close {closeButtonStyle}">
158
- <RoundButton onclick={close} aria-label="Close">
159
- <IconClose />
160
- </RoundButton>
161
- </div>
162
-
163
165
  {@render children()}
166
+
167
+ {#if !isMobile}
168
+ {@render closeButton()}
169
+ {/if}
164
170
  </div>
171
+
172
+ {#if isMobile}
173
+ {@render closeButton()}
174
+ {/if}
165
175
  </div>
166
176
 
167
177
  <style lang="scss">
@@ -174,7 +184,6 @@
174
184
  flex-direction: column;
175
185
  justify-content: flex-start;
176
186
  align-items: center;
177
- overflow: auto;
178
187
  top: 0;
179
188
  right: 0;
180
189
  bottom: 0;
@@ -182,6 +191,8 @@
182
191
  background: theme(detail-backdrop, rgba(0, 0, 0, 0.65));
183
192
 
184
193
  @include desktop() {
194
+ overflow: auto;
195
+ overscroll-behavior: contain;
185
196
  padding: margin(2) 0;
186
197
  }
187
198
 
@@ -208,9 +219,11 @@
208
219
  margin-top: auto;
209
220
  padding-bottom: env(safe-area-inset-bottom);
210
221
  overflow: auto;
222
+ overscroll-behavior: contain;
211
223
  border-radius: theme(detail-border-radius, margin(1) margin(1) 0 0);
212
224
  background: theme(detail-background, light);
213
- transition: transform 200ms;
225
+ will-change: top;
226
+ transition: top 200ms;
214
227
 
215
228
  @supports (height: 1dvh) {
216
229
  height: 80dvh;
@@ -218,7 +231,7 @@
218
231
 
219
232
  @include desktop() {
220
233
  height: unset;
221
- margin-top: 0;
234
+ margin: auto 0;
222
235
  padding-bottom: 0;
223
236
  overflow: visible;
224
237
  border-radius: theme(detail-border-radius, border-radius-large);
@@ -262,11 +275,14 @@
262
275
  --playpilot-button-text-color: #{theme(modal-close-button-text-color, text-color-alt)};
263
276
  z-index: 5;
264
277
  position: fixed;
265
- top: calc(var(--dialog-offset) + margin(1));
278
+ margin-top: calc(var(--dialog-offset) + margin(1));
279
+ top: 0;
266
280
  right: margin(1);
281
+ transition: top 200ms;
267
282
 
268
283
  @include desktop() {
269
284
  position: absolute;
285
+ margin-top: 0;
270
286
  top: margin(1);
271
287
  }
272
288
 
@@ -291,10 +307,6 @@
291
307
  }
292
308
  }
293
309
  }
294
-
295
- .transitioning & {
296
- top: margin(1);
297
- }
298
310
  }
299
311
 
300
312
  .prepend {
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import Disclaimer from '../Ads/Disclaimer.svelte'
3
+ import TrackingPixels from '../TrackingPixels.svelte'
3
4
  import { hasConsentedTo } from '$lib/consent'
4
5
  import { removeImageUrlPrefix } from '$lib/image'
5
6
  import { t } from '$lib/localization'
@@ -14,7 +15,7 @@
14
15
 
15
16
  const { playlink, disclaimer = '', hideCategory = false, onclick = () => null }: Props = $props()
16
17
 
17
- const { name, url, logo_url, highlighted, cta_text, action_text, extra_info: { category } } = $derived(playlink)
18
+ const { name, url, logo_url, highlighted, cta_text, action_text, pixels, extra_info: { category } } = $derived(playlink)
18
19
 
19
20
  const categoryStrings = {
20
21
  SVOD: t('Stream'),
@@ -33,6 +34,7 @@
33
34
  class="playlink"
34
35
  class:highlighted={highlighted && cta_text}
35
36
  class:no-category={hideCategory}
37
+ class:no-subtext={!cta_text && hideCategory}
36
38
  data-playlink={name}
37
39
  rel="sponsored">
38
40
 
@@ -57,6 +59,10 @@
57
59
  <Disclaimer {disclaimer} small />
58
60
  {/if}
59
61
  </div>
62
+
63
+ {#if pixels?.length}
64
+ <TrackingPixels {pixels} />
65
+ {/if}
60
66
  </svelte:element>
61
67
 
62
68
  <style lang="scss">
@@ -162,6 +168,10 @@
162
168
  .no-category & {
163
169
  grid-template-areas: "image name action" "image cta action";
164
170
  }
171
+
172
+ .no-subtext & {
173
+ grid-template-areas: "image name action";
174
+ }
165
175
  }
166
176
 
167
177
  .name {
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import TrackingPixels from '../TrackingPixels.svelte'
2
3
  import { removeImageUrlPrefix } from '$lib/image'
3
4
  import type { PlaylinkData } from '$lib/types/playlink'
4
5
 
@@ -11,13 +12,18 @@
11
12
 
12
13
  const { playlink, size = 30, onclick = () => null }: Props = $props()
13
14
 
14
- const { name, url, logo_url } = $derived(playlink)
15
+ const { name, url, logo_url, pixels } = $derived(playlink)
15
16
  </script>
16
17
 
17
18
  <a href={url} target="_blank" class="playlink" data-playlink={name} rel="sponsored" {onclick} style:--size="{size}px">
18
19
  <img src={removeImageUrlPrefix(logo_url)} alt={name} height={size} width={size} />
20
+
21
+ {#if pixels?.length}
22
+ <TrackingPixels {pixels} />
23
+ {/if}
19
24
  </a>
20
25
 
26
+
21
27
  <style lang="scss">
22
28
  .playlink {
23
29
  display: inline-block;
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  interface Props {
3
- target?: HTMLElement
3
+ targets: HTMLElement[]
4
4
  threshold?: number
5
5
  isDragging?: boolean
6
6
  passedThreshold?: boolean
@@ -8,7 +8,7 @@
8
8
  }
9
9
 
10
10
  let {
11
- target,
11
+ targets,
12
12
  threshold = 100,
13
13
  isDragging = $bindable(false),
14
14
  passedThreshold = $bindable(false),
@@ -28,28 +28,33 @@
28
28
 
29
29
  export function start(event: TouchEvent): void {
30
30
  if (!event) return
31
- if (!target) return
32
31
 
33
32
  isDragging = true
34
33
  hasBeenDragged = true
35
34
 
36
35
  movementStartY = event.touches[0].pageY
37
- target.style.transitionDuration = '0ms'
36
+
37
+ for (const target of targets) {
38
+ target.style.transitionDuration = '0ms'
39
+ }
38
40
  }
39
41
 
40
42
  export function move(event: TouchEvent): void {
41
43
  if (!event) return
42
- if (!target) return
43
44
  if (!isDragging) return
44
45
 
46
+ event.preventDefault()
47
+
45
48
  movementCurrentY = event.touches[0].pageY
46
49
 
47
- target.style.transform = `translateY(${difference}px)`
48
50
  handleElement!.style.transform = `translateY(${difference}px)`
51
+
52
+ for (const target of targets) {
53
+ target.style.top = `${difference}px`
54
+ }
49
55
  }
50
56
 
51
57
  export function end(): void {
52
- if (!target) return
53
58
  if (!isDragging) return
54
59
 
55
60
  isDragging = false
@@ -57,8 +62,12 @@
57
62
  if (passedThreshold) {
58
63
  onpassed()
59
64
  } else {
60
- target.removeAttribute('style')
61
65
  handleElement!.removeAttribute('style')
66
+
67
+ for (const target of targets) {
68
+ target.style.removeProperty('transition')
69
+ requestAnimationFrame(() => target.style.top = '0px')
70
+ }
62
71
  }
63
72
 
64
73
  movementStartY = 0
@@ -8,7 +8,6 @@
8
8
  import Title from './Title.svelte'
9
9
  import TopScroll from './Ads/TopScroll.svelte'
10
10
  import Display from './Ads/Display.svelte'
11
- import TrackingPixels from './TrackingPixels.svelte'
12
11
 
13
12
  interface Props {
14
13
  event: MouseEvent
@@ -68,9 +67,6 @@
68
67
  <div class="title-popover" bind:this={element} data-playpilot-title-popover role="region" aria-labelledby="title">
69
68
  <Popover append={displayAd ? append : null} bubble={topScroll ? bubble : null}>
70
69
  <Title {title} small />
71
-
72
- <!-- Temporary tracking pixel to verify our own tracking works as expected. The random id exists to bypass cache on subsequent requests. -->
73
- <TrackingPixels pixels={[`https://imp.pxf.io/i/2439446/837174/9358?id=${Math.random()}`]} />
74
70
  </Popover>
75
71
  </div>
76
72
 
@@ -6,13 +6,19 @@
6
6
  }
7
7
 
8
8
  const { pixels }: Props = $props()
9
+
10
+ let key = $state(Math.random())
9
11
  </script>
10
12
 
11
- {#if hasConsentedTo('pixels')}
12
- {#each pixels as src}
13
- <img {src} alt="" />
14
- {/each}
15
- {/if}
13
+ <svelte:window onupdateconsent={() => key = Math.random()} />
14
+
15
+ {#key key}
16
+ {#if hasConsentedTo('pixels')}
17
+ {#each pixels as src}
18
+ <img {src} alt="" data-playpilot-pixel />
19
+ {/each}
20
+ {/if}
21
+ {/key}
16
22
 
17
23
  <style lang="scss">
18
24
  img {
@@ -15,10 +15,13 @@ describe('Consent.svelte', () => {
15
15
  expect(onchange).toHaveBeenCalled()
16
16
  })
17
17
 
18
- it('Should call onchange after user has consented', () => {
18
+ it('Should call onchange and fire window event after user has consented', () => {
19
19
  // @ts-ignore
20
20
  window.PlayPilotLinkInjections = { require_consent: true }
21
21
 
22
+ const mock = vi.fn()
23
+ window.addEventListener('updateconsent', mock)
24
+
22
25
  // @ts-ignore
23
26
  window.__tcfapi = (command, _version, callback) => {
24
27
  if (command !== 'addEventListener') return
@@ -39,6 +42,7 @@ describe('Consent.svelte', () => {
39
42
  window.__tcfapi()
40
43
 
41
44
  expect(onchange).toHaveBeenCalled()
45
+ expect(mock).toHaveBeenCalled()
42
46
  })
43
47
 
44
48
  it('Should not call onchange after user has consented but eventStatus is invalid', () => {
@@ -6,6 +6,7 @@ import { fetchTitles } from '$lib/api/titles'
6
6
  import { title } from '$lib/fakeData'
7
7
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
8
8
  import { track } from '$lib/tracking'
9
+ import { fetchAds } from '$lib/api/ads'
9
10
 
10
11
  vi.mock('$lib/api/titles', () => ({
11
12
  fetchTitles: vi.fn(),
@@ -19,8 +20,15 @@ vi.mock('$lib/tracking', () => ({
19
20
  track: vi.fn(),
20
21
  }))
21
22
 
23
+ vi.mock('$lib/api/ads', () => ({
24
+ fetchAds: vi.fn(),
25
+ }))
26
+
22
27
  describe('Explore.svelte', () => {
23
28
  beforeEach(() => {
29
+ // @ts-ignore
30
+ window.PlayPilotLinkInjections = {}
31
+
24
32
  vi.resetAllMocks()
25
33
  })
26
34
 
@@ -30,10 +38,23 @@ describe('Explore.svelte', () => {
30
38
  expect(fetchTitles).toHaveBeenCalledWith({ page_size: 24, page: 1 })
31
39
  })
32
40
 
33
- it('Should call tracking event on mount', () => {
41
+ it('Should call tracking event and fetchAds on mount', () => {
34
42
  render(Explore)
35
43
 
36
44
  expect(track).toHaveBeenCalledWith(TrackingEvent.ExplorePageView)
45
+ expect(fetchAds).toHaveBeenCalled()
46
+ })
47
+
48
+ it('Should not call fetchAds on mount if ads are already present', () => {
49
+ // @ts-ignore
50
+ window.PlayPilotLinkInjections = {
51
+ // @ts-ignore
52
+ ads: ['a'],
53
+ }
54
+
55
+ render(Explore)
56
+
57
+ expect(fetchAds).not.toHaveBeenCalled()
37
58
  })
38
59
 
39
60
  it('Should render all returned titles', async () => {
@@ -131,4 +131,36 @@ describe('Playlink.svelte', () => {
131
131
 
132
132
  expect(getByTestId('disclaimer')).toBeTruthy()
133
133
  })
134
+
135
+
136
+ it('Should render given tracking pixels', () => {
137
+ vi.mocked(hasConsentedTo).mockImplementation(() => true)
138
+
139
+ const playlink = { name: 'Some playlink', logo_url: 'logo', pixels: ['https://some-pixel.com/a.jpg'], extra_info: { category: 'SVOD' } }
140
+ // @ts-ignore
141
+ const { container } = render(Playlink, { playlink })
142
+
143
+ expect(container.querySelector('img[src*="https://some-pixel.com/a.jpg"]')).toBeTruthy()
144
+ })
145
+
146
+ it('Should include no-subtext class if no cta_text is given and hideCategory is true', () => {
147
+ // @ts-ignore
148
+ const { container } = render(Playlink, { playlink: { ...playlink, cta_text: '' }, hideCategory: true })
149
+
150
+ expect(container.querySelector('.no-subtext')).toBeTruthy()
151
+ })
152
+
153
+ it('Should not include no-subtext class if no cta_text is given but hideCategory is false', () => {
154
+ // @ts-ignore
155
+ const { container } = render(Playlink, { playlink: { ...playlink, cta_text: '' }, hideCategory: false })
156
+
157
+ expect(container.querySelector('.no-subtext')).not.toBeTruthy()
158
+ })
159
+
160
+ it('Should not include no-subtext class if cta_text is given and hideCategory is true', () => {
161
+ // @ts-ignore
162
+ const { container } = render(Playlink, { playlink: { ...playlink, cta_text: 'Some text' }, hideCategory: true })
163
+
164
+ expect(container.querySelector('.no-subtext')).not.toBeTruthy()
165
+ })
134
166
  })