UNPKG

@tldraw/state

Version:

tldraw infinite canvas SDK (state).

197 lines (149 loc) • 3.71 kB
import { atom } from '../Atom' import { react, reactor } from '../EffectScheduler' import { advanceGlobalEpoch, transact } from '../transactions' describe('reactors', () => { it('can be started and stopped ', () => { const a = atom('', 1) const r = reactor('', () => { a.get() }) expect(r.scheduler.isActivelyListening).toBe(false) r.start() expect(r.scheduler.isActivelyListening).toBe(true) r.stop() expect(r.scheduler.isActivelyListening).toBe(false) r.start() expect(r.scheduler.isActivelyListening).toBe(true) }) it('can not set atom values indefinitely', () => { const a = atom('', 1) const r = reactor('', () => { if (a.get() < +Infinity) { a.update((a) => a + 1) } }) expect(() => r.start()).toThrowErrorMatchingInlineSnapshot( `"Reaction update depth limit exceeded"` ) }) it('will never be called twice after a single state update, even if that update affects multiple atoms to which the reactor is subscribed', () => { const atomA = atom('', 1) const atomB = atom('', 1) const react = jest.fn(() => { atomA.get() atomB.get() }) const r = reactor('', react) r.start() expect(react).toHaveBeenCalledTimes(1) transact(() => { atomA.set(2) atomB.set(2) }) expect(react).toHaveBeenCalledTimes(2) }) it('will not react if stopped', () => { const a = atom('', 1) const react = jest.fn(() => { a.get() }) const r = reactor('', react) r.scheduler.maybeScheduleEffect() expect(react).not.toHaveBeenCalled() }) it('will not react if the parents have not changed', () => { const a = atom('', 1) const react = jest .fn(() => { a.get() }) .mockName('react') const r = reactor('', react) r.start() expect(react).toHaveBeenCalledTimes(1) advanceGlobalEpoch() r.scheduler.maybeScheduleEffect() expect(react).toHaveBeenCalledTimes(1) }) }) describe('stopping', () => { it('works', () => { const a = atom('', 1) const rfn = jest.fn(() => { a.get() }) const r = reactor('', rfn) expect(a.children.isEmpty).toBe(true) r.start() expect(a.children.isEmpty).toBe(false) a.set(8) expect(rfn).toHaveBeenCalledTimes(2) r.stop() expect(a.children.isEmpty).toBe(true) expect(rfn).toHaveBeenCalledTimes(2) a.set(2) expect(rfn).toHaveBeenCalledTimes(2) a.set(3) expect(rfn).toHaveBeenCalledTimes(2) expect(a.children.isEmpty).toBe(true) }) }) test('.start() by default does not trigger a reaction if nothing has changed', () => { const a = atom('', 1) const rfn = jest.fn(() => { a.get() }) const r = reactor('', rfn) r.start() expect(rfn).toHaveBeenCalledTimes(1) r.stop() r.start() expect(rfn).toHaveBeenCalledTimes(1) }) test('.start({force: true}) will trigger a reaction even if nothing has changed', () => { const a = atom('', 1) const rfn = jest.fn(() => { a.get() }) const r = reactor('', rfn) r.start() expect(rfn).toHaveBeenCalledTimes(1) r.stop() r.start({ force: true }) expect(rfn).toHaveBeenCalledTimes(2) }) test('.start with a custom scheduler only schedules an effect, it does not execute it immediately', () => { let numSchedules = 0 let numExecutes = 0 const r = reactor( '', () => { numExecutes++ }, { scheduleEffect: () => { numSchedules++ }, } ) r.start() expect(numSchedules).toBe(1) expect(numExecutes).toBe(0) }) test('react() with a custom scheduler only schedules an effect, it does not execute it immediately', () => { let numSchedules = 0 let numExecutes = 0 react( '', () => { numExecutes++ }, { scheduleEffect: () => { numSchedules++ }, } ) expect(numSchedules).toBe(1) expect(numExecutes).toBe(0) })