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 });