/** * @jest-environment jsdom */ import Atom, {val} from '../../Atom' import Ticker from '../../Ticker' import type {$FixMe, $IntentionalAny} from '../../types' import ConstantDerivation from '../ConstantDerivation' import iterateAndCountTicks from '../iterateAndCountTicks' import prism, {PrismDerivation} from './prism' describe('prism', () => { let ticker: Ticker beforeEach(() => { ticker = new Ticker() }) it('should work', () => { const o = new Atom({foo: 'foo'}) const d = new PrismDerivation(() => { return val(o.pointer.foo) + 'boo' }) expect(d.getValue()).toEqual('fooboo') const changes: Array<$FixMe> = [] d.changes(ticker).tap((c) => { changes.push(c) }) o.reduceState(['foo'], () => 'foo2') ticker.tick() expect(changes).toMatchObject(['foo2boo']) }) it('should only collect immediate dependencies', () => { const aD = new ConstantDerivation(1) const bD = aD.map((v) => v * 2) const cD = prism(() => { return bD.getValue() }) expect(cD.getValue()).toEqual(2) expect((cD as $IntentionalAny)._dependencies.size).toEqual(1) }) describe('prism.ref()', () => { it('should work', () => { const theAtom: Atom<{n: number}> = new Atom({n: 2}) const isEvenD = prism((): {isEven: boolean} => { const ref = prism.ref<{isEven: boolean} | undefined>('cache', undefined) const currentN = val(theAtom.pointer.n) const isEven = currentN % 2 === 0 if (ref.current && ref.current.isEven === isEven) { return ref.current } else { ref.current = {isEven} return ref.current } }) const iterator = iterateAndCountTicks(isEvenD) theAtom.reduceState(['n'], () => 3) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 0, }) theAtom.reduceState(['n'], () => 5) theAtom.reduceState(['n'], () => 7) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 1, }) theAtom.reduceState(['n'], () => 2) theAtom.reduceState(['n'], () => 4) expect(iterator.next().value).toMatchObject({ value: {isEven: true}, ticks: 1, }) expect(iterator.next().value).toMatchObject({ value: {isEven: true}, ticks: 0, }) }) }) describe('prism.effect()', () => { it('should work', async () => { let iteration = 0 const sequence: unknown[] = [] let deps: unknown[] = [] const a = new Atom({letter: 'a'}) const derivation = prism(() => { const n = val(a.pointer.letter) const iterationAtTimeOfCall = iteration sequence.push({derivationCall: iterationAtTimeOfCall}) prism.effect( 'f', () => { sequence.push({effectCall: iterationAtTimeOfCall}) return () => { sequence.push({cleanupCall: iterationAtTimeOfCall}) } }, [...deps], ) return n }) const untap = derivation.changes(ticker).tap((change) => { sequence.push({change}) }) expect(sequence).toMatchObject([{derivationCall: 0}, {effectCall: 0}]) sequence.length = 0 iteration++ a.setIn(['letter'], 'b') ticker.tick() expect(sequence).toMatchObject([{derivationCall: 1}, {change: 'b'}]) sequence.length = 0 deps = [1] iteration++ a.setIn(['letter'], 'c') ticker.tick() expect(sequence).toMatchObject([ {derivationCall: 2}, {cleanupCall: 0}, {effectCall: 2}, {change: 'c'}, ]) sequence.length = 0 untap() // takes a tick before untap takes effect await new Promise((resolve) => setTimeout(resolve, 1)) expect(sequence).toMatchObject([{cleanupCall: 2}]) }) }) describe('prism.memo()', () => { it('should work', async () => { let iteration = 0 const sequence: unknown[] = [] let deps: unknown[] = [] const a = new Atom({letter: 'a'}) const derivation = prism(() => { const n = val(a.pointer.letter) const iterationAtTimeOfCall = iteration sequence.push({derivationCall: iterationAtTimeOfCall}) const resultOfMemo = prism.memo( 'memo', () => { sequence.push({memoCall: iterationAtTimeOfCall}) return iterationAtTimeOfCall }, [...deps], ) sequence.push({resultOfMemo}) return n }) const untap = derivation.changes(ticker).tap((change) => { sequence.push({change}) }) expect(sequence).toMatchObject([ {derivationCall: 0}, {memoCall: 0}, {resultOfMemo: 0}, ]) sequence.length = 0 iteration++ a.setIn(['letter'], 'b') ticker.tick() expect(sequence).toMatchObject([ {derivationCall: 1}, {resultOfMemo: 0}, {change: 'b'}, ]) sequence.length = 0 deps = [1] iteration++ a.setIn(['letter'], 'c') ticker.tick() expect(sequence).toMatchObject([ {derivationCall: 2}, {memoCall: 2}, {resultOfMemo: 2}, {change: 'c'}, ]) sequence.length = 0 untap() }) }) describe(`prism.scope()`, () => { it('should prevent name conflicts', () => { const d = prism(() => { const thisNameWillBeUsedForBothMemos = 'blah' const a = prism.scope('a', () => { return prism.memo(thisNameWillBeUsedForBothMemos, () => 'a', []) }) const b = prism.scope('b', () => { return prism.memo(thisNameWillBeUsedForBothMemos, () => 'b', []) }) return {a, b} }) expect(d.getValue()).toMatchObject({a: 'a', b: 'b'}) }) }) })