2019-01-09 03:23:17 +03:00
|
|
|
import { transformers } from '../rechallenge/transformers';
|
2018-09-30 11:37:19 +01:00
|
|
|
import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js';
|
2019-02-08 17:33:05 +03:00
|
|
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
|
|
|
import createWorker from './worker-executor';
|
2019-02-13 15:47:00 +03:00
|
|
|
import {
|
|
|
|
createTestFramer,
|
|
|
|
runTestInTestFrame,
|
|
|
|
createMainFramer
|
|
|
|
} from './frame';
|
2018-09-30 11:37:19 +01:00
|
|
|
|
2018-12-29 11:34:03 +03:00
|
|
|
const frameRunner = [
|
|
|
|
{
|
|
|
|
src: '/js/frame-runner.js'
|
|
|
|
}
|
|
|
|
];
|
2018-09-30 11:37:19 +01:00
|
|
|
|
|
|
|
const globalRequires = [
|
|
|
|
{
|
|
|
|
link:
|
|
|
|
'https://cdnjs.cloudflare.com/' +
|
|
|
|
'ajax/libs/normalize/4.2.0/normalize.min.css'
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
2018-12-29 16:42:09 +03:00
|
|
|
const applyFunction = fn =>
|
|
|
|
async function(file) {
|
|
|
|
try {
|
|
|
|
if (file.error) {
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
const newFile = await fn.call(this, file);
|
|
|
|
if (typeof newFile !== 'undefined') {
|
|
|
|
return newFile;
|
2018-10-06 02:36:38 +03:00
|
|
|
}
|
2018-12-29 16:42:09 +03:00
|
|
|
return file;
|
2019-01-15 17:18:56 +03:00
|
|
|
} catch (error) {
|
|
|
|
return { ...file, error };
|
2018-10-06 02:36:38 +03:00
|
|
|
}
|
2019-01-09 03:23:17 +03:00
|
|
|
};
|
2018-10-06 02:36:38 +03:00
|
|
|
|
2018-12-29 16:42:09 +03:00
|
|
|
const composeFunctions = (...fns) =>
|
2019-01-09 03:23:17 +03:00
|
|
|
fns.map(applyFunction).reduce((f, g) => x => f(x).then(g));
|
2018-09-30 11:37:19 +01:00
|
|
|
|
2018-12-29 11:34:03 +03:00
|
|
|
function buildSourceMap(files) {
|
|
|
|
return files.reduce((sources, file) => {
|
|
|
|
sources[file.name] = file.source || file.contents;
|
|
|
|
return sources;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
2019-01-09 03:23:17 +03:00
|
|
|
function checkFilesErrors(files) {
|
|
|
|
const errors = files.filter(({ error }) => error).map(({ error }) => error);
|
|
|
|
if (errors.length) {
|
|
|
|
throw errors;
|
|
|
|
}
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
2019-02-08 17:33:05 +03:00
|
|
|
const buildFunctions = {
|
|
|
|
[challengeTypes.js]: buildJSChallenge,
|
|
|
|
[challengeTypes.bonfire]: buildJSChallenge,
|
|
|
|
[challengeTypes.html]: buildDOMChallenge,
|
|
|
|
[challengeTypes.modern]: buildDOMChallenge,
|
|
|
|
[challengeTypes.backend]: buildBackendChallenge
|
|
|
|
};
|
|
|
|
|
|
|
|
export async function buildChallenge(challengeData) {
|
|
|
|
const { challengeType } = challengeData;
|
|
|
|
let build = buildFunctions[challengeType];
|
|
|
|
if (build) {
|
|
|
|
return build(challengeData);
|
|
|
|
}
|
2019-02-13 15:47:00 +03:00
|
|
|
throw new Error(`Cannot build challenge of type ${challengeType}`);
|
2019-02-08 17:33:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const testRunners = {
|
|
|
|
[challengeTypes.js]: getJSTestRunner,
|
|
|
|
[challengeTypes.html]: getDOMTestRunner,
|
|
|
|
[challengeTypes.backend]: getDOMTestRunner
|
|
|
|
};
|
|
|
|
export function getTestRunner(buildData, proxyLogger, document) {
|
2019-02-13 15:47:00 +03:00
|
|
|
const { challengeType } = buildData;
|
|
|
|
const testRunner = testRunners[challengeType];
|
|
|
|
if (testRunner) {
|
|
|
|
return testRunner(buildData, proxyLogger, document);
|
|
|
|
}
|
|
|
|
throw new Error(`Cannot get test runner for challenge type ${challengeType}`);
|
2019-02-08 17:33:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
function getJSTestRunner({ build, sources }, proxyLogger) {
|
|
|
|
const code = sources && 'index' in sources ? sources['index'] : '';
|
|
|
|
|
|
|
|
const testWorker = createWorker('test-evaluator');
|
|
|
|
|
2019-02-19 01:59:12 +03:00
|
|
|
return async (testString, testTimeout) => {
|
2019-02-08 17:33:05 +03:00
|
|
|
try {
|
|
|
|
testWorker.on('LOG', proxyLogger);
|
|
|
|
return await testWorker.execute(
|
|
|
|
{ build, testString, code, sources },
|
|
|
|
testTimeout
|
|
|
|
);
|
|
|
|
} finally {
|
|
|
|
testWorker.killWorker();
|
|
|
|
testWorker.remove('LOG', proxyLogger);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getDOMTestRunner(buildData, proxyLogger, document) {
|
|
|
|
await new Promise(resolve =>
|
|
|
|
createTestFramer(document, resolve, proxyLogger)(buildData)
|
|
|
|
);
|
|
|
|
return (testString, testTimeout) =>
|
|
|
|
runTestInTestFrame(document, testString, testTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function buildDOMChallenge({ files, required = [], template = '' }) {
|
2018-12-29 11:34:03 +03:00
|
|
|
const finalRequires = [...globalRequires, ...required, ...frameRunner];
|
2019-02-08 17:33:05 +03:00
|
|
|
const loadEnzyme = Object.keys(files).some(key => files[key].ext === 'jsx');
|
2018-12-29 16:42:09 +03:00
|
|
|
const toHtml = [jsToHtml, cssToHtml];
|
2019-01-15 17:18:56 +03:00
|
|
|
const pipeLine = composeFunctions(...transformers, ...toHtml);
|
2018-12-29 11:34:03 +03:00
|
|
|
const finalFiles = Object.keys(files)
|
2018-09-30 11:37:19 +01:00
|
|
|
.map(key => files[key])
|
2018-12-29 11:34:03 +03:00
|
|
|
.map(pipeLine);
|
2019-01-09 03:23:17 +03:00
|
|
|
return Promise.all(finalFiles)
|
|
|
|
.then(checkFilesErrors)
|
|
|
|
.then(files => ({
|
2019-02-08 17:33:05 +03:00
|
|
|
challengeType: challengeTypes.html,
|
2019-01-09 03:23:17 +03:00
|
|
|
build: concatHtml({ required: finalRequires, template, files }),
|
2019-02-08 17:33:05 +03:00
|
|
|
sources: buildSourceMap(files),
|
|
|
|
loadEnzyme
|
2019-01-09 03:23:17 +03:00
|
|
|
}));
|
2018-09-30 11:37:19 +01:00
|
|
|
}
|
|
|
|
|
2019-02-08 17:33:05 +03:00
|
|
|
export function buildJSChallenge({ files }) {
|
2019-01-15 17:18:56 +03:00
|
|
|
const pipeLine = composeFunctions(...transformers);
|
2018-11-26 02:17:38 +03:00
|
|
|
const finalFiles = Object.keys(files)
|
|
|
|
.map(key => files[key])
|
|
|
|
.map(pipeLine);
|
2019-01-09 03:23:17 +03:00
|
|
|
return Promise.all(finalFiles)
|
|
|
|
.then(checkFilesErrors)
|
|
|
|
.then(files => ({
|
2019-02-08 17:33:05 +03:00
|
|
|
challengeType: challengeTypes.js,
|
2019-01-09 03:23:17 +03:00
|
|
|
build: files
|
|
|
|
.reduce(
|
|
|
|
(body, file) => [...body, file.head, file.contents, file.tail],
|
|
|
|
[]
|
|
|
|
)
|
|
|
|
.join('\n'),
|
|
|
|
sources: buildSourceMap(files)
|
|
|
|
}));
|
2018-11-26 02:17:38 +03:00
|
|
|
}
|
|
|
|
|
2019-02-08 17:33:05 +03:00
|
|
|
export function buildBackendChallenge({ url }) {
|
2018-12-10 10:00:26 +03:00
|
|
|
return {
|
2019-02-08 17:33:05 +03:00
|
|
|
challengeType: challengeTypes.backend,
|
2019-01-09 03:23:17 +03:00
|
|
|
build: concatHtml({ required: frameRunner }),
|
2018-12-10 10:00:26 +03:00
|
|
|
sources: { url }
|
|
|
|
};
|
2018-09-30 11:37:19 +01:00
|
|
|
}
|
2019-02-13 15:47:00 +03:00
|
|
|
|
|
|
|
export function updatePreview(buildData, document) {
|
|
|
|
const { challengeType } = buildData;
|
|
|
|
if (challengeType === challengeTypes.html) {
|
|
|
|
createMainFramer(document)(buildData);
|
|
|
|
} else {
|
|
|
|
throw new Error(`Cannot show preview for challenge type ${challengeType}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function challengeHasPreview({ challengeType }) {
|
|
|
|
return (
|
|
|
|
challengeType === challengeTypes.html ||
|
|
|
|
challengeType === challengeTypes.modern
|
|
|
|
);
|
|
|
|
}
|