From 2168b6115161908121ec493d2119ecbfc58603e3 Mon Sep 17 00:00:00 2001 From: Valeriy S Date: Sat, 29 Dec 2018 11:34:03 +0300 Subject: [PATCH] fix: unify build of challenges --- client/src/client/workers/test-evaluator.js | 7 ++- .../Challenges/rechallenge/builders.js | 40 +++---------- .../redux/execute-challenge-saga.js | 36 +++++------ .../src/templates/Challenges/utils/build.js | 59 ++++++++++--------- 4 files changed, 64 insertions(+), 78 deletions(-) diff --git a/client/src/client/workers/test-evaluator.js b/client/src/client/workers/test-evaluator.js index ed44a8ac96..f43ecba264 100644 --- a/client/src/client/workers/test-evaluator.js +++ b/client/src/client/workers/test-evaluator.js @@ -1,5 +1,6 @@ import chai from 'chai'; import '@babel/polyfill'; +import __toString from 'lodash/toString'; const oldLog = self.console.log.bind(self.console); self.console.log = function proxyConsole(...args) { @@ -8,17 +9,17 @@ self.console.log = function proxyConsole(...args) { }; onmessage = async e => { - const { script: __test, code } = e.data; /* eslint-disable no-unused-vars */ + const { code = '' } = e.data; const assert = chai.assert; // Fake Deep Equal dependency const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); /* eslint-enable no-unused-vars */ try { // eslint-disable-next-line no-eval - const testResult = eval(__test); + const testResult = eval(e.data.script); if (typeof testResult === 'function') { - await testResult(() => code); + await testResult(fileName => __toString(e.data.sources[fileName])); } self.postMessage({ pass: true }); } catch (err) { diff --git a/client/src/templates/Challenges/rechallenge/builders.js b/client/src/templates/Challenges/rechallenge/builders.js index bd20ad4a8f..8d614cab6a 100644 --- a/client/src/templates/Challenges/rechallenge/builders.js +++ b/client/src/templates/Challenges/rechallenge/builders.js @@ -58,16 +58,11 @@ export const cssToHtml = cond([ // FileStream::concatHtml( // required: [ ...Object ], -// template: String -// ) => Observable[{ build: String, sources: Dictionary }] -export function concatHtml(required, template, files) { +// template: String, +// files: [ polyVinyl ] +// ) => String +export function concatHtml(required, template, files = []) { const createBody = template ? _template(template) : defaultTemplate; - const sourceMap = Promise.all(files).then(files => - files.reduce((sources, file) => { - sources[file.name] = file.source || file.contents; - return sources; - }, {}) - ); const head = required .map(({ link, src }) => { @@ -84,29 +79,12 @@ A required file can not have both a src and a link: src = ${src}, link = ${link} } return ''; }) - .reduce((head, required) => [...head, required], []) - .reduce((head, element, index, thisArray) => { - if (index + 1 === thisArray.length) { - return `${head.concat(element)}`; - } - return head.concat(element); - }, ''); + .reduce((head, element) => head.concat(element), ''); - const body = Promise.all(files).then(files => - files - .reduce((body, file) => [...body, file.contents + htmlCatch], []) - .map(source => createBody({ source })) + const source = files.reduce( + (source, file) => source.concat(file.contents, htmlCatch), + '' ); - const frameRunner = - ''; - - return ( - Promise.all([head, body, frameRunner, sourceMap]).then( - ([head, body, frameRunner, sourceMap]) => ({ - build: head + frameRunner + body, - sources: sourceMap - }) - ) - ); + return `${head}${createBody({ source })}`; } diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js index 71c1b9137f..0c66a7503d 100644 --- a/client/src/templates/Challenges/redux/execute-challenge-saga.js +++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js @@ -16,13 +16,12 @@ import { initLogs, updateLogs, logsToConsole, - updateTests, - challengeFilesSelector + updateTests } from './'; import { - buildJSFromFiles, - buildHtmlFromFiles, + buildJSChallenge, + buildDOMChallenge, buildBackendChallenge } from '../utils/build'; @@ -48,17 +47,19 @@ function* ExecuteChallengeSaga() { yield put(initConsole('// running tests')); yield fork(logToConsole, consoleProxy); + const state = yield select(); + let testResults; switch (challengeType) { case js: case bonfire: - testResults = yield ExecuteJSChallengeSaga(consoleProxy); + testResults = yield ExecuteJSChallengeSaga(state, consoleProxy); break; case backend: - testResults = yield ExecuteBackendChallengeSaga(consoleProxy); + testResults = yield ExecuteBackendChallengeSaga(state, consoleProxy); break; default: - testResults = yield ExecuteDOMChallengeSaga(consoleProxy); + testResults = yield ExecuteDOMChallengeSaga(state, consoleProxy); } yield put(updateTests(testResults)); @@ -77,9 +78,9 @@ function* logToConsole(channel) { }); } -function* ExecuteJSChallengeSaga(proxyLogger) { - const files = yield select(challengeFilesSelector); - const { code, solution } = yield call(buildJSFromFiles, files); +function* ExecuteJSChallengeSaga(state, proxyLogger) { + const { build, sources } = yield call(buildJSChallenge, state); + const code = sources && 'index' in sources ? sources['index'] : ''; const log = args => proxyLogger.put(args); testWorker.on('LOG', log); @@ -87,7 +88,10 @@ function* ExecuteJSChallengeSaga(proxyLogger) { try { return yield call(executeTests, (testString, testTimeout) => testWorker - .execute({ script: solution + '\n' + testString, code }, testTimeout) + .execute( + { script: build + '\n' + testString, code, sources }, + testTimeout + ) .then(result => { testWorker.killWorker(); return result; @@ -105,9 +109,8 @@ function createTestFrame(state, ctx, proxyLogger) { }); } -function* ExecuteDOMChallengeSaga(proxyLogger) { - const state = yield select(); - const ctx = yield call(buildHtmlFromFiles, state); +function* ExecuteDOMChallengeSaga(state, proxyLogger) { + const ctx = yield call(buildDOMChallenge, state); yield call(createTestFrame, state, ctx, proxyLogger); // wait for a code execution on a "ready" event in jQuery challenges @@ -124,8 +127,7 @@ function* ExecuteDOMChallengeSaga(proxyLogger) { } // TODO: use a web worker -function* ExecuteBackendChallengeSaga(proxyLogger) { - const state = yield select(); +function* ExecuteBackendChallengeSaga(state, proxyLogger) { const ctx = yield call(buildBackendChallenge, state); yield call(createTestFrame, state, ctx, proxyLogger); @@ -179,7 +181,7 @@ function* updateMainSaga() { } const state = yield select(); const frameMain = yield call(createMainFramer, document, state); - const ctx = yield call(buildHtmlFromFiles, state); + const ctx = yield call(buildDOMChallenge, state); yield call(frameMain, ctx); } catch (err) { console.error(err); diff --git a/client/src/templates/Challenges/utils/build.js b/client/src/templates/Challenges/utils/build.js index 6a8a33c732..b3ce2ef069 100644 --- a/client/src/templates/Challenges/utils/build.js +++ b/client/src/templates/Challenges/utils/build.js @@ -11,8 +11,11 @@ import { transformers, testJS$JSX } from '../rechallenge/transformers'; import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js'; import { isPromise } from './polyvinyl'; -const frameRunner = - ""; +const frameRunner = [ + { + src: '/js/frame-runner.js' + } +]; const globalRequires = [ { @@ -63,19 +66,30 @@ const pipeLine = flow( applyFunctions(toHtml) ); -export function buildHtmlFromFiles(state) { - const files = challengeFilesSelector(state); - const { required = [], template } = challengeMetaSelector(state); - const finalRequires = [...globalRequires, ...required]; - const requiredFiles = Object.keys(files) - .map(key => files[key]) - .filter(filterJSIfDisabled(state)) - .filter(Boolean); - const finalFiles = requiredFiles.map(pipeLine); - return concatHtml(finalRequires, template, finalFiles); +function buildSourceMap(files) { + return files.reduce((sources, file) => { + sources[file.name] = file.source || file.contents; + return sources; + }, {}); } -export function buildJSFromFiles(files) { +export function buildDOMChallenge(state) { + const files = challengeFilesSelector(state); + const { required = [], template } = challengeMetaSelector(state); + const finalRequires = [...globalRequires, ...required, ...frameRunner]; + const finalFiles = Object.keys(files) + .map(key => files[key]) + .filter(filterJSIfDisabled(state)) + .filter(Boolean) + .map(pipeLine); + return Promise.all(finalFiles).then(files => ({ + build: concatHtml(finalRequires, template, files), + sources: buildSourceMap(files) + })); +} + +export function buildJSChallenge(state) { + const files = challengeFilesSelector(state); const pipeLine = flow( applyFunctions(throwers), applyFunctions(transformers) @@ -83,14 +97,8 @@ export function buildJSFromFiles(files) { const finalFiles = Object.keys(files) .map(key => files[key]) .map(pipeLine); - const sourceMap = Promise.all(finalFiles).then(files => - files.reduce((sources, file) => { - sources[file.name] = file.source || file.contents; - return sources; - }, {}) - ); - const body = Promise.all(finalFiles).then(files => - files + return Promise.all(finalFiles).then(files => ({ + build: files .reduce( (body, file) => [ ...body, @@ -98,11 +106,8 @@ export function buildJSFromFiles(files) { ], [] ) - .join('/n') - ); - return Promise.all([body, sourceMap]).then(([body, sources]) => ({ - solution: body, - code: sources && 'index' in sources ? sources['index'] : '' + .join('/n'), + sources: buildSourceMap(files) })); } @@ -111,7 +116,7 @@ export function buildBackendChallenge(state) { solution: { value: url } } = backendFormValuesSelector(state); return { - build: frameRunner, + build: concatHtml(frameRunner, ''), sources: { url } }; }