@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.
- package/dist/link-injections.js +9 -9
- package/events.md +8 -2
- package/package.json +1 -1
- package/src/lib/ads.ts +38 -0
- package/src/lib/constants.ts +2 -1
- package/src/lib/enums/ImageDimensions.ts +7 -0
- package/src/lib/enums/SplitTest.ts +23 -0
- package/src/lib/enums/TrackingEvent.ts +3 -0
- package/src/lib/image.ts +12 -0
- package/src/lib/linkInjection.ts +36 -2
- package/src/lib/splitTest.ts +19 -14
- package/src/lib/types/campaign.d.ts +45 -0
- package/src/lib/types/playlink.d.ts +2 -0
- package/src/lib/types/script.d.ts +5 -0
- package/src/main.ts +6 -0
- package/src/routes/+layout.svelte +4 -0
- package/src/routes/+page.svelte +13 -2
- package/src/routes/components/Ads/Display.svelte +140 -0
- package/src/routes/components/Ads/TopScroll.svelte +240 -0
- package/src/routes/components/DragHandle.svelte +105 -0
- package/src/routes/components/Modal.svelte +125 -30
- package/src/routes/components/Playlink.svelte +185 -0
- package/src/routes/components/Playlinks.svelte +34 -168
- package/src/routes/components/Popover.svelte +30 -2
- package/src/routes/components/Title.svelte +13 -43
- package/src/routes/components/TitleModal.svelte +24 -10
- package/src/routes/components/TitlePopover.svelte +12 -6
- package/src/tests/lib/ads.test.js +55 -0
- package/src/tests/lib/splitTest.test.js +97 -18
- package/src/tests/routes/+page.test.js +33 -0
- package/src/tests/routes/components/Ads/Display.test.js +109 -0
- package/src/tests/routes/components/Ads/TopScroll.test.js +104 -0
- package/src/tests/routes/components/Playlink.test.js +95 -0
- package/src/tests/routes/components/Playlinks.test.js +0 -30
- package/src/tests/routes/components/TitleModal.test.js +36 -0
- package/src/tests/routes/components/TitlePopover.test.js +20 -0
- package/src/routes/components/Ads/FloatingPlaylink.svelte +0 -62
- 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}
|
|
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,
|
|
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(
|
|
28
|
+
const value = getSplitTestIdentifier(splitTest)
|
|
24
29
|
|
|
25
|
-
expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({
|
|
30
|
+
expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({ [splitTest.key]: value })
|
|
26
31
|
// @ts-ignore
|
|
27
|
-
expect(value).toBe(window.PlayPilotLinkInjections.split_test_identifiers[
|
|
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(
|
|
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(
|
|
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
|
-
|
|
51
|
+
[splitTest.key]: expect.any(Number),
|
|
47
52
|
'Some other key': expect.any(Number),
|
|
48
53
|
})
|
|
49
54
|
})
|
|
50
55
|
})
|
|
51
56
|
|
|
52
|
-
describe('
|
|
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[
|
|
61
|
+
window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.75
|
|
57
62
|
|
|
58
|
-
expect(
|
|
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[
|
|
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
|
-
|
|
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(
|
|
100
|
+
trackSplitTestView(splitTest)
|
|
73
101
|
|
|
74
|
-
const variant =
|
|
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(
|
|
83
|
-
const variant =
|
|
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
|
-
|
|
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
|
+
})
|