UNPKG

@tldraw/state

Version:

tldraw infinite canvas SDK (state).

241 lines (187 loc) • 6.46 kB
import { ArraySet } from '../ArraySet' import { atom } from '../Atom' import { computed } from '../Computed' import { react } from '../EffectScheduler' import { maybeCaptureParent, startCapturingParents, stopCapturingParents, unsafe__withoutCapture, } from '../capture' import { advanceGlobalEpoch, getGlobalEpoch } from '../transactions' import { Child } from '../types' const emptyChild = (props: Partial<Child> = {}) => ({ parentEpochs: [], parents: [], parentSet: new ArraySet(), isActivelyListening: false, lastTraversedEpoch: 0, ...props, }) as Child describe('capturing parents', () => { it('can be started and stopped', () => { const a = atom('', 1) const startEpoch = getGlobalEpoch() const child = emptyChild() const originalParentEpochs = child.parentEpochs const originalParents = child.parents startCapturingParents(child) maybeCaptureParent(a) stopCapturingParents() // the parents should be kept because no sharing is possible and we don't want to reallocate // when parents change expect(child.parentEpochs).toBe(originalParentEpochs) expect(child.parents).toBe(originalParents) expect(child.parentEpochs).toEqual([startEpoch]) expect(child.parents).toEqual([a]) }) it('can handle several parents', () => { const atomA = atom('', 1) const atomAEpoch = getGlobalEpoch() advanceGlobalEpoch() // let's say time has passed const atomB = atom('', 1) const atomBEpoch = getGlobalEpoch() advanceGlobalEpoch() // let's say time has passed const atomC = atom('', 1) const atomCEpoch = getGlobalEpoch() expect(atomAEpoch < atomBEpoch).toBe(true) expect(atomBEpoch < atomCEpoch).toBe(true) const child = emptyChild() const originalParentEpochs = child.parentEpochs const originalParents = child.parents startCapturingParents(child) maybeCaptureParent(atomA) maybeCaptureParent(atomB) maybeCaptureParent(atomC) stopCapturingParents() // the parents should be kept because no sharing is possible and we don't want to reallocate // when parents change expect(child.parentEpochs).toBe(originalParentEpochs) expect(child.parents).toBe(originalParents) expect(child.parentEpochs).toEqual([atomAEpoch, atomBEpoch, atomCEpoch]) expect(child.parents).toEqual([atomA, atomB, atomC]) }) it('will reorder if parents are captured in different orders each time', () => { const atomA = atom('', 1) advanceGlobalEpoch() // let's say time has passed const atomB = atom('', 1) advanceGlobalEpoch() // let's say time has passed const atomC = atom('', 1) const child = emptyChild() startCapturingParents(child) maybeCaptureParent(atomA) maybeCaptureParent(atomB) maybeCaptureParent(atomC) stopCapturingParents() expect(child.parents).toEqual([atomA, atomB, atomC]) startCapturingParents(child) maybeCaptureParent(atomB) maybeCaptureParent(atomA) maybeCaptureParent(atomC) stopCapturingParents() expect(child.parents).toEqual([atomB, atomA, atomC]) startCapturingParents(child) maybeCaptureParent(atomA) maybeCaptureParent(atomC) maybeCaptureParent(atomB) stopCapturingParents() expect(child.parents).toEqual([atomA, atomC, atomB]) }) it('will shrink the parent arrays if the number of captured parents shrinks', () => { const atomA = atom('', 1) const atomAEpoch = getGlobalEpoch() advanceGlobalEpoch() // let's say time has passed const atomB = atom('', 1) const atomBEpoch = getGlobalEpoch() advanceGlobalEpoch() // let's say time has passed const atomC = atom('', 1) const atomCEpoch = getGlobalEpoch() const child = emptyChild() const originalParents = child.parents const originalParentEpochs = child.parentEpochs startCapturingParents(child) maybeCaptureParent(atomA) maybeCaptureParent(atomB) maybeCaptureParent(atomC) stopCapturingParents() expect(child.parents).toEqual([atomA, atomB, atomC]) expect(child.parents).toBe(originalParents) startCapturingParents(child) maybeCaptureParent(atomB) maybeCaptureParent(atomA) stopCapturingParents() expect(child.parents).toEqual([atomB, atomA]) expect(child.parentEpochs).toEqual([atomBEpoch, atomAEpoch]) expect(child.parents).toBe(originalParents) startCapturingParents(child) stopCapturingParents() expect(child.parents).toEqual([]) expect(child.parentEpochs).toEqual([]) expect(child.parents).toBe(originalParents) expect(child.parentEpochs).toBe(originalParentEpochs) startCapturingParents(child) maybeCaptureParent(atomC) stopCapturingParents() expect(child.parents).toEqual([atomC]) expect(child.parentEpochs).toEqual([atomCEpoch]) expect(child.parents).toBe(originalParents) expect(child.parentEpochs).toBe(originalParentEpochs) }) it('doesnt do anything if you dont start capturing', () => { expect(() => { maybeCaptureParent(atom('', 1)) }).not.toThrow() }) }) describe(unsafe__withoutCapture, () => { it('allows executing comptuer code in a context that short-circuits the current capture frame', () => { const atomA = atom('a', 1) const atomB = atom('b', 1) const atomC = atom('c', 1) const child = computed('', () => { return atomA.get() + atomB.get() + unsafe__withoutCapture(() => atomC.get()) }) let lastValue: number | undefined let numReactions = 0 react('', () => { numReactions++ lastValue = child.get() }) expect(lastValue).toBe(3) expect(numReactions).toBe(1) atomA.set(2) expect(lastValue).toBe(4) expect(numReactions).toBe(2) atomB.set(2) expect(lastValue).toBe(5) expect(numReactions).toBe(3) atomC.set(2) // The reaction should not have run because C was not captured expect(lastValue).toBe(5) expect(numReactions).toBe(3) }) it('allows executing reactor code in a context that short-circuits the current capture frame', () => { const atomA = atom('a', 1) const atomB = atom('b', 1) const atomC = atom('c', 1) let lastValue: number | undefined let numReactions = 0 react('', () => { numReactions++ lastValue = atomA.get() + atomB.get() + unsafe__withoutCapture(() => atomC.get()) }) expect(lastValue).toBe(3) expect(numReactions).toBe(1) atomA.set(2) expect(lastValue).toBe(4) expect(numReactions).toBe(2) atomB.set(2) expect(lastValue).toBe(5) expect(numReactions).toBe(3) atomC.set(2) // The reaction should not have run because C was not captured expect(lastValue).toBe(5) expect(numReactions).toBe(3) }) })