UNPKG

@zeix/cause-effect

Version:

Cause & Effect - reactive state management primitives library for TypeScript.

293 lines (249 loc) 6.59 kB
import { describe, expect, test } from 'bun:test' import { createEffect, createScope, createState, unown, } from '../index.ts' describe('createScope with root option', () => { test('root scope is not registered on enclosing scope', () => { let rootCleanupRan = false const outerDispose = createScope(() => { createScope(() => { return () => { rootCleanupRan = true } }, { root: true }) }) outerDispose() expect(rootCleanupRan).toBe(false) }) test('root scope is not registered on enclosing effect', () => { const trigger = createState(0) let rootCleanupRuns = 0 const outerDispose = createScope(() => { createEffect((): undefined => { trigger.get() createScope(() => { return () => { rootCleanupRuns++ } }, { root: true }) }) }) expect(rootCleanupRuns).toBe(0) trigger.set(1) expect(rootCleanupRuns).toBe(0) trigger.set(2) expect(rootCleanupRuns).toBe(0) outerDispose() }) test('effects inside a root survive outer effect re-runs (Le Truc regression)', () => { const listChange = createState(0) let componentEffectRuns = 0 let componentCleanupRuns = 0 const connectComponent = () => createScope(() => { createEffect((): undefined => { componentEffectRuns++ }) return () => { componentCleanupRuns++ } }, { root: true }) const outerDispose = createScope(() => { createEffect((): undefined => { listChange.get() if (listChange.get() === 0) connectComponent() }) }) expect(componentEffectRuns).toBe(1) expect(componentCleanupRuns).toBe(0) listChange.set(1) expect(componentEffectRuns).toBe(1) expect(componentCleanupRuns).toBe(0) outerDispose() }) test('dispose returned from root scope still works', () => { let cleanupRan = false const dispose = createScope(() => { return () => { cleanupRan = true } }, { root: true }) expect(cleanupRan).toBe(false) dispose() expect(cleanupRan).toBe(true) }) test('effects inside root still run reactively', () => { const source = createState('a') let effectRuns = 0 const dispose = createScope(() => { createEffect((): undefined => { source.get() effectRuns++ }) }, { root: true }) expect(effectRuns).toBe(1) source.set('b') expect(effectRuns).toBe(2) dispose() source.set('c') expect(effectRuns).toBe(2) }) test('activeOwner is restored correctly even when fn throws', () => { let postCleanupRan = false const outerDispose = createScope(() => { try { createScope(() => { throw new Error('boom') }, { root: true }) } catch { // swallow } createScope(() => { return () => { postCleanupRan = true } }) }) outerDispose() expect(postCleanupRan).toBe(true) }) }) describe('unown', () => { test('should return the value of the callback', () => { const result = unown(() => 42) expect(result).toBe(42) }) test('should run the callback immediately and synchronously', () => { let ran = false unown(() => { ran = true }) expect(ran).toBe(true) }) test('scope created inside unown is not registered on the enclosing scope', () => { let innerCleanupRan = false const outerDispose = createScope(() => { unown(() => { createScope(() => { return () => { innerCleanupRan = true } }) }) }) outerDispose() expect(innerCleanupRan).toBe(false) }) test('scope created inside unown is not registered on the enclosing effect', () => { const trigger = createState(0) let innerCleanupRuns = 0 const outerDispose = createScope(() => { createEffect((): undefined => { trigger.get() unown(() => { createScope(() => { return () => { innerCleanupRuns++ } }) }) }) }) expect(innerCleanupRuns).toBe(0) trigger.set(1) expect(innerCleanupRuns).toBe(0) trigger.set(2) expect(innerCleanupRuns).toBe(0) outerDispose() }) test('effects inside an unowned scope survive effect re-runs (ownership bug regression)', () => { const listChange = createState(0) let componentEffectRuns = 0 let componentCleanupRuns = 0 const connectComponent = () => unown(() => createScope(() => { createEffect((): undefined => { componentEffectRuns++ }) return () => { componentCleanupRuns++ } }) ) const outerDispose = createScope(() => { createEffect((): undefined => { listChange.get() if (listChange.get() === 0) connectComponent() }) }) expect(componentEffectRuns).toBe(1) expect(componentCleanupRuns).toBe(0) listChange.set(1) expect(componentEffectRuns).toBe(1) expect(componentCleanupRuns).toBe(0) outerDispose() }) test('effects inside an unowned scope still run reactively', () => { const source = createState('a') let effectRuns = 0 const dispose = unown(() => createScope(() => { createEffect((): undefined => { source.get() effectRuns++ }) }) ) expect(effectRuns).toBe(1) source.set('b') expect(effectRuns).toBe(2) dispose() source.set('c') expect(effectRuns).toBe(2) }) test('dispose returned from an unowned scope still works', () => { let cleanupRan = false const dispose = unown(() => createScope(() => { return () => { cleanupRan = true } }) ) expect(cleanupRan).toBe(false) dispose() expect(cleanupRan).toBe(true) }) test('nested unown calls work correctly', () => { let innerCleanupRan = false const outerDispose = createScope(() => { unown(() => { unown(() => { createScope(() => { return () => { innerCleanupRan = true } }) }) }) }) outerDispose() expect(innerCleanupRan).toBe(false) }) test('restores the active owner after the callback completes', () => { let postCleanupRan = false const outerDispose = createScope(() => { unown(() => { /* some unowned work */ }) createScope(() => { return () => { postCleanupRan = true } }) }) outerDispose() expect(postCleanupRan).toBe(true) }) test('restores the active owner even if the callback throws', () => { let postCleanupRan = false const outerDispose = createScope(() => { try { unown(() => { throw new Error('boom') }) } catch { // swallow } createScope(() => { return () => { postCleanupRan = true } }) }) outerDispose() expect(postCleanupRan).toBe(true) }) test('works correctly when called outside any scope or effect', () => { let ran = false const dispose = unown(() => { ran = true return createScope(() => { return () => {} }) }) expect(ran).toBe(true) expect(typeof dispose).toBe('function') }) })