@playpilot/tpi 3.4.0 → 3.6.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 +8 -8
- package/package.json +1 -1
- package/src/lib/linkInjection.ts +29 -25
- package/src/lib/tracking.ts +5 -7
- package/src/lib/types/config.d.ts +2 -0
- package/src/lib/types/global.d.ts +2 -0
- package/src/main.js +5 -1
- package/src/routes/+page.svelte +26 -4
- package/src/routes/components/AfterArticlePlaylinks.svelte +4 -1
- package/src/routes/components/TitlePopover.svelte +38 -4
- package/src/tests/lib/linkInjection.test.js +7 -25
- package/src/tests/lib/tracking.test.js +9 -11
- package/src/tests/routes/+page.test.js +75 -24
- package/src/tests/routes/components/TitlePopover.test.js +2 -1
- package/src/lib/stores/organization.ts +0 -4
package/package.json
CHANGED
package/src/lib/linkInjection.ts
CHANGED
|
@@ -11,9 +11,8 @@ import { playFallbackViewTransition } from './viewTransition'
|
|
|
11
11
|
const keyDataAttribute = 'data-playpilot-injection-key'
|
|
12
12
|
const keySelector = `[${keyDataAttribute}]`
|
|
13
13
|
|
|
14
|
-
const activePopovers: Record<string, { injection: LinkInjection; component: object }> = {}
|
|
15
|
-
|
|
16
14
|
let currentlyHoveredInjection: EventTarget | null = null
|
|
15
|
+
let activePopoverInsertedComponent: object | null = null
|
|
17
16
|
let afterArticlePlaylinkInsertedComponent: object | null = null
|
|
18
17
|
let activeModalInsertedComponent: object | null = null
|
|
19
18
|
|
|
@@ -32,7 +31,7 @@ export function getLinkInjectionElements(parentElement: HTMLElement): HTMLElemen
|
|
|
32
31
|
if (validElements.includes(element)) continue
|
|
33
32
|
|
|
34
33
|
// Ignore links, buttons, and headers
|
|
35
|
-
if (/^(A|BUTTON|SCRIPT|NOSCRIPT|STYLE|IFRAME|FIGCAPTION|H[1-6])$/.test(element.tagName)) continue
|
|
34
|
+
if (/^(A|BUTTON|SCRIPT|NOSCRIPT|STYLE|IFRAME|FIGCAPTION|TIME|H[1-6])$/.test(element.tagName)) continue
|
|
36
35
|
|
|
37
36
|
// Check if this element has a direct text node
|
|
38
37
|
const hasTextNode = Array.from(element.childNodes).some(
|
|
@@ -71,12 +70,11 @@ export function getLinkInjectionElements(parentElement: HTMLElement): HTMLElemen
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
* Get the parent selector that will be used to find the link injections in.
|
|
74
|
+
* This selector is passed through the api config, or when the script is initialized.
|
|
75
|
+
* If no selector is passed a default is returned instead.
|
|
76
|
+
*/
|
|
78
77
|
export function getLinkInjectionsParentElement(): HTMLElement {
|
|
79
|
-
// @ts-ignore
|
|
80
78
|
const selector = window.PlayPilotLinkInjections?.selector
|
|
81
79
|
|
|
82
80
|
if (selector) {
|
|
@@ -241,11 +239,23 @@ function addLinkInjectionEventListeners(injections: LinkInjection[]): void {
|
|
|
241
239
|
event.preventDefault()
|
|
242
240
|
|
|
243
241
|
playFallbackViewTransition(() => {
|
|
244
|
-
destroyLinkPopover(
|
|
242
|
+
destroyLinkPopover(false)
|
|
245
243
|
openLinkModal(event, injection)
|
|
246
244
|
}, window.innerWidth >= 600 && !window.matchMedia("(pointer: coarse)").matches)
|
|
247
245
|
})
|
|
248
246
|
|
|
247
|
+
window.addEventListener('mousemove', (event) => {
|
|
248
|
+
if (!activePopoverInsertedComponent) return
|
|
249
|
+
|
|
250
|
+
const target = event.target as Element
|
|
251
|
+
|
|
252
|
+
// Mousemove is inside of popover or link that popover
|
|
253
|
+
if (target.hasAttribute('data-playpilot-title-popover') || target.closest('[data-playpilot-title-popover]') ||
|
|
254
|
+
target.hasAttribute(keyDataAttribute) || target.closest(keySelector)) return
|
|
255
|
+
|
|
256
|
+
destroyLinkPopover()
|
|
257
|
+
})
|
|
258
|
+
|
|
249
259
|
const createdInjectionElements = Array.from(document.querySelectorAll(keySelector)) as HTMLElement[]
|
|
250
260
|
|
|
251
261
|
// Open and close popover on mouseenter/mouseleave
|
|
@@ -257,7 +267,7 @@ function addLinkInjectionEventListeners(injections: LinkInjection[]): void {
|
|
|
257
267
|
|
|
258
268
|
// @ts-ignore
|
|
259
269
|
injectionElement.addEventListener('mouseenter', (event) => openLinkPopover(event, injection))
|
|
260
|
-
injectionElement.addEventListener('mouseleave', () =>
|
|
270
|
+
injectionElement.addEventListener('mouseleave', () => currentlyHoveredInjection = null)
|
|
261
271
|
})
|
|
262
272
|
}
|
|
263
273
|
|
|
@@ -291,6 +301,7 @@ function destroyLinkModal(outro: boolean = true): void {
|
|
|
291
301
|
function openLinkPopover(event: MouseEvent, injection: LinkInjection): void {
|
|
292
302
|
// Skip touch devices
|
|
293
303
|
if (window.matchMedia('(pointer: coarse)').matches) return
|
|
304
|
+
if (activePopoverInsertedComponent) destroyLinkPopover()
|
|
294
305
|
|
|
295
306
|
const target = event.currentTarget as Element
|
|
296
307
|
currentlyHoveredInjection = target
|
|
@@ -298,26 +309,22 @@ function openLinkPopover(event: MouseEvent, injection: LinkInjection): void {
|
|
|
298
309
|
// Only show if the link is hovered for more than 100ms. This is to prevent the popover from showing
|
|
299
310
|
// when a user just happens to mouseover as they are moving their mouse about.
|
|
300
311
|
setTimeout(() => {
|
|
301
|
-
if (activePopovers[injection.key]) return // Popover for this link was already open and was called again... for some reason
|
|
302
312
|
if (currentlyHoveredInjection !== target) return // User is no longer hovering this link
|
|
303
313
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
activePopovers[injection.key] = { injection, component: popover }
|
|
314
|
+
activePopoverInsertedComponent = mount(TitlePopover, { target: document.body, props: { event, title: injection.title_details! } })
|
|
307
315
|
}, 100)
|
|
308
316
|
}
|
|
309
317
|
|
|
310
318
|
/**
|
|
311
319
|
* Unmount the popover, removing it from the dom
|
|
312
320
|
*/
|
|
313
|
-
function destroyLinkPopover(
|
|
314
|
-
|
|
315
|
-
currentlyHoveredInjection = null
|
|
321
|
+
function destroyLinkPopover(outro: boolean = true) {
|
|
322
|
+
if (!activePopoverInsertedComponent) return
|
|
316
323
|
|
|
317
|
-
|
|
324
|
+
unmount(activePopoverInsertedComponent, { outro })
|
|
318
325
|
|
|
319
|
-
|
|
320
|
-
|
|
326
|
+
currentlyHoveredInjection = null
|
|
327
|
+
activePopoverInsertedComponent = null
|
|
321
328
|
}
|
|
322
329
|
|
|
323
330
|
/**
|
|
@@ -352,15 +359,12 @@ function clearAfterArticlePlaylinks(): void {
|
|
|
352
359
|
* Clear link injections from the page
|
|
353
360
|
*/
|
|
354
361
|
export function clearLinkInjections(): void {
|
|
355
|
-
Object.values(activePopovers).forEach(popover => destroyLinkPopover(popover.injection))
|
|
356
|
-
|
|
357
362
|
const elements = document.querySelectorAll(keySelector)
|
|
358
363
|
elements.forEach((element) => element.outerHTML = element.textContent || '')
|
|
359
364
|
|
|
360
|
-
Object.values(activePopovers).forEach(({ injection }) => destroyLinkPopover(injection, false))
|
|
361
|
-
|
|
362
365
|
clearAfterArticlePlaylinks()
|
|
363
|
-
destroyLinkModal()
|
|
366
|
+
destroyLinkModal(false)
|
|
367
|
+
destroyLinkPopover(false)
|
|
364
368
|
}
|
|
365
369
|
|
|
366
370
|
/**
|
package/src/lib/tracking.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { get } from "svelte/store"
|
|
2
|
-
import { currentDomainSid, currentOrganizationSid } from "./stores/organization"
|
|
3
1
|
import type { TitleData } from "./types/title"
|
|
4
2
|
import { getFullUrlPath } from "./url"
|
|
5
3
|
|
|
@@ -24,8 +22,8 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
payload.url = getFullUrlPath()
|
|
27
|
-
payload.organization_sid =
|
|
28
|
-
payload.domain_sid =
|
|
25
|
+
payload.organization_sid = window.PlayPilotLinkInjections?.organization_sid
|
|
26
|
+
payload.domain_sid = window.PlayPilotLinkInjections?.domain_sid
|
|
29
27
|
|
|
30
28
|
fetch(baseUrl, {
|
|
31
29
|
headers,
|
|
@@ -39,10 +37,10 @@ export async function track(event: string, title: TitleData | null = null, paylo
|
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
/**
|
|
42
|
-
* Set the sid of the organization and domain to
|
|
40
|
+
* Set the sid of the organization and domain to the window object.
|
|
43
41
|
* These are saved for tracking purposes and currently serve no other function.
|
|
44
42
|
*/
|
|
45
43
|
export function setTrackingSids({ organizationSid = null, domainSid = null }: { organizationSid?: string | null, domainSid?: string | null }): void {
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
window.PlayPilotLinkInjections.organization_sid = organizationSid || null
|
|
45
|
+
window.PlayPilotLinkInjections.domain_sid = domainSid || null
|
|
48
46
|
}
|
package/src/main.js
CHANGED
|
@@ -11,9 +11,11 @@ window.PlayPilotLinkInjections = {
|
|
|
11
11
|
after_article_selector: '',
|
|
12
12
|
after_article_insert_position: '',
|
|
13
13
|
language: null,
|
|
14
|
+
organization_sid: null,
|
|
15
|
+
domain_sid: null,
|
|
14
16
|
app: null,
|
|
15
17
|
|
|
16
|
-
initialize(config = { token: '', selector: '', after_article_selector: '', language: null, editorial_token: '' }) {
|
|
18
|
+
initialize(config = { token: '', selector: '', after_article_selector: '', language: null, organization_sid: null, domain_sid: null, editorial_token: '' }) {
|
|
17
19
|
if (!config.token) {
|
|
18
20
|
console.error('An API token is required.')
|
|
19
21
|
return
|
|
@@ -25,6 +27,8 @@ window.PlayPilotLinkInjections = {
|
|
|
25
27
|
this.after_article_selector = config.after_article_selector
|
|
26
28
|
this.after_article_insert_position = config.after_article_insert_position
|
|
27
29
|
this.language = config.language
|
|
30
|
+
this.organization_sid = config.organization_sid
|
|
31
|
+
this.domain_sid = config.domain_sid
|
|
28
32
|
|
|
29
33
|
if (this.app) this.destroy()
|
|
30
34
|
|
package/src/routes/+page.svelte
CHANGED
|
@@ -11,10 +11,8 @@
|
|
|
11
11
|
import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
|
|
12
12
|
import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const htmlString = elements.map(p => p.outerHTML).join('')
|
|
17
|
-
|
|
14
|
+
let parentElement: HTMLElement | null = $state(null)
|
|
15
|
+
let elements: HTMLElement[] = $state([])
|
|
18
16
|
let response: LinkInjectionResponse | null = $state(null)
|
|
19
17
|
let isEditorialMode = $state(isEditorialModeEnabled())
|
|
20
18
|
let hasAuthToken = $state(!!getAuthToken())
|
|
@@ -24,6 +22,7 @@
|
|
|
24
22
|
|
|
25
23
|
// @ts-ignore It's ok if the response is empty
|
|
26
24
|
const { ai_injections: aiInjections = [], link_injections: manualInjections = [] } = $derived(response || {})
|
|
25
|
+
const htmlString = $derived(elements.map(p => p.outerHTML).join(''))
|
|
27
26
|
|
|
28
27
|
// Rerender link injections when linkInjections change. This is only relevant for editiorial mode.
|
|
29
28
|
$effect(() => {
|
|
@@ -52,6 +51,9 @@
|
|
|
52
51
|
|
|
53
52
|
// URL was marked as being excluded, we stop injections here unless we're in editorial mode.
|
|
54
53
|
if (!isEditorialMode && config?.exclude_urls_pattern && url.match(config.exclude_urls_pattern)) return
|
|
54
|
+
if (config?.custom_style) insertCustomStyle(config.custom_style || '')
|
|
55
|
+
|
|
56
|
+
setElements(config?.html_selector || '')
|
|
55
57
|
} catch(error) {
|
|
56
58
|
// We also return if the config did not get fetched properly, as we can't determine what should and should
|
|
57
59
|
// get injected without it.
|
|
@@ -116,12 +118,32 @@
|
|
|
116
118
|
})
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
// Set elements to be used by script, if a selector is passed from the config request we update
|
|
122
|
+
// the selector on the window object.
|
|
123
|
+
function setElements(configSelector: string) {
|
|
124
|
+
if (configSelector) window.PlayPilotLinkInjections.selector = configSelector
|
|
125
|
+
|
|
126
|
+
parentElement = getLinkInjectionsParentElement()
|
|
127
|
+
elements = getLinkInjectionElements(parentElement)
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
function openEditorialMode() {
|
|
120
131
|
isEditorialMode = true
|
|
121
132
|
setEditorialParamInUrl()
|
|
122
133
|
|
|
123
134
|
initialize()
|
|
124
135
|
}
|
|
136
|
+
|
|
137
|
+
function insertCustomStyle(customStyleString: string) {
|
|
138
|
+
const id = 'playpilot-custom-style'
|
|
139
|
+
const existingElement = document.getElementById(id)
|
|
140
|
+
const styleElement = existingElement || document.createElement('style')
|
|
141
|
+
|
|
142
|
+
styleElement.textContent = `${window.PlayPilotLinkInjections?.selector || 'body'}, .modal, .popover { ${customStyleString} }`
|
|
143
|
+
styleElement.id = id
|
|
144
|
+
|
|
145
|
+
if (!existingElement) document.body.appendChild(styleElement)
|
|
146
|
+
}
|
|
125
147
|
</script>
|
|
126
148
|
|
|
127
149
|
<div class="playpilot-link-injections">
|
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
{#if after_article_style === 'modal_button'}
|
|
38
38
|
"{title}" {t('Is Available To Stream')}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
41
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
42
|
+
<!-- This onclick is here to prevent the event from regular injections from firing, as those are attached to the window and fire on any key data attribute -->
|
|
43
|
+
<span onclick={event => event.stopPropagation()}>
|
|
41
44
|
<button onclick={(event) => openModal(event, title_details as TitleData, linkInjection)}>
|
|
42
45
|
{t('View Streaming Options')}
|
|
43
46
|
</button>
|
|
@@ -2,20 +2,54 @@
|
|
|
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 { onMount } from 'svelte'
|
|
5
6
|
import Popover from './Popover.svelte'
|
|
6
7
|
import Title from './Title.svelte'
|
|
7
8
|
|
|
8
9
|
interface Props {
|
|
10
|
+
event: MouseEvent
|
|
9
11
|
title: TitleData
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const { title }: Props = $props()
|
|
14
|
+
const { event, title }: Props = $props()
|
|
13
15
|
|
|
14
16
|
let maxHeight = $state(0)
|
|
17
|
+
let element: HTMLElement | null = $state(null)
|
|
15
18
|
|
|
16
19
|
track(TrackingEvent.TitlePopoverView, title)
|
|
20
|
+
|
|
21
|
+
onMount(setOffset)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* An element can be split up over multiple lines, giving it multiple ClientRects.
|
|
25
|
+
* We calculate the distannce to the closest one and use that for it's positioning.
|
|
26
|
+
* The height is used to acculately flip the Popover when it's too close to the
|
|
27
|
+
* bottom of the screen. This is done in the Popover component itself.
|
|
28
|
+
*/
|
|
29
|
+
function setOffset(): void {
|
|
30
|
+
const target = event.target as Element
|
|
31
|
+
|
|
32
|
+
if (!target?.getClientRects) return
|
|
33
|
+
|
|
34
|
+
const rects = Array.from(target.getClientRects())
|
|
35
|
+
const xOffsets = rects.map(rect => rect.x)
|
|
36
|
+
const differences = xOffsets.map(x => Math.abs(x - event.clientX))
|
|
37
|
+
const closestRect = rects[differences.indexOf(Math.min(...differences))]
|
|
38
|
+
|
|
39
|
+
element!.style.top = closestRect.top + 'px'
|
|
40
|
+
element!.style.left = closestRect.left + 'px'
|
|
41
|
+
element!.style.height = closestRect.height + 'px'
|
|
42
|
+
}
|
|
17
43
|
</script>
|
|
18
44
|
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
|
|
45
|
+
<div class="title-popover" bind:this={element} data-playpilot-title-popover>
|
|
46
|
+
<Popover bind:maxHeight>
|
|
47
|
+
<Title {title} small compact={!!maxHeight && maxHeight < 250} />
|
|
48
|
+
</Popover>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<style lang="scss">
|
|
52
|
+
.title-popover {
|
|
53
|
+
position: absolute;
|
|
54
|
+
}
|
|
55
|
+
</style>
|
|
@@ -385,7 +385,7 @@ describe('linkInjection.js', () => {
|
|
|
385
385
|
expect(results[1].failed_message).toBe('Given sentence was not found in the article.')
|
|
386
386
|
})
|
|
387
387
|
|
|
388
|
-
it('Should mount popover component when
|
|
388
|
+
it('Should mount popover component when element other than popover or link is hovered', async () => {
|
|
389
389
|
document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
|
|
390
390
|
|
|
391
391
|
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
@@ -401,7 +401,10 @@ describe('linkInjection.js', () => {
|
|
|
401
401
|
vi.advanceTimersByTime(200)
|
|
402
402
|
expect(mount).toHaveBeenCalled()
|
|
403
403
|
|
|
404
|
-
await fireEvent.
|
|
404
|
+
await fireEvent.mouseMove(link)
|
|
405
|
+
expect(unmount).not.toHaveBeenCalled()
|
|
406
|
+
|
|
407
|
+
await fireEvent.mouseMove(document.body)
|
|
405
408
|
expect(unmount).toHaveBeenCalled()
|
|
406
409
|
})
|
|
407
410
|
|
|
@@ -419,7 +422,7 @@ describe('linkInjection.js', () => {
|
|
|
419
422
|
|
|
420
423
|
await fireEvent.mouseEnter(link)
|
|
421
424
|
vi.advanceTimersByTime(200)
|
|
422
|
-
await fireEvent.
|
|
425
|
+
await fireEvent.mouseMove(document.body)
|
|
423
426
|
await fireEvent.mouseEnter(link)
|
|
424
427
|
vi.advanceTimersByTime(200)
|
|
425
428
|
|
|
@@ -446,28 +449,6 @@ describe('linkInjection.js', () => {
|
|
|
446
449
|
expect(mount).not.toHaveBeenCalled()
|
|
447
450
|
})
|
|
448
451
|
|
|
449
|
-
it('Should mount popover component only once when the same popover is already open', async () => {
|
|
450
|
-
document.body.innerHTML = '<p>This is a sentence with an injection.</p>'
|
|
451
|
-
|
|
452
|
-
const elements = Array.from(document.body.querySelectorAll('p'))
|
|
453
|
-
const injection = generateInjection('This is a sentence with an injection.', 'an injection')
|
|
454
|
-
|
|
455
|
-
injectLinksInDocument(elements, { aiInjections: [injection], manualInjections: [] })
|
|
456
|
-
|
|
457
|
-
const link = /** @type {HTMLAnchorElement} */ (document.querySelector('[data-playpilot-injection-key]'))
|
|
458
|
-
|
|
459
|
-
vi.useFakeTimers()
|
|
460
|
-
|
|
461
|
-
await fireEvent.mouseEnter(link)
|
|
462
|
-
vi.advanceTimersByTime(200)
|
|
463
|
-
expect(mount).toHaveBeenCalled()
|
|
464
|
-
|
|
465
|
-
await fireEvent.mouseEnter(link)
|
|
466
|
-
vi.advanceTimersByTime(200)
|
|
467
|
-
|
|
468
|
-
expect(mount).toHaveBeenCalledTimes(1)
|
|
469
|
-
})
|
|
470
|
-
|
|
471
452
|
it('Should inject links of the same phrase when multiple are present', () => {
|
|
472
453
|
document.body.innerHTML = '<p>This is a sentence with an injection and another injection</p>'
|
|
473
454
|
|
|
@@ -709,6 +690,7 @@ describe('linkInjection.js', () => {
|
|
|
709
690
|
<iframe>I am an iframe</iframe>
|
|
710
691
|
<noscript>I am noscript</noscript>
|
|
711
692
|
<figcaption>I am a figcaption</figcaption>
|
|
693
|
+
<time>I am time</time>
|
|
712
694
|
|
|
713
695
|
<div>
|
|
714
696
|
<a>I am another link</a>
|
|
@@ -2,16 +2,14 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { setTrackingSids, track } from '$lib/tracking'
|
|
4
4
|
import { title } from '$lib/fakeData'
|
|
5
|
-
import { get } from 'svelte/store'
|
|
6
|
-
import { currentDomainSid, currentOrganizationSid } from '$lib/stores/organization'
|
|
7
5
|
import { getFullUrlPath } from '$lib/url'
|
|
8
6
|
|
|
9
7
|
global.fetch = vi.fn()
|
|
10
8
|
|
|
11
9
|
describe('$lib/tracking', () => {
|
|
12
10
|
beforeEach(() => {
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
window.PlayPilotLinkInjections = {}
|
|
15
13
|
|
|
16
14
|
vi.resetAllMocks()
|
|
17
15
|
})
|
|
@@ -57,7 +55,7 @@ describe('$lib/tracking', () => {
|
|
|
57
55
|
})
|
|
58
56
|
|
|
59
57
|
it('Should include domain sid in request', () => {
|
|
60
|
-
|
|
58
|
+
window.PlayPilotLinkInjections.domain_sid = 'some-domain'
|
|
61
59
|
|
|
62
60
|
track('Some event')
|
|
63
61
|
|
|
@@ -70,7 +68,7 @@ describe('$lib/tracking', () => {
|
|
|
70
68
|
})
|
|
71
69
|
|
|
72
70
|
it('Should include organization sid in request', () => {
|
|
73
|
-
|
|
71
|
+
window.PlayPilotLinkInjections.organization_sid = 'some-organization'
|
|
74
72
|
|
|
75
73
|
track('Some event')
|
|
76
74
|
|
|
@@ -83,7 +81,7 @@ describe('$lib/tracking', () => {
|
|
|
83
81
|
})
|
|
84
82
|
|
|
85
83
|
it('Should include organization sid in request', () => {
|
|
86
|
-
|
|
84
|
+
window.PlayPilotLinkInjections.organization_sid = 'some-organization'
|
|
87
85
|
|
|
88
86
|
track('Some event')
|
|
89
87
|
|
|
@@ -132,15 +130,15 @@ describe('$lib/tracking', () => {
|
|
|
132
130
|
it('Should set stores equal to the given values', () => {
|
|
133
131
|
setTrackingSids({ domainSid: 'some-domain', organizationSid: 'some-organization' })
|
|
134
132
|
|
|
135
|
-
expect(
|
|
136
|
-
expect(
|
|
133
|
+
expect(window.PlayPilotLinkInjections.domain_sid).toBe('some-domain')
|
|
134
|
+
expect(window.PlayPilotLinkInjections.organization_sid).toBe('some-organization')
|
|
137
135
|
})
|
|
138
136
|
|
|
139
137
|
it('Should set stores to null if invalid values are given', () => {
|
|
140
138
|
setTrackingSids({ domainSid: '', organizationSid: undefined })
|
|
141
139
|
|
|
142
|
-
expect(
|
|
143
|
-
expect(
|
|
140
|
+
expect(window.PlayPilotLinkInjections.domain_sid).toBe(null)
|
|
141
|
+
expect(window.PlayPilotLinkInjections.organization_sid).toBe(null)
|
|
144
142
|
})
|
|
145
143
|
})
|
|
146
144
|
})
|
|
@@ -58,6 +58,7 @@ vi.mock('$lib/url', () => ({
|
|
|
58
58
|
|
|
59
59
|
describe('$routes/+page.svelte', () => {
|
|
60
60
|
beforeEach(() => {
|
|
61
|
+
document.body.innerHTML = ''
|
|
61
62
|
vi.resetAllMocks()
|
|
62
63
|
vi.mocked(injectLinksInDocument).mockReturnValue([])
|
|
63
64
|
})
|
|
@@ -327,40 +328,90 @@ describe('$routes/+page.svelte', () => {
|
|
|
327
328
|
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
328
329
|
})
|
|
329
330
|
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
describe('Config', () => {
|
|
332
|
+
describe('exclude_urls_pattern', () => {
|
|
332
333
|
|
|
333
|
-
|
|
334
|
+
it('Should not inject if config exclude_urls_pattern matches current url', async () => {
|
|
335
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ exclude_urls_pattern: '/t' })
|
|
334
336
|
|
|
335
|
-
|
|
336
|
-
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
337
|
-
})
|
|
337
|
+
render(page)
|
|
338
338
|
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
await waitFor(() => expect(fetchConfig).toHaveBeenCalled())
|
|
340
|
+
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
341
|
+
})
|
|
341
342
|
|
|
342
|
-
|
|
343
|
+
it('Should not inject if config exclude_urls_pattern matches current url with more complex regex pattern', async () => {
|
|
344
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ exclude_urls_pattern: '^/test$' })
|
|
343
345
|
|
|
344
|
-
|
|
345
|
-
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
346
|
-
})
|
|
346
|
+
render(page)
|
|
347
347
|
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
await waitFor(() => expect(fetchConfig).toHaveBeenCalled())
|
|
349
|
+
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
350
|
+
})
|
|
350
351
|
|
|
351
|
-
|
|
352
|
+
it('Should not inject if config returns an error', async () => {
|
|
353
|
+
vi.mocked(fetchConfig).mockRejectedValueOnce('null')
|
|
352
354
|
|
|
353
|
-
|
|
354
|
-
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
355
|
-
expect(track).toHaveBeenCalledWith(TrackingEvent.FetchingConfigFailed)
|
|
356
|
-
})
|
|
355
|
+
render(page)
|
|
357
356
|
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
await waitFor(() => expect(fetchConfig).toHaveBeenCalled())
|
|
358
|
+
expect(pollLinkInjections).not.toHaveBeenCalled()
|
|
359
|
+
expect(track).toHaveBeenCalledWith(TrackingEvent.FetchingConfigFailed)
|
|
360
|
+
})
|
|
360
361
|
|
|
361
|
-
|
|
362
|
+
it('Should inject if config exclude_urls_pattern does not match current url', async () => {
|
|
363
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ exclude_urls_pattern: '/something-else' })
|
|
364
|
+
|
|
365
|
+
render(page)
|
|
366
|
+
|
|
367
|
+
await waitFor(() => expect(fetchConfig).toHaveBeenCalled())
|
|
368
|
+
expect(pollLinkInjections).toHaveBeenCalled()
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
describe('html_selector', () => {
|
|
373
|
+
it('Should set window object selector to selector given in config', async () => {
|
|
374
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ html_selector: 'some-config-selector' })
|
|
362
375
|
|
|
363
|
-
|
|
364
|
-
|
|
376
|
+
render(page)
|
|
377
|
+
|
|
378
|
+
await waitFor(() => expect(fetchConfig).toHaveBeenCalled())
|
|
379
|
+
expect(window.PlayPilotLinkInjections.selector).toBe('some-config-selector')
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
describe('custom_style', () => {
|
|
384
|
+
it('Should insert custom html tag with returned value for given selector from config', async () => {
|
|
385
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ custom_style: 'some: style' })
|
|
386
|
+
|
|
387
|
+
// @ts-ignore
|
|
388
|
+
window.PlayPilotLinkInjections = { selector: '.some-element' }
|
|
389
|
+
|
|
390
|
+
render(page)
|
|
391
|
+
|
|
392
|
+
await waitFor(() => {
|
|
393
|
+
expect(document.querySelector('#playpilot-custom-style')?.textContent).toBe('.some-element, .modal, .popover { some: style }')
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('Should insert custom html tag with returned value for body when no selector is given', async () => {
|
|
398
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ custom_style: 'some: style' })
|
|
399
|
+
|
|
400
|
+
render(page)
|
|
401
|
+
|
|
402
|
+
await waitFor(() => {
|
|
403
|
+
expect(document.querySelector('#playpilot-custom-style')?.textContent).toBe('body, .modal, .popover { some: style }')
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('Should not insert custom html tag when config returns no value for custom_style', async () => {
|
|
408
|
+
vi.mocked(fetchConfig).mockResolvedValueOnce({ exclude_urls_pattern: 'value' })
|
|
409
|
+
|
|
410
|
+
render(page)
|
|
411
|
+
|
|
412
|
+
await new Promise(res => setTimeout(res, 100)) // Await all fetches
|
|
413
|
+
expect(document.querySelector('#playpilot-custom-style')).not.toBeTruthy()
|
|
414
|
+
})
|
|
415
|
+
})
|
|
365
416
|
})
|
|
366
417
|
})
|
|
@@ -16,7 +16,8 @@ describe('TitlePopover.svelte', () => {
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
it('Should call track function when rendered', () => {
|
|
19
|
-
|
|
19
|
+
const event = new MouseEvent('mouseenter')
|
|
20
|
+
render(TitlePopover, { event, title })
|
|
20
21
|
|
|
21
22
|
expect(track).toHaveBeenCalledWith(TrackingEvent.TitlePopoverView, title)
|
|
22
23
|
})
|