index.test.js
  1  "use strict";
  2  
  3  const promisify = require("util.promisify");
  4  const gensync = require("../");
  5  
  6  const TEST_ERROR = new Error("TEST_ERROR");
  7  
  8  const DID_ERROR = new Error("DID_ERROR");
  9  
 10  const doSuccess = gensync({
 11    sync: () => 42,
 12    async: () => Promise.resolve(42),
 13  });
 14  
 15  const doError = gensync({
 16    sync: () => {
 17      throw DID_ERROR;
 18    },
 19    async: () => Promise.reject(DID_ERROR),
 20  });
 21  
 22  function throwTestError() {
 23    throw TEST_ERROR;
 24  }
 25  
 26  async function expectResult(
 27    fn,
 28    arg,
 29    { error, value, expectSync = false, syncErrback = expectSync }
 30  ) {
 31    if (!expectSync) {
 32      expect(() => fn.sync(arg)).toThrow(TEST_ERROR);
 33    } else if (error) {
 34      expect(() => fn.sync(arg)).toThrow(error);
 35    } else {
 36      expect(fn.sync(arg)).toBe(value);
 37    }
 38  
 39    if (error) {
 40      await expect(fn.async(arg)).rejects.toBe(error);
 41    } else {
 42      await expect(fn.async(arg)).resolves.toBe(value);
 43    }
 44  
 45    await new Promise((resolve, reject) => {
 46      let sync = true;
 47      fn.errback(arg, (err, val) => {
 48        try {
 49          expect(err).toBe(error);
 50          expect(val).toBe(value);
 51          expect(sync).toBe(syncErrback);
 52  
 53          resolve();
 54        } catch (e) {
 55          reject(e);
 56        }
 57      });
 58      sync = false;
 59    });
 60  }
 61  
 62  describe("gensync({})", () => {
 63    describe("option validation", () => {
 64      test("disallow async and errback handler together", () => {
 65        try {
 66          gensync({
 67            sync: throwTestError,
 68            async: throwTestError,
 69            errback: throwTestError,
 70          });
 71  
 72          throwTestError();
 73        } catch (err) {
 74          expect(err.message).toMatch(
 75            /Expected one of either opts.async or opts.errback, but got _both_\./
 76          );
 77          expect(err.code).toBe("GENSYNC_OPTIONS_ERROR");
 78        }
 79      });
 80  
 81      test("disallow missing sync handler", () => {
 82        try {
 83          gensync({
 84            async: throwTestError,
 85          });
 86  
 87          throwTestError();
 88        } catch (err) {
 89          expect(err.message).toMatch(/Expected opts.sync to be a function./);
 90          expect(err.code).toBe("GENSYNC_OPTIONS_ERROR");
 91        }
 92      });
 93  
 94      test("errback callback required", () => {
 95        const fn = gensync({
 96          sync: throwTestError,
 97          async: throwTestError,
 98        });
 99  
100        try {
101          fn.errback();
102  
103          throwTestError();
104        } catch (err) {
105          expect(err.message).toMatch(/function called without callback/);
106          expect(err.code).toBe("GENSYNC_ERRBACK_NO_CALLBACK");
107        }
108      });
109    });
110  
111    describe("generator function metadata", () => {
112      test("automatic naming", () => {
113        expect(
114          gensync({
115            sync: function readFileSync() {},
116            async: () => {},
117          }).name
118        ).toBe("readFile");
119        expect(
120          gensync({
121            sync: function readFile() {},
122            async: () => {},
123          }).name
124        ).toBe("readFile");
125        expect(
126          gensync({
127            sync: function readFileAsync() {},
128            async: () => {},
129          }).name
130        ).toBe("readFileAsync");
131  
132        expect(
133          gensync({
134            sync: () => {},
135            async: function readFileSync() {},
136          }).name
137        ).toBe("readFileSync");
138        expect(
139          gensync({
140            sync: () => {},
141            async: function readFile() {},
142          }).name
143        ).toBe("readFile");
144        expect(
145          gensync({
146            sync: () => {},
147            async: function readFileAsync() {},
148          }).name
149        ).toBe("readFile");
150  
151        expect(
152          gensync({
153            sync: () => {},
154            errback: function readFileSync() {},
155          }).name
156        ).toBe("readFileSync");
157        expect(
158          gensync({
159            sync: () => {},
160            errback: function readFile() {},
161          }).name
162        ).toBe("readFile");
163        expect(
164          gensync({
165            sync: () => {},
166            errback: function readFileAsync() {},
167          }).name
168        ).toBe("readFileAsync");
169      });
170  
171      test("explicit naming", () => {
172        expect(
173          gensync({
174            name: "readFile",
175            sync: () => {},
176            async: () => {},
177          }).name
178        ).toBe("readFile");
179      });
180  
181      test("default arity", () => {
182        expect(
183          gensync({
184            sync: function(a, b, c, d, e, f, g) {
185              throwTestError();
186            },
187            async: throwTestError,
188          }).length
189        ).toBe(7);
190      });
191  
192      test("explicit arity", () => {
193        expect(
194          gensync({
195            arity: 3,
196            sync: throwTestError,
197            async: throwTestError,
198          }).length
199        ).toBe(3);
200      });
201    });
202  
203    describe("'sync' handler", async () => {
204      test("success", async () => {
205        const fn = gensync({
206          sync: (...args) => JSON.stringify(args),
207        });
208  
209        await expectResult(fn, 42, { value: "[42]", expectSync: true });
210      });
211  
212      test("failure", async () => {
213        const fn = gensync({
214          sync: (...args) => {
215            throw JSON.stringify(args);
216          },
217        });
218  
219        await expectResult(fn, 42, { error: "[42]", expectSync: true });
220      });
221    });
222  
223    describe("'async' handler", async () => {
224      test("success", async () => {
225        const fn = gensync({
226          sync: throwTestError,
227          async: (...args) => Promise.resolve(JSON.stringify(args)),
228        });
229  
230        await expectResult(fn, 42, { value: "[42]" });
231      });
232  
233      test("failure", async () => {
234        const fn = gensync({
235          sync: throwTestError,
236          async: (...args) => Promise.reject(JSON.stringify(args)),
237        });
238  
239        await expectResult(fn, 42, { error: "[42]" });
240      });
241    });
242  
243    describe("'errback' sync handler", async () => {
244      test("success", async () => {
245        const fn = gensync({
246          sync: throwTestError,
247          errback: (...args) => args.pop()(null, JSON.stringify(args)),
248        });
249  
250        await expectResult(fn, 42, { value: "[42]", syncErrback: true });
251      });
252  
253      test("failure", async () => {
254        const fn = gensync({
255          sync: throwTestError,
256          errback: (...args) => args.pop()(JSON.stringify(args)),
257        });
258  
259        await expectResult(fn, 42, { error: "[42]", syncErrback: true });
260      });
261    });
262  
263    describe("'errback' async handler", async () => {
264      test("success", async () => {
265        const fn = gensync({
266          sync: throwTestError,
267          errback: (...args) =>
268            process.nextTick(() => args.pop()(null, JSON.stringify(args))),
269        });
270  
271        await expectResult(fn, 42, { value: "[42]" });
272      });
273  
274      test("failure", async () => {
275        const fn = gensync({
276          sync: throwTestError,
277          errback: (...args) =>
278            process.nextTick(() => args.pop()(JSON.stringify(args))),
279        });
280  
281        await expectResult(fn, 42, { error: "[42]" });
282      });
283    });
284  });
285  
286  describe("gensync(function* () {})", () => {
287    test("sync throw before body", async () => {
288      const fn = gensync(function*(arg = throwTestError()) {});
289  
290      await expectResult(fn, undefined, {
291        error: TEST_ERROR,
292        syncErrback: true,
293      });
294    });
295  
296    test("sync throw inside body", async () => {
297      const fn = gensync(function*() {
298        throwTestError();
299      });
300  
301      await expectResult(fn, undefined, {
302        error: TEST_ERROR,
303        syncErrback: true,
304      });
305    });
306  
307    test("async throw inside body", async () => {
308      const fn = gensync(function*() {
309        const val = yield* doSuccess();
310        throwTestError();
311      });
312  
313      await expectResult(fn, undefined, {
314        error: TEST_ERROR,
315      });
316    });
317  
318    test("error inside body", async () => {
319      const fn = gensync(function*() {
320        yield* doError();
321      });
322  
323      await expectResult(fn, undefined, {
324        error: DID_ERROR,
325        expectSync: true,
326        syncErrback: false,
327      });
328    });
329  
330    test("successful return value", async () => {
331      const fn = gensync(function*() {
332        const value = yield* doSuccess();
333  
334        expect(value).toBe(42);
335  
336        return 84;
337      });
338  
339      await expectResult(fn, undefined, {
340        value: 84,
341        expectSync: true,
342        syncErrback: false,
343      });
344    });
345  
346    test("successful final value", async () => {
347      const fn = gensync(function*() {
348        return 42;
349      });
350  
351      await expectResult(fn, undefined, {
352        value: 42,
353        expectSync: true,
354      });
355    });
356  
357    test("yield unexpected object", async () => {
358      const fn = gensync(function*() {
359        yield {};
360      });
361  
362      try {
363        await fn.async();
364  
365        throwTestError();
366      } catch (err) {
367        expect(err.message).toMatch(
368          /Got unexpected yielded value in gensync generator/
369        );
370        expect(err.code).toBe("GENSYNC_EXPECTED_START");
371      }
372    });
373  
374    test("yield suspend yield", async () => {
375      const fn = gensync(function*() {
376        yield Symbol.for("gensync:v1:start");
377  
378        // Should be "yield*" for no error.
379        yield {};
380      });
381  
382      try {
383        await fn.async();
384  
385        throwTestError();
386      } catch (err) {
387        expect(err.message).toMatch(/Expected GENSYNC_SUSPEND, got {}/);
388        expect(err.code).toBe("GENSYNC_EXPECTED_SUSPEND");
389      }
390    });
391  
392    test("yield suspend return", async () => {
393      const fn = gensync(function*() {
394        yield Symbol.for("gensync:v1:start");
395  
396        // Should be "yield*" for no error.
397        return {};
398      });
399  
400      try {
401        await fn.async();
402  
403        throwTestError();
404      } catch (err) {
405        expect(err.message).toMatch(/Unexpected generator completion/);
406        expect(err.code).toBe("GENSYNC_EXPECTED_SUSPEND");
407      }
408    });
409  });
410  
411  describe("gensync.all()", () => {
412    test("success", async () => {
413      const fn = gensync(function*() {
414        const result = yield* gensync.all([doSuccess(), doSuccess()]);
415  
416        expect(result).toEqual([42, 42]);
417      });
418  
419      await expectResult(fn, undefined, {
420        value: undefined,
421        expectSync: true,
422        syncErrback: false,
423      });
424    });
425  
426    test("error first", async () => {
427      const fn = gensync(function*() {
428        yield* gensync.all([doError(), doSuccess()]);
429      });
430  
431      await expectResult(fn, undefined, {
432        error: DID_ERROR,
433        expectSync: true,
434        syncErrback: false,
435      });
436    });
437  
438    test("error last", async () => {
439      const fn = gensync(function*() {
440        yield* gensync.all([doSuccess(), doError()]);
441      });
442  
443      await expectResult(fn, undefined, {
444        error: DID_ERROR,
445        expectSync: true,
446        syncErrback: false,
447      });
448    });
449  
450    test("empty list", async () => {
451      const fn = gensync(function*() {
452        yield* gensync.all([]);
453      });
454  
455      await expectResult(fn, undefined, {
456        value: undefined,
457        expectSync: true,
458        syncErrback: false,
459      });
460    });
461  });
462  
463  describe("gensync.race()", () => {
464    test("success", async () => {
465      const fn = gensync(function*() {
466        const result = yield* gensync.race([doSuccess(), doError()]);
467  
468        expect(result).toEqual(42);
469      });
470  
471      await expectResult(fn, undefined, {
472        value: undefined,
473        expectSync: true,
474        syncErrback: false,
475      });
476    });
477  
478    test("error", async () => {
479      const fn = gensync(function*() {
480        yield* gensync.race([doError(), doSuccess()]);
481      });
482  
483      await expectResult(fn, undefined, {
484        error: DID_ERROR,
485        expectSync: true,
486        syncErrback: false,
487      });
488    });
489  });