UNPKG

trampoline-ts

Version:

A type-safe way to emulate tail-call optimization with trampolines

95 lines 4.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const conditional_type_checks_1 = require("conditional-type-checks"); const src_1 = require("../src"); describe('trampoline', () => { describe('trampoline(fn)', () => { it('returns a tail call recursive function', () => { expect(src_1.trampoline(() => true)).toBeInstanceOf(Function); }); it('preserves argument types in the returned function', () => { const impl = (_1, _2, _3) => true; const fn = src_1.trampoline(impl); // tslint:disable-next-line:ban-types conditional_type_checks_1.assert(true); conditional_type_checks_1.assert(true); }); it('preserves return type in the returned function', () => { const impl = () => true; const fn = src_1.trampoline(impl); // tslint:disable-next-line:ban-types conditional_type_checks_1.assert(true); conditional_type_checks_1.assert(true); }); it('removes "ThunkOrValue" from the returned functions return type', () => { const impl = () => true; const fn = src_1.trampoline(impl); // tslint:disable-next-line:ban-types conditional_type_checks_1.assert(true); conditional_type_checks_1.assert(true); }); it('preserves argument types in "cont"', () => { const impl = (_1, _2, _3) => true; const { cont } = src_1.trampoline(impl); // tslint:disable-next-line:ban-types conditional_type_checks_1.assert(true); conditional_type_checks_1.assert(true); }); it('preserves return type in "cont" returned thunk', () => { const impl = () => true; const { cont } = src_1.trampoline(impl); const thunk = cont(); // tslint:disable-next-line:ban-types conditional_type_checks_1.assert(true); conditional_type_checks_1.assert(true); }); it('returns a function with a thunk returning "cont" method', () => { const fn = jest.fn((input) => `${input}!`); const { cont } = src_1.trampoline(fn); const thunk = cont('input'); expect(src_1.isThunk(thunk)).toBe(true); expect(fn).not.toHaveBeenCalled(); expect(thunk()).toBe('input!'); expect(fn).toHaveBeenNthCalledWith(1, 'input'); }); it(`loops until the passed function doesn't return a thunk`, () => { const timesToLoop = 5; const fn = src_1.trampoline((times = 0) => { return times < timesToLoop ? fn.cont(times + 1) : times; }); const contSpy = jest.spyOn(fn, 'cont'); fn(); expect(contSpy).toHaveBeenCalledTimes(5); }); it(`doesn't throw a stack overflow error`, () => { const brokenFactorial = (n, acc = 1) => { return n ? brokenFactorial(n - 1, acc * n) : acc; }; const factorial = src_1.trampoline((n, acc = 1) => { return n ? factorial.cont(n - 1, acc * n) : acc; }); expect(() => brokenFactorial(32768)).toThrowError('Maximum call stack size exceeded'); expect(factorial(32768)).toEqual(Infinity); }); it('supports returning functions', () => { const fn = src_1.trampoline(() => { return () => 'hello'; }); expect(fn()).toBeInstanceOf(Function); expect(fn()()).toBe('hello'); }); it('supports async functions', async () => { const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const factorial = src_1.trampolineAsync(async (n, acc = 1) => { await sleep(10); return n ? factorial.cont(n - 1, acc * n) : acc; }); expect(await factorial(2)).toBe(2); }); }); }); //# sourceMappingURL=trampoline.spec.js.map