@playpilot/tpi 5.5.1 → 5.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/events.md CHANGED
@@ -64,5 +64,5 @@ Event | Action | Info | Payload
64
64
  ### Split Testing
65
65
  Event | Action | Info | Payload
66
66
  --- | --- | --- | ---
67
- `ali_split_test_view` | _Should be fired for any active split test_ | | `key`, `variant` (0 or 1)
68
- `ali_split_test_action` | _Should be fired for any assertion in split tests_ | | `key` (matches the key of `ali_split_test_view`), `variant` (0 or 1), `action`
67
+ `ali_split_test_view` | _Should be fired for any active split test_ | | `key`, `variant` (a whole number starting at 0)
68
+ `ali_split_test_action` | _Should be fired for any assertion in split tests_ | | `key` (matches the key of `ali_split_test_view`), `variant` (a whole number starting at 0), `action`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "5.5.1",
3
+ "version": "5.6.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -0,0 +1,10 @@
1
+ export const SplitTest = {
2
+ InitialTest: {
3
+ key: 'initial_test',
4
+ numberOfVariants: 2,
5
+ },
6
+ MultipleVariants: {
7
+ key: 'multiple_variants',
8
+ numberOfVariants: 4,
9
+ }
10
+ } as const
@@ -219,7 +219,9 @@ export function injectLinksInDocument(elements: HTMLElement[], injections: LinkI
219
219
  if (replacementIndex === -1) continue
220
220
  if (hasBeenReplaced) continue
221
221
 
222
- element.innerHTML = replaceStartingFrom(element.innerHTML, injection.title, injectionElement.outerHTML, replacementIndex)
222
+ if (!replaceIfSafeInjection(element.innerHTML, injection.title, element, injectionElement, replacementIndex)) {
223
+ failedMessages[injection.key] = 'Injection would have lead to broken HTML.'
224
+ }
223
225
  }
224
226
 
225
227
  addLinkInjectionEventListeners(validInjections)
@@ -267,6 +269,31 @@ function createLinkInjectionElement(injection: LinkInjection): { injectionElemen
267
269
  return { injectionElement, linkElement }
268
270
  }
269
271
 
272
+ /**
273
+ * In some coses injections lead to broken HTML. The reason for this varies and is hard to figure out. In any case, we
274
+ * should never serve broken HTML.
275
+ * Before replacing the HTML, we apply the the replacement to a dummy element. From here we check if the number of links
276
+ * in the HTML increased by one. If it didn't, something went wrong. What exactly is hard to say, we just know something
277
+ * didn't go as intended and we should not inject.
278
+ *
279
+ * No tests exists for this function because writing tests would require finding a scenario in which things break. If we
280
+ * knew when things break we'd fix it instead.
281
+ */
282
+ function replaceIfSafeInjection(originalHtml: string, phrase: string, sentenceElement: HTMLElement, injectionElement: HTMLElement, replacementIndex: number): boolean {
283
+ const dummyElement = document.createElement('div')
284
+ dummyElement.innerHTML = originalHtml
285
+
286
+ const startingNumberOfLinks = Array.from(dummyElement.querySelectorAll<HTMLAnchorElement>('a')).filter(a => a.innerText).length
287
+ dummyElement.innerHTML = replaceStartingFrom(originalHtml, phrase, injectionElement.outerHTML, replacementIndex)
288
+ const finalNumberOfLinks = Array.from(dummyElement.querySelectorAll<HTMLAnchorElement>('a')).filter(a => a.innerText).length
289
+
290
+ if (finalNumberOfLinks != startingNumberOfLinks + 1) return false
291
+
292
+ sentenceElement.innerHTML = dummyElement.innerHTML
293
+
294
+ return true
295
+ }
296
+
270
297
  /**
271
298
  * Add all used CSS variables to a data attribute. This data attribute is then used for selectors that for each
272
299
  * individual CSS variable. This is done this way so that CSS variables are only set when they are used.
@@ -399,7 +426,14 @@ function openLinkPopover(event: MouseEvent, injection: LinkInjection): void {
399
426
  * Unmount the popover, removing it from the dom
400
427
  */
401
428
  function destroyLinkPopover(outro: boolean = true) {
402
- if (!activePopoverInsertedComponent) return
429
+ if (!activePopoverInsertedComponent) {
430
+ // In some cases a popover lingers even if it should have been removed. This happens sometimes during
431
+ // HMR during development. In that case we remove the element straight from the dom.
432
+ // Doing this will prevent the outro animation from playing, but this being a fallback, that's ok.
433
+ document.querySelectorAll<HTMLElement>('[data-playpilot-title-popover]').forEach(element => element.remove())
434
+
435
+ return
436
+ }
403
437
 
404
438
  unmount(activePopoverInsertedComponent, { outro })
405
439
 
@@ -1,11 +1,16 @@
1
1
  import { TrackingEvent } from "./enums/TrackingEvent"
2
2
  import { track } from "./tracking"
3
3
 
4
+ type SplitTest = {
5
+ key: string
6
+ numberOfVariants: number
7
+ }
8
+
4
9
  /**
5
10
  * Each split test uses a different split test identifier so a user can be part of one, multiple, or none, tests.
6
11
  * The identifier is saved on the window object so that it is consistent across this session.
7
12
  */
8
- export function getSplitTestIdentifier(key: string): number {
13
+ export function getSplitTestIdentifier({ key }: SplitTest): number {
9
14
  const windowIdentifier = window.PlayPilotLinkInjections?.split_test_identifiers?.[key]
10
15
  if (windowIdentifier) return windowIdentifier
11
16
 
@@ -17,24 +22,24 @@ export function getSplitTestIdentifier(key: string): number {
17
22
  return randomIdentifier
18
23
  }
19
24
 
20
- /**
21
- * Split tests are either on or off with a 50/50 split. That's all we need for now.
22
- */
23
- export function isInSplitTest(key: string): boolean {
24
- const identifier = getSplitTestIdentifier(key)
25
- const isInSplitTest = identifier > 0.5
25
+ export function getSplitTestVariantIndex(test: SplitTest): number {
26
+ const identifier = getSplitTestIdentifier(test)
27
+
28
+ return Math.floor(identifier * test.numberOfVariants)
29
+ }
26
30
 
27
- return isInSplitTest
31
+ export function isInSplitTestVariant(test: SplitTest, variant = 1): boolean {
32
+ return getSplitTestVariantIndex(test) === variant
28
33
  }
29
34
 
30
- export function trackSplitTestView(key: string): void {
31
- const variant = isInSplitTest(key) ? 1 : 0
35
+ export function trackSplitTestView(test: SplitTest): void {
36
+ const variant = getSplitTestVariantIndex(test)
32
37
 
33
- track(TrackingEvent.SplitTestView, null, { key, variant })
38
+ track(TrackingEvent.SplitTestView, null, { key: test.key, variant })
34
39
  }
35
40
 
36
- export function trackSplitTestAction(key: string, action: string): void {
37
- const variant = isInSplitTest(key) ? 1 : 0
41
+ export function trackSplitTestAction(test: SplitTest, action: string): void {
42
+ const variant = getSplitTestVariantIndex(test) ? 1 : 0
38
43
 
39
- track(TrackingEvent.SplitTestAction, null, { key, variant, action })
44
+ track(TrackingEvent.SplitTestAction, null, { key: test.key, variant, action })
40
45
  }
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { browser } from '$app/environment'
3
3
  import { page } from '$app/state'
4
+ import { SplitTest } from '$lib/enums/SplitTest'
5
+ import { getSplitTestVariantIndex } from '$lib/splitTest'
4
6
 
5
7
  /**
6
8
  * !! NOTE: This layout file is for development purposes only and will not be compiled with the final script.
@@ -75,6 +77,8 @@
75
77
 
76
78
  {#if browser}
77
79
  {@render children()}
80
+
81
+ Viewing with split test index: {getSplitTestVariantIndex(SplitTest.MultipleVariants)}
78
82
  {/if}
79
83
  {/key}
80
84
  {/if}
@@ -8,6 +8,7 @@
8
8
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
9
9
  import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/auth'
10
10
  import { trackSplitTestView } from '$lib/splitTest'
11
+ import { SplitTest } from '$lib/enums/SplitTest'
11
12
  import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
12
13
  import Editor from './components/Editorial/Editor.svelte'
13
14
  import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
@@ -42,7 +43,7 @@
42
43
  await initialize()
43
44
  track(TrackingEvent.ArticlePageView)
44
45
 
45
- trackSplitTestView('initial_test')
46
+ trackSplitTestView(SplitTest.MultipleVariants)
46
47
  })()
47
48
 
48
49
  return () => clearLinkInjections()
@@ -0,0 +1,105 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ target?: HTMLElement
4
+ threshold?: number
5
+ isDragging?: boolean
6
+ passedThreshold?: boolean
7
+ onpassed: () => void
8
+ }
9
+
10
+ let {
11
+ target,
12
+ threshold = 100,
13
+ isDragging = $bindable(false),
14
+ passedThreshold = $bindable(false),
15
+ onpassed = () => null,
16
+ }: Props = $props()
17
+
18
+ let movementStartY = $state(0)
19
+ let movementCurrentY = $state(0)
20
+ let handleElement: HTMLElement | null = $state(null)
21
+ let hasBeenDragged = $state(false) // This is used to prevent the element from transitioning into view on mount
22
+
23
+ const difference = $derived(Math.max(movementCurrentY - movementStartY, 0))
24
+
25
+ $effect(() => {
26
+ passedThreshold = difference > threshold
27
+ })
28
+
29
+ export function start(event: TouchEvent): void {
30
+ if (!event) return
31
+ if (!target) return
32
+
33
+ isDragging = true
34
+ hasBeenDragged = true
35
+
36
+ movementStartY = event.touches[0].pageY
37
+ target.style.transitionDuration = '0ms'
38
+ }
39
+
40
+ export function move(event: TouchEvent): void {
41
+ if (!event) return
42
+ if (!target) return
43
+ if (!isDragging) return
44
+
45
+ movementCurrentY = event.touches[0].pageY
46
+
47
+ target.style.transform = `translateY(${difference}px)`
48
+ handleElement!.style.transform = `translateY(${difference}px)`
49
+ }
50
+
51
+ export function end(): void {
52
+ if (!target) return
53
+ if (!isDragging) return
54
+
55
+ isDragging = false
56
+
57
+ if (passedThreshold) {
58
+ onpassed()
59
+ } else {
60
+ target.removeAttribute('style')
61
+ handleElement!.removeAttribute('style')
62
+ }
63
+
64
+ movementStartY = 0
65
+ movementCurrentY = 0
66
+ }
67
+ </script>
68
+
69
+ <svelte:window ontouchmove={move} ontouchend={end} />
70
+
71
+ <div class="handle" class:dragging={isDragging} class:dragged={hasBeenDragged} bind:this={handleElement} ontouchstart={start}></div>
72
+
73
+ <style lang="scss">
74
+ .handle {
75
+ position: relative;
76
+ width: 100%;
77
+ height: var(--playpilot-drag-handle-height, margin(3));
78
+
79
+ &::before {
80
+ content: '';
81
+ display: block;
82
+ position: absolute;
83
+ top: calc(50% - (var(--playpilot-drag-handle-bar-height, margin(0.25)) / 2));
84
+ width: 100%;
85
+ height: var(--playpilot-drag-handle-bar-height, margin(0.25));
86
+ border-radius: margin(1);
87
+ background: var(--playpilot-drag-handle-color, currentColor);
88
+ opacity: 0.65;
89
+ transition: opacity 100ms, scale 100ms;
90
+ }
91
+
92
+ &.dragged {
93
+ transition: transform 200ms;
94
+ }
95
+
96
+ &.dragging {
97
+ transition: none;
98
+
99
+ &::before {
100
+ scale: 1.5 1;
101
+ opacity: 1;
102
+ }
103
+ }
104
+ }
105
+ </style>
@@ -2,6 +2,7 @@
2
2
  import { fade, fly, scale, type TransitionConfig } from 'svelte/transition'
3
3
  import IconClose from './Icons/IconClose.svelte'
4
4
  import RoundButton from './RoundButton.svelte'
5
+ import DragHandle from './DragHandle.svelte'
5
6
  import { onMount, setContext, type Snippet } from 'svelte'
6
7
  import { prefersReducedMotion } from 'svelte/motion'
7
8
 
@@ -13,6 +14,15 @@
13
14
 
14
15
  const { children, onclose = () => null, onscroll = () => null }: Props = $props()
15
16
 
17
+ let windowWidth = $state(0)
18
+ let dialogElement: HTMLElement | null = $state(null)
19
+ let dragHandleOffset: number = $state(0)
20
+
21
+ const isMobile = $derived(windowWidth < 600)
22
+
23
+ $effect(() => { if (windowWidth) dragHandleOffset = dialogElement?.offsetTop || 0 })
24
+ $effect(() => { setTimeout(() => dragHandleOffset = dialogElement?.offsetTop || 0) }) // Set after the dialog has shown to get the proper height
25
+
16
26
  setContext('scope', 'modal')
17
27
 
18
28
  onMount(() => {
@@ -25,17 +35,21 @@
25
35
  function scaleOrFly(node: Element): TransitionConfig {
26
36
  if (prefersReducedMotion.current) return fade(node, { duration: 0 })
27
37
 
28
- const shouldFly = window.innerWidth < 600
29
-
30
- if (shouldFly) return fly(node, { duration: 250, y: window.innerHeight })
38
+ if (isMobile) return fly(node, { duration: 250, y: window.innerHeight })
31
39
  return scale(node, { duration: 150, start: 0.85 })
32
40
  }
33
41
  </script>
34
42
 
35
- <svelte:window on:keydown={({ key }) => { if (key === 'Escape') onclose() }} />
43
+ <svelte:window onkeydown={({ key }) => { if (key === 'Escape') onclose() }} bind:innerWidth={windowWidth} />
36
44
 
37
45
  <div class="modal" transition:fade|global={{ duration: 150 }}>
38
- <div class="dialog" {onscroll} role="dialog" aria-labelledby="title" transition:scaleOrFly|global data-view-transition-new>
46
+ {#if isMobile}
47
+ <div class="drag-handle" style:top="{dragHandleOffset}px" transition:scaleOrFly|global>
48
+ <DragHandle target={dialogElement!} onpassed={() => onclose()} />
49
+ </div>
50
+ {/if}
51
+
52
+ <div class="dialog" {onscroll} bind:this={dialogElement} role="dialog" aria-labelledby="title" transition:scaleOrFly|global data-view-transition-new>
39
53
  <div class="close">
40
54
  <RoundButton onclick={() => onclose()}>
41
55
  <IconClose />
@@ -80,6 +94,7 @@
80
94
  margin-top: auto;
81
95
  border-radius: var(--playpilot-detail-border-radius, margin(1) margin(1) 0 0);
82
96
  background: var(--playpilot-detail-background, var(--playpilot-light));
97
+ transition: transform 200ms;
83
98
 
84
99
  @media (min-width: 600px) {
85
100
  margin-top: 0;
@@ -88,7 +103,6 @@
88
103
  }
89
104
  }
90
105
 
91
-
92
106
  .backdrop {
93
107
  z-index: 0;
94
108
  position: absolute;
@@ -98,6 +112,15 @@
98
112
  left: 0;
99
113
  }
100
114
 
115
+ .drag-handle {
116
+ z-index: 5;
117
+ position: absolute;
118
+ left: 50%;
119
+ transform: translateY(margin(-0.5)) translateX(-50%);
120
+ width: margin(7);
121
+ color: var(--playpilot-text-color);
122
+ }
123
+
101
124
  .close {
102
125
  --playpilot-button-background: var(--playpilot-modal-close-button-background, var(--playpilot-content));
103
126
  --playpilot-button-shadow: var(--playpilot-modal-close-button-shadow, var(--playpilot-shadow));
@@ -2,7 +2,6 @@
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 { trackSplitTestAction } from '$lib/splitTest'
6
5
  import { onMount } from 'svelte'
7
6
  import Modal from './Modal.svelte'
8
7
  import Title from './Title.svelte'
@@ -21,8 +20,6 @@
21
20
  onMount(() => {
22
21
  const openTimestamp = Date.now()
23
22
 
24
- trackSplitTestAction('initial_test', 'modal')
25
-
26
23
  return () => track(TrackingEvent.TitleModalClose, title, { time_spent: Date.now() - openTimestamp })
27
24
  })
28
25
 
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, afterEach } from 'vitest'
2
- import { getSplitTestIdentifier, isInSplitTest, trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
2
+ import { getSplitTestIdentifier, getSplitTestVariantIndex, isInSplitTestVariant, trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
3
3
  import { track } from '$lib/tracking'
4
4
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
5
5
 
@@ -8,6 +8,11 @@ vi.mock('$lib/tracking', () => ({
8
8
  }))
9
9
 
10
10
  describe('$lib/splitTest', () => {
11
+ const splitTest = {
12
+ key: 'Some key',
13
+ numberOfVariants: 2,
14
+ }
15
+
11
16
  describe('getSplitTestIdentifier', () => {
12
17
  afterEach(() => {
13
18
  // @ts-ignore
@@ -20,11 +25,11 @@ describe('$lib/splitTest', () => {
20
25
  // @ts-ignore
21
26
  window.PlayPilotLinkInjections = {}
22
27
 
23
- const value = getSplitTestIdentifier('Some key')
28
+ const value = getSplitTestIdentifier(splitTest)
24
29
 
25
- expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({ 'Some key': value })
30
+ expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({ [splitTest.key]: value })
26
31
  // @ts-ignore
27
- expect(value).toBe(window.PlayPilotLinkInjections.split_test_identifiers['Some key'])
32
+ expect(value).toBe(window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key])
28
33
  })
29
34
 
30
35
  it('Should return value as stored in the window object', () => {
@@ -32,46 +37,69 @@ describe('$lib/splitTest', () => {
32
37
  window.PlayPilotLinkInjections.split_test_identifiers = {}
33
38
  window.PlayPilotLinkInjections.split_test_identifiers['Some key'] = 0.5
34
39
 
35
- expect(getSplitTestIdentifier('Some key')).toBe(0.5)
40
+ expect(getSplitTestIdentifier(splitTest)).toBe(0.5)
36
41
  })
37
42
 
38
43
  it('Should set separate value for different keys', () => {
39
44
  // @ts-ignore
40
45
  window.PlayPilotLinkInjections.split_test_identifiers = {}
41
46
 
42
- getSplitTestIdentifier('Some key')
43
- getSplitTestIdentifier('Some other key')
47
+ getSplitTestIdentifier(splitTest)
48
+ getSplitTestIdentifier({ key: 'Some other key', numberOfVariants: 2 })
44
49
 
45
50
  expect(window.PlayPilotLinkInjections.split_test_identifiers).toEqual({
46
- 'Some key': expect.any(Number),
51
+ [splitTest.key]: expect.any(Number),
47
52
  'Some other key': expect.any(Number),
48
53
  })
49
54
  })
50
55
  })
51
56
 
52
- describe('isInSplitTest', () => {
57
+ describe('isInSplitTestVariant', () => {
53
58
  it('Should return true if stored value is higher than 0.5', () => {
54
59
  // @ts-ignore
55
60
  window.PlayPilotLinkInjections.split_test_identifiers = {}
56
- window.PlayPilotLinkInjections.split_test_identifiers['Some key'] = 0.75
61
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.75
57
62
 
58
- expect(isInSplitTest('Some key')).toBe(true)
63
+ expect(isInSplitTestVariant(splitTest)).toBe(true)
59
64
  })
60
65
 
61
66
  it('Should return false if stored value is lower than 0.5', () => {
62
67
  // @ts-ignore
63
68
  window.PlayPilotLinkInjections.split_test_identifiers = {}
64
- window.PlayPilotLinkInjections.split_test_identifiers['Some key'] = 0.25
69
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.25
70
+
71
+ expect(isInSplitTestVariant(splitTest)).toBe(false)
72
+ })
73
+
74
+ it('Should return correct values when numberOfVariants is given based on the stored value', () => {
75
+ const multiple = { key: 'multiple', numberOfVariants: 3 }
76
+
77
+ // @ts-ignore
78
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
79
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.25
80
+
81
+
82
+ expect(isInSplitTestVariant(multiple, 0)).toBe(true)
83
+ expect(isInSplitTestVariant(multiple, 1)).toBe(false)
84
+ expect(isInSplitTestVariant(multiple, 2)).toBe(false)
65
85
 
66
- expect(isInSplitTest('Some key')).toBe(false)
86
+ window.PlayPilotLinkInjections.split_test_identifiers['multiple'] = 0.5
87
+ expect(isInSplitTestVariant(multiple, 0)).toBe(false)
88
+ expect(isInSplitTestVariant(multiple, 1)).toBe(true)
89
+ expect(isInSplitTestVariant(multiple, 2)).toBe(false)
90
+
91
+ window.PlayPilotLinkInjections.split_test_identifiers['multiple'] = 0.75
92
+ expect(isInSplitTestVariant(multiple, 0)).toBe(false)
93
+ expect(isInSplitTestVariant(multiple, 1)).toBe(false)
94
+ expect(isInSplitTestVariant(multiple, 2)).toBe(true)
67
95
  })
68
96
  })
69
97
 
70
98
  describe('trackSplitTestView', () => {
71
99
  it('Should track view event with the given key', () => {
72
- trackSplitTestView('Some key')
100
+ trackSplitTestView(splitTest)
73
101
 
74
- const variant = isInSplitTest('Some key') ? 1 : 0
102
+ const variant = isInSplitTestVariant(splitTest) ? 1 : 0
75
103
 
76
104
  expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestView, null, { key: 'Some key', variant })
77
105
  })
@@ -79,10 +107,61 @@ describe('$lib/splitTest', () => {
79
107
 
80
108
  describe('trackSplitTestAction', () => {
81
109
  it('Should track action event with the given key and action', () => {
82
- trackSplitTestAction('Some key', 'Some action')
83
- const variant = isInSplitTest('Some key') ? 1 : 0
110
+ trackSplitTestAction(splitTest, 'Some action')
111
+ const variant = isInSplitTestVariant(splitTest) ? 1 : 0
112
+
113
+ expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestAction, null, { key: splitTest.key, variant, action: 'Some action' })
114
+ })
115
+ })
116
+
117
+ describe('getSplitTestVariantIndex', () => {
118
+ it('Should return the expected index split test consists of 2 variants', () => {
119
+ // @ts-ignore
120
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
121
+
122
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.25
123
+ expect(getSplitTestVariantIndex(splitTest)).toBe(0)
124
+
125
+ window.PlayPilotLinkInjections.split_test_identifiers[splitTest.key] = 0.75
126
+ expect(getSplitTestVariantIndex(splitTest)).toBe(1)
127
+ })
128
+
129
+ it('Should return the expected index when split test includes more than 2 variants', () => {
130
+ const multiple = { key: 'multiple', numberOfVariants: 3 }
131
+
132
+ // @ts-ignore
133
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
134
+
135
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.25
136
+ expect(getSplitTestVariantIndex(multiple)).toBe(0)
137
+
138
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.5
139
+ expect(getSplitTestVariantIndex(multiple)).toBe(1)
140
+
141
+ window.PlayPilotLinkInjections.split_test_identifiers[multiple.key] = 0.75
142
+ expect(getSplitTestVariantIndex(multiple)).toBe(2)
143
+ })
144
+
145
+ it('Should return the expected index when split test includes a large amount of variants', () => {
146
+ const many = { key: 'many', numberOfVariants: 10 }
147
+
148
+ // @ts-ignore
149
+ window.PlayPilotLinkInjections.split_test_identifiers = {}
150
+
151
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.05
152
+ expect(getSplitTestVariantIndex(many)).toBe(0)
153
+
154
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.35
155
+ expect(getSplitTestVariantIndex(many)).toBe(3)
156
+
157
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.65
158
+ expect(getSplitTestVariantIndex(many)).toBe(6)
159
+
160
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.78
161
+ expect(getSplitTestVariantIndex(many)).toBe(7)
84
162
 
85
- expect(track).toHaveBeenCalledWith(TrackingEvent.SplitTestAction, null, { key: 'Some key', variant, action: 'Some action' })
163
+ window.PlayPilotLinkInjections.split_test_identifiers[many.key] = 0.98
164
+ expect(getSplitTestVariantIndex(many)).toBe(9)
86
165
  })
87
166
  })
88
167
  })