UNPKG

@zeix/cause-effect

Version:

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

284 lines (240 loc) 7.78 kB
import { describe, expect, test } from 'bun:test' import { createComputed, createList, createMemo, createMutableSignal, createScope, createSignal, createSlot, createState, createStore, createTask, InvalidSignalValueError, isComputed, isList, isMemo, isMutableSignal, isSignal, isState, isStore, isTask, type List, type Memo, type Signal, type State, type Store, type Task, } from '../index.ts' /* === Tests === */ describe('createComputed', () => { test('creates a Memo from a sync callback', () => { const count = createState(2) const doubled = createComputed(() => count.get() * 2) expect(isMemo(doubled)).toBe(true) expect(doubled.get()).toBe(4) const typedResult: Memo<number> = doubled expect(typedResult).toBeDefined() }) test('creates a Task from an async callback', () => { const cleanup = createScope(() => { const result = createComputed(async () => 'hello') expect(isTask(result)).toBe(true) const typedResult: Task<string> = result expect(typedResult).toBeDefined() }) cleanup() }) }) describe('createSignal', () => { test('converts a primitive to State', () => { const result = createSignal(42) expect(isState(result)).toBe(true) expect(result.get()).toBe(42) const typedResult: State<number> = result expect(typedResult).toBeDefined() }) test('converts a non-plain object to State', () => { const date = new Date('2024-01-01') const result = createSignal(date) expect(isState(result)).toBe(true) expect(result.get()).toBe(date) const typedResult: State<Date> = result expect(typedResult).toBeDefined() }) test('converts a record to Store', () => { const result = createSignal({ name: 'Alice', age: 30 }) expect(isStore(result)).toBe(true) expect(result.name.get()).toBe('Alice') expect(result.age.get()).toBe(30) const typedResult: Store<{ name: string; age: number }> = result expect(typedResult).toBeDefined() }) test('converts an array to List', () => { const result = createSignal([ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ]) expect(isList(result)).toBe(true) expect(result.at(0)?.get()).toEqual({ id: 1, name: 'Alice' }) expect(result.at(1)?.get()).toEqual({ id: 2, name: 'Bob' }) const typedResult: List<{ id: number; name: string }> = result expect(typedResult).toBeDefined() }) test('converts an empty array to List', () => { const result = createSignal([]) expect(isList(result)).toBe(true) expect(result.length).toBe(0) }) test('converts a sync function to Memo', () => { const result = createSignal(() => Math.random()) expect(isMemo(result)).toBe(true) expect(typeof result.get()).toBe('number') const typedResult: Memo<number> = result expect(typedResult).toBeDefined() }) test('converts an async function to Task', () => { const cleanup = createScope(() => { const result = createSignal(async () => 'hello') expect(isTask(result)).toBe(true) const typedResult: Task<string> = result expect(typedResult).toBeDefined() }) cleanup() }) test('passes through an existing signal without wrapping', () => { const state = createState(42) expect(createSignal(state)).toBe(state) const memo = createMemo(() => 'hello') expect(createSignal(memo)).toBe(memo) const store = createStore({ a: 1 }) expect(createSignal(store)).toBe(store) const list = createList([1, 2, 3]) expect(createSignal(list)).toBe(list) }) test('throws InvalidSignalValueError for null', () => { // biome-ignore lint/suspicious/noExplicitAny: testing invalid input expect(() => createSignal(null as any)).toThrow(InvalidSignalValueError) }) test('throws InvalidSignalValueError for undefined', () => { // biome-ignore lint/suspicious/noExplicitAny: testing invalid input expect(() => createSignal(undefined as any)).toThrow( InvalidSignalValueError, ) }) }) describe('createMutableSignal', () => { test('converts a primitive to State', () => { const result = createMutableSignal(42) expect(isState(result)).toBe(true) expect(result.get()).toBe(42) }) test('converts a record to Store', () => { const result = createMutableSignal({ name: 'Alice' }) expect(isStore(result)).toBe(true) }) test('converts an array to List', () => { const result = createMutableSignal([1, 2, 3]) expect(isList(result)).toBe(true) }) test('passes through an existing mutable signal without wrapping', () => { const state = createState(42) expect(createMutableSignal(state)).toBe(state) const store = createStore({ a: 1 }) expect(createMutableSignal(store)).toBe(store) const list = createList([1, 2, 3]) expect(createMutableSignal(list)).toBe(list) }) test('throws InvalidSignalValueError for null', () => { // biome-ignore lint/suspicious/noExplicitAny: testing invalid input expect(() => createMutableSignal(null as any)).toThrow( InvalidSignalValueError, ) }) test('throws InvalidSignalValueError for a function', () => { // biome-ignore lint/suspicious/noExplicitAny: testing invalid input expect(() => createMutableSignal((() => 42) as any)).toThrow( InvalidSignalValueError, ) }) test('throws InvalidSignalValueError for a read-only signal', () => { const memo = createMemo(() => 42) // biome-ignore lint/suspicious/noExplicitAny: testing invalid input expect(() => createMutableSignal(memo as any)).toThrow( InvalidSignalValueError, ) }) }) describe('isComputed', () => { test('returns true for Memo', () => { expect(isComputed(createMemo(() => 42))).toBe(true) }) test('returns true for Task', () => { const cleanup = createScope(() => { expect(isComputed(createTask(async () => 42))).toBe(true) }) cleanup() }) test('returns false for State', () => { expect(isComputed(createState(42))).toBe(false) }) test('returns false for non-signals', () => { expect(isComputed(42)).toBe(false) expect(isComputed('hello')).toBe(false) expect(isComputed(null)).toBe(false) }) }) describe('isSignal', () => { test('returns true for all signal types', () => { const cleanup = createScope(() => { expect(isSignal(createState(42))).toBe(true) expect(isSignal(createMemo(() => 42))).toBe(true) expect(isSignal(createTask(async () => 42))).toBe(true) expect(isSignal(createStore({ a: 1 }))).toBe(true) expect(isSignal(createList([1, 2, 3]))).toBe(true) expect(isSignal(createSlot(createState(1)))).toBe(true) }) cleanup() }) test('returns false for non-signals', () => { expect(isSignal(42)).toBe(false) expect(isSignal('hello')).toBe(false) expect(isSignal({ get: () => 42 })).toBe(false) expect(isSignal(null)).toBe(false) expect(isSignal(undefined)).toBe(false) }) }) describe('isMutableSignal', () => { test('returns true for State, Store, and List', () => { expect(isMutableSignal(createState(42))).toBe(true) expect(isMutableSignal(createStore({ a: 1 }))).toBe(true) expect(isMutableSignal(createList([1, 2, 3]))).toBe(true) }) test('returns false for read-only signals', () => { const cleanup = createScope(() => { expect(isMutableSignal(createMemo(() => 42))).toBe(false) expect(isMutableSignal(createTask(async () => 42))).toBe(false) }) cleanup() }) test('returns false for non-signals', () => { expect(isMutableSignal(42)).toBe(false) expect(isMutableSignal(null)).toBe(false) }) }) describe('Signal compatibility', () => { test('all signal factory results implement Signal<T>', () => { const cleanup = createScope(() => { const signals: Signal<unknown & {}>[] = [ createSignal(42), createSignal({ a: 1 }), createSignal([1, 2, 3]), createSignal(() => 'hello'), ] for (const signal of signals) { expect(typeof signal.get).toBe('function') } }) cleanup() }) })