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.
		
		
		
		
		
			
		
			
				
					
					
						
							490 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							490 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,
 | |
|     });
 | |
|   });
 | |
| });
 |