@playpilot/tpi 5.6.0-beta.video.7 → 5.7.0-beta.display-2

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.
Files changed (38) hide show
  1. package/dist/link-injections.js +9 -9
  2. package/events.md +8 -2
  3. package/package.json +1 -1
  4. package/src/lib/ads.ts +38 -0
  5. package/src/lib/constants.ts +2 -1
  6. package/src/lib/enums/ImageDimensions.ts +7 -0
  7. package/src/lib/enums/SplitTest.ts +23 -0
  8. package/src/lib/enums/TrackingEvent.ts +3 -0
  9. package/src/lib/image.ts +12 -0
  10. package/src/lib/linkInjection.ts +36 -2
  11. package/src/lib/splitTest.ts +19 -14
  12. package/src/lib/types/campaign.d.ts +45 -0
  13. package/src/lib/types/playlink.d.ts +2 -0
  14. package/src/lib/types/script.d.ts +5 -0
  15. package/src/main.ts +6 -0
  16. package/src/routes/+layout.svelte +4 -0
  17. package/src/routes/+page.svelte +13 -2
  18. package/src/routes/components/Ads/Display.svelte +140 -0
  19. package/src/routes/components/Ads/TopScroll.svelte +240 -0
  20. package/src/routes/components/DragHandle.svelte +105 -0
  21. package/src/routes/components/Modal.svelte +125 -30
  22. package/src/routes/components/Playlink.svelte +185 -0
  23. package/src/routes/components/Playlinks.svelte +34 -168
  24. package/src/routes/components/Popover.svelte +30 -2
  25. package/src/routes/components/Title.svelte +13 -43
  26. package/src/routes/components/TitleModal.svelte +24 -10
  27. package/src/routes/components/TitlePopover.svelte +12 -6
  28. package/src/tests/lib/ads.test.js +55 -0
  29. package/src/tests/lib/splitTest.test.js +97 -18
  30. package/src/tests/routes/+page.test.js +33 -0
  31. package/src/tests/routes/components/Ads/Display.test.js +109 -0
  32. package/src/tests/routes/components/Ads/TopScroll.test.js +104 -0
  33. package/src/tests/routes/components/Playlink.test.js +95 -0
  34. package/src/tests/routes/components/Playlinks.test.js +0 -30
  35. package/src/tests/routes/components/TitleModal.test.js +36 -0
  36. package/src/tests/routes/components/TitlePopover.test.js +20 -0
  37. package/src/routes/components/Ads/FloatingPlaylink.svelte +0 -62
  38. package/src/routes/components/Ads/VideoBackground.svelte +0 -28
@@ -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,13 +15,11 @@
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
 
19
- // This is all placeholder stuff that hardcodes a video ad for Apple TV+.
20
- const useVideoBackground = title.providers.some(p => p.name === 'Apple TV+')
21
- const backgroundFormat = useVideoBackground ? 'landscape' : ''
22
-
23
23
  track(TrackingEvent.TitlePopoverView, title)
24
24
 
25
25
  onMount(() => {
@@ -51,9 +51,15 @@
51
51
  }
52
52
  </script>
53
53
 
54
+ {#snippet bubble()}
55
+ {#if topScroll}
56
+ <TopScroll campaign={topScroll} />
57
+ {/if}
58
+ {/snippet}
59
+
54
60
  <div class="title-popover" bind:this={element} data-playpilot-title-popover role="region" aria-labelledby="title">
55
- <Popover bind:maxHeight>
56
- <Title {title} {useVideoBackground} {backgroundFormat} small compact={!!maxHeight && maxHeight < 250} />
61
+ <Popover bind:maxHeight bubble={topScroll ? bubble : null}>
62
+ <Title {title} small compact={!!maxHeight && maxHeight < 250} />
57
63
  </Popover>
58
64
  </div>
59
65
 
@@ -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
+ })
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, afterEach } from 'vitest'
2
- import { getSplitTestIdentifier, isInSplitTest, trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
2
+ import { getSplitTestIdentifier, getSplitTestVariantIndex, isInSplitTestVariant, trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
3
3
  import { track } from '$lib/tracking'
4
4
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
5
5
 
@@ -8,6 +8,11 @@ vi.mock('$lib/tracking', () => ({
8
8
  }))
9
9
 
10
10
  describe('$lib/splitTest', () => {
11
+ const splitTest = {
12
+ key: 'Some key',
13
+ numberOfVariants: 2,
14
+ }
15
+
11
16
  describe('getSplitTestIdentifier', () => {
12
17
  afterEach(() => {
13
18
  // @ts-ignore
@@ -20,11 +25,11 @@ describe('$lib/splitTest', () => {
20
25
  // @ts-ignore
21
26
  window.PlayPilotLinkInjections = {}
22
27
 
23
- const value = getSplitTestIdentifier('Some key')
28
+ const value = getSplitTestIdentifier(splitTest)
24
29
 
25
- expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({ 'Some key': value })
30
+ expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({ [splitTest.key]: value })
26
31
  // @ts-ignore
27
- expect(value).toBe(window.PlayPilotLinkInjections.split_test_identifiers['Some key'])
32
+ expect(value).toBe(window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key])
28
33
  })
29
34
 
30
35
  it('Should return value as stored in the window object', () => {
@@ -32,46 +37,69 @@ describe('$lib/splitTest', () => {
32
37
  window.PlayPilotLinkInjections.split_test_identifiers = {}
33
38
  window.PlayPilotLinkInjections.split_test_identifiers['Some key'] = 0.5
34
39
 
35
- expect(getSplitTestIdentifier('Some key')).toBe(0.5)
40
+ expect(getSplitTestIdentifier(splitTest)).toBe(0.5)
36
41
  })
37
42
 
38
43
  it('Should set separate value for different keys', () => {
39
44
  // @ts-ignore
40
45
  window.PlayPilotLinkInjections.split_test_identifiers = {}
41
46
 
42
- getSplitTestIdentifier('Some key')
43
- getSplitTestIdentifier('Some other key')
47
+ getSplitTestIdentifier(splitTest)
48
+ getSplitTestIdentifier({ key: 'Some other key', numberOfVariants: 2 })
44
49
 
45
50
  expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({
46
- 'Some key': expect.any(Number),
51
+ [splitTest.key]: expect.any(Number),
47
52
  'Some other key': expect.any(Number),
48
53
  })
49
54
  })
50
55
  })
51
56
 
52
- describe('isInSplitTest', () => {
57
+ describe('isInSplitTestVariant', () => {
53
58
  it('Should return true if stored value is higher than 0.5', () => {
54
59
  // @ts-ignore
55
60
  window.PlayPilotLinkInjections.split_test_identifiers = {}
56
- window.PlayPilotLinkInjections.split_test_identifiers['Some key'] = 0.75
61
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.75
57
62
 
58
- expect(isInSplitTest('Some key')).toBe(true)
63
+ expect(isInSplitTestVariant(splitTest)).toBe(true)
59
64
  })
60
65
 
61
66
  it('Should return false if stored value is lower than 0.5', () => {
62
67
  // @ts-ignore
63
68
  window.PlayPilotLinkInjections.split_test_identifiers = {}
64
- window.PlayPilotLinkInjections.split_test_identifiers['Some key'] = 0.25
69
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.25
70
+
71
+ expect(isInSplitTestVariant(splitTest)).toBe(false)
72
+ })
73
+
74
+ it('Should return correct values when numberOfVariants is given based on the stored value', () => {
75
+ const multiple = { key: 'multiple', numberOfVariants: 3 }
76
+
77
+ // @ts-ignore
78
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
79
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.25
80
+
81
+
82
+ expect(isInSplitTestVariant(multiple, 0)).toBe(true)
83
+ expect(isInSplitTestVariant(multiple, 1)).toBe(false)
84
+ expect(isInSplitTestVariant(multiple, 2)).toBe(false)
65
85
 
66
- expect(isInSplitTest('Some key')).toBe(false)
86
+ window.PlayPilotLinkInjections.split_test_identifiers['multiple'] = 0.5
87
+ expect(isInSplitTestVariant(multiple, 0)).toBe(false)
88
+ expect(isInSplitTestVariant(multiple, 1)).toBe(true)
89
+ expect(isInSplitTestVariant(multiple, 2)).toBe(false)
90
+
91
+ window.PlayPilotLinkInjections.split_test_identifiers['multiple'] = 0.75
92
+ expect(isInSplitTestVariant(multiple, 0)).toBe(false)
93
+ expect(isInSplitTestVariant(multiple, 1)).toBe(false)
94
+ expect(isInSplitTestVariant(multiple, 2)).toBe(true)
67
95
  })
68
96
  })
69
97
 
70
98
  describe('trackSplitTestView', () => {
71
99
  it('Should track view event with the given key', () => {
72
- trackSplitTestView('Some key')
100
+ trackSplitTestView(splitTest)
73
101
 
74
- const variant = isInSplitTest('Some key') ? 1 : 0
102
+ const variant = isInSplitTestVariant(splitTest) ? 1 : 0
75
103
 
76
104
  expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestView, null, { key: 'Some key', variant })
77
105
  })
@@ -79,10 +107,61 @@ describe('$lib/splitTest', () => {
79
107
 
80
108
  describe('trackSplitTestAction', () => {
81
109
  it('Should track action event with the given key and action', () => {
82
- trackSplitTestAction('Some key', 'Some action')
83
- const variant = isInSplitTest('Some key') ? 1 : 0
110
+ trackSplitTestAction(splitTest, 'Some action')
111
+ const variant = isInSplitTestVariant(splitTest) ? 1 : 0
112
+
113
+ expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestAction, null, { key: splitTest.key, variant, action: 'Some action' })
114
+ })
115
+ })
116
+
117
+ describe('getSplitTestVariantIndex', () => {
118
+ it('Should return the expected index split test consists of 2 variants', () => {
119
+ // @ts-ignore
120
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
121
+
122
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.25
123
+ expect(getSplitTestVariantIndex(splitTest)).toBe(0)
124
+
125
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.75
126
+ expect(getSplitTestVariantIndex(splitTest)).toBe(1)
127
+ })
128
+
129
+ it('Should return the expected index when split test includes more than 2 variants', () => {
130
+ const multiple = { key: 'multiple', numberOfVariants: 3 }
131
+
132
+ // @ts-ignore
133
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
134
+
135
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.25
136
+ expect(getSplitTestVariantIndex(multiple)).toBe(0)
137
+
138
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.5
139
+ expect(getSplitTestVariantIndex(multiple)).toBe(1)
140
+
141
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.75
142
+ expect(getSplitTestVariantIndex(multiple)).toBe(2)
143
+ })
144
+
145
+ it('Should return the expected index when split test includes a large amount of variants', () => {
146
+ const many = { key: 'many', numberOfVariants: 10 }
147
+
148
+ // @ts-ignore
149
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
150
+
151
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.05
152
+ expect(getSplitTestVariantIndex(many)).toBe(0)
153
+
154
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.35
155
+ expect(getSplitTestVariantIndex(many)).toBe(3)
156
+
157
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.65
158
+ expect(getSplitTestVariantIndex(many)).toBe(6)
159
+
160
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.78
161
+ expect(getSplitTestVariantIndex(many)).toBe(7)
84
162
 
85
- expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestAction, null, { key: 'Some key', variant, action: 'Some action' })
163
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.98
164
+ expect(getSplitTestVariantIndex(many)).toBe(9)
86
165
  })
87
166
  })
88
167
  })
@@ -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,109 @@
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
+
60
+ it('Should render link without any content by default', () => {
61
+ /** @ts-ignore */
62
+ const { container } = render(Display, { campaign: { cta: {}, content: {} } })
63
+
64
+ expect(/** @type {HTMLAnchorElement} */ (container.querySelector('a'))).toBeTruthy()
65
+ expect(/** @type {HTMLAnchorElement} */ (container.querySelector('a')).children).toHaveLength(0)
66
+ })
67
+
68
+ it('Should render disclaimer when given', () => {
69
+ /** @ts-ignore */
70
+ const { getByText } = render(Display, { campaign: { disclaimer: 'Some disclaimer', cta: {}, content: {} } })
71
+
72
+ expect(getByText('Some disclaimer')).toBeTruthy()
73
+ })
74
+
75
+ it('Should render logo when given', () => {
76
+ /** @ts-ignore */
77
+ const { getByRole } = render(Display, { campaign: { cta: {}, content: { header_logo: 'test' } } })
78
+
79
+ expect(getByRole('presentation').getAttribute('src')).toBe('test')
80
+ })
81
+
82
+ it('Should render background image when given', () => {
83
+ /** @ts-ignore */
84
+ const { getByRole } = render(Display, { campaign: { cta: {}, content: { image: 'test' } } })
85
+
86
+ expect(getByRole('presentation').getAttribute('src')).toBe('test')
87
+ })
88
+
89
+ it('Should render header when given', () => {
90
+ /** @ts-ignore */
91
+ const { getByText } = render(Display, { campaign: { cta: {}, content: { header: 'Some header' } } })
92
+
93
+ expect(getByText('Some header')).toBeTruthy()
94
+ })
95
+
96
+ it('Should render subheader when given', () => {
97
+ /** @ts-ignore */
98
+ const { getByText } = render(Display, { campaign: { cta: {}, content: { header: 'Some subheader' } } })
99
+
100
+ expect(getByText('Some subheader')).toBeTruthy()
101
+ })
102
+
103
+ it('Should render button when cta header is given', () => {
104
+ /** @ts-ignore */
105
+ const { getByText } = render(Display, { campaign: { cta: { header: 'Some button label' }, content: {} } })
106
+
107
+ expect(getByText('Some button label')).toBeTruthy()
108
+ })
109
+ })
@@ -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,95 @@
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
+
89
+ it('Should use action_text in button label when given', () => {
90
+ // @ts-ignore
91
+ const { getByText } = render(Playlink, { playlink: { name: 'Some highlighted playlink', action_text: 'Some action text', extra_info: { category: 'BUY' } } })
92
+
93
+ expect(getByText('Some action text')).toBeTruthy()
94
+ })
95
+ })