UNPKG

@zeix/cause-effect

Version:

Cause & Effect - reactive state management with signals.

223 lines (176 loc) 5.77 kB
import { describe, test, expect } from 'bun:test' import { isComputed, isState, state, UNSET } from '../' /* === Utility Functions === */ const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) /* === Tests === */ describe('State', function () { describe("State type guard", () => { test("isState identifies state signals", () => { const count = state(42) expect(isState(count)).toBe(true) expect(isComputed(count)).toBe(false) }) }) describe('Boolean cause', function () { test('should be boolean', function () { const cause = state(false) expect(typeof cause.get()).toBe('boolean') }) test('should set initial value to false', function () { const cause = state(false) expect(cause.get()).toBe(false) }) test('should set initial value to true', function () { const cause = state(true) expect(cause.get()).toBe(true) }) test('should set new value with .set(true)', function () { const cause = state(false) cause.set(true) expect(cause.get()).toBe(true) }) test('should toggle initial value with .set(v => !v)', function () { const cause = state(false) cause.update((v) => !v) expect(cause.get()).toBe(true) }) }) describe('Number cause', function () { test('should be number', function () { const cause = state(0) expect(typeof cause.get()).toBe('number') }) test('should set initial value to 0', function () { const cause = state(0) expect(cause.get()).toBe(0) }) test('should set new value with .set(42)', function () { const cause = state(0) cause.set(42) expect(cause.get()).toBe(42) }) test('should increment value with .set(v => ++v)', function () { const cause = state(0) cause.update(v => ++v) expect(cause.get()).toBe(1) }) }) describe('String cause', function () { test('should be string', function () { const cause = state('foo') expect(typeof cause.get()).toBe('string') }) test('should set initial value to "foo"', function () { const cause = state('foo') expect(cause.get()).toBe('foo') }) test('should set new value with .set("bar")', function () { const cause = state('foo') cause.set('bar') expect(cause.get()).toBe('bar') }) test('should upper case value with .set(v => v.toUpperCase())', function () { const cause = state('foo') cause.update(v => v ? v.toUpperCase() : '') expect(cause.get()).toBe("FOO") }) }) describe('Array cause', function () { test('should be array', function () { const cause = state([1, 2, 3]) expect(Array.isArray(cause.get())).toBe(true) }) test('should set initial value to [1, 2, 3]', function () { const cause = state([1, 2, 3]) expect(cause.get()).toEqual([1, 2, 3]) }) test('should set new value with .set([4, 5, 6])', function () { const cause = state([1, 2, 3]) cause.set([4, 5, 6]) expect(cause.get()).toEqual([4, 5, 6]) }) test('should reflect current value of array after modification', function () { const array = [1, 2, 3] const cause = state(array) array.push(4); // don't do this! the result will be correct, but we can't trigger effects expect(cause.get()).toEqual([1, 2, 3, 4]) }) test('should set new value with .set([...array, 4])', function () { const array = [1, 2, 3] const cause = state(array) cause.set([...array, 4]); // use destructuring instead! expect(cause.get()).toEqual([1, 2, 3, 4]) }) }) describe('Object cause', function () { test('should be object', function () { const cause = state({ a: 'a', b: 1 }) expect(typeof cause.get()).toBe('object') }) test('should set initial value to { a: "a", b: 1 }', function () { const cause = state({ a: 'a', b: 1 }) expect(cause.get()).toEqual({ a: 'a', b: 1 }) }) test('should set new value with .set({ c: true })', function () { const cause = state<Record<string, any>>({ a: 'a', b: 1 }) cause.set({ c: true }) expect(cause.get()).toEqual({ c: true }) }) test('should reflect current value of object after modification', function () { const obj = { a: 'a', b: 1 } const cause = state<Record<string, any>>(obj) // @ts-expect-error obj.c = true; // don't do this! the result will be correct, but we can't trigger effects expect(cause.get()).toEqual({ a: 'a', b: 1, c: true }) }) test('should set new value with .set({...obj, c: true})', function () { const obj = { a: 'a', b: 1 } const cause = state<Record<string, any>>(obj) cause.set({...obj, c: true}); // use destructuring instead! expect(cause.get()).toEqual({ a: 'a', b: 1, c: true }) }) }) describe('Map method', function () { test('should return a computed signal', function() { const cause = state(42) const double = cause.map(v => v * 2) expect(isComputed(double)).toBe(true) expect(double.get()).toBe(84) }) test('should return a computed signal for an async function', async function() { const cause = state(42) const asyncDouble = cause.map(async value => { await wait(100) return value * 2 }) expect(isComputed(asyncDouble)).toBe(true) expect(asyncDouble.get()).toBe(UNSET) await wait(110) expect(asyncDouble.get()).toBe(84) }) }) describe('Tap method', function () { test('should create an effect that reacts on signal changes', function() { const cause = state(42) let okCount = 0 let nilCount = 0 let result = 0 cause.tap({ ok: v => { result = v okCount++ }, nil: () => { nilCount++ } }) cause.set(43) expect(okCount).toBe(2); // + 1 for effect initialization expect(nilCount).toBe(0) expect(result).toBe(43) cause.set(UNSET) expect(okCount).toBe(2) expect(nilCount).toBe(1) }) }) });