@playpilot/tpi 8.17.1 → 8.18.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 (43) hide show
  1. package/.env +1 -1
  2. package/dist/editorial.mount.js +9 -9
  3. package/dist/link-injections.js +1 -1
  4. package/dist/mount.js +9 -9
  5. package/package.json +1 -1
  6. package/src/lib/api/titles.ts +2 -2
  7. package/src/lib/enums/TrackingEvent.ts +4 -4
  8. package/src/lib/fakeData.ts +44 -32
  9. package/src/lib/injection.ts +10 -9
  10. package/src/lib/modal.ts +3 -1
  11. package/src/lib/popover.ts +13 -12
  12. package/src/lib/scss/_mixins.scss +27 -0
  13. package/src/lib/scss/global.scss +0 -27
  14. package/src/lib/tracking.ts +2 -0
  15. package/src/lib/types/injection.d.ts +5 -0
  16. package/src/routes/components/Editorial/Editor.svelte +8 -5
  17. package/src/routes/components/Editorial/EditorItem.svelte +23 -9
  18. package/src/routes/components/Editorial/ManualInjection.svelte +1 -0
  19. package/src/routes/components/Explore/ExploreLayout.svelte +3 -1
  20. package/src/routes/components/Explore/Filter/Dropdown.svelte +3 -1
  21. package/src/routes/components/{TitlePopover.svelte → InjectionPopover.svelte} +26 -9
  22. package/src/routes/components/ListTitle.svelte +27 -6
  23. package/src/routes/components/Modals/Modal.svelte +2 -0
  24. package/src/routes/components/Modals/RailModal.svelte +3 -1
  25. package/src/routes/components/Participant.svelte +18 -6
  26. package/src/routes/components/Playlinks/Playlinks.svelte +2 -1
  27. package/src/routes/components/Popover.svelte +2 -1
  28. package/src/routes/components/Title.svelte +1 -1
  29. package/src/routes/elements/+page.svelte +3 -3
  30. package/src/tests/helpers.js +1 -0
  31. package/src/tests/lib/api/titles.test.js +4 -4
  32. package/src/tests/lib/injection.test.js +44 -3
  33. package/src/tests/lib/popover.test.js +7 -7
  34. package/src/tests/lib/tracking.test.js +8 -0
  35. package/src/tests/routes/components/Editorial/EditorItem.test.js +10 -0
  36. package/src/tests/routes/components/Editorial/ManualInjection.test.js +4 -0
  37. package/src/tests/routes/components/Editorial/PlaylinkTypeSelect.test.js +2 -0
  38. package/src/tests/routes/components/InjectionPopover.test.js +117 -0
  39. package/src/tests/routes/components/ListTitle.test.js +7 -0
  40. package/src/tests/routes/components/Participant.test.js +7 -0
  41. package/src/tests/routes/components/Playlinks/AfterArticlePlaylinks.test.js +4 -0
  42. package/src/tests/routes/components/Playlinks/Playlinks.test.js +1 -1
  43. package/src/tests/routes/components/TitlePopover.test.js +0 -78
@@ -8,15 +8,16 @@
8
8
 
9
9
  interface Props {
10
10
  title: TitleData
11
+ compact?: boolean
11
12
  onclick?: (event: MouseEvent) => void
12
13
  }
13
14
 
14
- const { title, onclick = () => null }: Props = $props()
15
+ const { title, compact = false, onclick = () => null }: Props = $props()
15
16
 
16
17
  const noAffiliate = !!window.PlayPilotLinkInjections?.no_affiliate
17
18
  </script>
18
19
 
19
- <button class="title" {onclick} data-testid="title">
20
+ <button class="title" class:compact {onclick} data-testid="title">
20
21
  <div class="poster">
21
22
  <TitlePoster {title} width={30} height={43} lazy />
22
23
  </div>
@@ -38,12 +39,14 @@
38
39
  {/if}
39
40
  </div>
40
41
 
41
- <div class="description" class:large={noAffiliate}>
42
- {title.description}
43
- </div>
42
+ {#if !compact}
43
+ <div class="description" class:large={noAffiliate}>
44
+ {title.description}
45
+ </div>
46
+ {/if}
44
47
 
45
48
  {#if !noAffiliate}
46
- <PlaylinksCompact playlinks={title.providers} />
49
+ <PlaylinksCompact playlinks={title.providers} size={compact ? 24 : 30} />
47
50
  {/if}
48
51
  </div>
49
52
 
@@ -84,6 +87,10 @@
84
87
  border-radius: theme(detail-image-border-radius, border-radius);
85
88
  background: theme(detail-image-background, content);
86
89
  overflow: hidden;
90
+
91
+ .compact & {
92
+ width: margin(3.5);
93
+ }
87
94
  }
88
95
 
89
96
  .content {
@@ -97,11 +104,17 @@
97
104
  @include desktop() {
98
105
  padding-right: margin(1);
99
106
  }
107
+
108
+ .compact & {
109
+ padding-left: margin(0.75);
110
+ padding-right: 0;
111
+ }
100
112
  }
101
113
 
102
114
  .heading {
103
115
  color: theme(list-item-title-text-color, text-color);
104
116
  font-weight: theme(list-item-title-font-weight, font-bold);
117
+ line-height: 1.2;
105
118
  }
106
119
 
107
120
  .meta {
@@ -116,6 +129,10 @@
116
129
  > div {
117
130
  text-transform: capitalize;
118
131
  }
132
+
133
+ .compact & {
134
+ margin: margin(0.125) 0 margin(0.25) 0;
135
+ }
119
136
  }
120
137
 
121
138
  .imdb {
@@ -152,5 +169,9 @@
152
169
  &:active {
153
170
  color: theme(list-item-action-hover-color, text-color);
154
171
  }
172
+
173
+ .compact & {
174
+ display: none;
175
+ }
155
176
  }
156
177
  </style>
@@ -240,6 +240,8 @@
240
240
  will-change: top;
241
241
  transition: top 200ms;
242
242
 
243
+ @include styled-scrollbar;
244
+
243
245
  @supports (height: 1dvh) {
244
246
  height: 80dvh;
245
247
  }
@@ -137,7 +137,9 @@
137
137
  overscroll-behavior: contain;
138
138
  transition: box-shadow var(--transition-duration);
139
139
 
140
- @include desktop() {
140
+ @include styled-scrollbar;
141
+
142
+ @include desktop {
141
143
  height: auto;
142
144
  max-height: 90vh;
143
145
  border-radius: theme(rail-modal-item-border-radius, border-radius-huge);
@@ -13,9 +13,10 @@
13
13
 
14
14
  interface Props {
15
15
  participant: ParticipantData
16
+ small?: boolean
16
17
  }
17
18
 
18
- const { participant }: Props = $props()
19
+ const { participant, small = false }: Props = $props()
19
20
 
20
21
  const { name, birth_date, death_date } = $derived(participant)
21
22
 
@@ -52,8 +53,8 @@
52
53
  }
53
54
  </script>
54
55
 
55
- <div class="header">
56
- <div class="heading" use:heading={2} id="name">{name}</div>
56
+ <div class="header" class:small>
57
+ <div class="heading" use:heading={2} id="heading">{name}</div>
57
58
 
58
59
  {#if birth_date}
59
60
  <p class="dates">
@@ -63,11 +64,13 @@
63
64
  </div>
64
65
 
65
66
  <div class="content">
66
- <div class="heading small" use:heading={3} id="credits">{t('Credits')}</div>
67
+ {#if !small}
68
+ <div class="heading subheading" use:heading={3} id="credits">{t('Credits')}</div>
69
+ {/if}
67
70
 
68
71
  <div class="list">
69
72
  {#each titles as title}
70
- <ListTitle {title} onclick={(event) => openModal({ event, data: title })} />
73
+ <ListTitle {title} compact={small} onclick={(event) => openModal({ event, data: title })} />
71
74
  {/each}
72
75
 
73
76
  {#if loading}
@@ -88,6 +91,10 @@
88
91
  font-size: theme(detail-font-size, font-size-base);
89
92
  line-height: theme(participant-description-line-height, normal);
90
93
  color: theme(detail-text-color, text-color);
94
+
95
+ &.small {
96
+ padding: margin(1);
97
+ }
91
98
  }
92
99
 
93
100
  .heading {
@@ -98,7 +105,7 @@
98
105
  line-height: normal;
99
106
  font-style: theme(detail-title-font-style, normal);
100
107
 
101
- &.small {
108
+ &.subheading {
102
109
  margin: 0 0 margin(0.5);
103
110
  font-size: theme(detail-title-small-font-size, margin(1.25));
104
111
  }
@@ -116,6 +123,11 @@
116
123
  font-size: theme(detail-font-size, font-size-base);
117
124
  line-height: normal;
118
125
  font-style: normal;
126
+
127
+ ::view-transition-old(content),
128
+ ::view-transition-new(content) {
129
+ height: 100%;
130
+ }
119
131
  }
120
132
 
121
133
  .list {
@@ -22,6 +22,7 @@
22
22
  const { playlinks, title }: Props = $props()
23
23
 
24
24
  const isModal = getContext('scope') === 'modal'
25
+ const type = getContext('type')
25
26
  const displayAd = getFirstAdOfType('card')
26
27
  const categorize = !!window.PlayPilotLinkInjections?.config?.categorize_playlinks
27
28
 
@@ -37,7 +38,7 @@
37
38
  const list = $derived(outerWidth < 500 || !!displayAd || mergedPlaylinks.length === 1)
38
39
 
39
40
  function onclick(playlink: string): void {
40
- track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink })
41
+ track(isModal ? TrackingEvent.TitleModalPlaylinkClick : TrackingEvent.InjectionPopoverPlaylinkClick, title, { playlink, type })
41
42
  }
42
43
 
43
44
  function categorizePlaylinks(playlinks: PlaylinkData[]): CategorizedPlaylinks {
@@ -194,11 +194,12 @@
194
194
  background: theme(popover-background, light);
195
195
  box-shadow: theme(popover-shadow, shadow-large);
196
196
  max-height: min(var(--max-height, 100vh), margin(36)); // --max-height defaults to 100vh to make it unused while it's not defined
197
- scrollbar-width: thin;
198
197
  overflow-y: overlay;
199
198
  overflow-x: hidden;
200
199
  view-transition-name: playpilot-title-content;
201
200
 
201
+ @include styled-scrollbar;
202
+
202
203
  .has-bubble & {
203
204
  border-top-left-radius: 0;
204
205
  border-top-right-radius: 0;
@@ -56,7 +56,7 @@
56
56
  <TitlePoster {title} onload={() => posterLoaded = true} lazy={false} />
57
57
  </div>
58
58
 
59
- <div class="heading" use:heading={2} class:truncate={small} id="title">{title.title}</div>
59
+ <div class="heading" use:heading={2} class:truncate={small} id="heading">{title.title}</div>
60
60
 
61
61
  <div class="info">
62
62
  <div class="imdb">
@@ -16,7 +16,7 @@
16
16
  import RoundButton from '../components/RoundButton.svelte'
17
17
  import SkeletonText from '../components/SkeletonText.svelte'
18
18
  import Title from '../components/Title.svelte'
19
- import TitlePopover from '../components/TitlePopover.svelte'
19
+ import InjectionPopover from '../components/InjectionPopover.svelte'
20
20
  import Tooltip from '../components/Tooltip.svelte'
21
21
  import ExploreRouter from '../components/Explore/ExploreRouter.svelte'
22
22
  import TitlesRail from '../components/Rails/TitlesRail.svelte'
@@ -69,10 +69,10 @@
69
69
  </div>
70
70
 
71
71
  <div>
72
- <h3>TitlePopover.svelte</h3>
72
+ <h3>InjectionPopover.svelte</h3>
73
73
  <div class="item">
74
74
  <div style:height="500px"></div>
75
- <TitlePopover {title} event={{} as MouseEvent} />
75
+ <InjectionPopover data={title} event={{} as MouseEvent} />
76
76
  </div>
77
77
  </div>
78
78
  </div>
@@ -44,5 +44,6 @@ export function generateInjection(sentence, title) {
44
44
  playpilot_url: 'https://some-link.com/',
45
45
  key: 'some-key-' + Math.random().toString(),
46
46
  title_details,
47
+ type: 'title',
47
48
  }
48
49
  }
@@ -67,20 +67,20 @@ describe('$lib/api/titles', () => {
67
67
  expect(api).toHaveBeenCalledWith('/titles/browse?api-token=some-token&region=be&language=en-US&include_count=false')
68
68
  })
69
69
 
70
- it('Should not fetch and return empty results if region is not supported', async () => {
70
+ it('Should fetch for global region if user region is not supported', async () => {
71
71
  vi.mocked(isUserRegionSupported).mockResolvedValue(false)
72
72
 
73
73
  await fetchTitles()
74
74
 
75
- expect(api).not.toHaveBeenCalled()
75
+ expect(api).toHaveBeenCalledWith(expect.stringContaining('region=gl'))
76
76
  })
77
77
 
78
- it('Should fetch if region is unsupported but region was given as null', async () => {
78
+ it('Should fetch without region if region is unsupported but region was given as null', async () => {
79
79
  vi.mocked(isUserRegionSupported).mockResolvedValue(false)
80
80
 
81
81
  await fetchTitles({ region: null })
82
82
 
83
- expect(api).toHaveBeenCalled()
83
+ expect(api).not.toHaveBeenCalledWith(expect.stringContaining('region=gl'))
84
84
  })
85
85
  })
86
86
 
@@ -1,11 +1,12 @@
1
1
  import { fireEvent } from '@testing-library/svelte'
2
2
  import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
3
3
 
4
- import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, isAvailableAsManualInjection, filterRemovedAndInactiveInjections, isEquivalentInjection, filterInvalidInTextInjections, filterInvalidAfterArticleInjections, isValidInjection, isValidPlaylinkType, removePlayPilotTitleLinks } from '$lib/injection'
4
+ import { injectLinksInDocument, clearLinkInjections, clearLinkInjection, isAvailableAsManualInjection, filterRemovedAndInactiveInjections, isEquivalentInjection, filterInvalidInTextInjections, filterInvalidAfterArticleInjections, isValidInjection, isValidPlaylinkType, removePlayPilotTitleLinks, hasValidTypeData } from '$lib/injection'
5
5
  import { mount, unmount } from 'svelte'
6
6
  import { fakeFetch, generateInjection } from '../helpers'
7
7
  import { openModalForInjectedLink } from '$lib/modal'
8
8
  import { getLinkInjectionElements } from '$lib/injectionElements'
9
+ import { participants } from '$lib/fakeData'
9
10
  import { titleUrl } from '$lib/routes'
10
11
 
11
12
  vi.mock('svelte', () => ({
@@ -572,7 +573,7 @@ describe('injection.ts', () => {
572
573
  })
573
574
 
574
575
  it('Should remove all popovers that may not have been removed properly in previous destroy attempts', async () => {
575
- document.body.innerHTML = '<div data-playpilot-title-popover></div> <p>This is a sentence with an injection.</p>'
576
+ document.body.innerHTML = '<div data-playpilot-injection-popover></div> <p>This is a sentence with an injection.</p>'
576
577
 
577
578
  const elements = Array.from(document.body.querySelectorAll('p'))
578
579
  const injection = generateInjection('This is a sentence with an injection.', 'an injection')
@@ -587,7 +588,7 @@ describe('injection.ts', () => {
587
588
  await fireEvent.mouseMove(document.body)
588
589
  vi.advanceTimersByTime(200)
589
590
 
590
- expect(document.querySelectorAll('[data-playpilot-title-popover]')).toHaveLength(0)
591
+ expect(document.querySelectorAll('[data-playpilot-injection-popover]')).toHaveLength(0)
591
592
  })
592
593
 
593
594
  it('Should inject links of the same phrase when multiple are present', () => {
@@ -951,6 +952,20 @@ describe('injection.ts', () => {
951
952
  expect(document.querySelector('a')?.closest('[data-playpilot-injection-key]')).toBeTruthy()
952
953
  })
953
954
 
955
+ describe('Participants', () => {
956
+ it('Should inject participants just as a title', () => {
957
+ document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
958
+
959
+ const elements = Array.from(document.body.querySelectorAll('p'))
960
+ const injection = { ...generateInjection('This is a sentence with an injection.', 'an injection'), type: 'participant', participant_details: participants[0] }
961
+
962
+ // @ts-ignore
963
+ injectLinksInDocument(elements, { aiInjections: [injection], manualInjections: [] })
964
+
965
+ expect(document.querySelector('[data-playpilot-injection-key]')).toBeTruthy()
966
+ })
967
+ })
968
+
954
969
  describe('config.open_tpi_links_in_explore', () => {
955
970
  beforeEach(() => {
956
971
  window.PlayPilotLinkInjections.config = {
@@ -1168,6 +1183,32 @@ describe('injection.ts', () => {
1168
1183
  })
1169
1184
  })
1170
1185
 
1186
+ describe('hasValidTypeData', () => {
1187
+ it('Should return true if injection has type title and has title_details', () => {
1188
+ // @ts-ignore
1189
+ expect(hasValidTypeData({ type: 'title', title_details: {} })).toBe(true)
1190
+ })
1191
+
1192
+ it('Should return true if injection has type participant and has participant_details', () => {
1193
+ // @ts-ignore
1194
+ expect(hasValidTypeData({ type: 'participant', participant_details: {} })).toBe(true)
1195
+ })
1196
+
1197
+ it('Should return false if injection has type title but has no title_details', () => {
1198
+ // @ts-ignore
1199
+ expect(hasValidTypeData({ type: 'title', title_details: null })).toBe(false)
1200
+ // @ts-ignore
1201
+ expect(hasValidTypeData({ type: 'title' })).toBe(false)
1202
+ })
1203
+
1204
+ it('Should return false if injection has type participant but has no participant_details', () => {
1205
+ // @ts-ignore
1206
+ expect(hasValidTypeData({ type: 'participant', participant_details: null })).toBe(false)
1207
+ // @ts-ignore
1208
+ expect(hasValidTypeData({ type: 'participant' })).toBe(false)
1209
+ })
1210
+ })
1211
+
1171
1212
  describe('removePlayPilotTitleLinks', () => {
1172
1213
  it('Should remove existing title links', () => {
1173
1214
  document.body.innerHTML = `
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
2
  import { linkInjections } from '$lib/fakeData'
3
3
  import { mount, unmount } from 'svelte'
4
4
  import { clearCurrentlyHoveredInjection, currentlyHoveredInjection, destroyLinkPopover, isPopoverActive, openPopoverForInjectedLink } from '$lib/popover'
5
- import TitlePopover from '../../routes/components/TitlePopover.svelte'
5
+ import InjectionPopover from '../../routes/components/InjectionPopover.svelte'
6
6
  import { waitFor } from '@testing-library/svelte'
7
7
 
8
8
  vi.mock('svelte', () => ({
@@ -22,17 +22,17 @@ describe('popover.js', () => {
22
22
  describe('openPopoverForInjectedLink', () => {
23
23
  it('Should mount popover', async () => {
24
24
  // @ts-ignore
25
- openPopoverForInjectedLink({ currentTarget: document.querySelector('a') }, linkInjections)
25
+ openPopoverForInjectedLink({ currentTarget: document.querySelector('a') }, linkInjections[0])
26
26
 
27
27
  await waitFor(() => {
28
- expect(mount).toHaveBeenCalledWith(TitlePopover, expect.any(Object))
28
+ expect(mount).toHaveBeenCalledWith(InjectionPopover, expect.any(Object))
29
29
  expect(currentlyHoveredInjection).toBeTruthy()
30
30
  })
31
31
  })
32
32
 
33
33
  it('Should not mount popover if user is no longer hovering after delay', async () => {
34
34
  // @ts-ignore
35
- openPopoverForInjectedLink({ currentTarget: document.querySelector('a') }, linkInjections)
35
+ openPopoverForInjectedLink({ currentTarget: document.querySelector('a') }, linkInjections[0])
36
36
 
37
37
  await new Promise(res => setTimeout(res, 50))
38
38
 
@@ -46,19 +46,19 @@ describe('popover.js', () => {
46
46
 
47
47
  describe('destroyLinkPopover', () => {
48
48
  it('Should not call unmount but still remove potential popover elements if no active popover is set', async () => {
49
- document.body.innerHTML = '<div data-playpilot-title-popover></div> <div data-playpilot-title-popover></div>'
49
+ document.body.innerHTML = '<div data-playpilot-injection-popover></div> <div data-playpilot-injection-popover></div>'
50
50
 
51
51
  destroyLinkPopover()
52
52
 
53
53
  expect(unmount).not.toHaveBeenCalled()
54
- expect(document.querySelectorAll('[data-playpilot-title-popover]')).toHaveLength(0)
54
+ expect(document.querySelectorAll('[data-playpilot-injection-popover]')).toHaveLength(0)
55
55
  })
56
56
  })
57
57
 
58
58
  describe('clearCurrentlyHoveredInjection', () => {
59
59
  it('Should clear currentlyHoveredInjection', async () => {
60
60
  // @ts-ignore
61
- openPopoverForInjectedLink({ currentTarget: document.querySelector('a') }, linkInjections)
61
+ openPopoverForInjectedLink({ currentTarget: document.querySelector('a') }, linkInjections[0])
62
62
 
63
63
  await waitFor(() => expect(currentlyHoveredInjection).toBeTruthy())
64
64
 
@@ -233,6 +233,14 @@ describe('$lib/tracking', () => {
233
233
  }),
234
234
  )
235
235
  })
236
+
237
+ it('Should not track when a very large amount of events have already been fired', () => {
238
+ window.PlayPilotLinkInjections.tracked_events = new Array(600).fill(1)
239
+
240
+ track('Some event')
241
+
242
+ expect(global.fetch).not.toHaveBeenCalled()
243
+ })
236
244
  })
237
245
 
238
246
  describe('setTrackingSids', () => {
@@ -6,6 +6,7 @@ import { injectLinksInDocument } from '$lib/injection'
6
6
  import { track } from '$lib/tracking'
7
7
  import { generateInjection } from '../../../helpers'
8
8
  import { removeImageUrlPrefix } from '$lib/image'
9
+ import { linkInjections as fakeLinkInjections } from '$lib/fakeData'
9
10
 
10
11
  vi.mock('$lib/tracking', () => ({
11
12
  track: vi.fn(),
@@ -200,4 +201,13 @@ describe('EditorItem.svelte', () => {
200
201
 
201
202
  expect(getByText('What would you like to report?')).toBeTruthy()
202
203
  })
204
+
205
+ it('Should render as participant when type is participant', () => {
206
+ const { container, queryByLabelText, getByText } = render(EditorItem, { linkInjection: fakeLinkInjections[4] })
207
+
208
+ expect(queryByLabelText('Expand')).not.toBeTruthy()
209
+ expect(getByText(fakeLinkInjections[4].participant_details?.name || '-')).toBeTruthy()
210
+ expect(container.querySelector('.poster')).not.toBeTruthy()
211
+ expect(container.querySelector('.placeholder-image')).toBeTruthy()
212
+ })
203
213
  })
@@ -99,6 +99,7 @@ describe('ManualInjection.svelte', () => {
99
99
  manual: true,
100
100
  phrase_before: 'Some',
101
101
  phrase_after: 'in a',
102
+ type: 'title',
102
103
  })
103
104
  })
104
105
 
@@ -141,6 +142,7 @@ describe('ManualInjection.svelte', () => {
141
142
  manual: true,
142
143
  phrase_before: '',
143
144
  phrase_after: '',
145
+ type: 'title',
144
146
  })
145
147
  })
146
148
 
@@ -183,6 +185,7 @@ describe('ManualInjection.svelte', () => {
183
185
  manual: true,
184
186
  phrase_before: expect.any(String),
185
187
  phrase_after: expect.any(String),
188
+ type: 'title',
186
189
  })
187
190
  })
188
191
 
@@ -225,6 +228,7 @@ describe('ManualInjection.svelte', () => {
225
228
  manual: true,
226
229
  phrase_before: expect.any(String),
227
230
  phrase_after: expect.any(String),
231
+ type: 'title',
228
232
  })
229
233
  })
230
234
 
@@ -11,6 +11,8 @@ describe('PlaylinkTypeSelect.svelte', () => {
11
11
  sentence: 'Some sentence',
12
12
  playpilot_url: 'https://playpilot.com/movie/example-3/',
13
13
  key: 'some-key',
14
+ /** @type {import('$lib/types/injection').LinkInjectionDataType} */
15
+ type: 'title',
14
16
  }
15
17
 
16
18
  beforeEach(() => {
@@ -0,0 +1,117 @@
1
+ import { render } from '@testing-library/svelte'
2
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
3
+
4
+ import InjectionPopover from '../../../routes/components/InjectionPopover.svelte'
5
+ import { participants, title } from '$lib/fakeData'
6
+ import { track } from '$lib/tracking'
7
+ import { TrackingEvent } from '$lib/enums/TrackingEvent'
8
+ import { hasConsentedTo } from '$lib/consent'
9
+
10
+ vi.mock('$lib/consent', () => ({
11
+ hasConsentedTo: vi.fn(() => true),
12
+ }))
13
+
14
+ vi.mock('$lib/tracking', () => ({
15
+ track: vi.fn(),
16
+ }))
17
+
18
+ vi.mock('$lib/pixel', () => ({
19
+ isPixelAllowed: vi.fn(),
20
+ }))
21
+
22
+ vi.mock('$lib/api/participants', () => ({
23
+ fetchParticipantsForTitle: vi.fn(),
24
+ }))
25
+
26
+ vi.mock('$lib/api/titles', () => ({
27
+ fetchSimilarTitles: vi.fn(),
28
+ }))
29
+
30
+ describe('InjectionPopover.svelte', () => {
31
+ beforeEach(() => {
32
+ vi.resetAllMocks()
33
+ vi.mocked(hasConsentedTo).mockImplementation(() => true)
34
+ })
35
+
36
+ afterEach(() => {
37
+ vi.useRealTimers()
38
+ })
39
+
40
+ it('Should render top scroll ad when given', () => {
41
+ // @ts-ignore
42
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'top_scroll', content: {}, cta: {} }] }
43
+
44
+ const event = new MouseEvent('mouseenter')
45
+ const { container } = render(InjectionPopover, { event, type: 'title', data: title })
46
+
47
+ expect(container.querySelector('.top-scroll')).toBeTruthy()
48
+ })
49
+
50
+ it('Should not render top scroll ad when not given', () => {
51
+ // @ts-ignore
52
+ window.PlayPilotLinkInjections = { ads: null }
53
+
54
+ const event = new MouseEvent('mouseenter')
55
+ const { container } = render(InjectionPopover, { event, type: 'title', data: title })
56
+
57
+ expect(container.querySelector('.top-scroll')).not.toBeTruthy()
58
+ })
59
+
60
+ describe('Title type', () => {
61
+ it('Should render the given title', () => {
62
+ const event = new MouseEvent('mouseenter')
63
+ const { getByText } = render(InjectionPopover, { event, type: 'title', data: title })
64
+
65
+ expect(getByText(title.title)).toBeTruthy()
66
+ })
67
+
68
+ it('Should call track function when rendered', () => {
69
+ const event = new MouseEvent('mouseenter')
70
+ render(InjectionPopover, { event, type: 'title', data: title })
71
+
72
+ expect(track).toHaveBeenCalledWith(TrackingEvent.InjectionPopoverView, title, { type: 'title' })
73
+ })
74
+
75
+ it('Should call track function with time_spent when destroyed', async () => {
76
+ vi.useFakeTimers()
77
+
78
+ const event = new MouseEvent('mouseenter')
79
+ const { unmount } = render(InjectionPopover, { event, type: 'title', data: title })
80
+
81
+ vi.advanceTimersByTime(200)
82
+ unmount()
83
+
84
+ expect(track).toHaveBeenCalledWith(TrackingEvent.InjectionPopoverClose, title, expect.objectContaining({ time_spent: 200, type: 'title' }))
85
+ })
86
+ })
87
+
88
+ describe('Participant type', () => {
89
+ const participant = participants[0]
90
+
91
+ it('Should render the given participant', () => {
92
+ const event = new MouseEvent('mouseenter')
93
+ const { getByText } = render(InjectionPopover, { event, type: 'participant', data: participant })
94
+
95
+ expect(getByText(participant.name)).toBeTruthy()
96
+ })
97
+
98
+ it('Should call track function when rendered', () => {
99
+ const event = new MouseEvent('mouseenter')
100
+ render(InjectionPopover, { event, type: 'participant', data: participant })
101
+
102
+ expect(track).toHaveBeenCalledWith(TrackingEvent.InjectionPopoverView, null, { type: 'participant', participant: participant.name })
103
+ })
104
+
105
+ it('Should call track function with time_spent when destroyed', async () => {
106
+ vi.useFakeTimers()
107
+
108
+ const event = new MouseEvent('mouseenter')
109
+ const { unmount } = render(InjectionPopover, { event, type: 'participant', data: participant })
110
+
111
+ vi.advanceTimersByTime(200)
112
+ unmount()
113
+
114
+ expect(track).toHaveBeenCalledWith(TrackingEvent.InjectionPopoverClose, null, expect.objectContaining({ type: 'participant', time_spent: 200, participant: participant.name }))
115
+ })
116
+ })
117
+ })
@@ -50,6 +50,13 @@ describe('ListTitle.svelte', () => {
50
50
  expect(onclick).toHaveBeenCalled()
51
51
  })
52
52
 
53
+ it('Should render as small variant when given', () => {
54
+ const { container } = render(ListTitle, { title, compact: true })
55
+
56
+ expect(container.querySelector('.compact')).toBeTruthy()
57
+ expect(container.querySelector('.description')).not.toBeTruthy()
58
+ })
59
+
53
60
  describe('Playlinks', () => {
54
61
  const playlinks = [
55
62
  { name: 'Some playlink', logo_url: 'logo', extra_info: { category: 'SVOD' } },
@@ -73,4 +73,11 @@ describe('Participant.svelte', () => {
73
73
 
74
74
  expect(fetchTitlesForParticipant).toHaveBeenCalledWith(participants[0], { page: 2 })
75
75
  })
76
+
77
+ it('Should render as small variant when given', () => {
78
+ const { container, queryByText } = render(Participant, { participant: participants[0], small: true })
79
+
80
+ expect(queryByText('Credits')).not.toBeTruthy()
81
+ expect(container.querySelector('.small')).toBeTruthy()
82
+ })
76
83
  })
@@ -23,6 +23,8 @@ const linkInjections = [{
23
23
  poster: 'some-poster',
24
24
  key: 'some-key-1',
25
25
  title_details: title,
26
+ /** @type {import('$lib/types/injection').LinkInjectionDataType} */
27
+ type: 'title',
26
28
  }, {
27
29
  sid: '2',
28
30
  title: 'a sentence',
@@ -31,6 +33,8 @@ const linkInjections = [{
31
33
  poster: 'some-poster',
32
34
  key: 'some-key-1',
33
35
  title_details: title,
36
+ /** @type {import('$lib/types/injection').LinkInjectionDataType} */
37
+ type: 'title',
34
38
  }]
35
39
 
36
40
  describe('AfterArticlePlaylinks.svelte', () => {
@@ -85,7 +85,7 @@ describe('Playlinks.svelte', () => {
85
85
 
86
86
  await fireEvent.click(getByText(playlinks[0].name))
87
87
 
88
- expect(track).toHaveBeenCalledWith(TrackingEvent.TitlePopoverPlaylinkClick, title, { playlink: playlinks[0].name })
88
+ expect(track).toHaveBeenCalledWith(TrackingEvent.InjectionPopoverPlaylinkClick, title, { playlink: playlinks[0].name })
89
89
  })
90
90
 
91
91
  it('Should call track function for modal when clicked inside of modal scope', async () => {