You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
489 lines
11 KiB
489 lines
11 KiB
"use strict"; |
|
|
|
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, |
|
}); |
|
}); |
|
});
|
|
|