fix(tracker): split uxt ui and signals, cover signal manager with tests
This commit is contained in:
parent
44dc49b135
commit
1b70d463d0
6 changed files with 358 additions and 189 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "11.0.2-20",
|
||||
"version": "11.0.2-26",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
import { TEST_START, TASK_IND, SESSION_ID } from './utils.js'
|
||||
|
||||
export default class SignalManager {
|
||||
private readonly durations = {
|
||||
testStart: 0,
|
||||
tasks: [] as unknown as {
|
||||
taskId: number
|
||||
started: number
|
||||
}[],
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly ingestPoint: string,
|
||||
private readonly getTimestamp: () => number,
|
||||
private readonly token: string,
|
||||
private readonly testId: number,
|
||||
private readonly storageKey: string,
|
||||
private readonly setStorageKey: (key: string, value: string) => void,
|
||||
private readonly removeStorageKey: (key: string) => void,
|
||||
private readonly getStorageKey: (key: string) => string | null,
|
||||
private readonly getSessionId: () => string | undefined,
|
||||
) {
|
||||
const possibleStart = this.getStorageKey(TEST_START)
|
||||
if (possibleStart) {
|
||||
this.durations.testStart = parseInt(possibleStart, 10)
|
||||
}
|
||||
}
|
||||
|
||||
getDurations = () => {
|
||||
return this.durations
|
||||
}
|
||||
|
||||
setDurations = (durations: {
|
||||
testStart: number
|
||||
tasks: {
|
||||
taskId: number
|
||||
started: number
|
||||
}[]
|
||||
}) => {
|
||||
this.durations.testStart = durations.testStart
|
||||
this.durations.tasks = durations.tasks
|
||||
}
|
||||
|
||||
signalTask = (taskId: number, status: 'begin' | 'done' | 'skipped', taskAnswer?: string) => {
|
||||
if (!taskId) return console.error('User Testing: No Task ID Given')
|
||||
const taskStart = this.durations.tasks.find((t) => t.taskId === taskId)
|
||||
const timestamp = this.getTimestamp()
|
||||
const duration = taskStart ? timestamp - taskStart.started : 0
|
||||
return fetch(`${this.ingestPoint}/v1/web/uxt/signals/task`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
testId: this.testId,
|
||||
taskId,
|
||||
status,
|
||||
duration,
|
||||
timestamp,
|
||||
taskAnswer,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
signalTest = (status: 'begin' | 'done' | 'skipped') => {
|
||||
const timestamp = this.getTimestamp()
|
||||
if (status === 'begin' && this.testId) {
|
||||
const sessionId = this.getSessionId()
|
||||
this.setStorageKey(SESSION_ID, sessionId as unknown as string)
|
||||
this.setStorageKey(this.storageKey, this.testId.toString())
|
||||
this.setStorageKey(TEST_START, timestamp.toString())
|
||||
} else {
|
||||
this.removeStorageKey(this.storageKey)
|
||||
this.removeStorageKey(TASK_IND)
|
||||
this.removeStorageKey(TEST_START)
|
||||
}
|
||||
const start = this.durations.testStart || timestamp
|
||||
const duration = timestamp - start
|
||||
|
||||
return fetch(`${this.ingestPoint}/v1/web/uxt/signals/test`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
testId: this.testId,
|
||||
status,
|
||||
duration,
|
||||
timestamp,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -2,25 +2,17 @@ import App from '../../app/index.js'
|
|||
import * as styles from './styles.js'
|
||||
import Recorder, { Quality } from './recorder.js'
|
||||
import attachDND from './dnd.js'
|
||||
|
||||
function createElement(
|
||||
tag: string,
|
||||
className: string,
|
||||
styles: any,
|
||||
textContent?: string,
|
||||
id?: string,
|
||||
) {
|
||||
const element = document.createElement(tag)
|
||||
element.className = className
|
||||
Object.assign(element.style, styles)
|
||||
if (textContent) {
|
||||
element.textContent = textContent
|
||||
}
|
||||
if (id) {
|
||||
element.id = id
|
||||
}
|
||||
return element
|
||||
}
|
||||
import {
|
||||
generateGrid,
|
||||
generateChevron,
|
||||
createSpinner,
|
||||
createElement,
|
||||
TEST_START,
|
||||
TASK_IND,
|
||||
SESSION_ID,
|
||||
TEST_ID,
|
||||
} from './utils.js'
|
||||
import SignalManager from './SignalManager.js'
|
||||
|
||||
interface Test {
|
||||
title: string
|
||||
|
|
@ -57,16 +49,10 @@ export default class UserTestManager {
|
|||
private taskSection: HTMLElement | null = null
|
||||
private endSection: HTMLElement | null = null
|
||||
private stopButton: HTMLElement | null = null
|
||||
private stopButtonContainer: HTMLElement | null = null
|
||||
private test: Test | null = null
|
||||
private testId: number | null = null
|
||||
private token: string | null = null
|
||||
private readonly durations = {
|
||||
testStart: 0,
|
||||
tasks: [] as unknown as {
|
||||
taskId: number
|
||||
started: number
|
||||
}[],
|
||||
}
|
||||
private signalManager: SignalManager | null = null
|
||||
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
|
|
@ -74,23 +60,19 @@ export default class UserTestManager {
|
|||
) {
|
||||
this.userRecorder = new Recorder(app)
|
||||
const sessionId = this.app.getSessionID()
|
||||
const savedSessionId = this.app.localStorage.getItem('or_uxt_session_id')
|
||||
const savedSessionId = this.app.localStorage.getItem(SESSION_ID)
|
||||
console.log(sessionId, savedSessionId)
|
||||
if (sessionId !== savedSessionId) {
|
||||
this.app.localStorage.removeItem(this.storageKey)
|
||||
this.app.localStorage.removeItem('or_uxt_session_id')
|
||||
this.app.localStorage.removeItem('or_uxt_test_id')
|
||||
this.app.localStorage.removeItem('or_uxt_task_index')
|
||||
this.app.localStorage.removeItem('or_uxt_test_start')
|
||||
this.app.localStorage.removeItem(SESSION_ID)
|
||||
this.app.localStorage.removeItem(TEST_ID)
|
||||
this.app.localStorage.removeItem(TASK_IND)
|
||||
this.app.localStorage.removeItem(TEST_START)
|
||||
}
|
||||
|
||||
const taskIndex = this.app.localStorage.getItem('or_uxt_task_index')
|
||||
const taskIndex = this.app.localStorage.getItem(TASK_IND)
|
||||
if (taskIndex) {
|
||||
this.currentTaskIndex = parseInt(taskIndex, 10)
|
||||
this.durations.testStart = parseInt(
|
||||
this.app.localStorage.getItem('or_uxt_test_start') as string,
|
||||
10,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,63 +80,8 @@ export default class UserTestManager {
|
|||
return this.testId
|
||||
}
|
||||
|
||||
signalTask = (taskId: number, status: 'begin' | 'done' | 'skipped', answer?: string) => {
|
||||
if (!taskId) return console.error('OR: no task id')
|
||||
const taskStart = this.durations.tasks.find((t) => t.taskId === taskId)
|
||||
const timestamp = this.app.timestamp()
|
||||
const duration = taskStart ? timestamp - taskStart.started : 0
|
||||
const ingest = this.app.options.ingestPoint
|
||||
return fetch(`${ingest}/v1/web/uxt/signals/task`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
testId: this.testId,
|
||||
taskId,
|
||||
status,
|
||||
duration,
|
||||
timestamp,
|
||||
answer,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
signalTest = (status: 'begin' | 'done' | 'skipped') => {
|
||||
const timestamp = this.app.timestamp()
|
||||
if (status === 'begin' && this.testId) {
|
||||
const sessionId = this.app.getSessionID()
|
||||
this.app.localStorage.setItem('or_uxt_session_id', sessionId as unknown as string)
|
||||
this.app.localStorage.setItem(this.storageKey, this.testId.toString())
|
||||
this.app.localStorage.setItem('or_uxt_test_start', timestamp.toString())
|
||||
} else {
|
||||
this.app.localStorage.removeItem(this.storageKey)
|
||||
this.app.localStorage.removeItem('or_uxt_task_index')
|
||||
this.app.localStorage.removeItem('or_uxt_test_start')
|
||||
}
|
||||
const ingest = this.app.options.ingestPoint
|
||||
const start = this.durations.testStart || timestamp
|
||||
const duration = timestamp - start
|
||||
|
||||
return fetch(`${ingest}/v1/web/uxt/signals/test`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
testId: this.testId,
|
||||
status,
|
||||
duration,
|
||||
timestamp,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
getTest = (id: number, token: string, inProgress?: boolean) => {
|
||||
this.testId = id
|
||||
this.token = token
|
||||
const ingest = this.app.options.ingestPoint
|
||||
return fetch(`${ingest}/v1/web/uxt/test/${id}`, {
|
||||
headers: {
|
||||
|
|
@ -165,6 +92,17 @@ export default class UserTestManager {
|
|||
.then(({ test }: { test: Test }) => {
|
||||
this.isActive = true
|
||||
this.test = test
|
||||
this.signalManager = new SignalManager(
|
||||
this.app.options.ingestPoint,
|
||||
() => this.app.timestamp(),
|
||||
token,
|
||||
id,
|
||||
this.storageKey,
|
||||
(k: string, v: string) => this.app.localStorage.setItem(k, v),
|
||||
(k) => this.app.localStorage.removeItem(k),
|
||||
(k) => this.app.localStorage.getItem(k),
|
||||
() => this.app.getSessionID(),
|
||||
)
|
||||
this.createGreeting(test.title, test.reqMic, test.reqCamera)
|
||||
if (inProgress) {
|
||||
if (test.reqMic || test.reqCamera) {
|
||||
|
|
@ -214,8 +152,12 @@ export default class UserTestManager {
|
|||
}
|
||||
buttonElement.onclick = () => {
|
||||
this.removeGreeting()
|
||||
this.durations.testStart = this.app.timestamp()
|
||||
void this.signalTest('begin')
|
||||
const durations = this.signalManager?.getDurations()
|
||||
if (durations && this.signalManager) {
|
||||
durations.testStart = this.app.timestamp()
|
||||
this.signalManager.setDurations(durations)
|
||||
}
|
||||
void this.signalManager?.signalTest('begin')
|
||||
this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`
|
||||
Object.assign(this.container.style, styles.containerWidgetStyle)
|
||||
this.showWidget(this.test?.guidelines || '', this.test?.tasks || [])
|
||||
|
|
@ -265,9 +207,10 @@ export default class UserTestManager {
|
|||
this.taskSection = tasksSection
|
||||
this.descriptionSection = descriptionSection
|
||||
this.stopButton = stopButton
|
||||
this.stopButtonContainer = stopContainer
|
||||
stopButton.onclick = () => {
|
||||
this.userRecorder.discard()
|
||||
void this.signalTest('skipped')
|
||||
void this.signalManager?.signalTest('skipped')
|
||||
document.body.removeChild(this.bg)
|
||||
window.close()
|
||||
}
|
||||
|
|
@ -404,17 +347,18 @@ export default class UserTestManager {
|
|||
button.onclick = () => {
|
||||
toggleDescriptionVisibility()
|
||||
if (this.test) {
|
||||
if (
|
||||
this.durations.tasks.findIndex(
|
||||
(t) => this.test && t.taskId === this.test.tasks[0].task_id,
|
||||
) === -1
|
||||
) {
|
||||
this.durations.tasks.push({
|
||||
const durations = this.signalManager?.getDurations()
|
||||
const taskDurationInd = durations
|
||||
? durations.tasks.findIndex((t) => this.test && t.taskId === this.test.tasks[0].task_id)
|
||||
: null
|
||||
if (durations && taskDurationInd === -1) {
|
||||
durations.tasks.push({
|
||||
taskId: this.test.tasks[0].task_id,
|
||||
started: this.app.timestamp(),
|
||||
})
|
||||
this.signalManager?.setDurations(durations)
|
||||
}
|
||||
void this.signalTask(this.test.tasks[0].task_id, 'begin')
|
||||
void this.signalManager?.signalTask(this.test.tasks[0].task_id, 'begin')
|
||||
}
|
||||
this.showTaskSection()
|
||||
content.removeChild(button)
|
||||
|
|
@ -558,21 +502,22 @@ export default class UserTestManager {
|
|||
nextButton.onclick = () => {
|
||||
const textAnswer = tasks[this.currentTaskIndex].allow_typing ? inputArea.value : undefined
|
||||
inputArea.value = ''
|
||||
void this.signalTask(tasks[this.currentTaskIndex].task_id, 'done', textAnswer)
|
||||
void this.signalManager?.signalTask(tasks[this.currentTaskIndex].task_id, 'done', textAnswer)
|
||||
if (this.currentTaskIndex < tasks.length - 1) {
|
||||
this.currentTaskIndex++
|
||||
updateTaskContent()
|
||||
const durations = this.signalManager?.getDurations()
|
||||
if (
|
||||
this.durations.tasks.findIndex(
|
||||
(t) => t.taskId === tasks[this.currentTaskIndex].task_id,
|
||||
) === -1
|
||||
durations &&
|
||||
durations.tasks.findIndex((t) => t.taskId === tasks[this.currentTaskIndex].task_id) === -1
|
||||
) {
|
||||
this.durations.tasks.push({
|
||||
durations.tasks.push({
|
||||
taskId: tasks[this.currentTaskIndex].task_id,
|
||||
started: this.app.timestamp(),
|
||||
})
|
||||
this.signalManager?.setDurations(durations)
|
||||
}
|
||||
void this.signalTask(tasks[this.currentTaskIndex].task_id, 'begin')
|
||||
void this.signalManager?.signalTask(tasks[this.currentTaskIndex].task_id, 'begin')
|
||||
highlightActive()
|
||||
} else {
|
||||
this.showEndSection()
|
||||
|
|
@ -593,7 +538,7 @@ export default class UserTestManager {
|
|||
|
||||
showEndSection() {
|
||||
let isLoading = true
|
||||
void this.signalTest('done')
|
||||
void this.signalManager?.signalTest('done')
|
||||
const section = createElement('div', 'end_section_or', styles.endSectionStyle)
|
||||
const title = createElement(
|
||||
'div',
|
||||
|
|
@ -648,8 +593,8 @@ export default class UserTestManager {
|
|||
if (this.descriptionSection) {
|
||||
this.container.removeChild(this.descriptionSection)
|
||||
}
|
||||
if (this.stopButton) {
|
||||
this.container.removeChild(this.stopButton)
|
||||
if (this.stopButton && this.stopButtonContainer) {
|
||||
this.container.removeChild(this.stopButtonContainer)
|
||||
}
|
||||
|
||||
button.onclick = () => {
|
||||
|
|
@ -662,80 +607,3 @@ export default class UserTestManager {
|
|||
this.container.append(section)
|
||||
}
|
||||
}
|
||||
|
||||
function generateGrid() {
|
||||
const grid = document.createElement('div')
|
||||
grid.className = 'grid'
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const cell = document.createElement('div')
|
||||
Object.assign(cell.style, {
|
||||
width: '2px',
|
||||
height: '2px',
|
||||
borderRadius: '10px',
|
||||
background: 'white',
|
||||
})
|
||||
cell.className = 'cell'
|
||||
grid.appendChild(cell)
|
||||
}
|
||||
Object.assign(grid.style, {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
gridTemplateRows: 'repeat(4, 1fr)',
|
||||
gap: '2px',
|
||||
cursor: 'grab',
|
||||
})
|
||||
return grid
|
||||
}
|
||||
|
||||
function generateChevron() {
|
||||
const triangle = document.createElement('div')
|
||||
Object.assign(triangle.style, {
|
||||
width: '0',
|
||||
height: '0',
|
||||
borderLeft: '7px solid transparent',
|
||||
borderRight: '7px solid transparent',
|
||||
borderBottom: '7px solid white',
|
||||
})
|
||||
const container = document.createElement('div')
|
||||
container.appendChild(triangle)
|
||||
Object.assign(container.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
cursor: 'pointer',
|
||||
marginLeft: 'auto',
|
||||
transform: 'rotate(180deg)',
|
||||
})
|
||||
return container
|
||||
}
|
||||
|
||||
const spinnerStyles = {
|
||||
border: '4px solid rgba(255, 255, 255, 0.4)',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
borderRadius: '50%',
|
||||
borderLeftColor: '#fff',
|
||||
animation: 'spin 0.5s linear infinite',
|
||||
}
|
||||
|
||||
function addKeyframes() {
|
||||
const styleSheet = document.createElement('style')
|
||||
styleSheet.type = 'text/css'
|
||||
styleSheet.innerText = `@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}`
|
||||
document.head.appendChild(styleSheet)
|
||||
}
|
||||
|
||||
function createSpinner() {
|
||||
addKeyframes()
|
||||
const spinner = document.createElement('div')
|
||||
spinner.classList.add('spinner')
|
||||
|
||||
Object.assign(spinner.style, spinnerStyles)
|
||||
|
||||
return spinner
|
||||
}
|
||||
|
|
|
|||
|
|
@ -273,3 +273,12 @@ export const taskButtonsRow = {
|
|||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
}
|
||||
|
||||
export const spinnerStyles = {
|
||||
border: '4px solid rgba(255, 255, 255, 0.4)',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
borderRadius: '50%',
|
||||
borderLeftColor: '#fff',
|
||||
animation: 'spin 0.5s linear infinite',
|
||||
}
|
||||
|
|
|
|||
93
tracker/tracker/src/main/modules/userTesting/utils.ts
Normal file
93
tracker/tracker/src/main/modules/userTesting/utils.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { spinnerStyles } from './styles.js'
|
||||
|
||||
export function generateGrid() {
|
||||
const grid = document.createElement('div')
|
||||
grid.className = 'grid'
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const cell = document.createElement('div')
|
||||
Object.assign(cell.style, {
|
||||
width: '2px',
|
||||
height: '2px',
|
||||
borderRadius: '10px',
|
||||
background: 'white',
|
||||
})
|
||||
cell.className = 'cell'
|
||||
grid.appendChild(cell)
|
||||
}
|
||||
Object.assign(grid.style, {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
gridTemplateRows: 'repeat(4, 1fr)',
|
||||
gap: '2px',
|
||||
cursor: 'grab',
|
||||
})
|
||||
return grid
|
||||
}
|
||||
|
||||
export function generateChevron() {
|
||||
const triangle = document.createElement('div')
|
||||
Object.assign(triangle.style, {
|
||||
width: '0',
|
||||
height: '0',
|
||||
borderLeft: '7px solid transparent',
|
||||
borderRight: '7px solid transparent',
|
||||
borderBottom: '7px solid white',
|
||||
})
|
||||
const container = document.createElement('div')
|
||||
container.appendChild(triangle)
|
||||
Object.assign(container.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
cursor: 'pointer',
|
||||
marginLeft: 'auto',
|
||||
transform: 'rotate(180deg)',
|
||||
})
|
||||
return container
|
||||
}
|
||||
|
||||
export function addKeyframes() {
|
||||
const styleSheet = document.createElement('style')
|
||||
styleSheet.type = 'text/css'
|
||||
styleSheet.innerText = `@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}`
|
||||
document.head.appendChild(styleSheet)
|
||||
}
|
||||
|
||||
export function createSpinner() {
|
||||
addKeyframes()
|
||||
const spinner = document.createElement('div')
|
||||
spinner.classList.add('spinner')
|
||||
|
||||
Object.assign(spinner.style, spinnerStyles)
|
||||
|
||||
return spinner
|
||||
}
|
||||
|
||||
export function createElement(
|
||||
tag: string,
|
||||
className: string,
|
||||
styles: any,
|
||||
textContent?: string,
|
||||
id?: string,
|
||||
) {
|
||||
const element = document.createElement(tag)
|
||||
element.className = className
|
||||
Object.assign(element.style, styles)
|
||||
if (textContent) {
|
||||
element.textContent = textContent
|
||||
}
|
||||
if (id) {
|
||||
element.id = id
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
export const TEST_START = 'or_uxt_test_start'
|
||||
export const TASK_IND = 'or_uxt_task_index'
|
||||
export const SESSION_ID = 'or_uxt_session_id'
|
||||
export const TEST_ID = 'or_uxt_test_id'
|
||||
104
tracker/tracker/src/tests/signalManager.test.ts
Normal file
104
tracker/tracker/src/tests/signalManager.test.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// @ts-nocheck
|
||||
import SignalManager from '../main/modules/userTesting/SignalManager'
|
||||
import { TEST_START, SESSION_ID } from '../main/modules/userTesting/utils'
|
||||
import { jest, describe, beforeEach, expect, test } from '@jest/globals'
|
||||
|
||||
global.fetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
json: () => Promise.resolve({}),
|
||||
}),
|
||||
)
|
||||
|
||||
const localStorageMock = (() => {
|
||||
let store = {}
|
||||
return {
|
||||
getItem: jest.fn((key) => store[key] || null),
|
||||
setItem: jest.fn((key, value) => {
|
||||
store[key] = value.toString()
|
||||
}),
|
||||
removeItem: jest.fn((key) => {
|
||||
delete store[key]
|
||||
}),
|
||||
clear: jest.fn(() => {
|
||||
store = {}
|
||||
}),
|
||||
}
|
||||
})()
|
||||
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock })
|
||||
|
||||
const ingestPoint = 'https://example.com'
|
||||
const getTimestamp = jest.fn()
|
||||
const token = 'test-token'
|
||||
const testId = 'testId'
|
||||
const storageKey = 'test-storage-key'
|
||||
const setStorageKey = jest.fn()
|
||||
const removeStorageKey = jest.fn()
|
||||
const getStorageKey = jest.fn()
|
||||
const getSessionId = jest.fn()
|
||||
|
||||
let signalManager
|
||||
|
||||
beforeEach(() => {
|
||||
signalManager = new SignalManager(
|
||||
ingestPoint,
|
||||
getTimestamp,
|
||||
token,
|
||||
testId,
|
||||
storageKey,
|
||||
setStorageKey,
|
||||
removeStorageKey,
|
||||
getStorageKey,
|
||||
getSessionId,
|
||||
)
|
||||
|
||||
// Reset mocks
|
||||
fetch.mockClear()
|
||||
localStorageMock.clear()
|
||||
getTimestamp.mockClear()
|
||||
setStorageKey.mockClear()
|
||||
removeStorageKey.mockClear()
|
||||
getStorageKey.mockClear()
|
||||
getSessionId.mockClear()
|
||||
})
|
||||
|
||||
describe('UXT SignalManager tests', () => {
|
||||
test('Constructor initializes durations from local storage', () => {
|
||||
getStorageKey.mockReturnValueOnce('1000')
|
||||
const localSignalManager = new SignalManager(
|
||||
ingestPoint,
|
||||
getTimestamp,
|
||||
token,
|
||||
testId,
|
||||
storageKey,
|
||||
setStorageKey,
|
||||
removeStorageKey,
|
||||
getStorageKey,
|
||||
getSessionId,
|
||||
)
|
||||
expect(localSignalManager.getDurations().testStart).toBe(1000)
|
||||
})
|
||||
|
||||
test('signalTask sends correct data for valid task ID', async () => {
|
||||
getTimestamp.mockReturnValueOnce(2000)
|
||||
signalManager.setDurations({ testStart: 1000, tasks: [{ taskId: 1, started: 1500 }] })
|
||||
await signalManager.signalTask(1, 'done')
|
||||
expect(fetch).toHaveBeenCalledWith(`${ingestPoint}/v1/web/uxt/signals/task`, expect.anything())
|
||||
expect(fetch.mock.calls[0][1].body).toEqual(expect.stringContaining('"taskId":1'))
|
||||
})
|
||||
|
||||
test('signalTask handles missing taskId', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
||||
signalManager.signalTask(0, 'done')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('User Testing: No Task ID Given')
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
test('signalTest sets storage keys on test begin', () => {
|
||||
getTimestamp.mockReturnValueOnce(3000)
|
||||
getSessionId.mockReturnValueOnce('session-id')
|
||||
signalManager.signalTest('begin')
|
||||
expect(setStorageKey).toHaveBeenCalledWith(SESSION_ID, 'session-id')
|
||||
expect(setStorageKey).toHaveBeenCalledWith(TEST_START, '3000')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue