mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
Mechanically re-derived: bin/debrand runs over a fresh import of posthog/posthog@fa1a9a60d4 (2026-08-06). The pipeline (.hanzo/workflows), the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md and the squashed initial migration are restored from the shipped tree, same as the previous import. The prune's import-closure guard kept 21 products the new core now imports; dropping them is a refactor now, recorded rather than forced.
213 lines
7.3 KiB
TypeScript
213 lines
7.3 KiB
TypeScript
import 'jest-canvas-mock'
|
|
|
|
import { configure } from '@testing-library/react'
|
|
import { TextDecoder, TextEncoder } from 'util'
|
|
import { deserialize, serialize } from 'v8'
|
|
|
|
// Jest/JSDom don't know about TextEncoder but the browsers we support do
|
|
// https://github.com/jsdom/jsdom/issues/2524
|
|
global.TextDecoder = TextDecoder as any
|
|
global.TextEncoder = TextEncoder as any
|
|
|
|
window.scrollTo = jest.fn()
|
|
window.matchMedia = jest.fn(
|
|
(query: string) =>
|
|
({
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addListener: jest.fn(),
|
|
removeListener: jest.fn(),
|
|
addEventListener: jest.fn(),
|
|
removeEventListener: jest.fn(),
|
|
dispatchEvent: jest.fn(),
|
|
}) as any
|
|
)
|
|
|
|
type StorageLike = {
|
|
getItem: (key: string) => string | null
|
|
setItem: (key: string, value: string) => void
|
|
removeItem: (key: string) => void
|
|
clear: () => void
|
|
}
|
|
|
|
const createInMemoryStorage = (): StorageLike => {
|
|
const store = new Map<string, string>()
|
|
return {
|
|
getItem: (key) => (store.has(key) ? store.get(key)! : null),
|
|
setItem: (key, value) => {
|
|
store.set(key, String(value))
|
|
},
|
|
removeItem: (key) => {
|
|
store.delete(key)
|
|
},
|
|
clear: () => {
|
|
store.clear()
|
|
},
|
|
}
|
|
}
|
|
|
|
// Some Jest/JSDom environments might not provide an unqualified `sessionStorage`
|
|
// binding. `sceneLogic` and other Kea logic reference `sessionStorage` as a
|
|
// free variable, so we need to ensure the identifier exists.
|
|
if (typeof sessionStorage === 'undefined') {
|
|
const sessionStorageStub = createInMemoryStorage()
|
|
;(globalThis as any).sessionStorage = sessionStorageStub
|
|
;(window as any).sessionStorage = sessionStorageStub
|
|
;(global as any).sessionStorage = sessionStorageStub
|
|
}
|
|
|
|
if (typeof localStorage === 'undefined') {
|
|
const localStorageStub = createInMemoryStorage()
|
|
;(globalThis as any).localStorage = localStorageStub
|
|
;(window as any).localStorage = localStorageStub
|
|
;(global as any).localStorage = localStorageStub
|
|
}
|
|
|
|
// jsdom does not implement structuredClone — polyfill via Node's v8 serializer
|
|
if (typeof globalThis.structuredClone !== 'function') {
|
|
globalThis.structuredClone = <T>(value: T): T => deserialize(serialize(value))
|
|
}
|
|
|
|
// jsdom does not implement AbortSignal.timeout — polyfill for tests
|
|
if (typeof AbortSignal.timeout !== 'function') {
|
|
AbortSignal.timeout = (ms: number): AbortSignal => {
|
|
const controller = new AbortController()
|
|
setTimeout(() => controller.abort(new DOMException('TimeoutError', 'TimeoutError')), ms)
|
|
return controller.signal
|
|
}
|
|
}
|
|
|
|
// Base UI's ScrollArea calls getAnimations() which jsdom doesn't support
|
|
if (typeof Element.prototype.getAnimations !== 'function') {
|
|
Element.prototype.getAnimations = () => []
|
|
}
|
|
|
|
// Menu calls scrollIntoView which jsdom doesn't support
|
|
if (typeof Element.prototype.scrollIntoView !== 'function') {
|
|
Element.prototype.scrollIntoView = () => {}
|
|
}
|
|
|
|
// maplibre-gl probes window.URL.createObjectURL at module-eval time, but jsdom
|
|
// doesn't implement it. Provide a noop so importing modules that transitively
|
|
// pull in maplibre-gl (e.g. NotebookNodeMap) doesn't throw.
|
|
if (typeof window !== 'undefined' && typeof window.URL.createObjectURL !== 'function') {
|
|
window.URL.createObjectURL = () => ''
|
|
window.URL.revokeObjectURL = () => {}
|
|
}
|
|
|
|
// we use CSS.escape in the toolbar, but Jest/JSDom doesn't support it
|
|
if (typeof (globalThis as any).CSS === 'undefined') {
|
|
;(globalThis as any).CSS = {}
|
|
}
|
|
|
|
if (typeof (globalThis as any).CSS.escape !== 'function') {
|
|
;(globalThis as any).CSS.escape = (value: string) => value
|
|
}
|
|
|
|
const mockIntersectionObserver = jest.fn()
|
|
mockIntersectionObserver.mockReturnValue({
|
|
observe: () => null,
|
|
unobserve: () => null,
|
|
disconnect: () => null,
|
|
})
|
|
;(globalThis as any).IntersectionObserver = mockIntersectionObserver
|
|
|
|
const mockResizeObserver = jest.fn()
|
|
mockResizeObserver.mockReturnValue({
|
|
observe: () => null,
|
|
unobserve: () => null,
|
|
disconnect: () => null,
|
|
})
|
|
;(globalThis as any).ResizeObserver = mockResizeObserver
|
|
|
|
// jsdom has no layout, so hit-testing at a point can't return anything
|
|
document.elementsFromPoint = document.elementsFromPoint ?? ((): Element[] => [])
|
|
|
|
// Tell React Testing Library to use "data-attr" as the test ID attribute
|
|
configure({ testIdAttribute: 'data-attr' })
|
|
|
|
// Mock DecompressionWorkerManager globally to avoid import.meta.url issues in tests
|
|
jest.mock('scenes/session-recordings/player/snapshot-processing/DecompressionWorkerManager')
|
|
|
|
// Mock ConcurrencyController to be a pass-through. The module-level singleton in
|
|
// dataNodeLogic persists across test files on the same Jest worker (resetModules is
|
|
// disabled). Under CI load, stale items can block subsequent tests' queries indefinitely.
|
|
// Tests don't need concurrency limiting, so we bypass it entirely.
|
|
jest.mock('lib/utils/concurrencyController', () => {
|
|
class ConcurrencyController {
|
|
run = <T>({ fn }: { fn: () => Promise<T> }): Promise<T> => fn()
|
|
setConcurrencyLimit = (): void => {}
|
|
}
|
|
return { ConcurrencyController }
|
|
})
|
|
|
|
// Mock insights-js surveys-preview to avoid ESM import issues in tests
|
|
jest.mock('insights-js/dist/surveys-preview', () => ({
|
|
renderFeedbackWidgetPreview: jest.fn(),
|
|
renderSurveysPreview: jest.fn(),
|
|
getNextSurveyStep: jest.fn(),
|
|
}))
|
|
|
|
// Mock insights-js product-tours-preview to avoid ESM import issues in tests
|
|
jest.mock('insights-js/dist/product-tours-preview', () => ({
|
|
renderProductTourPreview: jest.fn(),
|
|
}))
|
|
|
|
// Mock insights-js element-inference to avoid ESM import issues in tests
|
|
jest.mock('insights-js/dist/element-inference', () => ({
|
|
findElement: jest.fn(),
|
|
getElementPath: jest.fn(),
|
|
}))
|
|
|
|
// Mock insights-js to avoid issues in tests
|
|
jest.mock('insights-js', () => {
|
|
// Get the actual module to preserve type exports (enums, etc.)
|
|
const actual = jest.requireActual('insights-js')
|
|
|
|
const mock: Record<string, any> = {
|
|
capture: jest.fn(),
|
|
captureException: jest.fn(),
|
|
captureRaw: jest.fn(),
|
|
opt_in_capturing: jest.fn(),
|
|
identify: jest.fn(),
|
|
getFeatureFlag: jest.fn(),
|
|
getFeatureFlagPayload: jest.fn(),
|
|
getFeatureFlagResult: jest.fn(),
|
|
getAllFlags: jest.fn(),
|
|
isFeatureEnabled: jest.fn(),
|
|
getEarlyAccessFeatures: jest.fn(),
|
|
getSurveys: jest.fn(),
|
|
onFeatureFlags: jest.fn(() => () => {}),
|
|
debug: jest.fn(),
|
|
get_session_id: jest.fn(),
|
|
get_session_replay_url: jest.fn(),
|
|
get_distinct_id: jest.fn(),
|
|
register: jest.fn(),
|
|
reset: jest.fn(),
|
|
group: jest.fn(),
|
|
updateEarlyAccessFeatureEnrollment: jest.fn(),
|
|
people: { set: jest.fn() },
|
|
featureFlags: { override: jest.fn() },
|
|
metrics: { count: jest.fn(), gauge: jest.fn(), histogram: jest.fn() },
|
|
}
|
|
mock.init = jest.fn(() => mock)
|
|
|
|
// Return mock functions but preserve actual type exports
|
|
return { ...actual, __esModule: true, default: mock, insights: mock }
|
|
})
|
|
|
|
jest.mock('@tiptap/extension-code-block-lowlight', () => {
|
|
const mockExtension = {
|
|
configure: jest.fn(() => ({})),
|
|
extend: jest.fn(() => ({
|
|
configure: jest.fn(() => ({})),
|
|
})),
|
|
}
|
|
return {
|
|
__esModule: true,
|
|
default: mockExtension,
|
|
CodeBlockLowlight: mockExtension,
|
|
}
|
|
})
|