@playpilot/tpi 5.6.0 → 5.7.0-beta.display.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.
@@ -1,17 +1,22 @@
1
1
  <script lang="ts">
2
+ import { SplitTest } from '$lib/enums/SplitTest'
3
+ import { isInSplitTestVariant } from '$lib/splitTest'
2
4
  import { onMount, setContext, tick, type Snippet } from 'svelte'
3
5
  import { prefersReducedMotion } from 'svelte/motion'
4
6
  import { fly } from 'svelte/transition'
5
7
 
6
8
  interface Props {
7
9
  children: Snippet
10
+ bubble?: Snippet | null
8
11
  maxHeight?: number
9
12
  }
10
13
 
11
- let { children, maxHeight = $bindable() }: Props = $props()
14
+ let { children, bubble, maxHeight = $bindable() }: Props = $props()
12
15
 
13
16
  setContext('scope', 'popover')
14
17
 
18
+ const inlineBubble = isInSplitTestVariant(SplitTest.TopScrollFormat, 1)
19
+
15
20
  let element: HTMLElement | null = $state(null)
16
21
  let flip = $state(false)
17
22
 
@@ -59,15 +64,24 @@
59
64
  }
60
65
  </script>
61
66
 
62
- <div class="popover" class:flip bind:this={element} style:--max-height={maxHeight ? maxHeight + 'px' : null} tabindex="-1" aria-hidden="true">
67
+ <div class="popover" class:flip class:inline={inlineBubble} class:has-bubble={!!bubble && inlineBubble} bind:this={element} style:--max-height={maxHeight ? maxHeight + 'px' : null} tabindex="-1" aria-hidden="true">
63
68
  <div class="dialog" transition:fly|global={{ duration: prefersReducedMotion.current ? 0 : 100, y: 10 }} data-view-transition-old>
64
69
  {@render children()}
65
70
  </div>
71
+
72
+ {#if bubble}
73
+ <div transition:fly|global={{ duration: prefersReducedMotion.current ? 0 : 100, y: 10 }}>
74
+ {@render bubble()}
75
+ </div>
76
+ {/if}
66
77
  </div>
67
78
 
68
79
  <style lang="scss">
69
80
  .popover {
70
81
  --offset: #{margin(0.5)};
82
+ display: flex;
83
+ flex-direction: column-reverse;
84
+ gap: margin(0.5);
71
85
  position: absolute;
72
86
  top: calc((var(--offset) - 1px) * -1); /* Add 1 pixel to account for rounding errors */
73
87
  left: 0;
@@ -78,9 +92,18 @@
78
92
  z-index: 2147483647; // As high as she goes
79
93
 
80
94
  &.flip {
95
+ flex-direction: column;
81
96
  top: auto;
82
97
  bottom: calc(var(--offset) + 1px); /* Add 1 pixel to account for rounding errors */
83
98
  transform: translateY(calc(100% + var(--offset)));
99
+
100
+ &.inline {
101
+ flex-direction: column-reverse;
102
+ }
103
+ }
104
+
105
+ &.inline {
106
+ gap: 0;
84
107
  }
85
108
  }
86
109
 
@@ -96,5 +119,10 @@
96
119
  overflow-y: overlay;
97
120
  overflow-x: hidden;
98
121
  view-transition-name: playpilot-title-content;
122
+
123
+ .has-bubble & {
124
+ border-top-left-radius: 0;
125
+ border-top-right-radius: 0;
126
+ }
99
127
  }
100
128
  </style>
@@ -183,6 +183,7 @@
183
183
  left: 0;
184
184
  width: 100%;
185
185
  height: margin(12);
186
+ border-radius: var(--playpilot-detail-background-border-radius);
186
187
  overflow: hidden;
187
188
  background: var(--playpilot-detail-background, var(--playpilot-lighter));
188
189
  z-index: 0;
@@ -5,6 +5,11 @@
5
5
  import { onMount } from 'svelte'
6
6
  import Modal from './Modal.svelte'
7
7
  import Title from './Title.svelte'
8
+ import TopScroll from './Ads/TopScroll.svelte'
9
+ import Display from './Ads/Display.svelte'
10
+ import { getFirstAdOfType } from '$lib/ads'
11
+ import { isInSplitTestVariant } from '$lib/splitTest'
12
+ import { SplitTest } from '$lib/enums/SplitTest'
8
13
 
9
14
  interface Props {
10
15
  onclose: () => void,
@@ -13,6 +18,11 @@
13
18
 
14
19
  const { onclose, title }: Props = $props()
15
20
 
21
+ const topScrollAd = getFirstAdOfType('top_scroll')
22
+ const displayAd = getFirstAdOfType('display')
23
+
24
+ const isInDisplayAdFormatSplitTest = isInSplitTestVariant(SplitTest.DisplayAdPosition, 1) || isInSplitTestVariant(SplitTest.DisplayAdPosition, 2)
25
+
16
26
  track(TrackingEvent.TitleModalView, title)
17
27
 
18
28
  let hasTrackedScrolling = false
@@ -31,6 +41,18 @@
31
41
  }
32
42
  </script>
33
43
 
34
- <Modal {onclose} {onscroll}>
44
+ {#snippet bubble()}
45
+ {#if topScrollAd}
46
+ <TopScroll campaign={topScrollAd} />
47
+ {/if}
48
+ {/snippet}
49
+
50
+ {#snippet prepend()}
51
+ {#if displayAd}
52
+ <Display campaign={displayAd} />
53
+ {/if}
54
+ {/snippet}
55
+
56
+ <Modal {onclose} {onscroll} prepend={isInDisplayAdFormatSplitTest ? prepend : null} bubble={topScrollAd ? bubble : null}>
35
57
  <Title {title} />
36
58
  </Modal>
@@ -1,10 +1,12 @@
1
1
  <script lang="ts">
2
2
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
3
  import { track } from '$lib/tracking'
4
+ import { getFirstAdOfType } from '$lib/ads'
4
5
  import type { TitleData } from '$lib/types/title'
5
6
  import { onMount } from 'svelte'
6
7
  import Popover from './Popover.svelte'
7
8
  import Title from './Title.svelte'
9
+ import TopScroll from './Ads/TopScroll.svelte'
8
10
 
9
11
  interface Props {
10
12
  event: MouseEvent
@@ -13,6 +15,8 @@
13
15
 
14
16
  const { event, title }: Props = $props()
15
17
 
18
+ const topScroll = getFirstAdOfType('top_scroll')
19
+
16
20
  let maxHeight = $state(0)
17
21
  let element: HTMLElement | null = $state(null)
18
22
 
@@ -47,8 +51,14 @@
47
51
  }
48
52
  </script>
49
53
 
54
+ {#snippet bubble()}
55
+ {#if topScroll}
56
+ <TopScroll campaign={topScroll} />
57
+ {/if}
58
+ {/snippet}
59
+
50
60
  <div class="title-popover" bind:this={element} data-playpilot-title-popover role="region" aria-labelledby="title">
51
- <Popover bind:maxHeight>
61
+ <Popover bind:maxHeight bubble={topScroll ? bubble : null}>
52
62
  <Title {title} small compact={!!maxHeight && maxHeight < 250} />
53
63
  </Popover>
54
64
  </div>
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'
2
+ import { fakeFetch } from '../helpers'
3
+
4
+ import { fetchAds, getFirstAdOfType } from '$lib/ads'
5
+
6
+ describe('$lib/api', () => {
7
+ afterEach(() => {
8
+ vi.resetAllMocks()
9
+ // @ts-ignore
10
+ window.PlayPilotLinkInjections = null
11
+ })
12
+
13
+ describe('fetchAds', () => {
14
+ beforeEach(() => {
15
+ // @ts-ignore
16
+ window.PlayPilotLinkInjections = { token: 'a' }
17
+ })
18
+
19
+ it('Should call fetch with given url', async () => {
20
+ fakeFetch({ response: 'Some response' })
21
+
22
+ const response = await fetchAds()
23
+
24
+ expect(response).toBe('Some response')
25
+ expect(global.fetch).toHaveBeenCalledWith(
26
+ expect.stringContaining('api-token'),
27
+ expect.objectContaining({}),
28
+ )
29
+ })
30
+
31
+ it('Should not call fetch if no api token is present', async () => {
32
+ // @ts-ignore
33
+ window.PlayPilotLinkInjections = { token: '' }
34
+
35
+ await expect(async () => await fetchAds()).rejects.toThrowError('No token was provided')
36
+ expect(global.fetch).not.toHaveBeenCalled()
37
+ })
38
+ })
39
+
40
+ describe('getFirstAdOfType', () => {
41
+ it('Should return the first ad for the given type in the window object', () => {
42
+ // @ts-ignore
43
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'top_scroll' }] }
44
+
45
+ expect(getFirstAdOfType('top_scroll')).toEqual({ campaign_format: 'top_scroll' })
46
+ })
47
+
48
+ it('Should not return any ads if no valid ads are given', () => {
49
+ // @ts-ignore
50
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'hero' }] }
51
+
52
+ expect(getFirstAdOfType('top_scroll')).toBe(null)
53
+ })
54
+ })
55
+ })
@@ -10,6 +10,7 @@ import { generateInjection } from '../helpers'
10
10
  import { injectLinksInDocument } from '$lib/linkInjection'
11
11
  import { isCrawler } from '$lib/crawler'
12
12
  import { getFullUrlPath } from '$lib/url'
13
+ import { fetchAds } from '$lib/ads'
13
14
 
14
15
  vi.mock('$lib/api', () => ({
15
16
  fetchLinkInjections: vi.fn(() => {}),
@@ -61,6 +62,10 @@ vi.mock('$lib/splitTest', () => ({
61
62
  trackSplitTestView: vi.fn(),
62
63
  }))
63
64
 
65
+ vi.mock('$lib/ads', () => ({
66
+ fetchAds: vi.fn(),
67
+ }))
68
+
64
69
  describe('$routes/+page.svelte', () => {
65
70
  beforeEach(() => {
66
71
  document.body.innerHTML = ''
@@ -373,6 +378,34 @@ describe('$routes/+page.svelte', () => {
373
378
  })
374
379
  })
375
380
 
381
+ it('Should fetch ads if any injections are given', async () => {
382
+ // @ts-ignore
383
+ vi.mocked(pollLinkInjections).mockResolvedValueOnce({ ai_injections: [{}], manual_injections: [] })
384
+ render(page)
385
+ await waitFor(() => expect(fetchAds).toHaveBeenCalled())
386
+
387
+ // @ts-ignore
388
+ vi.mocked(pollLinkInjections).mockResolvedValueOnce({ ai_injections: [], manual_injections: [{}] })
389
+ render(page)
390
+ await waitFor(() => expect(fetchAds).toHaveBeenCalledTimes(2))
391
+
392
+ // @ts-ignore
393
+ vi.mocked(pollLinkInjections).mockResolvedValueOnce({ ai_injections: [{}], manual_injections: [{}] })
394
+ render(page)
395
+ await waitFor(() => expect(fetchAds).toHaveBeenCalledTimes(3))
396
+ })
397
+
398
+ it('Should not fetch ads if any no injections are given', async () => {
399
+ // @ts-ignore
400
+ vi.mocked(pollLinkInjections).mockResolvedValueOnce({ ai_injections: [], manual_injections: [] })
401
+
402
+ render(page)
403
+
404
+ await new Promise(res => setTimeout(res, 100)) // Await possible fetches
405
+
406
+ expect(fetchAds).not.toHaveBeenCalled()
407
+ })
408
+
376
409
  describe('Config', () => {
377
410
  describe('exclude_urls_pattern', () => {
378
411
  it('Should not inject if config exclude_urls_pattern matches current url', async () => {
@@ -0,0 +1,59 @@
1
+ import { fireEvent, render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+
4
+ import Display from '../../../../routes/components/Ads/Display.svelte'
5
+ import { track } from '$lib/tracking'
6
+ import { TrackingEvent } from '$lib/enums/TrackingEvent'
7
+
8
+ vi.mock('$lib/tracking', () => ({
9
+ track: vi.fn(),
10
+ }))
11
+
12
+ export const campaign = {
13
+ campaign_format: 'display',
14
+ campaign_type: 'video',
15
+ campaign_platforms: ['web', 'android_app', 'ios_app'],
16
+ campaign_name: 'some_campaign_NL',
17
+ campaign_start: '2025-04-24T00:00:00Z',
18
+ campaign_end: '2025-04-30T23:59:00Z',
19
+ campaign_region: 'nl',
20
+ content: {
21
+ header: 'Some top scroll header',
22
+ header_logo: 'https://example.com/',
23
+ header_logo_uuid: '4fb7d6ea152111f08dd20a58a9feac02',
24
+ subheader: null,
25
+ image: 'https://example.com/',
26
+ image_uuid: '4fb7d6ea152111f08dd20a58a9feac02',
27
+ video: null,
28
+ format: null,
29
+ },
30
+ cta: {
31
+ header: 'Button Label',
32
+ subheader: null,
33
+ image: null,
34
+ image_uuid: null,
35
+ url: 'https://google.com/',
36
+ },
37
+ content_playlist: null,
38
+ provider: null,
39
+ impression_trackers: [],
40
+ target_title_uuid: null,
41
+ target_title_sid: null,
42
+ backfill_providers: [],
43
+ autogenerated: false,
44
+ enabled: true,
45
+ hide_imdb_score: false,
46
+ hide_sponsored_message: false,
47
+ disclaimer: 'Some disclaimer',
48
+ }
49
+
50
+ describe('Display.svelte', () => {
51
+ it('Should fire track even on click', async () => {
52
+ /** @ts-ignore */
53
+ const { getByRole } = render(Display, { campaign })
54
+
55
+ await fireEvent.click(getByRole('link'))
56
+
57
+ expect(track).toHaveBeenCalledWith(TrackingEvent.DisplayAdClick, null, { campaign_name: campaign.campaign_name })
58
+ })
59
+ })
@@ -0,0 +1,104 @@
1
+ import { fireEvent, render, waitFor } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+
4
+ import TopScroll from '../../../../routes/components/Ads/TopScroll.svelte'
5
+ import { track } from '$lib/tracking'
6
+ import { TrackingEvent } from '$lib/enums/TrackingEvent'
7
+
8
+ vi.mock('$lib/tracking', () => ({
9
+ track: vi.fn(),
10
+ }))
11
+
12
+ export const campaign = {
13
+ campaign_format: 'top_scroll',
14
+ campaign_type: 'video',
15
+ campaign_platforms: ['web', 'android_app', 'ios_app'],
16
+ campaign_name: 'some_campaign_NL',
17
+ campaign_start: '2025-04-24T00:00:00Z',
18
+ campaign_end: '2025-04-30T23:59:00Z',
19
+ campaign_region: 'nl',
20
+ content: {
21
+ header: 'Some top scroll header',
22
+ header_logo: 'https://example.com/',
23
+ header_logo_uuid: '4fb7d6ea152111f08dd20a58a9feac02',
24
+ subheader: null,
25
+ image: 'https://example.com/',
26
+ image_uuid: '4fb7d6ea152111f08dd20a58a9feac02',
27
+ video: null,
28
+ format: null,
29
+ },
30
+ cta: {
31
+ header: 'Button Label',
32
+ subheader: null,
33
+ image: null,
34
+ image_uuid: null,
35
+ url: 'https://google.com/',
36
+ },
37
+ content_playlist: null,
38
+ provider: null,
39
+ impression_trackers: [],
40
+ target_title_uuid: null,
41
+ target_title_sid: null,
42
+ backfill_providers: [],
43
+ autogenerated: false,
44
+ enabled: true,
45
+ hide_imdb_score: false,
46
+ hide_sponsored_message: false,
47
+ disclaimer: 'Some disclaimer',
48
+ }
49
+
50
+ describe('TopScroll.svelte', () => {
51
+ it('Should render the given content', () => {
52
+ /** @ts-ignore */
53
+ const { getByText, container } = render(TopScroll, { campaign })
54
+
55
+ expect(getByText(campaign.cta.header)).toBeTruthy()
56
+ expect(getByText(campaign.content.header)).toBeTruthy()
57
+ expect(container.querySelector('.tooltip')).toBeTruthy()
58
+ })
59
+
60
+ it('Should not show disclaimer when not given', async () => {
61
+ const { getByText, container } = render(TopScroll, {
62
+ /** @ts-ignore */
63
+ campaign: { ...campaign, disclaimer: null },
64
+ })
65
+
66
+ await waitFor(() => getByText(campaign.cta.header))
67
+
68
+ expect(container.querySelector('.tooltip')).not.toBeTruthy()
69
+ })
70
+
71
+ it('Should toggle disclaimer on click', async () => {
72
+ /** @ts-ignore */
73
+ const { getByText, container, queryByText } = render(TopScroll, { campaign })
74
+
75
+ await waitFor(() => getByText(campaign.cta.header))
76
+
77
+ await fireEvent.click(/** @type {HTMLElement} */ (container.querySelector('.tooltip')))
78
+ expect(getByText(campaign.disclaimer)).toBeTruthy()
79
+
80
+ await fireEvent.click(/** @type {HTMLElement} */ (container.querySelector('.tooltip')))
81
+ expect(queryByText(campaign.disclaimer)).not.toBeTruthy()
82
+ })
83
+
84
+ it('Should fire track even on click', async () => {
85
+ /** @ts-ignore */
86
+ const { getByRole } = render(TopScroll, { campaign })
87
+
88
+ await fireEvent.click(getByRole('link'))
89
+
90
+ expect(track).toHaveBeenCalledWith(TrackingEvent.TopScrollClick, null, { campaign_name: campaign.campaign_name })
91
+ })
92
+
93
+ it('Should render as simple variant when content format is large', async () => {
94
+ const simpleCampaign = { ...campaign, content: { ...campaign.content, format: 'large' } }
95
+ /** @ts-ignore */
96
+ const { queryByText, container } = render(TopScroll, { campaign: simpleCampaign })
97
+
98
+ expect(queryByText(simpleCampaign.content.header)).not.toBeTruthy()
99
+ expect(queryByText(simpleCampaign.cta.header)).not.toBeTruthy()
100
+ expect(container.querySelector('.background')).not.toBeTruthy()
101
+ expect(container.querySelector('.disclaimer')).not.toBeTruthy()
102
+ expect(container.querySelector('.content-image')).toBeTruthy()
103
+ })
104
+ })
@@ -0,0 +1,88 @@
1
+ import { fireEvent, render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+
4
+ import Playlink from '../../../routes/components/Playlink.svelte'
5
+
6
+ vi.mock('$lib/tracking', () => ({
7
+ track: vi.fn(),
8
+ }))
9
+
10
+ vi.mock('svelte', async (importActual) => ({
11
+ ...(await importActual()),
12
+ getContext: vi.fn(),
13
+ }))
14
+
15
+ describe('Playlink.svelte', () => {
16
+ const playlink = { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } }
17
+
18
+ it('Should render category as words', () => {
19
+ // @ts-ignore
20
+ let { unmount, getByText } = render(Playlink, { playlink })
21
+ expect(getByText('Stream')).toBeTruthy()
22
+ unmount()
23
+
24
+ // @ts-ignore
25
+ ;({ unmount, getByText } = render(Playlink, { playlink: { name: 'Some buy playlink', logo_url: 'logo', extra_info: { category: 'BUY' } } }))
26
+ expect(getByText('Buy')).toBeTruthy()
27
+ unmount()
28
+
29
+ // @ts-ignore
30
+ ;({ unmount, getByText } = render(Playlink, { playlink: { name: 'Some rent playlink', logo_url: 'logo', extra_info: { category: 'RENT' } } }))
31
+ expect(getByText('Rent')).toBeTruthy()
32
+ unmount()
33
+
34
+ // @ts-ignore
35
+ ;({ unmount, getByText } = render(Playlink, { playlink: { name: 'Some rent playlink', logo_url: 'logo', extra_info: { category: 'other' } } }))
36
+ expect(getByText('Stream')).toBeTruthy()
37
+ unmount()
38
+ })
39
+
40
+ it('Should render the category when hideCategory is false', () => {
41
+ // @ts-ignore
42
+ const { queryByTestId } = render(Playlink, { playlink, hideCategory: false })
43
+
44
+ expect(queryByTestId('category')).toBeTruthy()
45
+ })
46
+
47
+ it('Should not render the category when hideCategory is given', () => {
48
+ // @ts-ignore
49
+ const { queryByTestId } = render(Playlink, { playlink, hideCategory: true })
50
+
51
+ expect(queryByTestId('category')).not.toBeTruthy()
52
+ })
53
+
54
+ it('Should call given onclick function', async () => {
55
+ const onclick = vi.fn()
56
+
57
+ // @ts-ignore
58
+ const { getByText } = render(Playlink, { playlink, onclick })
59
+
60
+ await fireEvent.click(getByText(playlink.name))
61
+
62
+ expect(onclick).toHaveBeenCalled()
63
+ })
64
+
65
+ it('Should highlight and show category text for playlinks that have highlighted as true', () => {
66
+ // @ts-ignore
67
+ const { getByText } = render(Playlink, { playlink: { name: 'Some highlighted playlink', logo_url: 'logo', highlighted: true, cta_text: 'Some CTA', extra_info: { category: 'BUY' } } })
68
+
69
+ expect(getByText('Some highlighted playlink').closest('.playlink')?.classList).toContain('highlighted')
70
+ expect(getByText('Some CTA')).toBeTruthy()
71
+ })
72
+
73
+ it('Should not highlight or show category text for playlinks that have highlighted as true but have no cta_text', () => {
74
+ // @ts-ignore
75
+ const { getByText, queryByText } = render(Playlink, { playlink: { name: 'Some playlink', logo_url: 'logo', highlighted: true, cta_text: '', extra_info: { category: 'BUY' } } })
76
+
77
+ expect(getByText('Some playlink').closest('.playlink')?.classList).not.toContain('highlighted')
78
+ expect(queryByText('Some CTA')).not.toBeTruthy()
79
+ })
80
+
81
+ it('Should not highlight playlinks that do not have highlighted set to true', () => {
82
+ // @ts-ignore
83
+ const { getByText, queryByText } = render(Playlink, { playlink })
84
+
85
+ expect(getByText('Some playlink').closest('.playlink')?.classList).not.toContain('highlighted')
86
+ expect(queryByText('Some CTA')).not.toBeTruthy()
87
+ })
88
+ })
@@ -49,21 +49,6 @@ describe('Playlinks.svelte', () => {
49
49
  expect(queryByTestId('commission-disclaimer')).not.toBeTruthy()
50
50
  })
51
51
 
52
- it('Should render category as words', () => {
53
- const playlinks = [
54
- { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
55
- { name: 'Some buy playlink', logo_url: 'logo', extra_info: { category: 'BUY' } },
56
- { name: 'Some rent playlink', logo_url: 'logo', extra_info: { category: 'RENT' } },
57
- { name: 'Some other playlink', logo_url: 'logo', extra_info: { category: 'other' } },
58
- ]
59
- // @ts-ignore
60
- const { getByText, getAllByText } = render(Playlinks, { playlinks, title })
61
-
62
- expect(getAllByText('Stream')).toHaveLength(2)
63
- expect(getByText('Buy')).toBeTruthy()
64
- expect(getByText('Rent')).toBeTruthy()
65
- })
66
-
67
52
  it('Should call track function when clicked', async () => {
68
53
  const playlinks = [
69
54
  { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
@@ -103,19 +88,4 @@ describe('Playlinks.svelte', () => {
103
88
 
104
89
  expect(getAllByText('Some playlink')).toHaveLength(1)
105
90
  })
106
-
107
- it('Should highlight and show category text for playlinks that have highlighted as true', () => {
108
- const playlinks = [
109
- { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
110
- { name: 'Some other playlink', logo_url: 'logo', highlighted: true, cta_text: '', extra_info: { category: 'BUY' } },
111
- { name: 'Some highlighted playlink', logo_url: 'logo', highlighted: true, cta_text: 'Some CTA', extra_info: { category: 'BUY' } },
112
- ]
113
- // @ts-ignore
114
- const { getByText } = render(Playlinks, { playlinks, title })
115
-
116
- expect(getByText('Some playlink').closest('.playlink')?.classList).not.toContain('highlighted')
117
- expect(getByText('Some other playlink').closest('.playlink')?.classList).not.toContain('highlighted')
118
- expect(getByText('Some highlighted playlink').closest('.playlink')?.classList).toContain('highlighted')
119
- expect(getByText('Some CTA')).toBeTruthy()
120
- })
121
91
  })
@@ -45,4 +45,40 @@ describe('TitleModal.svelte', () => {
45
45
 
46
46
  expect(track).toHaveBeenCalledWith(TrackingEvent.TitleModalClose, title, { time_spent: 200 })
47
47
  })
48
+
49
+ it('Should render top scroll ad when given', () => {
50
+ // @ts-ignore
51
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'top_scroll', content: {}, cta: {} }] }
52
+
53
+ const { container } = render(TitleModal, { onclose, title })
54
+
55
+ expect(container.querySelector('.top-scroll')).toBeTruthy()
56
+ })
57
+
58
+ it('Should not render top scroll ad when not given', () => {
59
+ // @ts-ignore
60
+ window.PlayPilotLinkInjections = { ads: null }
61
+
62
+ const { container } = render(TitleModal, { onclose, title })
63
+
64
+ expect(container.querySelector('.top-scroll')).not.toBeTruthy()
65
+ })
66
+
67
+ it('Should render display ad when given', () => {
68
+ // @ts-ignore
69
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'display', content: {}, cta: {} }] }
70
+
71
+ const { container } = render(TitleModal, { onclose, title })
72
+
73
+ expect(container.querySelector('.display')).toBeTruthy()
74
+ })
75
+
76
+ it('Should not render top scroll ad when not given', () => {
77
+ // @ts-ignore
78
+ window.PlayPilotLinkInjections = { ads: null }
79
+
80
+ const { container } = render(TitleModal, { onclose, title })
81
+
82
+ expect(container.querySelector('.display')).not.toBeTruthy()
83
+ })
48
84
  })
@@ -37,4 +37,24 @@ describe('TitlePopover.svelte', () => {
37
37
 
38
38
  expect(track).toHaveBeenCalledWith(TrackingEvent.TitlePopoverClose, title, { time_spent: 200 })
39
39
  })
40
+
41
+ it('Should render top scroll ad when given', () => {
42
+ // @ts-ignore
43
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'top_scroll', content: {}, cta: {} }] }
44
+
45
+ const event = new MouseEvent('mouseenter')
46
+ const { container } = render(TitlePopover, { event, title })
47
+
48
+ expect(container.querySelector('.top-scroll')).toBeTruthy()
49
+ })
50
+
51
+ it('Should not render top scroll ad when not given', () => {
52
+ // @ts-ignore
53
+ window.PlayPilotLinkInjections = { ads: null }
54
+
55
+ const event = new MouseEvent('mouseenter')
56
+ const { container } = render(TitlePopover, { event, title })
57
+
58
+ expect(container.querySelector('.top-scroll')).not.toBeTruthy()
59
+ })
40
60
  })