From 97e0bc8e87f1caa68950e5e2f2ddd7865b3705f6 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 12 Sep 2016 13:21:41 -0700 Subject: [PATCH] Feat(challenges): Adds the ability to test with async methods --- client/frame-runner.js | 59 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/client/frame-runner.js b/client/frame-runner.js index e6e038a248..35cf7c9c43 100644 --- a/client/frame-runner.js +++ b/client/frame-runner.js @@ -3,6 +3,7 @@ document.addEventListener('DOMContentLoaded', function() { var frameId = window.__frameId; var frameReady = common[frameId + 'Ready$'] || { onNext() {} }; var Rx = document.Rx; + var helpers = Rx.helpers; var chai = parent.chai; var source = document.__source; @@ -37,32 +38,68 @@ document.addEventListener('DOMContentLoaded', function() { // add delay here for firefox to catch up .delay(200) /* eslint-disable no-unused-vars */ - .map(({ text, testString }) => { + .flatMap(({ text, testString }) => { const assert = chai.assert; /* eslint-enable no-unused-vars */ const newTest = { text, testString }; let test; + let __result; try { /* eslint-disable no-eval */ + // eval test string to actual JavaScript + // This return can be a function + // i.e. function() { assert(true, 'happy coding'); } test = eval(testString); /* eslint-enable no-eval */ if (typeof test === 'function') { - // maybe sync/promise/observable + + // we know that the test eval'ed to a function + // the function could expect a callback + // or it could return a promise/observable + // or it could still be sync if (test.length === 0) { - test(); + // a function with length 0 means it expects 0 args + // We call it and store the result + // This result may be a promise or an observable or undefined + __result = test(); + } else { + // if function takes arguments + // we expect it to be of the form + // function(cb) { /* ... */ } + // and callback has the following signature + // function(err) { /* ... */ } + __result = Rx.Observable.fromNodeCallback(test)(); } - // callback test - if (test.length === 1) { - console.log('callback test'); + + if (helpers.isPromise(__result)) { + // turn promise into an observable + __result = Rx.Observable.fromPromise(__result); } + } else { + // test is not a function + // fill result with for compatibility + __result = Rx.Observable.of(null); } } catch (e) { - newTest.err = e.message + '\n' + e.stack; + // something threw an uncaught error + // we catch here and wrap it in an observable + __result = Rx.Observable.throw(e); } - if (!newTest.err) { - newTest.pass = true; - } - return newTest; + return __result + .map(() => { + // we don't need the result of a promise/observable/cb here + // all data asserts should happen further up the chain + // mark test as passing + newTest.pass = true; + return newTest; + }) + .catch(err => { + // we catch the error here to prevent the error from bubbling up + // and collapsing the pipe + newTest.err = err.message + '\n' + err.stack; + // RxJS catch expects an observable as a return + return Rx.Observable.of(err); + }); }) // gather tests back into an array .toArray();