Spaces:
Running
Running
| ; | |
| const promisify = require("util.promisify"); | |
| const gensync = require("../"); | |
| const TEST_ERROR = new Error("TEST_ERROR"); | |
| const DID_ERROR = new Error("DID_ERROR"); | |
| const doSuccess = gensync({ | |
| sync: () => 42, | |
| async: () => Promise.resolve(42), | |
| }); | |
| const doError = gensync({ | |
| sync: () => { | |
| throw DID_ERROR; | |
| }, | |
| async: () => Promise.reject(DID_ERROR), | |
| }); | |
| function throwTestError() { | |
| throw TEST_ERROR; | |
| } | |
| async function expectResult( | |
| fn, | |
| arg, | |
| { error, value, expectSync = false, syncErrback = expectSync } | |
| ) { | |
| if (!expectSync) { | |
| expect(() => fn.sync(arg)).toThrow(TEST_ERROR); | |
| } else if (error) { | |
| expect(() => fn.sync(arg)).toThrow(error); | |
| } else { | |
| expect(fn.sync(arg)).toBe(value); | |
| } | |
| if (error) { | |
| await expect(fn.async(arg)).rejects.toBe(error); | |
| } else { | |
| await expect(fn.async(arg)).resolves.toBe(value); | |
| } | |
| await new Promise((resolve, reject) => { | |
| let sync = true; | |
| fn.errback(arg, (err, val) => { | |
| try { | |
| expect(err).toBe(error); | |
| expect(val).toBe(value); | |
| expect(sync).toBe(syncErrback); | |
| resolve(); | |
| } catch (e) { | |
| reject(e); | |
| } | |
| }); | |
| sync = false; | |
| }); | |
| } | |
| describe("gensync({})", () => { | |
| describe("option validation", () => { | |
| test("disallow async and errback handler together", () => { | |
| try { | |
| gensync({ | |
| sync: throwTestError, | |
| async: throwTestError, | |
| errback: throwTestError, | |
| }); | |
| throwTestError(); | |
| } catch (err) { | |
| expect(err.message).toMatch( | |
| /Expected one of either opts.async or opts.errback, but got _both_\./ | |
| ); | |
| expect(err.code).toBe("GENSYNC_OPTIONS_ERROR"); | |
| } | |
| }); | |
| test("disallow missing sync handler", () => { | |
| try { | |
| gensync({ | |
| async: throwTestError, | |
| }); | |
| throwTestError(); | |
| } catch (err) { | |
| expect(err.message).toMatch(/Expected opts.sync to be a function./); | |
| expect(err.code).toBe("GENSYNC_OPTIONS_ERROR"); | |
| } | |
| }); | |
| test("errback callback required", () => { | |
| const fn = gensync({ | |
| sync: throwTestError, | |
| async: throwTestError, | |
| }); | |
| try { | |
| fn.errback(); | |
| throwTestError(); | |
| } catch (err) { | |
| expect(err.message).toMatch(/function called without callback/); | |
| expect(err.code).toBe("GENSYNC_ERRBACK_NO_CALLBACK"); | |
| } | |
| }); | |
| }); | |
| describe("generator function metadata", () => { | |
| test("automatic naming", () => { | |
| expect( | |
| gensync({ | |
| sync: function readFileSync() {}, | |
| async: () => {}, | |
| }).name | |
| ).toBe("readFile"); | |
| expect( | |
| gensync({ | |
| sync: function readFile() {}, | |
| async: () => {}, | |
| }).name | |
| ).toBe("readFile"); | |
| expect( | |
| gensync({ | |
| sync: function readFileAsync() {}, | |
| async: () => {}, | |
| }).name | |
| ).toBe("readFileAsync"); | |
| expect( | |
| gensync({ | |
| sync: () => {}, | |
| async: function readFileSync() {}, | |
| }).name | |
| ).toBe("readFileSync"); | |
| expect( | |
| gensync({ | |
| sync: () => {}, | |
| async: function readFile() {}, | |
| }).name | |
| ).toBe("readFile"); | |
| expect( | |
| gensync({ | |
| sync: () => {}, | |
| async: function readFileAsync() {}, | |
| }).name | |
| ).toBe("readFile"); | |
| expect( | |
| gensync({ | |
| sync: () => {}, | |
| errback: function readFileSync() {}, | |
| }).name | |
| ).toBe("readFileSync"); | |
| expect( | |
| gensync({ | |
| sync: () => {}, | |
| errback: function readFile() {}, | |
| }).name | |
| ).toBe("readFile"); | |
| expect( | |
| gensync({ | |
| sync: () => {}, | |
| errback: function readFileAsync() {}, | |
| }).name | |
| ).toBe("readFileAsync"); | |
| }); | |
| test("explicit naming", () => { | |
| expect( | |
| gensync({ | |
| name: "readFile", | |
| sync: () => {}, | |
| async: () => {}, | |
| }).name | |
| ).toBe("readFile"); | |
| }); | |
| test("default arity", () => { | |
| expect( | |
| gensync({ | |
| sync: function(a, b, c, d, e, f, g) { | |
| throwTestError(); | |
| }, | |
| async: throwTestError, | |
| }).length | |
| ).toBe(7); | |
| }); | |
| test("explicit arity", () => { | |
| expect( | |
| gensync({ | |
| arity: 3, | |
| sync: throwTestError, | |
| async: throwTestError, | |
| }).length | |
| ).toBe(3); | |
| }); | |
| }); | |
| describe("'sync' handler", async () => { | |
| test("success", async () => { | |
| const fn = gensync({ | |
| sync: (...args) => JSON.stringify(args), | |
| }); | |
| await expectResult(fn, 42, { value: "[42]", expectSync: true }); | |
| }); | |
| test("failure", async () => { | |
| const fn = gensync({ | |
| sync: (...args) => { | |
| throw JSON.stringify(args); | |
| }, | |
| }); | |
| await expectResult(fn, 42, { error: "[42]", expectSync: true }); | |
| }); | |
| }); | |
| describe("'async' handler", async () => { | |
| test("success", async () => { | |
| const fn = gensync({ | |
| sync: throwTestError, | |
| async: (...args) => Promise.resolve(JSON.stringify(args)), | |
| }); | |
| await expectResult(fn, 42, { value: "[42]" }); | |
| }); | |
| test("failure", async () => { | |
| const fn = gensync({ | |
| sync: throwTestError, | |
| async: (...args) => Promise.reject(JSON.stringify(args)), | |
| }); | |
| await expectResult(fn, 42, { error: "[42]" }); | |
| }); | |
| }); | |
| describe("'errback' sync handler", async () => { | |
| test("success", async () => { | |
| const fn = gensync({ | |
| sync: throwTestError, | |
| errback: (...args) => args.pop()(null, JSON.stringify(args)), | |
| }); | |
| await expectResult(fn, 42, { value: "[42]", syncErrback: true }); | |
| }); | |
| test("failure", async () => { | |
| const fn = gensync({ | |
| sync: throwTestError, | |
| errback: (...args) => args.pop()(JSON.stringify(args)), | |
| }); | |
| await expectResult(fn, 42, { error: "[42]", syncErrback: true }); | |
| }); | |
| }); | |
| describe("'errback' async handler", async () => { | |
| test("success", async () => { | |
| const fn = gensync({ | |
| sync: throwTestError, | |
| errback: (...args) => | |
| process.nextTick(() => args.pop()(null, JSON.stringify(args))), | |
| }); | |
| await expectResult(fn, 42, { value: "[42]" }); | |
| }); | |
| test("failure", async () => { | |
| const fn = gensync({ | |
| sync: throwTestError, | |
| errback: (...args) => | |
| process.nextTick(() => args.pop()(JSON.stringify(args))), | |
| }); | |
| await expectResult(fn, 42, { error: "[42]" }); | |
| }); | |
| }); | |
| }); | |
| describe("gensync(function* () {})", () => { | |
| test("sync throw before body", async () => { | |
| const fn = gensync(function*(arg = throwTestError()) {}); | |
| await expectResult(fn, undefined, { | |
| error: TEST_ERROR, | |
| syncErrback: true, | |
| }); | |
| }); | |
| test("sync throw inside body", async () => { | |
| const fn = gensync(function*() { | |
| throwTestError(); | |
| }); | |
| await expectResult(fn, undefined, { | |
| error: TEST_ERROR, | |
| syncErrback: true, | |
| }); | |
| }); | |
| test("async throw inside body", async () => { | |
| const fn = gensync(function*() { | |
| const val = yield* doSuccess(); | |
| throwTestError(); | |
| }); | |
| await expectResult(fn, undefined, { | |
| error: TEST_ERROR, | |
| }); | |
| }); | |
| test("error inside body", async () => { | |
| const fn = gensync(function*() { | |
| yield* doError(); | |
| }); | |
| await expectResult(fn, undefined, { | |
| error: DID_ERROR, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| test("successful return value", async () => { | |
| const fn = gensync(function*() { | |
| const value = yield* doSuccess(); | |
| expect(value).toBe(42); | |
| return 84; | |
| }); | |
| await expectResult(fn, undefined, { | |
| value: 84, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| test("successful final value", async () => { | |
| const fn = gensync(function*() { | |
| return 42; | |
| }); | |
| await expectResult(fn, undefined, { | |
| value: 42, | |
| expectSync: true, | |
| }); | |
| }); | |
| test("yield unexpected object", async () => { | |
| const fn = gensync(function*() { | |
| yield {}; | |
| }); | |
| try { | |
| await fn.async(); | |
| throwTestError(); | |
| } catch (err) { | |
| expect(err.message).toMatch( | |
| /Got unexpected yielded value in gensync generator/ | |
| ); | |
| expect(err.code).toBe("GENSYNC_EXPECTED_START"); | |
| } | |
| }); | |
| test("yield suspend yield", async () => { | |
| const fn = gensync(function*() { | |
| yield Symbol.for("gensync:v1:start"); | |
| // Should be "yield*" for no error. | |
| yield {}; | |
| }); | |
| try { | |
| await fn.async(); | |
| throwTestError(); | |
| } catch (err) { | |
| expect(err.message).toMatch(/Expected GENSYNC_SUSPEND, got {}/); | |
| expect(err.code).toBe("GENSYNC_EXPECTED_SUSPEND"); | |
| } | |
| }); | |
| test("yield suspend return", async () => { | |
| const fn = gensync(function*() { | |
| yield Symbol.for("gensync:v1:start"); | |
| // Should be "yield*" for no error. | |
| return {}; | |
| }); | |
| try { | |
| await fn.async(); | |
| throwTestError(); | |
| } catch (err) { | |
| expect(err.message).toMatch(/Unexpected generator completion/); | |
| expect(err.code).toBe("GENSYNC_EXPECTED_SUSPEND"); | |
| } | |
| }); | |
| }); | |
| describe("gensync.all()", () => { | |
| test("success", async () => { | |
| const fn = gensync(function*() { | |
| const result = yield* gensync.all([doSuccess(), doSuccess()]); | |
| expect(result).toEqual([42, 42]); | |
| }); | |
| await expectResult(fn, undefined, { | |
| value: undefined, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| test("error first", async () => { | |
| const fn = gensync(function*() { | |
| yield* gensync.all([doError(), doSuccess()]); | |
| }); | |
| await expectResult(fn, undefined, { | |
| error: DID_ERROR, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| test("error last", async () => { | |
| const fn = gensync(function*() { | |
| yield* gensync.all([doSuccess(), doError()]); | |
| }); | |
| await expectResult(fn, undefined, { | |
| error: DID_ERROR, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| test("empty list", async () => { | |
| const fn = gensync(function*() { | |
| yield* gensync.all([]); | |
| }); | |
| await expectResult(fn, undefined, { | |
| value: undefined, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| }); | |
| describe("gensync.race()", () => { | |
| test("success", async () => { | |
| const fn = gensync(function*() { | |
| const result = yield* gensync.race([doSuccess(), doError()]); | |
| expect(result).toEqual(42); | |
| }); | |
| await expectResult(fn, undefined, { | |
| value: undefined, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| test("error", async () => { | |
| const fn = gensync(function*() { | |
| yield* gensync.race([doError(), doSuccess()]); | |
| }); | |
| await expectResult(fn, undefined, { | |
| error: DID_ERROR, | |
| expectSync: true, | |
| syncErrback: false, | |
| }); | |
| }); | |
| }); | |