@zeix/cause-effect
Version:
Cause & Effect - reactive state management primitives library for TypeScript.
214 lines (197 loc) • 4.79 kB
text/typescript
import { describe, expect, test } from 'bun:test'
import { createEffect, createScope, createState } from '../index.ts'
/* === Tests === */
describe('createScope', () => {
test('should return a dispose function', () => {
const dispose = createScope(() => {})
expect(typeof dispose).toBe('function')
})
test('should run the callback immediately', () => {
let ran = false
createScope(() => {
ran = true
})
expect(ran).toBe(true)
})
test('should call returned cleanup on dispose', () => {
let cleaned = false
const dispose = createScope(() => {
return () => {
cleaned = true
}
})
expect(cleaned).toBe(false)
dispose()
expect(cleaned).toBe(true)
})
test('should dispose child effects', () => {
const source = createState(0)
let count = 0
const dispose = createScope(() => {
createEffect((): undefined => {
source.get()
count++
})
})
expect(count).toBe(1)
source.set(1)
expect(count).toBe(2)
dispose()
source.set(2)
expect(count).toBe(2) // effect should no longer run
})
test('should dispose multiple child effects', () => {
const a = createState(0)
const b = createState(0)
let countA = 0
let countB = 0
const dispose = createScope(() => {
createEffect((): undefined => {
a.get()
countA++
})
createEffect((): undefined => {
b.get()
countB++
})
})
expect(countA).toBe(1)
expect(countB).toBe(1)
dispose()
a.set(1)
b.set(1)
expect(countA).toBe(1)
expect(countB).toBe(1)
})
test('should call returned cleanup and dispose child effects', () => {
const source = createState(0)
let effectCount = 0
let cleaned = false
const dispose = createScope(() => {
createEffect((): undefined => {
source.get()
effectCount++
})
return () => {
cleaned = true
}
})
expect(effectCount).toBe(1)
expect(cleaned).toBe(false)
dispose()
expect(cleaned).toBe(true)
source.set(1)
expect(effectCount).toBe(1)
})
test('should handle nested scopes independently', () => {
const source = createState(0)
let outerCount = 0
let innerCount = 0
let innerDispose!: () => void
const outerDispose = createScope(() => {
createEffect((): undefined => {
source.get()
outerCount++
})
innerDispose = createScope(() => {
createEffect((): undefined => {
source.get()
innerCount++
})
})
})
expect(outerCount).toBe(1)
expect(innerCount).toBe(1)
source.set(1)
expect(outerCount).toBe(2)
expect(innerCount).toBe(2)
// disposing inner scope should not affect outer
innerDispose()
source.set(2)
expect(outerCount).toBe(3)
expect(innerCount).toBe(2)
// disposing outer scope should have no further effect
outerDispose()
source.set(3)
expect(outerCount).toBe(3)
expect(innerCount).toBe(2)
})
test('should dispose nested scopes when parent is disposed', () => {
const source = createState(0)
let innerCount = 0
const outerDispose = createScope(() => {
createScope(() => {
createEffect((): undefined => {
source.get()
innerCount++
})
})
})
expect(innerCount).toBe(1)
source.set(1)
expect(innerCount).toBe(2)
// disposing outer should also dispose inner
outerDispose()
source.set(2)
expect(innerCount).toBe(2)
})
test('should call nested cleanup functions on parent dispose', () => {
let outerCleaned = false
let innerCleaned = false
const dispose = createScope(() => {
createScope(() => {
return () => {
innerCleaned = true
}
})
return () => {
outerCleaned = true
}
})
expect(outerCleaned).toBe(false)
expect(innerCleaned).toBe(false)
dispose()
expect(outerCleaned).toBe(true)
expect(innerCleaned).toBe(true)
})
test('should be safe to call dispose multiple times', () => {
let cleanCount = 0
const dispose = createScope(() => {
return () => {
cleanCount++
}
})
dispose()
expect(cleanCount).toBe(1)
dispose()
// cleanup should only run once since it's nulled after first run
expect(cleanCount).toBe(1)
})
test('should handle scope with no cleanup return', () => {
const dispose = createScope(() => {
// no return
})
expect(() => dispose()).not.toThrow()
})
test('should dispose child effects even when fn() throws', () => {
const source = createState(0)
let count = 0
const outerDispose = createScope(() => {
try {
createScope(() => {
createEffect((): undefined => {
source.get()
count++
})
throw new Error('oops')
})
} catch (_) {}
})
expect(count).toBe(1) // effect ran once during setup
source.set(1)
expect(count).toBe(2) // effect re-runs before outer dispose
outerDispose()
source.set(2)
expect(count).toBe(2) // effect must NOT re-run after outer dispose
})
})