@playpilot/tpi 5.14.0 → 5.16.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.
Files changed (59) hide show
  1. package/dist/link-injections.js +10 -9
  2. package/package.json +2 -1
  3. package/src/lib/ads.ts +5 -0
  4. package/src/lib/consent.ts +13 -0
  5. package/src/lib/enums/SplitTest.ts +4 -0
  6. package/src/lib/fakeData.ts +70 -0
  7. package/src/lib/linkInjection.ts +11 -29
  8. package/src/lib/modal.ts +97 -0
  9. package/src/lib/playlink.ts +4 -1
  10. package/src/lib/splitTest.ts +5 -0
  11. package/src/lib/tracking.ts +3 -0
  12. package/src/lib/types/consent.d.ts +9 -0
  13. package/src/lib/types/participant.d.ts +14 -0
  14. package/src/lib/types/script.d.ts +3 -0
  15. package/src/lib/types/title.d.ts +2 -0
  16. package/src/main.ts +21 -1
  17. package/src/routes/+layout.svelte +20 -3
  18. package/src/routes/+page.svelte +14 -10
  19. package/src/routes/components/Consent.svelte +72 -0
  20. package/src/routes/components/Icons/IconArrow.svelte +22 -0
  21. package/src/routes/components/Icons/IconClose.svelte +1 -1
  22. package/src/routes/components/Icons/IconIMDb.svelte +9 -1
  23. package/src/routes/components/ListTitle.svelte +204 -0
  24. package/src/routes/components/Modal.svelte +63 -13
  25. package/src/routes/components/Participant.svelte +92 -0
  26. package/src/routes/components/ParticipantModal.svelte +31 -0
  27. package/src/routes/components/Playlink.svelte +16 -4
  28. package/src/routes/components/PlaylinkIcon.svelte +41 -0
  29. package/src/routes/components/PlaylinkLabel.svelte +37 -0
  30. package/src/routes/components/Playlinks.svelte +1 -3
  31. package/src/routes/components/Rails/ParticipantsRail.svelte +56 -0
  32. package/src/routes/components/Rails/Rail.svelte +91 -0
  33. package/src/routes/components/Rails/SimilarRail.svelte +16 -0
  34. package/src/routes/components/Rails/TitlesRail.svelte +95 -0
  35. package/src/routes/components/Tabs.svelte +47 -0
  36. package/src/routes/components/Title.svelte +19 -16
  37. package/src/routes/components/TitleModal.svelte +3 -3
  38. package/src/routes/components/TitlePoster.svelte +30 -0
  39. package/src/routes/components/TrackingPixels.svelte +7 -3
  40. package/src/tests/lib/ads.test.js +24 -1
  41. package/src/tests/lib/consent.test.js +50 -0
  42. package/src/tests/lib/linkInjection.test.js +10 -22
  43. package/src/tests/lib/modal.test.js +148 -0
  44. package/src/tests/lib/playlink.test.js +25 -10
  45. package/src/tests/lib/splitTest.test.js +30 -6
  46. package/src/tests/lib/tracking.test.js +18 -3
  47. package/src/tests/routes/components/Consent.test.js +69 -0
  48. package/src/tests/routes/components/ListTitle.test.js +84 -0
  49. package/src/tests/routes/components/Modal.test.js +51 -19
  50. package/src/tests/routes/components/Playlink.test.js +22 -1
  51. package/src/tests/routes/components/PlaylinkIcon.test.js +27 -0
  52. package/src/tests/routes/components/PlaylinkLabel.test.js +19 -0
  53. package/src/tests/routes/components/Rails/ParticipantsRail.test.js +41 -0
  54. package/src/tests/routes/components/Rails/TitleRail.test.js +38 -0
  55. package/src/tests/routes/components/TitleModal.test.js +6 -0
  56. package/src/tests/routes/components/TitlePopover.test.js +6 -0
  57. package/src/tests/routes/components/TitlePoster.test.js +20 -0
  58. package/src/tests/routes/components/TrackingPixels.test.js +15 -1
  59. package/src/tests/setup.js +14 -1
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ import { openModal } from '$lib/modal'
3
+ import type { ParticipantData } from '$lib/types/participant'
4
+ import Rail from './Rail.svelte'
5
+
6
+ interface Props {
7
+ participants: ParticipantData[]
8
+ }
9
+
10
+ const { participants }: Props = $props()
11
+ </script>
12
+
13
+ <Rail heading="Cast">
14
+ {#each participants.slice(0, 15) as participant}
15
+ <button class="participant" onclick={event => openModal({ event, type: 'participant', data: participant })}>
16
+ <span class="truncate">{participant.name}</span>
17
+
18
+ <div class="character truncate">{participant.character}</div>
19
+ </button>
20
+ {/each}
21
+ </Rail>
22
+
23
+ <style lang="scss">
24
+ .participant {
25
+ display: block;
26
+ flex: 0 0 10rem;
27
+ width: 10rem;
28
+ padding: margin(0.5);
29
+ border: 0;
30
+ border-radius: var(--playpilot-cast-border-radius, var(--playpilot-playlink-border-radius, margin(0.5)));
31
+ background: var(--playpilot-cast-background, var(--playpilot-playlink-background, var(--playpilot-lighter)));
32
+ cursor: pointer;
33
+ font-family: inherit;
34
+ text-align: left;
35
+ color: inherit;
36
+ font-size: var(--playpilot-cast-font-size, var(--playpilot-playlinks-font-size, margin(0.75)));
37
+ white-space: nowrap;
38
+
39
+ &:hover,
40
+ &:active {
41
+ filter: var(--playpilot-cast-hover-filter, var(--playpilot-playlink-hover-filter, brightness(1.1)));
42
+ text-decoration: none !important;
43
+ }
44
+ }
45
+
46
+ .character {
47
+ color: var(--playpilot-cast-character-text-color, var(--playpilot-text-color-alt));
48
+ font-style: italic;
49
+ }
50
+
51
+ .truncate {
52
+ overflow: hidden;
53
+ text-overflow: ellipsis;
54
+ white-space: nowrap;
55
+ }
56
+ </style>
@@ -0,0 +1,91 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import TinySlider from 'svelte-tiny-slider'
4
+ import IconArrow from '../Icons/IconArrow.svelte'
5
+ import { heading as _heading } from '$lib/actions/heading'
6
+
7
+ interface Props {
8
+ heading?: string
9
+ children: Snippet
10
+ }
11
+
12
+ const { heading = '', children }: Props = $props()
13
+ </script>
14
+
15
+ {#if heading}
16
+ <div class="heading" use:_heading={2}>{heading}</div>
17
+ {/if}
18
+
19
+ <div class="rail">
20
+ <TinySlider allowWheel>
21
+ {@render children()}
22
+
23
+ {#snippet controls({ setIndex, currentIndex, reachedEnd })}
24
+ {#if currentIndex > 0}
25
+ <button class="arrow left" onclick={() => setIndex(currentIndex - 1)}><IconArrow direction="left" /></button>
26
+ {/if}
27
+
28
+ {#if !reachedEnd}
29
+ <button class="arrow right" onclick={() => setIndex(currentIndex + 1)}><IconArrow /></button>
30
+ {/if}
31
+ {/snippet}
32
+ </TinySlider>
33
+ </div>
34
+
35
+ <style lang="scss">
36
+ .heading {
37
+ margin: margin(1) 0 margin(0.5);
38
+ font-size: var(--playpilot-rail-title-font-size, 18px);
39
+ line-height: normal;
40
+ font-weight: inherit;
41
+ color: var(--playpilot-rail-title-text-color, var(--playpilot-text-color));
42
+ }
43
+
44
+ .rail {
45
+ --gap: #{margin(0.5)};
46
+ position: relative;
47
+ width: calc(100% + margin(2));
48
+ margin: 0 margin(-1);
49
+
50
+ :global(.slider) {
51
+ padding: 0 margin(1);
52
+ }
53
+
54
+ :global(.slider-content > :last-child) {
55
+ margin-right: margin(2);
56
+ }
57
+ }
58
+
59
+ .arrow {
60
+ display: none;
61
+ align-items: center;
62
+ justify-content: center;
63
+ position: absolute;
64
+ left: margin(2);
65
+ top: 50%;
66
+ width: margin(2);
67
+ height: margin(2);
68
+ padding: 0;
69
+ margin: 0;
70
+ border: 0;
71
+ border-radius: 50%;
72
+ transform: translateX(-50%) translateY(-50%);
73
+ background: var(--playpilot-rails-arrow-background, var(--playpilot-content-light));
74
+ z-index: 2;
75
+ cursor: pointer;
76
+ color: var(--playpilot-rails-arrow-color, var(--playpilot-detail-text-color, white));
77
+
78
+ @media (hover: hover) {
79
+ display: flex;
80
+ }
81
+
82
+ &:hover {
83
+ filter: brightness(1.2);
84
+ }
85
+
86
+ &.right {
87
+ left: auto;
88
+ right: 0;
89
+ }
90
+ }
91
+ </style>
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import type { TitleData } from '$lib/types/title'
3
+ import TitlesRail from './TitlesRail.svelte'
4
+
5
+ const titles = fetchTitles()
6
+
7
+ async function fetchTitles(): Promise<TitleData[]> {
8
+ // This is just a fake loading state for now
9
+ await new Promise(res => setTimeout(res, 500))
10
+
11
+ // Imagine this being a fetch request that returns titles instead.
12
+ return (window.PlayPilotLinkInjections?.evaluated_link_injections?.map(i => i.title_details) || []) as TitleData[]
13
+ }
14
+ </script>
15
+
16
+ <TitlesRail {titles} heading="Similar movies & shows" />
@@ -0,0 +1,95 @@
1
+ <script lang="ts">
2
+ import TitlePoster from '../TitlePoster.svelte'
3
+ import Rail from './Rail.svelte'
4
+ import { playPilotBaseUrl } from '$lib/constants'
5
+ import type { TitleData } from '$lib/types/title'
6
+ import { openModal } from '$lib/modal'
7
+
8
+ interface Props {
9
+ titles: Promise<TitleData[]> | TitleData[]
10
+ heading?: string
11
+ }
12
+
13
+ const { titles, heading = '' }: Props = $props()
14
+ </script>
15
+
16
+ <div class="titles">
17
+ <Rail {heading}>
18
+ {#await titles}
19
+ {#each { length: 8 }}
20
+ <div class="title" data-testid="skeleton">
21
+ <div class="poster"></div>
22
+
23
+ <div class="heading">
24
+ <span class="skeleton">&nbsp;</span>
25
+ <span class="skeleton">&nbsp;</span>
26
+ </div>
27
+ </div>
28
+ {/each}
29
+ {:then titles}
30
+ {#each titles as title}
31
+ {@const href = `${playPilotBaseUrl}/${title.type}/${title.slug}`}
32
+
33
+ <div class="title" data-testid="title">
34
+ <a class="poster" {href} onclick={(event) => openModal({ event, data: title })}>
35
+ <TitlePoster {title} width={96} height={144} />
36
+ </a>
37
+
38
+ <a {href} class="heading" onclick={(event) => openModal({ event, data: title })}>
39
+ {title.title}
40
+ </a>
41
+ </div>
42
+ {/each}
43
+ {/await}
44
+ </Rail>
45
+ </div>
46
+
47
+ <style lang="scss">
48
+ $width: margin(6);
49
+
50
+ .title {
51
+ width: $width;
52
+
53
+ &:hover,
54
+ &:active {
55
+ filter: brightness(1.1);
56
+ }
57
+ }
58
+
59
+ .poster {
60
+ display: block;
61
+ border-radius: var(--playpilot-rail-border-radius, var(--playpilot-playlink-border-radius, margin(0.5)));
62
+ overflow: hidden;
63
+ width: 100%;
64
+ aspect-ratio: 2 / 3;
65
+ background: var(--playpilot-detail-background-light, var(--playpilot-lighter));
66
+ }
67
+
68
+ .heading {
69
+ display: -webkit-box;
70
+ padding-top: margin(0.5);
71
+ overflow: hidden;
72
+ text-decoration: none;
73
+ color: var(--playpilot-detail-text-color, var(--playpilot-text-color-alt)) !important;
74
+ font-size: var(--playpilot-rail-font-size, var(--playpilot-detail-font-size-small, 12px));
75
+ font-style: normal !important;
76
+ line-height: 1.2;
77
+ line-clamp: 2;
78
+ -webkit-line-clamp: 2;
79
+ -webkit-box-orient: vertical;
80
+ text-overflow: ellipsis;
81
+ }
82
+
83
+ .skeleton {
84
+ display: block;
85
+ height: 1em;
86
+ width: 100%;
87
+ background: var(--playpilot-detail-background-light, var(--playpilot-lighter));
88
+ border-radius: margin(2);
89
+
90
+ &:last-child {
91
+ margin-top: margin(0.3);
92
+ width: 50%;
93
+ }
94
+ }
95
+ </style>
@@ -0,0 +1,47 @@
1
+ <script lang="ts">
2
+ interface Option {
3
+ label: string
4
+ value: string
5
+ }
6
+
7
+ interface Props {
8
+ options: Option[]
9
+ }
10
+
11
+ const { options }: Props = $props()
12
+
13
+ let active = $state(options[0]?.value)
14
+ </script>
15
+
16
+ <div class="tabs">
17
+ {#each options as { label, value }}
18
+ <button class="tab" class:active={value === active} onclick={() => active = value}>{label}</button>
19
+ {/each}
20
+ </div>
21
+
22
+ <style lang="scss">
23
+ .tabs {
24
+ display: flex;
25
+ gap: margin(0.5);
26
+ }
27
+
28
+ .tab {
29
+ cursor: pointer;
30
+ appearance: none;
31
+ background: transparent;
32
+ padding: margin(0.25) margin(0.5);
33
+ border: 1px solid currentColor;
34
+ border-radius: var(--playpilot-tabs-border-radius, margin(0.25));
35
+ transition: opacity 100ms;
36
+ font-family: inherit;
37
+ font-weight: var(--playpilot-tabs-font-weight, 500);
38
+ color: var(--playpilot-tabs-text-color, var(--playpilot-detail-text-color, var(--playpilot-text-color-alt)));
39
+ font-size: inherit;
40
+
41
+ &.active,
42
+ &:hover {
43
+ background: var(--playpilot-tabs-background-active, var(--playpilot-content));
44
+ color: var(--playpilot-tabs-text-color-active, var(--playpilot-detail-text-color, var(--playpilot-text-color)));
45
+ }
46
+ }
47
+ </style>
@@ -3,10 +3,14 @@
3
3
  import Playlinks from './Playlinks.svelte'
4
4
  import Description from './Description.svelte'
5
5
  import IconIMDb from './Icons/IconIMDb.svelte'
6
+ import ParticipantsRail from './Rails/ParticipantsRail.svelte'
7
+ import SimilarRail from './Rails/SimilarRail.svelte'
8
+ import TitlePoster from './TitlePoster.svelte'
6
9
  import { t } from '$lib/localization'
7
10
  import type { TitleData } from '$lib/types/title'
8
11
  import { heading } from '$lib/actions/heading'
9
12
  import { removeImageUrlPrefix } from '$lib/image'
13
+ import { participants } from '$lib/fakeData'
10
14
 
11
15
  interface Props {
12
16
  title: TitleData
@@ -22,13 +26,8 @@
22
26
 
23
27
  <div class="content" class:small data-playpilot-link-injections-title>
24
28
  <div class="header">
25
- <div class="top">
26
- <img
27
- class="poster"
28
- class:loaded={posterLoaded}
29
- src={removeImageUrlPrefix(title.standing_poster)}
30
- alt="Movie poster for '{title.title}'"
31
- onload={() => posterLoaded = true} />
29
+ <div class="poster" class:loaded={posterLoaded}>
30
+ <TitlePoster {title} onload={() => posterLoaded = true} />
32
31
  </div>
33
32
 
34
33
  <div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
@@ -56,6 +55,15 @@
56
55
  {#if !small}
57
56
  <Description text={title.description} blurb={title.blurb} />
58
57
  {/if}
58
+
59
+ <!-- Temporarily not available on production as there is not yet an API endpoint for either -->
60
+ {#if process.env.NODE_ENV !== 'production'}
61
+ {#if true || title.participants?.length}
62
+ <ParticipantsRail participants={participants} />
63
+ {/if}
64
+
65
+ <SimilarRail />
66
+ {/if}
59
67
  </div>
60
68
  </div>
61
69
 
@@ -103,14 +111,14 @@
103
111
  }
104
112
 
105
113
  &.small {
106
- font-size: var(--playpilot-detail-font-size, 12px);
114
+ font-size: var(--playpilot-detail-font-size-small, 12px);
107
115
  line-height: 1.45;
108
- padding-bottom: margin(0.5);
116
+ padding-bottom: margin(1);
109
117
  }
110
118
  }
111
119
 
112
120
  .header {
113
- padding: margin(2) 0 margin(1);
121
+ padding: margin(3) 0 margin(1);
114
122
  background: transparent;
115
123
 
116
124
  .small & {
@@ -118,12 +126,6 @@
118
126
  }
119
127
  }
120
128
 
121
- .top {
122
- display: flex;
123
- justify-content: space-between;
124
- align-items: flex-end;
125
- }
126
-
127
129
  .poster {
128
130
  display: block;
129
131
  width: margin(4.5);
@@ -132,6 +134,7 @@
132
134
  border-radius: var(--playpilot-detail-image-border-radius, margin(0.5));
133
135
  background: var(--playpilot-detail-image-background, var(--playpilot-content));
134
136
  box-shadow: var(--playpilot-detail-image-shadow, var(--playpilot-shadow));
137
+ overflow: hidden;
135
138
  opacity: 0;
136
139
 
137
140
  &.loaded {
@@ -10,11 +10,11 @@
10
10
  import { getFirstAdOfType } from '$lib/ads'
11
11
 
12
12
  interface Props {
13
- onclose: () => void,
14
13
  title: TitleData
14
+ initialScrollPosition?: number
15
15
  }
16
16
 
17
- const { onclose, title }: Props = $props()
17
+ const { title, initialScrollPosition = 0 }: Props = $props()
18
18
 
19
19
  const topScrollAd = getFirstAdOfType('top_scroll')
20
20
  const displayAd = getFirstAdOfType('card')
@@ -49,6 +49,6 @@
49
49
  {/if}
50
50
  {/snippet}
51
51
 
52
- <Modal {onclose} {onscroll} prepend={displayAd ? prepend : null} bubble={topScrollAd ? bubble : null} tall>
52
+ <Modal {onscroll} {initialScrollPosition} prepend={displayAd ? prepend : null} bubble={topScrollAd ? bubble : null} tall>
53
53
  <Title {title} />
54
54
  </Modal>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import { imagePlaceholderDataUrl } from '$lib/constants'
3
+ import { removeImageUrlPrefix } from '$lib/image'
4
+ import type { TitleData } from '$lib/types/title'
5
+
6
+ interface Props {
7
+ title: TitleData
8
+ width?: number
9
+ height?: number
10
+ onload?: () => void
11
+ }
12
+
13
+ const { title, width = 96, height = 144, onload = () => null }: Props = $props()
14
+ </script>
15
+
16
+ <img
17
+ src={removeImageUrlPrefix(title.standing_poster)}
18
+ alt="Movie poster for '{title.title}'"
19
+ {width}
20
+ {height}
21
+ onerror={({ target }) => (target as HTMLImageElement).src = imagePlaceholderDataUrl}
22
+ {onload} />
23
+
24
+ <style lang="scss">
25
+ img {
26
+ display: block;
27
+ width: 100%;
28
+ height: auto;
29
+ }
30
+ </style>
@@ -1,4 +1,6 @@
1
1
  <script lang="ts">
2
+ import { hasConsentedTo } from '$lib/consent'
3
+
2
4
  interface Props {
3
5
  pixels: string[]
4
6
  }
@@ -6,9 +8,11 @@
6
8
  const { pixels }: Props = $props()
7
9
  </script>
8
10
 
9
- {#each pixels as src}
10
- <img {src} alt="" />
11
- {/each}
11
+ {#if hasConsentedTo('pixels')}
12
+ {#each pixels as src}
13
+ <img {src} alt="" />
14
+ {/each}
15
+ {/if}
12
16
 
13
17
  <style lang="scss">
14
18
  img {
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'
2
2
  import { fakeFetch } from '../helpers'
3
3
 
4
4
  import { fetchAds, getFirstAdOfType } from '$lib/ads'
5
+ import { hasConsentedTo } from '$lib/consent'
5
6
  import { track } from '$lib/tracking'
6
7
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
7
8
 
@@ -9,11 +10,17 @@ vi.mock('$lib/tracking', () => ({
9
10
  track: vi.fn(),
10
11
  }))
11
12
 
12
- describe('$lib/api', () => {
13
+ vi.mock('$lib/consent', () => ({
14
+ hasConsentedTo: vi.fn(() => true),
15
+ }))
16
+
17
+ describe('$lib/ads', () => {
13
18
  afterEach(() => {
14
19
  vi.resetAllMocks()
15
20
  // @ts-ignore
16
21
  window.PlayPilotLinkInjections = null
22
+
23
+ vi.mocked(hasConsentedTo).mockImplementation(() => true)
17
24
  })
18
25
 
19
26
  describe('fetchAds', () => {
@@ -42,6 +49,13 @@ describe('$lib/api', () => {
42
49
  expect(global.fetch).not.toHaveBeenCalled()
43
50
  })
44
51
 
52
+ it('Should not call fetch if user did not consent to ads', async () => {
53
+ vi.mocked(hasConsentedTo).mockImplementation(() => false)
54
+
55
+ await fetchAds()
56
+ expect(global.fetch).not.toHaveBeenCalled()
57
+ })
58
+
45
59
  it('Should fire track event when ads failed to fetch', async () => {
46
60
  fakeFetch({ ok: false, status: 505 })
47
61
 
@@ -64,5 +78,14 @@ describe('$lib/api', () => {
64
78
 
65
79
  expect(getFirstAdOfType('top_scroll')).toBe(null)
66
80
  })
81
+
82
+ it('Should not return ads if valid ad was given but user did not consent to ads', async () => {
83
+ vi.mocked(hasConsentedTo).mockImplementation(() => false)
84
+
85
+ // @ts-ignore
86
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'top_scroll' }] }
87
+
88
+ expect(getFirstAdOfType('top_scroll')).toBe(null)
89
+ })
67
90
  })
68
91
  })
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect, afterEach } from 'vitest'
2
+
3
+ import { setConsent, hasConsentedTo } from '$lib/consent'
4
+
5
+ describe('$lib/consent', () => {
6
+ afterEach(() => {
7
+ // @ts-ignore
8
+ window.PlayPilotLinkInjections = null
9
+ })
10
+
11
+ describe('setConsent', () => {
12
+ it('Should set consent in window object to given values', async () => {
13
+ // @ts-ignore
14
+ window.PlayPilotLinkInjections = {}
15
+
16
+ setConsent({ ads: true, affiliate: true, pixels: true, split_tests: false, tracking: false })
17
+
18
+ expect(window.PlayPilotLinkInjections.consents).toEqual({ ads: true, affiliate: true, pixels: true, split_tests: false, tracking: false })
19
+ })
20
+ })
21
+
22
+ describe('hasConsentedTo', () => {
23
+ it('Should return values for given keys from window object', async () => {
24
+ // @ts-ignore
25
+ window.PlayPilotLinkInjections = {
26
+ consents: { ads: true, affiliate: true, pixels: true, split_tests: false, tracking: false },
27
+ }
28
+
29
+ expect(hasConsentedTo('ads')).toBe(true)
30
+ expect(hasConsentedTo('affiliate')).toBe(true)
31
+ expect(hasConsentedTo('pixels')).toBe(true)
32
+ expect(hasConsentedTo('split_tests')).toBe(false)
33
+ expect(hasConsentedTo('tracking')).toBe(false)
34
+ })
35
+
36
+ it('Should return true regardless of values if require_consent is false', async () => {
37
+ // @ts-ignore
38
+ window.PlayPilotLinkInjections = {
39
+ require_consent: false,
40
+ consents: { ads: true, affiliate: true, pixels: true, split_tests: false, tracking: false },
41
+ }
42
+
43
+ expect(hasConsentedTo('ads')).toBe(true)
44
+ expect(hasConsentedTo('affiliate')).toBe(true)
45
+ expect(hasConsentedTo('pixels')).toBe(true)
46
+ expect(hasConsentedTo('split_tests')).toBe(true)
47
+ expect(hasConsentedTo('tracking')).toBe(true)
48
+ })
49
+ })
50
+ })
@@ -4,6 +4,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
4
4
  import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, getLinkInjectionElements, insertAfterArticlePlaylinks, getLinkInjectionsParentElement, isAvailableAsManualInjection, filterRemovedAndInactiveInjections, isEquivalentInjection, filterInvalidInTextInjections, filterInvalidAfterArticleInjections, isValidInjection, isValidPlaylinkType, removePlayPilotTitleLinks, trackLinkIntersection } from '$lib/linkInjection'
5
5
  import { mount, unmount } from 'svelte'
6
6
  import { fakeFetch, generateInjection } from '../helpers'
7
+ import { openModal } from '$lib/modal'
7
8
  import { track } from '$lib/tracking'
8
9
 
9
10
  vi.mock('svelte', () => ({
@@ -11,6 +12,13 @@ vi.mock('svelte', () => ({
11
12
  unmount: vi.fn(),
12
13
  }))
13
14
 
15
+ vi.mock('$lib/modal', () => ({
16
+ openModal: vi.fn(),
17
+ destroyAllModals: vi.fn(),
18
+ getPreviousModal: vi.fn(),
19
+ goBackToPreviousModal: vi.fn(),
20
+ }))
21
+
14
22
  vi.mock('$lib/tracking', () => ({
15
23
  track: vi.fn(),
16
24
  }))
@@ -311,7 +319,7 @@ describe('linkInjection.js', () => {
311
319
  expect(() => injectLinksInDocument(elements, { aiInjections: linkInjections, manualInjections: [] })).not.toThrow()
312
320
  })
313
321
 
314
- it('Should mount modal when clicked', async () => {
322
+ it('Should open modal when link is clicked', async () => {
315
323
  document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
316
324
 
317
325
  const elements = Array.from(document.body.querySelectorAll('p'))
@@ -322,27 +330,7 @@ describe('linkInjection.js', () => {
322
330
  const link = /** @type {HTMLAnchorElement} */ (document.querySelector('a'))
323
331
  await fireEvent.click(link)
324
332
 
325
- expect(mount).toHaveBeenCalled()
326
- })
327
-
328
- it('Should not mount modal multiple times if modal is already open', async () => {
329
- const sentence = 'This is a sentence with an injection.'
330
- document.body.innerHTML = `<p>${sentence}</p>`
331
-
332
- const elements = Array.from(document.body.querySelectorAll('p'))
333
-
334
- const linkInjections = [
335
- generateInjection(sentence, 'a sentence'),
336
- generateInjection(sentence, 'an injection'),
337
- ]
338
-
339
- injectLinksInDocument(elements, { aiInjections: linkInjections, manualInjections: [] })
340
-
341
- const links = document.querySelectorAll('a')
342
- await fireEvent.click(links[0])
343
- await fireEvent.click(links[1])
344
-
345
- expect(mount).toHaveBeenCalledTimes(1)
333
+ expect(openModal).toHaveBeenCalled()
346
334
  })
347
335
 
348
336
  it('Should not fire given onclick function when clicked with modifier keys or not left click', async () => {