@tldraw/store
Version:
tldraw infinite canvas SDK (store).
142 lines (117 loc) • 4.35 kB
text/typescript
import { afterAll, beforeEach, describe, expect, it, MockedFunction, vi } from 'vitest'
import { devFreeze } from './devFreeze'
import { isDev } from './isDev'
// Mock process.env for testing
const originalEnv = process.env.NODE_ENV
vi.mock('./isDev', () => ({
isDev: vi.fn(() => true),
}))
describe('devFreeze', () => {
beforeEach(() => {
// Reset any mocks
vi.restoreAllMocks()
})
describe('production mode behavior', () => {
beforeEach(() => {
// Mock production environment
;(isDev as MockedFunction<typeof isDev>).mockReturnValue(false)
})
it('should return objects unchanged in production mode', () => {
const obj = { a: 1, b: { c: 2 } }
const result = devFreeze(obj)
expect(result).toBe(obj) // Same reference
expect(Object.isFrozen(result)).toBe(false)
expect(Object.isFrozen(result.b)).toBe(false)
})
it('should not validate prototypes in production mode', () => {
const _consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
// Create object with custom prototype that would normally throw
class CustomClass {
value = 42
}
const obj = new CustomClass()
expect(() => devFreeze(obj)).not.toThrow()
expect(_consoleSpy).not.toHaveBeenCalled()
})
})
describe('development mode behavior', () => {
beforeEach(() => {
// Mock development environment
vi.stubGlobal('process', { env: { NODE_ENV: 'development' } })
})
it('should freeze objects recursively', () => {
const obj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
},
},
f: [1, { g: 4 }],
}
const result = devFreeze(obj)
expect(result).toBe(obj) // Same reference
expect(Object.isFrozen(result)).toBe(true)
expect(Object.isFrozen(result.b)).toBe(true)
expect(Object.isFrozen(result.b.d)).toBe(true)
expect(Object.isFrozen(result.f)).toBe(true)
expect(Object.isFrozen(result.f[1])).toBe(true)
})
it('should reject primitives', () => {
const _consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
// Primitives have built-in prototypes that aren't allowed
expect(() => devFreeze('string')).toThrow('cannot include non-js data in a record')
expect(() => devFreeze(42)).toThrow('cannot include non-js data in a record')
expect(() => devFreeze(true)).toThrow('cannot include non-js data in a record')
// null and undefined cause TypeError when Object.getPrototypeOf is called
expect(() => devFreeze(null)).toThrow('Cannot convert undefined or null to object')
expect(() => devFreeze(undefined)).toThrow('Cannot convert undefined or null to object')
})
it('should allow valid prototypes', () => {
// Object.prototype
const obj = { a: 1 }
expect(() => devFreeze(obj)).not.toThrow()
expect(Object.isFrozen(obj)).toBe(true)
// null prototype
const nullProtoObj = Object.create(null)
nullProtoObj.a = 1
expect(() => devFreeze(nullProtoObj)).not.toThrow()
expect(Object.isFrozen(nullProtoObj)).toBe(true)
// Arrays
const arr = [1, 2, 3]
expect(() => devFreeze(arr)).not.toThrow()
expect(Object.isFrozen(arr)).toBe(true)
// structuredClone objects
const cloned = structuredClone({ a: 1 })
expect(() => devFreeze(cloned)).not.toThrow()
expect(Object.isFrozen(cloned)).toBe(true)
})
it('should reject invalid prototypes', () => {
const _consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
// Custom class instances
class CustomClass {
value = 42
}
expect(() => devFreeze(new CustomClass())).toThrow('cannot include non-js data in a record')
// Built-in object types
expect(() => devFreeze(new Date())).toThrow('cannot include non-js data in a record')
expect(() => devFreeze(/regex/)).toThrow('cannot include non-js data in a record')
expect(() => devFreeze(new Map())).toThrow('cannot include non-js data in a record')
expect(() => devFreeze(new Set())).toThrow('cannot include non-js data in a record')
// Nested invalid objects
const objWithInvalidNested = {
valid: { a: 1 },
invalid: new Date(),
}
expect(() => devFreeze(objWithInvalidNested)).toThrow(
'cannot include non-js data in a record'
)
})
})
// Clean up after all tests
afterAll(() => {
// Restore original environment
vi.stubGlobal('process', { env: { NODE_ENV: originalEnv } })
})
})