@playpilot/tpi 5.9.2 → 5.10.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
@@ -7,6 +7,7 @@ All events share a common payload:
7
7
  - `url`: The URL of the related article. This is a URL with only the protocol, base url, and pathname. No parameters are included
8
8
  - `organization_sid`: The sid for the related organization
9
9
  - `domain_sid`: The sid for the related domain
10
+ - `device`: Basic device info containing the screen type, size, touch, and orientation
10
11
 
11
12
  Events related to titles share an additional set of data (referred to below as `Title`):
12
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "5.9.2",
3
+ "version": "5.10.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -0,0 +1,18 @@
1
+ export function middlemouse(node: HTMLElement, options: { onclick: (event: MouseEvent) => void }) {
2
+ let { onclick } = options
3
+
4
+ function mouseup(event: MouseEvent): MouseEvent | null {
5
+ if (event.button !== 1) return null
6
+
7
+ onclick(event)
8
+ return event
9
+ }
10
+
11
+ node.addEventListener('mouseup', mouseup)
12
+
13
+ return {
14
+ destroy() {
15
+ node.removeEventListener('mouseup', mouseup)
16
+ },
17
+ }
18
+ }
@@ -3,3 +3,4 @@ export const apiBaseUrl = 'https://partner-api.playpilot.tech/1.0.0'
3
3
  export const imageBaseUrl = 'https://img-external.playpilot.tech'
4
4
 
5
5
  export const imagePlaceholderDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAAB+CAMAAACNgsajAAAAb1BMVEU1Q2fI1N5YZoNVYoFndI9qd5JBT3FFU3S8yNS3xNC4xNFBTnDG0t2lscJJV3e9ytaptcWToLOOm6/BzdiZprh3hJ1aaIWJlatte5VPXHx7iJ9zgZlfbYk3RWmNmq5jcY1XZIO2wtCjsMB+i6I9S25IprTeAAABqUlEQVRo3u3W2ZKCMBCF4T5ExRmRVXBfZnn/Z5wKiU5pz1AJ7ZXV/x03HxVCB0jTNE3TNE3TNO0l2rbPNxcFdk8334AINTUD5eSaWbPoQs0qw0CVN98BzNNgE4NVv+ZHsJliuNVt7UgotATAafp/5mZiG4waAB0N5k0kUeg0wMykKDfLvRTl5pxyCFFupjQVo9ykiRTlphzl5nNQNu8C9Hv2lylDT0W2NMyUoeXdLC68KUNLuM7O9HskQ0uLLAEUR2aOQjfORE5LzHP2PMehxpl2k6otM8eh2aQGkBlieyRBYbs3y5Rk6IPZn8mT65XJR6LcROGErwaoxqLm4XvkiTVsy1FoYe5n06zcjazp1Wk0umHz3k9BT6+bXjXR6PnRtKpr755PfRjx4WPz7tXW/26gGYnOvOmrM7xtiK4qYtDOrpGZtnR7JFcSi+Z1XZt7k5d4NCZmcrWxqMzkbRgqN+nAULHpx1RiylFvftJwlxjUz2bWdpPB7NnSxZih5RFrD20Vai4izGOgeenPukMSUE6hte5denI7NMyU1xrSNE3TNE3TNE17hX4ADHsS0j0OCOoAAAAASUVORK5CYII='
6
+ export const mobileBreakpoint = 600
@@ -1,12 +1,4 @@
1
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
2
  TopScrollFormat: {
11
3
  key: 'top_scroll_format',
12
4
  numberOfVariants: 2,
@@ -8,6 +8,7 @@ import { isHoldingSpecialKey } from './event'
8
8
  import { playFallbackViewTransition } from './viewTransition'
9
9
  import { prefersReducedMotion } from 'svelte/motion'
10
10
  import { getNumberOfOccurrencesInArray } from './array'
11
+ import { mobileBreakpoint } from './constants'
11
12
 
12
13
  const keyDataAttribute = 'data-playpilot-injection-key'
13
14
  const keySelector = `[${keyDataAttribute}]`
@@ -348,7 +349,7 @@ function addLinkInjectionEventListeners(injections: LinkInjection[]): void {
348
349
  playFallbackViewTransition(() => {
349
350
  destroyLinkPopover(false)
350
351
  openLinkModal(event, injection)
351
- }, !prefersReducedMotion.current && window.innerWidth >= 600 && !window.matchMedia("(pointer: coarse)").matches)
352
+ }, !prefersReducedMotion.current && window.innerWidth >= mobileBreakpoint && !window.matchMedia("(pointer: coarse)").matches)
352
353
  })
353
354
 
354
355
  window.addEventListener('mousemove', (event) => {
@@ -1,3 +1,4 @@
1
+ import { mobileBreakpoint } from "./constants"
1
2
  import type { TitleData } from "./types/title"
2
3
  import { getFullUrlPath } from "./url"
3
4
 
@@ -10,7 +11,7 @@ const baseUrl = 'https://insights.playpilot.net'
10
11
  * @param [title] Title related to the event
11
12
  * @param [payload] Any data that will be included with the event
12
13
  */
13
- export async function track(event: string, title: TitleData | null = null, payload: Record<string, string | string[] | number | boolean | null | undefined> = {}): Promise<void> {
14
+ export async function track(event: string, title: TitleData | null = null, payload: Record<string, any> = {}): Promise<void> {
14
15
  const headers = new Headers({ 'Content-Type': 'application/json' })
15
16
 
16
17
  if (title) {
@@ -26,6 +27,13 @@ export async function track(event: string, title: TitleData | null = null, paylo
26
27
  payload.url = getFullUrlPath()
27
28
  payload.organization_sid = window.PlayPilotLinkInjections?.organization_sid
28
29
  payload.domain_sid = window.PlayPilotLinkInjections?.domain_sid
30
+ payload.device = {
31
+ type: window.innerWidth > mobileBreakpoint ? 'desktop' : 'mobile',
32
+ width: window.innerWidth,
33
+ height: window.innerHeight,
34
+ touch: window.matchMedia('(pointer: coarse)').matches,
35
+ orientation: screen.orientation?.type || 'undefined', // It would be excluded from the payload if it was `undefined`, we still want the string if it is actually undefined
36
+ }
29
37
 
30
38
  fetch(baseUrl, {
31
39
  headers,
@@ -7,6 +7,7 @@ declare global {
7
7
  initialize(config: ScriptConfig): void
8
8
  destroy(): void
9
9
  debug(): void
10
+ mockAd(override?: Record<any, any> = {}): void
10
11
  }
11
12
  }
12
13
  }
package/src/main.ts CHANGED
@@ -2,6 +2,7 @@ import { mount } from 'svelte'
2
2
  import App from './routes/+page.svelte'
3
3
  import { clearLinkInjections, getLinkInjectionElements, getLinkInjectionsParentElement, getPageText } from '$lib/linkInjection'
4
4
  import { getPageMetaData } from '$lib/meta'
5
+ import type { Campaign } from '$lib/types/campaign'
5
6
 
6
7
  window.PlayPilotLinkInjections = {
7
8
  token: '',
@@ -59,40 +60,67 @@ window.PlayPilotLinkInjections = {
59
60
  const parentElement = getLinkInjectionsParentElement()
60
61
  const elements = getLinkInjectionElements(parentElement)
61
62
 
62
- console.group('PlayPilot Link Injection Debug')
63
- console.groupCollapsed('Config')
64
- console.table(Object.entries(this))
65
- console.groupEnd()
63
+ console.groupCollapsed('Config')
64
+ console.table(Object.entries(this))
65
+ console.groupEnd()
66
66
 
67
- console.groupCollapsed('Elements')
68
- console.log('Parent element', parentElement)
69
- console.log('Valid elements', elements)
70
- console.groupEnd()
67
+ console.groupCollapsed('Elements')
68
+ console.log('Parent element', parentElement)
69
+ console.log('Valid elements', elements)
70
+ console.groupEnd()
71
71
 
72
- console.groupCollapsed('Last fetch')
73
- console.log(this.last_successful_fetch)
74
- console.groupEnd()
72
+ console.groupCollapsed('Last fetch')
73
+ console.log(this.last_successful_fetch)
74
+ console.groupEnd()
75
75
 
76
- console.groupCollapsed('Meta')
77
- console.log(getPageMetaData())
78
- console.groupEnd()
76
+ console.groupCollapsed('Meta')
77
+ console.log(getPageMetaData())
78
+ console.groupEnd()
79
79
 
80
- console.groupCollapsed('Page text')
81
- console.log(getPageText(elements))
82
- console.groupEnd()
80
+ console.groupCollapsed('Page text')
81
+ console.log(getPageText(elements))
82
+ console.groupEnd()
83
83
 
84
- console.groupCollapsed('Evaluated injections')
85
- console.log(this.evaluated_link_injections)
86
- console.groupEnd()
84
+ console.groupCollapsed('Evaluated injections')
85
+ console.log(this.evaluated_link_injections)
86
+ console.groupEnd()
87
87
 
88
- console.groupCollapsed('Tracked events')
89
- console.log(this.tracked_events)
90
- console.groupEnd()
88
+ console.groupCollapsed('Tracked events')
89
+ console.log(this.tracked_events)
90
+ console.groupEnd()
91
91
 
92
- console.groupCollapsed('Split tests')
93
- console.log(this.split_test_identifiers)
94
- console.groupEnd()
92
+ console.groupCollapsed('Split tests')
93
+ console.log(this.split_test_identifiers)
95
94
  console.groupEnd()
95
+ },
96
+
97
+ mockAd(override = {}): void {
98
+ /** @ts-ignore We're not including every field here, that's ok. */
99
+ const campaign: Campaign = {
100
+ campaign_format: 'card',
101
+ campaign_type: 'image',
102
+ campaign_name: 'some_campaign',
103
+ content: {
104
+ header: 'Some content header',
105
+ header_logo: 'https://picsum.photos/seed/picsum/40/40',
106
+ header_logo_uuid: '',
107
+ subheader: 'Some content subheader',
108
+ image: 'https://picsum.photos/seed/picsum/640/360',
109
+ image_uuid: '',
110
+ video: null,
111
+ format: null,
112
+ },
113
+ cta: {
114
+ header: 'Some cta header',
115
+ subheader: 'Some cta subheader',
116
+ url: 'https://google.com/',
117
+ image: null,
118
+ image_uuid: null
119
+ },
120
+ disclaimer: 'Some disclaimer',
121
+ }
122
+
123
+ this.ads = [{ ...campaign, ...override }]
96
124
  }
97
125
  }
98
126
 
@@ -1,8 +1,6 @@
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'
6
4
 
7
5
  /**
8
6
  * !! NOTE: This layout file is for development purposes only and will not be compiled with the final script.
@@ -80,8 +78,6 @@
80
78
 
81
79
  {#if browser}
82
80
  {@render children()}
83
-
84
- Viewing with split test index: {getSplitTestVariantIndex(SplitTest.MultipleVariants)}
85
81
  {/if}
86
82
  {/key}
87
83
  {/if}
@@ -7,13 +7,12 @@
7
7
  import { isCrawler } from '$lib/crawler'
8
8
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
9
9
  import { authorize, getAuthToken, isEditorialModeEnabled, removeAuthCookie, setEditorialParamInUrl } from '$lib/auth'
10
- import { trackSplitTestView } from '$lib/splitTest'
11
- import { SplitTest } from '$lib/enums/SplitTest'
12
10
  import type { LinkInjectionResponse, LinkInjection, LinkInjectionTypes } from '$lib/types/injection'
13
11
  import Editor from './components/Editorial/Editor.svelte'
14
12
  import EditorTrigger from './components/Editorial/EditorTrigger.svelte'
15
13
  import Alert from './components/Editorial/Alert.svelte'
16
14
  import TrackingPixels from './components/TrackingPixels.svelte'
15
+ import Debugger from './components/Debugger.svelte'
17
16
  import { fetchAds } from '$lib/ads'
18
17
 
19
18
  let parentElement: HTMLElement | null = $state(null)
@@ -45,8 +44,6 @@
45
44
  track(TrackingEvent.ArticlePageView)
46
45
 
47
46
  if (aiInjections.length || manualInjections.length) window.PlayPilotLinkInjections.ads = await fetchAds()
48
-
49
- trackSplitTestView(SplitTest.MultipleVariants)
50
47
  })()
51
48
 
52
49
  return () => clearLinkInjections()
@@ -202,6 +199,8 @@
202
199
  {/if}
203
200
  </div>
204
201
 
202
+ <Debugger />
203
+
205
204
  {#if response?.pixels?.length}
206
205
  <TrackingPixels pixels={response.pixels} />
207
206
  {/if}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { middlemouse } from '$lib/actions/middlemouse'
2
3
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
3
4
  import { track } from '$lib/tracking'
4
5
  import type { Campaign } from '$lib/types/campaign'
@@ -22,7 +23,7 @@
22
23
  }
23
24
  </script>
24
25
 
25
- <a {href} target="_blank" class="display" class:compact rel="sponsored" {onclick}>
26
+ <a {href} target="_blank" class="display" class:compact rel="sponsored" {onclick} use:middlemouse={{ onclick }}>
26
27
  {#if disclaimer}
27
28
  <div class="disclaimer">
28
29
  {#if compact}
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import { middlemouse } from '$lib/actions/middlemouse'
2
3
  import { ImageDimensions } from '$lib/enums/ImageDimensions'
3
4
  import { SplitTest } from '$lib/enums/SplitTest'
4
5
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
@@ -30,7 +31,7 @@
30
31
  let clientWidth = $state(0)
31
32
  let tooltipVisible = $state(false)
32
33
 
33
- function trackClick(): void {
34
+ function onclick(): void {
34
35
  track(TrackingEvent.TopScrollClick, null, { campaign_name: campaign.campaign_name })
35
36
  }
36
37
 
@@ -44,15 +45,16 @@
44
45
 
45
46
  <a
46
47
  {href}
48
+ {onclick}
49
+ use:middlemouse={{ onclick }}
50
+ bind:clientWidth
47
51
  target="_blank"
48
52
  class="top-scroll"
49
53
  class:simple
50
54
  class:inline
51
55
  tabindex="-1"
52
- onclick={trackClick}
53
56
  rel="sponsored"
54
- style:--width="{clientWidth}px"
55
- bind:clientWidth>
57
+ style:--width="{clientWidth}px">
56
58
  <div class="content">
57
59
  {#if simple}
58
60
  <img class="content-image" src={contentImage} alt={header} width="728" height="90" />
@@ -0,0 +1,111 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte'
3
+
4
+ const secrets = ['tpidebug', 'debugtpi']
5
+ const lastInputs: string[] = []
6
+
7
+ let data = $state(dataToReadable())
8
+ let shown = $state(false)
9
+ let interval: ReturnType<typeof setInterval> | null = null
10
+
11
+ onDestroy(() => {
12
+ if (interval) clearInterval(interval)
13
+ })
14
+
15
+ function dataToReadable() {
16
+ const data = window.PlayPilotLinkInjections
17
+
18
+ const succesfulInjections = data.evaluated_link_injections?.filter(injection => !injection.failed) || []
19
+ const failedInjections = data.evaluated_link_injections?.filter(injection => injection.failed) || []
20
+
21
+ return {
22
+ 'Config': [
23
+ { label: 'Domain', data: data.domain_sid },
24
+ { label: 'Organization', data: data.organization_sid },
25
+ { label: 'HTML selector', data: data.selector },
26
+ ],
27
+ [`Succesful injections (${succesfulInjections.length})`]: succesfulInjections.map(injection => ({ label: injection.title, data: injection.sentence })),
28
+ [`Failed injections (${failedInjections.length})`]: failedInjections.map(injection => ({ label: injection.title, data: injection.failed_message })),
29
+ [`Fetched ads (${data.ads?.length || 0})`]: data.ads?.map(ad => ({ label: ad.campaign_name, data: ad })),
30
+ [`Tracking events (${data.tracked_events?.length || 0})`]: data.tracked_events?.map(event => ({ label: event.event, data: event.payload })),
31
+ }
32
+ }
33
+
34
+ // Keep track of all last inputs. If the last inputs match the "secret" key we show the debugger and start setting data
35
+ function onkeydown(event: KeyboardEvent): void {
36
+ const key = event.key
37
+
38
+ lastInputs.push(key)
39
+
40
+ // Check all secrets. Both "tpidebug" and "debugtpi" are accepted because remembering is hard.
41
+ // It's checked by comparing the last inputs to each secret.
42
+ shown = secrets.some(secret => lastInputs.slice(lastInputs.length - secret.length, lastInputs.length).join('') === secret)
43
+
44
+ interval = setInterval(() => data = dataToReadable(), 200)
45
+ }
46
+ </script>
47
+
48
+ <svelte:window {onkeydown} />
49
+
50
+ {#if shown}
51
+ <div class="debugger">
52
+ <strong>TPI Debugger</strong>
53
+
54
+ <hr />
55
+
56
+ {#each Object.entries(data) as [key, value]}
57
+ <details>
58
+ <summary>{key}</summary>
59
+
60
+ {#if value?.length}
61
+ {#if Array.isArray(value)}
62
+ {#each value as { label, data }}
63
+ <div class="item">
64
+ <span class="label">{label}</span>:
65
+ <span class="data">{JSON.stringify(data)}</span>
66
+ </div>
67
+ {/each}
68
+ {/if}
69
+ {:else}
70
+ No data
71
+ {/if}
72
+ </details>
73
+ {/each}
74
+ </div>
75
+ {/if}
76
+
77
+ <style lang="scss">
78
+ summary {
79
+ color: var(--playpilot-primary);
80
+ font-weight: bold;
81
+ }
82
+
83
+ hr {
84
+ border-color: var(--playpilot-primary);
85
+ }
86
+
87
+ .debugger {
88
+ z-index: 2147483647; // As high as she goes
89
+ position: fixed;
90
+ top: 0;
91
+ left: 0;
92
+ max-width: 70dvw;
93
+ max-height: 50dvh;
94
+ padding: margin(1);
95
+ border: 1px solid var(--playpilot-primary);
96
+ background: black;
97
+ overflow: auto;
98
+ color: white;
99
+ font-family: "Consolas", monospace;
100
+ }
101
+
102
+ .item {
103
+ white-space: nowrap;
104
+ overflow-x: auto;
105
+ scrollbar-width: thin;
106
+ }
107
+
108
+ .data {
109
+ color: gray;
110
+ }
111
+ </style>
@@ -7,6 +7,7 @@
7
7
  import { prefersReducedMotion } from 'svelte/motion'
8
8
  import { isInSplitTestVariant } from '$lib/splitTest'
9
9
  import { SplitTest } from '$lib/enums/SplitTest'
10
+ import { mobileBreakpoint } from '$lib/constants'
10
11
 
11
12
  interface Props {
12
13
  children: Snippet
@@ -25,7 +26,7 @@
25
26
  let dialogElement: HTMLElement | null = $state(null)
26
27
  let dialogOffset: number = $state(0)
27
28
 
28
- const isMobile = $derived(windowWidth < 600)
29
+ const isMobile = $derived(windowWidth < mobileBreakpoint)
29
30
 
30
31
  $effect(() => { if (windowWidth) dialogOffset = dialogElement?.offsetTop || 0 })
31
32
  $effect(() => { setTimeout(() => dialogOffset = dialogElement?.offsetTop || 0) }) // Set after the dialog has shown to get the proper height
@@ -137,20 +137,31 @@ describe('$lib/tracking', () => {
137
137
  )
138
138
  })
139
139
 
140
- describe('setTrackingSids', () => {
141
- it('Should set stores equal to the given values', () => {
142
- setTrackingSids({ domainSid: 'some-domain', organizationSid: 'some-organization' })
140
+ it('Should include device information', () => {
141
+ track('Some event')
143
142
 
144
- expect(window.PlayPilotLinkInjections.domain_sid).toBe('some-domain')
145
- expect(window.PlayPilotLinkInjections.organization_sid).toBe('some-organization')
146
- })
143
+ expect(global.fetch).toHaveBeenCalledWith(
144
+ expect.any(String),
145
+ expect.objectContaining({
146
+ body: expect.stringContaining('"device":{"type":"desktop","width":1024,"height":768,"touch":false,"orientation":"undefined"}'),
147
+ }),
148
+ )
149
+ })
150
+ })
151
+
152
+ describe('setTrackingSids', () => {
153
+ it('Should set stores equal to the given values', () => {
154
+ setTrackingSids({ domainSid: 'some-domain', organizationSid: 'some-organization' })
155
+
156
+ expect(window.PlayPilotLinkInjections.domain_sid).toBe('some-domain')
157
+ expect(window.PlayPilotLinkInjections.organization_sid).toBe('some-organization')
158
+ })
147
159
 
148
- it('Should set stores to null if invalid values are given', () => {
149
- setTrackingSids({ domainSid: '', organizationSid: undefined })
160
+ it('Should set stores to null if invalid values are given', () => {
161
+ setTrackingSids({ domainSid: '', organizationSid: undefined })
150
162
 
151
- expect(window.PlayPilotLinkInjections.domain_sid).toBe(null)
152
- expect(window.PlayPilotLinkInjections.organization_sid).toBe(null)
153
- })
163
+ expect(window.PlayPilotLinkInjections.domain_sid).toBe(null)
164
+ expect(window.PlayPilotLinkInjections.organization_sid).toBe(null)
154
165
  })
155
166
  })
156
167
  })
@@ -59,9 +59,14 @@ describe('Display.svelte', () => {
59
59
  /** @ts-ignore */
60
60
  const { getByRole } = render(Display, { campaign })
61
61
 
62
- await fireEvent.click(getByRole('link'))
62
+ vi.resetAllMocks()
63
63
 
64
+ await fireEvent.click(getByRole('link'))
64
65
  expect(track).toHaveBeenCalledWith(TrackingEvent.DisplayAdClick, null, { campaign_name: campaign.campaign_name })
66
+ expect(track).toHaveBeenCalledTimes(1)
67
+
68
+ await fireEvent.mouseUp(getByRole('link'), { button: 1 })
69
+ expect(track).toHaveBeenCalledTimes(2)
65
70
  })
66
71
 
67
72
  it('Should render link without any content by default', () => {
@@ -92,9 +92,14 @@ describe('TopScroll.svelte', () => {
92
92
  /** @ts-ignore */
93
93
  const { getByRole } = render(TopScroll, { campaign })
94
94
 
95
- await fireEvent.click(getByRole('link'))
95
+ vi.resetAllMocks()
96
96
 
97
+ await fireEvent.click(getByRole('link'))
97
98
  expect(track).toHaveBeenCalledWith(TrackingEvent.TopScrollClick, null, { campaign_name: campaign.campaign_name })
99
+ expect(track).toHaveBeenCalledTimes(1)
100
+
101
+ await fireEvent.mouseUp(getByRole('link'), { button: 1 })
102
+ expect(track).toHaveBeenCalledTimes(2)
98
103
  })
99
104
 
100
105
  it('Should render as simple variant when content format is large', async () => {
@@ -0,0 +1,54 @@
1
+ import { fireEvent, render } from '@testing-library/svelte'
2
+ import { afterEach, describe, expect, it } from 'vitest'
3
+
4
+ import Debugger from '../../../routes/components/Debugger.svelte'
5
+
6
+ describe('Debugger.svelte', () => {
7
+ afterEach(() => {
8
+ localStorage.clear()
9
+ })
10
+
11
+ it('Should show debugger after entering correct input', async () => {
12
+ const { getByText } = render(Debugger)
13
+
14
+ const keys = ['t', 'p', 'i', 'd', 'e', 'b', 'u', 'g']
15
+ for (const key of keys) {
16
+ await fireEvent.keyDown(window, { key })
17
+ }
18
+
19
+ expect(getByText('TPI Debugger')).toBeTruthy()
20
+ })
21
+
22
+ it('Should show debugger after entering other correct input', async () => {
23
+ const { getByText } = render(Debugger)
24
+
25
+ const keys = ['d', 'e', 'b', 'u', 'g', 't', 'p', 'i']
26
+ for (const key of keys) {
27
+ await fireEvent.keyDown(window, { key })
28
+ }
29
+
30
+ expect(getByText('TPI Debugger')).toBeTruthy()
31
+ })
32
+
33
+ it('Should not show debugger after entering incorrect input', async () => {
34
+ const { queryByText } = render(Debugger)
35
+
36
+ const keys = ['n', 'o', 't', 'd', 'e', 'b', 'u', 'g']
37
+ for (const key of keys) {
38
+ await fireEvent.keyDown(window, { key })
39
+ }
40
+
41
+ expect(queryByText('TPI Debugger')).not.toBeTruthy()
42
+ })
43
+
44
+ it('Should show debugger after entering correct input after first enter incorrect keys', async () => {
45
+ const { getByText } = render(Debugger)
46
+
47
+ const keys = ['a', 'b', 'c', 't', 'p', 'i', 'd', 'e', 'b', 'u', 'g']
48
+ for (const key of keys) {
49
+ await fireEvent.keyDown(window, { key })
50
+ }
51
+
52
+ expect(getByText('TPI Debugger')).toBeTruthy()
53
+ })
54
+ })