@playpilot/tpi 5.17.0 → 5.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.
- package/dist/link-injections.js +9 -9
- package/eslint.config.js +16 -0
- package/events.md +3 -0
- package/package.json +1 -1
- package/src/app.d.ts +13 -13
- package/src/lib/actions/heading.ts +2 -2
- package/src/lib/actions/middlemouse.ts +2 -2
- package/src/lib/{ads.ts → api/ads.ts} +19 -22
- package/src/lib/api/api.ts +21 -0
- package/src/lib/{auth.ts → api/auth.ts} +8 -12
- package/src/lib/api/config.ts +16 -0
- package/src/lib/{api.ts → api/externalPages.ts} +17 -48
- package/src/lib/api/search.ts +14 -0
- package/src/lib/{session.ts → api/session.ts} +3 -3
- package/src/lib/array.ts +2 -2
- package/src/lib/consent.ts +1 -1
- package/src/lib/data/translations.ts +1 -1
- package/src/lib/enums/SplitTest.ts +2 -2
- package/src/lib/enums/TrackingEvent.ts +5 -0
- package/src/lib/event.ts +1 -1
- package/src/lib/fakeData.ts +4 -4
- package/src/lib/hash.ts +1 -1
- package/src/lib/image.ts +2 -2
- package/src/lib/{linkInjection.ts → injection.ts} +25 -18
- package/src/lib/meta.ts +1 -1
- package/src/lib/modal.ts +1 -1
- package/src/lib/playlink.ts +1 -1
- package/src/lib/routes.ts +9 -0
- package/src/lib/splitTest.ts +4 -4
- package/src/lib/text.ts +1 -1
- package/src/lib/token.ts +3 -0
- package/src/lib/tracking.ts +5 -5
- package/src/lib/types/global.d.ts +1 -1
- package/src/lib/types/injection.d.ts +1 -1
- package/src/lib/types/script.d.ts +18 -3
- package/src/lib/types/session.d.ts +1 -1
- package/src/lib/types/title.d.ts +2 -2
- package/src/main.ts +12 -12
- package/src/routes/+page.svelte +5 -4
- package/src/routes/components/Debugger.svelte +26 -1
- package/src/routes/components/Editorial/Editor.svelte +7 -7
- package/src/routes/components/Editorial/EditorItem.svelte +1 -1
- package/src/routes/components/Editorial/ManualInjection.svelte +8 -8
- package/src/routes/components/Editorial/PlaylinkTypeSelect.svelte +1 -1
- package/src/routes/components/Editorial/Search/TitleSearch.svelte +1 -1
- package/src/routes/components/Editorial/Search/TitleSearchItem.svelte +3 -2
- package/src/routes/components/Editorial/Session.svelte +1 -1
- package/src/routes/components/ListTitle.svelte +2 -2
- package/src/routes/components/ParticipantModal.svelte +11 -0
- package/src/routes/components/Playlinks.svelte +1 -1
- package/src/routes/components/Rails/SimilarRail.svelte +9 -1
- package/src/routes/components/Rails/TitlesRail.svelte +11 -7
- package/src/routes/components/TitleModal.svelte +3 -3
- package/src/routes/components/TitlePopover.svelte +1 -1
- package/src/tests/lib/{ads.test.js → api/ads.test.js} +17 -14
- package/src/tests/lib/api/api.test.js +49 -0
- package/src/tests/lib/{auth.test.js → api/auth.test.js} +10 -23
- package/src/tests/lib/api/config.test.js +53 -0
- package/src/tests/lib/{api.test.js → api/externalPages.test.js} +71 -101
- package/src/tests/lib/{search.test.js → api/search.test.js} +10 -9
- package/src/tests/lib/{session.test.js → api/session.test.js} +4 -4
- package/src/tests/lib/{linkInjection.test.js → injections.test.js} +26 -2
- package/src/tests/lib/routes.test.js +15 -0
- package/src/tests/routes/+page.test.js +17 -9
- package/src/tests/routes/components/Editorial/Editor.test.js +3 -3
- package/src/tests/routes/components/Editorial/EditorItem.test.js +1 -1
- package/src/tests/routes/components/Editorial/ManualInjection.test.js +2 -2
- package/src/tests/routes/components/Editorial/Search/TitleSearch.test.js +2 -2
- package/src/tests/routes/components/Editorial/Session.test.js +2 -2
- package/src/tests/routes/components/ParticipantModal.test.js +35 -0
- package/src/tests/routes/components/Rails/{TitleRail.test.js → TitlesRail.test.js} +10 -1
- package/src/tests/setup.js +2 -0
- package/src/lib/search.ts +0 -23
package/src/lib/token.ts
ADDED
package/src/lib/tracking.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { hasConsentedTo } from
|
|
2
|
-
import { mobileBreakpoint } from
|
|
3
|
-
import type { TitleData } from
|
|
4
|
-
import { getFullUrlPath } from
|
|
1
|
+
import { hasConsentedTo } from './consent'
|
|
2
|
+
import { mobileBreakpoint } from './constants'
|
|
3
|
+
import type { TitleData } from './types/title'
|
|
4
|
+
import { getFullUrlPath } from './url'
|
|
5
5
|
|
|
6
6
|
const baseUrl = 'https://insights.playpilot.net'
|
|
7
7
|
|
|
@@ -53,7 +53,7 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** Save this event to the window object. Used when calling .debug() */
|
|
56
|
-
function pushEventToWindow(data: { event: string, payload: Record<string, any> }) {
|
|
56
|
+
function pushEventToWindow(data: { event: string, payload: Record<string, any> }): void {
|
|
57
57
|
if (!window.PlayPilotLinkInjections.tracked_events) window.PlayPilotLinkInjections.tracked_events = []
|
|
58
58
|
window.PlayPilotLinkInjections.tracked_events?.push(data)
|
|
59
59
|
}
|
|
@@ -1,21 +1,36 @@
|
|
|
1
|
-
import type { Campaign } from
|
|
2
|
-
import type { ConsentOptions } from
|
|
3
|
-
import type { LinkInjection } from
|
|
1
|
+
import type { Campaign } from './campaign'
|
|
2
|
+
import type { ConsentOptions } from './consent'
|
|
3
|
+
import type { LinkInjection } from './injection'
|
|
4
4
|
|
|
5
5
|
export type ScriptConfig = {
|
|
6
|
+
// The API token to authenticate with the backend.
|
|
6
7
|
token: string
|
|
8
|
+
// Editiorial token as given by the Partner Portal, required to access and authenticate the Editor.
|
|
7
9
|
editorial_token?: string
|
|
10
|
+
// The organization sid is returned from the external pages endpoint and is set by the script.
|
|
8
11
|
organization_sid?: string | null,
|
|
12
|
+
// The domain sid is returned from the external pages endpoint and is set by the script.
|
|
9
13
|
domain_sid?: string | null,
|
|
14
|
+
// Selector for the article content. Should be as narrow as possible. Will fall back to `article` > `main` > `body` if not provided. Can be set via config endpoint.
|
|
10
15
|
selector?: string
|
|
16
|
+
// Selector for the after article playlinks. Will be placed _after_ the given selector.
|
|
11
17
|
after_article_selector?: string
|
|
18
|
+
// Selector to change the insert position of the after article playlinks. Should only be used if no viable selector can be given with `after_article_selector`.
|
|
12
19
|
after_article_insert_position?: InsertPosition | ''
|
|
20
|
+
// The language of the page will be inferred from the html `lang` attribute. Can be set manually using this option in the config object.
|
|
13
21
|
language?: string | null
|
|
22
|
+
// Set to the last success external-pages fetch.
|
|
14
23
|
last_successful_fetch?: LinkInjectionResponse | null
|
|
24
|
+
// Lists all tracked events through the `track()` function.
|
|
15
25
|
tracked_events?: { event: string, payload: Record<string, any> }[]
|
|
26
|
+
// Lists all split test identifiers as created by each running split test. Identifiers are only created when a split test is fired.
|
|
16
27
|
split_test_identifiers?: Record<string, number>
|
|
28
|
+
// All link injections as returned from external-pages, with AI and manual injections merged.
|
|
17
29
|
evaluated_link_injections?: LinkInjection[]
|
|
30
|
+
// By default the script requires consent from the user through tcfapi. Can be disabled using this setting.
|
|
18
31
|
require_consent?: boolean
|
|
32
|
+
// Used to check if a user has consented via tcfapi to various consent categories.
|
|
19
33
|
consents?: ConsentOptions
|
|
34
|
+
// All ads as fetched from the ads endpoint. This is used as the primary store for ads, each individual ads gets it's data from here.
|
|
20
35
|
ads?: Campaign[]
|
|
21
36
|
}
|
package/src/lib/types/title.d.ts
CHANGED
package/src/main.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mount } from 'svelte'
|
|
2
2
|
import App from './routes/+page.svelte'
|
|
3
|
-
import { clearLinkInjections, getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/
|
|
3
|
+
import { clearLinkInjections, getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/injection'
|
|
4
4
|
import { getPageMetaData } from '$lib/meta'
|
|
5
5
|
import { setConsent } from '$lib/consent'
|
|
6
6
|
import type { Campaign } from '$lib/types/campaign'
|
|
@@ -81,36 +81,36 @@ window.PlayPilotLinkInjections = {
|
|
|
81
81
|
const elements = getLinkInjectionElements(parentElement)
|
|
82
82
|
|
|
83
83
|
console.groupCollapsed('Config')
|
|
84
|
-
|
|
84
|
+
console.table(Object.entries(this))
|
|
85
85
|
console.groupEnd()
|
|
86
86
|
|
|
87
87
|
console.groupCollapsed('Elements')
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
console.log('Parent element', parentElement)
|
|
89
|
+
console.log('Valid elements', elements)
|
|
90
90
|
console.groupEnd()
|
|
91
91
|
|
|
92
92
|
console.groupCollapsed('Last fetch')
|
|
93
|
-
|
|
93
|
+
console.log(this.last_successful_fetch)
|
|
94
94
|
console.groupEnd()
|
|
95
95
|
|
|
96
96
|
console.groupCollapsed('Meta')
|
|
97
|
-
|
|
97
|
+
console.log(getPageMetaData())
|
|
98
98
|
console.groupEnd()
|
|
99
99
|
|
|
100
100
|
console.groupCollapsed('Page text')
|
|
101
|
-
|
|
101
|
+
console.log(getPageText(elements))
|
|
102
102
|
console.groupEnd()
|
|
103
103
|
|
|
104
104
|
console.groupCollapsed('Evaluated injections')
|
|
105
|
-
|
|
105
|
+
console.log(this.evaluated_link_injections)
|
|
106
106
|
console.groupEnd()
|
|
107
107
|
|
|
108
108
|
console.groupCollapsed('Tracked events')
|
|
109
|
-
|
|
109
|
+
console.log(this.tracked_events)
|
|
110
110
|
console.groupEnd()
|
|
111
111
|
|
|
112
112
|
console.groupCollapsed('Split tests')
|
|
113
|
-
|
|
113
|
+
console.log(this.split_test_identifiers)
|
|
114
114
|
console.groupEnd()
|
|
115
115
|
},
|
|
116
116
|
|
|
@@ -135,13 +135,13 @@ window.PlayPilotLinkInjections = {
|
|
|
135
135
|
subheader: 'Some cta subheader',
|
|
136
136
|
url: 'https://google.com/',
|
|
137
137
|
image: null,
|
|
138
|
-
image_uuid: null
|
|
138
|
+
image_uuid: null,
|
|
139
139
|
},
|
|
140
140
|
disclaimer: 'Some disclaimer',
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
this.ads = [{ ...campaign, ...override }]
|
|
144
|
-
}
|
|
144
|
+
},
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
export default window.PlayPilotLinkInjections
|
package/src/routes/+page.svelte
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy } from 'svelte'
|
|
3
|
-
import {
|
|
4
|
-
import { clearLinkInjections, getLinkInjectionElements, getLinkInjectionsParentElement, getPageText, injectLinksInDocument, separateLinkInjectionTypes } from '$lib/
|
|
3
|
+
import { pollLinkInjections } from '$lib/api/externalPages'
|
|
4
|
+
import { clearLinkInjections, getLinkInjectionElements, getLinkInjectionsParentElement, getPageText, injectLinksInDocument, separateLinkInjectionTypes } from '$lib/injection'
|
|
5
5
|
import { setTrackingSids, track } from '$lib/tracking'
|
|
6
6
|
import { getFullUrlPath } from '$lib/url'
|
|
7
7
|
import { isCrawler } from '$lib/crawler'
|
|
8
8
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
9
|
-
import {
|
|
9
|
+
import { fetchAds } from '$lib/api/ads'
|
|
10
|
+
import { fetchConfig } from '$lib/api/config'
|
|
11
|
+
import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/api/auth'
|
|
10
12
|
import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
|
|
11
13
|
import Editor from './components/Editorial/Editor.svelte'
|
|
12
14
|
import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
|
|
@@ -14,7 +16,6 @@
|
|
|
14
16
|
import TrackingPixels from './components/TrackingPixels.svelte'
|
|
15
17
|
import Consent from './components/Consent.svelte'
|
|
16
18
|
import Debugger from './components/Debugger.svelte'
|
|
17
|
-
import { fetchAds } from '$lib/ads'
|
|
18
19
|
|
|
19
20
|
let parentElement: HTMLElement | null = $state(null)
|
|
20
21
|
let elements: HTMLElement[] = $state([])
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { SplitTest } from '$lib/enums/SplitTest'
|
|
2
3
|
import { onDestroy } from 'svelte'
|
|
3
4
|
|
|
4
5
|
const secrets = ['tpidebug', 'debugtpi']
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
if (interval) clearInterval(interval)
|
|
13
14
|
})
|
|
14
15
|
|
|
15
|
-
function dataToReadable() {
|
|
16
|
+
function dataToReadable(): Record<string, undefined | Record<string, any>[]> {
|
|
16
17
|
const data = window.PlayPilotLinkInjections
|
|
17
18
|
|
|
18
19
|
const succesfulInjections = data.evaluated_link_injections?.filter(injection => !injection.failed) || []
|
|
@@ -65,6 +66,16 @@
|
|
|
65
66
|
disclaimer: 'Disclaimer',
|
|
66
67
|
})
|
|
67
68
|
}
|
|
69
|
+
|
|
70
|
+
function forceSplitTestVariant(variantIndex: number): void {
|
|
71
|
+
window.PlayPilotLinkInjections.split_test_identifiers = {}
|
|
72
|
+
|
|
73
|
+
Object.values(SplitTest).forEach(({ key, numberOfVariants }) => {
|
|
74
|
+
const variantDivide = 1 / numberOfVariants
|
|
75
|
+
|
|
76
|
+
window.PlayPilotLinkInjections.split_test_identifiers![key] = Math.min(variantDivide * variantIndex, 1)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
68
79
|
</script>
|
|
69
80
|
|
|
70
81
|
<svelte:window {onkeydown} />
|
|
@@ -98,6 +109,14 @@
|
|
|
98
109
|
|
|
99
110
|
<button onclick={() => insertMockAd('card')}>Insert mock card ad</button>
|
|
100
111
|
<button onclick={() => insertMockAd('top_scroll')}>Insert mock top scroll ad</button>
|
|
112
|
+
|
|
113
|
+
<hr />
|
|
114
|
+
|
|
115
|
+
<small>Force split test variant</small>
|
|
116
|
+
|
|
117
|
+
{#each { length: 4 }, i}
|
|
118
|
+
<button onclick={() => forceSplitTestVariant(i)}>{i}</button>
|
|
119
|
+
{/each}
|
|
101
120
|
</div>
|
|
102
121
|
{/if}
|
|
103
122
|
|
|
@@ -119,6 +138,11 @@
|
|
|
119
138
|
color: var(--playpilot-primary);
|
|
120
139
|
font-family: inherit;
|
|
121
140
|
font-weight: bold;
|
|
141
|
+
|
|
142
|
+
&:hover {
|
|
143
|
+
color: black;
|
|
144
|
+
background: var(--playpilot-primary);
|
|
145
|
+
}
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
.debugger {
|
|
@@ -134,6 +158,7 @@
|
|
|
134
158
|
overflow: auto;
|
|
135
159
|
color: white;
|
|
136
160
|
font-family: "Consolas", monospace;
|
|
161
|
+
color: var(--playpilot-primary);
|
|
137
162
|
}
|
|
138
163
|
|
|
139
164
|
.item {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Position } from '$lib/types/position'
|
|
3
|
+
import type { LinkInjection } from '$lib/types/injection'
|
|
4
|
+
import { track } from '$lib/tracking'
|
|
5
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
6
|
+
import { heading } from '$lib/actions/heading'
|
|
7
|
+
import { separateLinkInjectionTypes } from '$lib/injection'
|
|
8
|
+
import { saveLinkInjections } from '$lib/api/externalPages'
|
|
2
9
|
import { fade, fly, slide } from 'svelte/transition'
|
|
3
10
|
import EditorItem from './EditorItem.svelte'
|
|
4
11
|
import DragHandle from './DragHandle.svelte'
|
|
@@ -7,15 +14,8 @@
|
|
|
7
14
|
import ManualInjection from './ManualInjection.svelte'
|
|
8
15
|
import RoundButton from '../RoundButton.svelte'
|
|
9
16
|
import Session from './Session.svelte'
|
|
10
|
-
import { saveLinkInjections } from '$lib/api'
|
|
11
17
|
import { untrack } from 'svelte'
|
|
12
18
|
import AIIndicator from './AIIndicator.svelte'
|
|
13
|
-
import type { Position } from '$lib/types/position'
|
|
14
|
-
import type { LinkInjection } from '$lib/types/injection'
|
|
15
|
-
import { track } from '$lib/tracking'
|
|
16
|
-
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
17
|
-
import { heading } from '$lib/actions/heading'
|
|
18
|
-
import { separateLinkInjectionTypes } from '$lib/linkInjection'
|
|
19
19
|
|
|
20
20
|
interface Props {
|
|
21
21
|
linkInjections: LinkInjection[],
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { LinkInjection } from '$lib/types/injection'
|
|
14
14
|
import type { TitleData } from '$lib/types/title'
|
|
15
15
|
import { cleanPhrase, truncateAroundPhrase } from '$lib/text'
|
|
16
|
-
import { getLinkInjectionElements, getLinkInjectionsParentElement, isValidPlaylinkType } from '$lib/
|
|
16
|
+
import { getLinkInjectionElements, getLinkInjectionsParentElement, isValidPlaylinkType } from '$lib/injection'
|
|
17
17
|
import { imagePlaceholderDataUrl } from '$lib/constants'
|
|
18
18
|
import { removeImageUrlPrefix } from '$lib/image'
|
|
19
19
|
import ReportIssueModal from './ReportIssueModal.svelte'
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { LinkInjection } from '$lib/types/injection'
|
|
3
|
+
import type { TitleData } from '$lib/types/title'
|
|
2
4
|
import { onMount } from 'svelte'
|
|
5
|
+
import { playPilotBaseUrl } from '$lib/constants'
|
|
6
|
+
import { generateInjectionKey } from '$lib/api/externalPages'
|
|
7
|
+
import { getLinkInjectionsParentElement } from '$lib/injection'
|
|
8
|
+
import { heading } from '$lib/actions/heading'
|
|
9
|
+
import { findSurroundingPhrases, cleanPhrase } from '$lib/text'
|
|
10
|
+
import { getIndexOfSelection } from '$lib/selection'
|
|
3
11
|
import IconBack from '../Icons/IconBack.svelte'
|
|
4
12
|
import RoundButton from '../RoundButton.svelte'
|
|
5
13
|
import Alert from './Alert.svelte'
|
|
6
14
|
import TextInput from './TextInput.svelte'
|
|
7
15
|
import TitleSearch from './Search/TitleSearch.svelte'
|
|
8
|
-
import { playPilotBaseUrl } from '$lib/constants'
|
|
9
|
-
import { generateInjectionKey } from '$lib/api'
|
|
10
|
-
import { getLinkInjectionsParentElement } from '$lib/linkInjection'
|
|
11
|
-
import type { LinkInjection } from '$lib/types/injection'
|
|
12
|
-
import type { TitleData } from '$lib/types/title'
|
|
13
|
-
import { heading } from '$lib/actions/heading'
|
|
14
|
-
import { findSurroundingPhrases, cleanPhrase } from '$lib/text'
|
|
15
|
-
import { getIndexOfSelection } from '$lib/selection'
|
|
16
16
|
|
|
17
17
|
interface Props {
|
|
18
18
|
pageText: string
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import IconAlign from '../Icons/IconAlign.svelte'
|
|
4
4
|
import Switch from './Switch.svelte'
|
|
5
5
|
import type { LinkInjection } from '$lib/types/injection'
|
|
6
|
-
import { isValidPlaylinkType } from '$lib/
|
|
6
|
+
import { isValidPlaylinkType } from '$lib/injection'
|
|
7
7
|
import Alert from './Alert.svelte'
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { imagePlaceholderDataUrl
|
|
2
|
+
import { imagePlaceholderDataUrl } from '$lib/constants'
|
|
3
3
|
import { removeImageUrlPrefix } from '$lib/image'
|
|
4
|
+
import { titleUrl } from '$lib/routes'
|
|
4
5
|
import type { TitleData } from '$lib/types/title'
|
|
5
6
|
import IconIMDb from '../../Icons/IconIMDb.svelte'
|
|
6
7
|
import IconNewTab from '../../Icons/IconNewTab.svelte'
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
</div>
|
|
37
38
|
|
|
38
39
|
<a
|
|
39
|
-
href=
|
|
40
|
+
href={titleUrl(title)}
|
|
40
41
|
target="_blank"
|
|
41
42
|
class="open-in-new-tab"
|
|
42
43
|
onclick={event => event.stopImmediatePropagation()}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { fetchAsSession, isAllowedToEdit, saveCurrentSession, sessionPollPeriodMilliseconds } from '$lib/session'
|
|
2
|
+
import { fetchAsSession, isAllowedToEdit, saveCurrentSession, sessionPollPeriodMilliseconds } from '$lib/api/session'
|
|
3
3
|
import { onMount } from 'svelte'
|
|
4
4
|
import Alert from './Alert.svelte'
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { titleUrl } from '$lib/routes'
|
|
3
3
|
import { SplitTest } from '$lib/enums/SplitTest'
|
|
4
4
|
import { t } from '$lib/localization'
|
|
5
5
|
import { mergePlaylinks } from '$lib/playlink'
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
}
|
|
30
30
|
</script>
|
|
31
31
|
|
|
32
|
-
<a class="title" href=
|
|
32
|
+
<a class="title" href={titleUrl(title)} {onclick}>
|
|
33
33
|
<div class="poster">
|
|
34
34
|
<TitlePoster {title} width={30} height={43} />
|
|
35
35
|
</div>
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import Modal from './Modal.svelte'
|
|
4
4
|
import Participant from './Participant.svelte'
|
|
5
5
|
import type { ParticipantData } from '$lib/types/participant'
|
|
6
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
7
|
+
import { track } from '$lib/tracking'
|
|
8
|
+
import { onMount } from 'svelte'
|
|
6
9
|
|
|
7
10
|
interface Props {
|
|
8
11
|
participant: ParticipantData
|
|
@@ -22,6 +25,14 @@
|
|
|
22
25
|
}, initialScrollPosition = 0 }: Props = $props()
|
|
23
26
|
|
|
24
27
|
let windowWidth = $state(0)
|
|
28
|
+
|
|
29
|
+
track(TrackingEvent.ParticipantModalView, null, { participant: participant.name })
|
|
30
|
+
|
|
31
|
+
onMount(() => {
|
|
32
|
+
const openTimestamp = Date.now()
|
|
33
|
+
|
|
34
|
+
return () => track(TrackingEvent.ParticipantModalClose, null, { participant: participant.name, time_spent: Date.now() - openTimestamp })
|
|
35
|
+
})
|
|
25
36
|
</script>
|
|
26
37
|
|
|
27
38
|
<svelte:window bind:innerWidth={windowWidth} />
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import type { PlaylinkData } from '$lib/types/playlink'
|
|
7
7
|
import type { TitleData } from '$lib/types/title'
|
|
8
8
|
import { heading } from '$lib/actions/heading'
|
|
9
|
+
import { campaignToPlaylink, getFirstAdOfType } from '$lib/api/ads'
|
|
9
10
|
import { getContext } from 'svelte'
|
|
10
11
|
import Playlink from './Playlink.svelte'
|
|
11
|
-
import { campaignToPlaylink, getFirstAdOfType } from '$lib/ads'
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
playlinks: PlaylinkData[]
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
|
+
import { track } from '$lib/tracking'
|
|
2
4
|
import type { TitleData } from '$lib/types/title'
|
|
3
5
|
import TitlesRail from './TitlesRail.svelte'
|
|
4
6
|
|
|
7
|
+
interface Props {
|
|
8
|
+
title: TitleData
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { title }: Props = $props()
|
|
12
|
+
|
|
5
13
|
const titles = fetchTitles()
|
|
6
14
|
|
|
7
15
|
async function fetchTitles(): Promise<TitleData[]> {
|
|
@@ -13,4 +21,4 @@
|
|
|
13
21
|
}
|
|
14
22
|
</script>
|
|
15
23
|
|
|
16
|
-
<TitlesRail {titles} heading="Similar movies & shows" />
|
|
24
|
+
<TitlesRail {titles} heading="Similar movies & shows" onclick={(targetTitle) => track(TrackingEvent.SimilarTitleClick, targetTitle, { title_source: title.original_title })} />
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import TitlePoster from '../TitlePoster.svelte'
|
|
3
3
|
import Rail from './Rail.svelte'
|
|
4
|
-
import { playPilotBaseUrl } from '$lib/constants'
|
|
5
4
|
import type { TitleData } from '$lib/types/title'
|
|
6
5
|
import { openModal } from '$lib/modal'
|
|
6
|
+
import { titleUrl } from '$lib/routes'
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
titles: Promise<TitleData[]> | TitleData[]
|
|
10
|
-
heading?: string
|
|
10
|
+
heading?: string,
|
|
11
|
+
onclick?: (title: TitleData) => void
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const { titles, heading = '' }: Props = $props()
|
|
14
|
+
const { titles, heading = '', onclick = () => null }: Props = $props()
|
|
15
|
+
|
|
16
|
+
function openTitle(event: MouseEvent, title: TitleData): void {
|
|
17
|
+
openModal({ event, data: title })
|
|
18
|
+
onclick(title)
|
|
19
|
+
}
|
|
14
20
|
</script>
|
|
15
21
|
|
|
16
22
|
<div class="titles">
|
|
@@ -28,14 +34,12 @@
|
|
|
28
34
|
{/each}
|
|
29
35
|
{:then titles}
|
|
30
36
|
{#each titles as title}
|
|
31
|
-
{@const href = `${playPilotBaseUrl}/${title.type}/${title.slug}`}
|
|
32
|
-
|
|
33
37
|
<div class="title" data-testid="title">
|
|
34
|
-
<a class="poster" {
|
|
38
|
+
<a class="poster" href={titleUrl(title)} onclick={(event) => openTitle(event, title)}>
|
|
35
39
|
<TitlePoster {title} width={96} height={144} />
|
|
36
40
|
</a>
|
|
37
41
|
|
|
38
|
-
<a {
|
|
42
|
+
<a href={titleUrl(title)} class="heading" onclick={(event) => openTitle(event, title)}>
|
|
39
43
|
{title.title}
|
|
40
44
|
</a>
|
|
41
45
|
</div>
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
3
3
|
import { track } from '$lib/tracking'
|
|
4
4
|
import type { TitleData } from '$lib/types/title'
|
|
5
|
+
import { getFirstAdOfType } from '$lib/api/ads'
|
|
5
6
|
import { onMount } from 'svelte'
|
|
6
7
|
import Modal from './Modal.svelte'
|
|
7
8
|
import Title from './Title.svelte'
|
|
8
9
|
import TopScroll from './Ads/TopScroll.svelte'
|
|
9
10
|
import Display from './Ads/Display.svelte'
|
|
10
|
-
import { getFirstAdOfType } from '$lib/ads'
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
title: TitleData
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
const topScrollAd = getFirstAdOfType('top_scroll')
|
|
20
20
|
const displayAd = getFirstAdOfType('card')
|
|
21
21
|
|
|
22
|
-
track(TrackingEvent.TitleModalView, title)
|
|
23
|
-
|
|
24
22
|
let hasTrackedScrolling = false
|
|
25
23
|
|
|
24
|
+
track(TrackingEvent.TitleModalView, title)
|
|
25
|
+
|
|
26
26
|
onMount(() => {
|
|
27
27
|
const openTimestamp = Date.now()
|
|
28
28
|
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
+
import { getFirstAdOfType } from '$lib/api/ads'
|
|
5
5
|
import type { TitleData } from '$lib/types/title'
|
|
6
6
|
import { onMount } from 'svelte'
|
|
7
7
|
import Popover from './Popover.svelte'
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'
|
|
2
|
-
import { fakeFetch } from '../helpers'
|
|
3
2
|
|
|
4
|
-
import { fetchAds, getFirstAdOfType } from '$lib/ads'
|
|
3
|
+
import { fetchAds, getFirstAdOfType } from '$lib/api/ads'
|
|
5
4
|
import { hasConsentedTo } from '$lib/consent'
|
|
6
5
|
import { track } from '$lib/tracking'
|
|
7
6
|
import { TrackingEvent } from '$lib/enums/TrackingEvent'
|
|
7
|
+
import { api } from '$lib/api/api'
|
|
8
|
+
|
|
9
|
+
vi.mock('$lib/api/api', () => ({
|
|
10
|
+
api: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
8
13
|
|
|
9
14
|
vi.mock('$lib/tracking', () => ({
|
|
10
15
|
track: vi.fn(),
|
|
@@ -14,9 +19,10 @@ vi.mock('$lib/consent', () => ({
|
|
|
14
19
|
hasConsentedTo: vi.fn(() => true),
|
|
15
20
|
}))
|
|
16
21
|
|
|
17
|
-
describe('$lib/ads', () => {
|
|
22
|
+
describe('$lib/api/ads', () => {
|
|
18
23
|
afterEach(() => {
|
|
19
24
|
vi.resetAllMocks()
|
|
25
|
+
|
|
20
26
|
// @ts-ignore
|
|
21
27
|
window.PlayPilotLinkInjections = null
|
|
22
28
|
|
|
@@ -29,35 +35,32 @@ describe('$lib/ads', () => {
|
|
|
29
35
|
window.PlayPilotLinkInjections = { token: 'a' }
|
|
30
36
|
})
|
|
31
37
|
|
|
32
|
-
it('Should call
|
|
33
|
-
|
|
38
|
+
it('Should call api with given url', async () => {
|
|
39
|
+
vi.mocked(api).mockResolvedValueOnce('Some response')
|
|
34
40
|
|
|
35
41
|
const response = await fetchAds()
|
|
36
42
|
|
|
37
43
|
expect(response).toBe('Some response')
|
|
38
|
-
expect(
|
|
39
|
-
expect.stringContaining('api-token'),
|
|
40
|
-
expect.objectContaining({}),
|
|
41
|
-
)
|
|
44
|
+
expect(api).toHaveBeenCalledWith(expect.stringContaining('api-token'))
|
|
42
45
|
})
|
|
43
46
|
|
|
44
|
-
it('Should not call
|
|
47
|
+
it('Should not call api if no api token is present', async () => {
|
|
45
48
|
// @ts-ignore
|
|
46
49
|
window.PlayPilotLinkInjections = { token: '' }
|
|
47
50
|
|
|
48
51
|
await expect(async () => await fetchAds()).rejects.toThrowError('No token was provided')
|
|
49
|
-
expect(
|
|
52
|
+
expect(api).not.toHaveBeenCalled()
|
|
50
53
|
})
|
|
51
54
|
|
|
52
|
-
it('Should not call
|
|
55
|
+
it('Should not call api if user did not consent to ads', async () => {
|
|
53
56
|
vi.mocked(hasConsentedTo).mockImplementation(() => false)
|
|
54
57
|
|
|
55
58
|
await fetchAds()
|
|
56
|
-
expect(
|
|
59
|
+
expect(api).not.toHaveBeenCalled()
|
|
57
60
|
})
|
|
58
61
|
|
|
59
62
|
it('Should fire track event when ads failed to fetch', async () => {
|
|
60
|
-
|
|
63
|
+
vi.mocked(api).mockRejectedValueOnce({ ok: false, status: 505 })
|
|
61
64
|
|
|
62
65
|
await expect(async () => await fetchAds()).rejects.toThrowError()
|
|
63
66
|
expect(track).toHaveBeenCalledWith(TrackingEvent.AdsFetchFailed, null, { status: 505 })
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { fakeFetch } from '../../helpers'
|
|
3
|
+
import { api } from '$lib/api/api'
|
|
4
|
+
|
|
5
|
+
describe('$lib/api/api', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
fakeFetch()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('$lib/api', () => {
|
|
11
|
+
describe('api', () => {
|
|
12
|
+
it('Should fetch and return result', async () => {
|
|
13
|
+
fakeFetch({ response: 'Some response' })
|
|
14
|
+
|
|
15
|
+
const result = await api('/some-path')
|
|
16
|
+
|
|
17
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
18
|
+
expect.stringContaining('/some-path'),
|
|
19
|
+
expect.objectContaining({
|
|
20
|
+
headers: expect.any(Object),
|
|
21
|
+
body: null,
|
|
22
|
+
method: 'GET',
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
25
|
+
expect(result).toBe('Some response')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('Should fetch with POST and given stringified body', async () => {
|
|
29
|
+
fakeFetch({ response: 'Some response' })
|
|
30
|
+
|
|
31
|
+
await api('/some-path', { method: 'POST', body: { key: 'some value' } })
|
|
32
|
+
|
|
33
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
34
|
+
expect.stringContaining('/some-path'),
|
|
35
|
+
expect.objectContaining({
|
|
36
|
+
method: 'POST',
|
|
37
|
+
body: '{"key":"some value"}',
|
|
38
|
+
}),
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('Should return error when response was not ok', async () => {
|
|
43
|
+
fakeFetch({ ok: false })
|
|
44
|
+
|
|
45
|
+
await expect(async () => await api('/some-path')).rejects.toThrow()
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
})
|