fix(review): cover markup crypto fallback call sites with regression tests

PR #8804 replaced two unguarded crypto.randomUUID() calls with
createBrowserUuid(), matching the convention established in #5471, but
shipped without extending that PR's regression-test pattern to the new
call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown
and useMarkupEditor.commitPendingText under a simulated non-secure-context
crypto global.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinjing
2026-07-15 16:47:07 -07:00
co-authored by Orca
parent 93d4938417
commit d4cf98fdf0
2 changed files with 110 additions and 0 deletions
@@ -0,0 +1,47 @@
// @vitest-environment happy-dom
import { act, renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useMarkupEditor } from './useMarkupEditor'
const realCrypto = globalThis.crypto
function pointerDownEvent(clientX: number, clientY: number): React.PointerEvent<HTMLCanvasElement> {
return {
button: 0,
clientX,
clientY,
currentTarget: { setPointerCapture: vi.fn() },
pointerId: 1,
preventDefault: vi.fn()
} as unknown as React.PointerEvent<HTMLCanvasElement>
}
describe('useMarkupEditor.commitPendingText in a non-secure browser context', () => {
beforeEach(() => {
// Match a non-secure browser context (LAN web client over plain HTTP):
// getRandomValues stays, randomUUID is undefined.
Object.defineProperty(globalThis, 'crypto', {
configurable: true,
value: { getRandomValues: realCrypto.getRandomValues.bind(realCrypto) }
})
})
afterEach(() => {
Object.defineProperty(globalThis, 'crypto', { configurable: true, value: realCrypto })
})
it('commits a text shape with a valid id instead of throwing', () => {
const { result } = renderHook(() => useMarkupEditor(false, vi.fn()))
act(() => result.current.setTool('text'))
act(() => result.current.onPointerDown(pointerDownEvent(5, 5)))
expect(result.current.pendingText).not.toBeNull()
expect(() => act(() => result.current.commitPendingText('hello'))).not.toThrow()
const shape = result.current.shapes.at(-1)
expect(shape?.kind).toBe('text')
expect(shape?.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/)
})
})
@@ -0,0 +1,63 @@
// @vitest-environment happy-dom
import { act, renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useMarkupPointerHandlers, type MarkupPointerParams } from './useMarkupPointerHandlers'
import type { MarkupShape } from './markup-drawing-model'
const realCrypto = globalThis.crypto
function pointerDownEvent(clientX: number, clientY: number): React.PointerEvent<HTMLCanvasElement> {
return {
button: 0,
clientX,
clientY,
currentTarget: { setPointerCapture: vi.fn() },
pointerId: 1
} as unknown as React.PointerEvent<HTMLCanvasElement>
}
function baseParams(overrides: Partial<MarkupPointerParams> = {}): MarkupPointerParams {
return {
busy: false,
tool: 'pen',
color: '#ef4444',
width: 4,
pendingText: null,
canvasRef: {
current: {
getBoundingClientRect: () => ({ left: 0, top: 0 })
} as unknown as HTMLCanvasElement
},
setInProgress: vi.fn(),
setPendingText: vi.fn(),
setDoc: vi.fn(),
...overrides
}
}
describe('useMarkupPointerHandlers in a non-secure browser context', () => {
beforeEach(() => {
// Match a non-secure browser context (LAN web client over plain HTTP):
// getRandomValues stays, randomUUID is undefined.
Object.defineProperty(globalThis, 'crypto', {
configurable: true,
value: { getRandomValues: realCrypto.getRandomValues.bind(realCrypto) }
})
})
afterEach(() => {
Object.defineProperty(globalThis, 'crypto', { configurable: true, value: realCrypto })
})
it('starts a pen stroke with a valid id instead of throwing', () => {
const setInProgress = vi.fn()
const { result } = renderHook(() => useMarkupPointerHandlers(baseParams({ setInProgress })))
expect(() => act(() => result.current.onPointerDown(pointerDownEvent(10, 10)))).not.toThrow()
expect(setInProgress).toHaveBeenCalledTimes(1)
const shape = setInProgress.mock.calls[0][0] as MarkupShape
expect(shape.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/)
})
})