fix(client): disable build on error
This commit is contained in:
@ -10,56 +10,46 @@ import {
|
|||||||
import { delay, channel } from 'redux-saga';
|
import { delay, channel } from 'redux-saga';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
backendFormValuesSelector,
|
challengeDataSelector,
|
||||||
challengeFilesSelector,
|
|
||||||
challengeMetaSelector,
|
|
||||||
challengeTestsSelector,
|
challengeTestsSelector,
|
||||||
initConsole,
|
initConsole,
|
||||||
updateConsole,
|
updateConsole,
|
||||||
initLogs,
|
initLogs,
|
||||||
updateLogs,
|
updateLogs,
|
||||||
logsToConsole,
|
logsToConsole,
|
||||||
updateTests
|
updateTests,
|
||||||
|
isBuildEnabledSelector,
|
||||||
|
disableBuildOnError
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import {
|
import { buildChallenge, getTestRunner } from '../utils/build';
|
||||||
buildJSChallenge,
|
|
||||||
buildDOMChallenge,
|
|
||||||
buildBackendChallenge
|
|
||||||
} from '../utils/build';
|
|
||||||
|
|
||||||
import { challengeTypes } from '../../../../utils/challengeTypes';
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||||
|
|
||||||
import createWorker from '../utils/worker-executor';
|
import { createMainFramer } from '../utils/frame.js';
|
||||||
import {
|
|
||||||
createMainFramer,
|
|
||||||
createTestFramer,
|
|
||||||
runTestInTestFrame
|
|
||||||
} from '../utils/frame.js';
|
|
||||||
|
|
||||||
export function* executeChallengeSaga() {
|
export function* executeChallengeSaga() {
|
||||||
|
const isBuildEnabled = yield select(isBuildEnabledSelector);
|
||||||
|
if (!isBuildEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const consoleProxy = yield channel();
|
const consoleProxy = yield channel();
|
||||||
try {
|
try {
|
||||||
const { js, bonfire, backend } = challengeTypes;
|
|
||||||
const { challengeType } = yield select(challengeMetaSelector);
|
|
||||||
|
|
||||||
yield put(initLogs());
|
yield put(initLogs());
|
||||||
yield put(initConsole('// running tests'));
|
yield put(initConsole('// running tests'));
|
||||||
yield fork(logToConsole, consoleProxy);
|
yield fork(logToConsole, consoleProxy);
|
||||||
const proxyLogger = args => consoleProxy.put(args);
|
const proxyLogger = args => consoleProxy.put(args);
|
||||||
|
|
||||||
let testResults;
|
const buildData = yield buildChallengeData();
|
||||||
switch (challengeType) {
|
const document = yield getContext('document');
|
||||||
case js:
|
const testRunner = yield call(
|
||||||
case bonfire:
|
getTestRunner,
|
||||||
testResults = yield executeJSChallengeSaga(proxyLogger);
|
buildData,
|
||||||
break;
|
proxyLogger,
|
||||||
case backend:
|
document
|
||||||
testResults = yield executeBackendChallengeSaga(proxyLogger);
|
);
|
||||||
break;
|
const testResults = yield executeTests(testRunner);
|
||||||
default:
|
|
||||||
testResults = yield executeDOMChallengeSaga(proxyLogger);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield put(updateTests(testResults));
|
yield put(updateTests(testResults));
|
||||||
yield put(updateConsole('// tests completed'));
|
yield put(updateConsole('// tests completed'));
|
||||||
@ -77,63 +67,16 @@ function* logToConsole(channel) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function* executeJSChallengeSaga(proxyLogger) {
|
function* buildChallengeData() {
|
||||||
const files = yield select(challengeFilesSelector);
|
const challengeData = yield select(challengeDataSelector);
|
||||||
const { build, sources } = yield call(buildJSChallenge, files);
|
|
||||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
|
||||||
|
|
||||||
const testWorker = createWorker('test-evaluator');
|
|
||||||
testWorker.on('LOG', proxyLogger);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return yield call(executeTests, async(testString, testTimeout) => {
|
return yield call(buildChallenge, challengeData);
|
||||||
try {
|
} catch (e) {
|
||||||
return await testWorker.execute(
|
yield put(disableBuildOnError(e));
|
||||||
{ build, testString, code, sources },
|
throw ['Build failed'];
|
||||||
testTimeout
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
testWorker.killWorker();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
testWorker.remove('LOG', proxyLogger);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTestFrame(document, ctx, proxyLogger) {
|
|
||||||
return new Promise(resolve =>
|
|
||||||
createTestFramer(document, resolve, proxyLogger)(ctx)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function* executeDOMChallengeSaga(proxyLogger) {
|
|
||||||
const files = yield select(challengeFilesSelector);
|
|
||||||
const meta = yield select(challengeMetaSelector);
|
|
||||||
const document = yield getContext('document');
|
|
||||||
const ctx = yield call(buildDOMChallenge, files, meta);
|
|
||||||
ctx.loadEnzyme = Object.keys(files).some(key => files[key].ext === 'jsx');
|
|
||||||
yield call(createTestFrame, document, ctx, proxyLogger);
|
|
||||||
// wait for a code execution on a "ready" event in jQuery challenges
|
|
||||||
yield delay(100);
|
|
||||||
|
|
||||||
return yield call(executeTests, (testString, testTimeout) =>
|
|
||||||
runTestInTestFrame(document, testString, testTimeout)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use a web worker
|
|
||||||
function* executeBackendChallengeSaga(proxyLogger) {
|
|
||||||
const formValues = yield select(backendFormValuesSelector);
|
|
||||||
const document = yield getContext('document');
|
|
||||||
const ctx = yield call(buildBackendChallenge, formValues);
|
|
||||||
yield call(createTestFrame, document, ctx, proxyLogger);
|
|
||||||
|
|
||||||
return yield call(executeTests, (testString, testTimeout) =>
|
|
||||||
runTestInTestFrame(document, testString, testTimeout)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function* executeTests(testRunner) {
|
function* executeTests(testRunner) {
|
||||||
const tests = yield select(challengeTestsSelector);
|
const tests = yield select(challengeTestsSelector);
|
||||||
const testTimeout = 5000;
|
const testTimeout = 5000;
|
||||||
@ -168,16 +111,20 @@ function* executeTests(testRunner) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function* updateMainSaga() {
|
function* updateMainSaga() {
|
||||||
yield delay(500);
|
const isBuildEnabled = yield select(isBuildEnabledSelector);
|
||||||
|
if (!isBuildEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield delay(700);
|
||||||
try {
|
try {
|
||||||
|
yield put(initConsole(''));
|
||||||
const { html, modern } = challengeTypes;
|
const { html, modern } = challengeTypes;
|
||||||
const meta = yield select(challengeMetaSelector);
|
const { challengeType } = yield select(challengeDataSelector);
|
||||||
const { challengeType } = meta;
|
|
||||||
if (challengeType !== html && challengeType !== modern) {
|
if (challengeType !== html && challengeType !== modern) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const files = yield select(challengeFilesSelector);
|
const ctx = yield buildChallengeData();
|
||||||
const ctx = yield call(buildDOMChallenge, files, meta);
|
|
||||||
const document = yield getContext('document');
|
const document = yield getContext('document');
|
||||||
const frameMain = yield call(createMainFramer, document);
|
const frameMain = yield call(createMainFramer, document);
|
||||||
yield call(frameMain, ctx);
|
yield call(frameMain, ctx);
|
||||||
|
@ -14,6 +14,7 @@ import codeStorageEpic from './code-storage-epic';
|
|||||||
import { createIdToNameMapSaga } from './id-to-name-map-saga';
|
import { createIdToNameMapSaga } from './id-to-name-map-saga';
|
||||||
import { createExecuteChallengeSaga } from './execute-challenge-saga';
|
import { createExecuteChallengeSaga } from './execute-challenge-saga';
|
||||||
import { createCurrentChallengeSaga } from './current-challenge-saga';
|
import { createCurrentChallengeSaga } from './current-challenge-saga';
|
||||||
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||||
|
|
||||||
export const ns = 'challenge';
|
export const ns = 'challenge';
|
||||||
export const backendNS = 'backendChallenge';
|
export const backendNS = 'backendChallenge';
|
||||||
@ -23,12 +24,14 @@ const initialState = {
|
|||||||
challengeIdToNameMap: {},
|
challengeIdToNameMap: {},
|
||||||
challengeMeta: {
|
challengeMeta: {
|
||||||
id: '',
|
id: '',
|
||||||
nextChallengePath: '/'
|
nextChallengePath: '/',
|
||||||
|
introPath: '',
|
||||||
|
challengeType: -1
|
||||||
},
|
},
|
||||||
challengeTests: [],
|
challengeTests: [],
|
||||||
consoleOut: '',
|
consoleOut: '',
|
||||||
isCodeLocked: false,
|
isCodeLocked: false,
|
||||||
isJSEnabled: true,
|
isBuildEnabled: true,
|
||||||
modal: {
|
modal: {
|
||||||
completion: false,
|
completion: false,
|
||||||
help: false,
|
help: false,
|
||||||
@ -59,7 +62,7 @@ export const types = createTypes(
|
|||||||
|
|
||||||
'lockCode',
|
'lockCode',
|
||||||
'unlockCode',
|
'unlockCode',
|
||||||
'disableJSOnError',
|
'disableBuildOnError',
|
||||||
'storedCodeFound',
|
'storedCodeFound',
|
||||||
'noStoredCodeFound',
|
'noStoredCodeFound',
|
||||||
|
|
||||||
@ -136,7 +139,7 @@ export const logsToConsole = createAction(types.logsToConsole);
|
|||||||
|
|
||||||
export const lockCode = createAction(types.lockCode);
|
export const lockCode = createAction(types.lockCode);
|
||||||
export const unlockCode = createAction(types.unlockCode);
|
export const unlockCode = createAction(types.unlockCode);
|
||||||
export const disableJSOnError = createAction(types.disableJSOnError);
|
export const disableBuildOnError = createAction(types.disableBuildOnError);
|
||||||
export const storedCodeFound = createAction(types.storedCodeFound);
|
export const storedCodeFound = createAction(types.storedCodeFound);
|
||||||
export const noStoredCodeFound = createAction(types.noStoredCodeFound);
|
export const noStoredCodeFound = createAction(types.noStoredCodeFound);
|
||||||
|
|
||||||
@ -165,13 +168,55 @@ export const isCompletionModalOpenSelector = state =>
|
|||||||
export const isHelpModalOpenSelector = state => state[ns].modal.help;
|
export const isHelpModalOpenSelector = state => state[ns].modal.help;
|
||||||
export const isVideoModalOpenSelector = state => state[ns].modal.video;
|
export const isVideoModalOpenSelector = state => state[ns].modal.video;
|
||||||
export const isResetModalOpenSelector = state => state[ns].modal.reset;
|
export const isResetModalOpenSelector = state => state[ns].modal.reset;
|
||||||
export const isJSEnabledSelector = state => state[ns].isJSEnabled;
|
export const isBuildEnabledSelector = state => state[ns].isBuildEnabled;
|
||||||
export const successMessageSelector = state => state[ns].successMessage;
|
export const successMessageSelector = state => state[ns].successMessage;
|
||||||
|
|
||||||
export const backendFormValuesSelector = state => state.form[backendNS];
|
export const backendFormValuesSelector = state => state.form[backendNS];
|
||||||
export const projectFormValuesSelector = state =>
|
export const projectFormValuesSelector = state =>
|
||||||
state[ns].projectFormValues || {};
|
state[ns].projectFormValues || {};
|
||||||
|
|
||||||
|
export const challengeDataSelector = state => {
|
||||||
|
const { challengeType } = challengeMetaSelector(state);
|
||||||
|
let challengeData = { challengeType };
|
||||||
|
if (
|
||||||
|
challengeType === challengeTypes.js ||
|
||||||
|
challengeType === challengeTypes.bonfire
|
||||||
|
) {
|
||||||
|
challengeData = {
|
||||||
|
...challengeData,
|
||||||
|
files: challengeFilesSelector(state)
|
||||||
|
};
|
||||||
|
} else if (challengeType === challengeTypes.backend) {
|
||||||
|
const {
|
||||||
|
solution: { value: url }
|
||||||
|
} = backendFormValuesSelector(state);
|
||||||
|
challengeData = {
|
||||||
|
...challengeData,
|
||||||
|
url
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
challengeType === challengeTypes.frontEndProject ||
|
||||||
|
challengeType === challengeTypes.backendEndProject
|
||||||
|
) {
|
||||||
|
challengeData = {
|
||||||
|
...challengeData,
|
||||||
|
...projectFormValuesSelector(state)
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
challengeType === challengeTypes.html ||
|
||||||
|
challengeType === challengeTypes.modern
|
||||||
|
) {
|
||||||
|
const { required = [], template = '' } = challengeMetaSelector(state);
|
||||||
|
challengeData = {
|
||||||
|
...challengeData,
|
||||||
|
files: challengeFilesSelector(state),
|
||||||
|
required,
|
||||||
|
template
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return challengeData;
|
||||||
|
};
|
||||||
|
|
||||||
export const reducer = handleActions(
|
export const reducer = handleActions(
|
||||||
{
|
{
|
||||||
[types.fetchIdToNameMapComplete]: (state, { payload }) => ({
|
[types.fetchIdToNameMapComplete]: (state, { payload }) => ({
|
||||||
@ -269,13 +314,13 @@ export const reducer = handleActions(
|
|||||||
}),
|
}),
|
||||||
[types.unlockCode]: state => ({
|
[types.unlockCode]: state => ({
|
||||||
...state,
|
...state,
|
||||||
isJSEnabled: true,
|
isBuildEnabled: true,
|
||||||
isCodeLocked: false
|
isCodeLocked: false
|
||||||
}),
|
}),
|
||||||
[types.disableJSOnError]: (state, { payload }) => ({
|
[types.disableBuildOnError]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
consoleOut: state.consoleOut + ' \n' + payload,
|
consoleOut: state.consoleOut + ' \n' + payload,
|
||||||
isJSEnabled: false
|
isBuildEnabled: false
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[types.updateSuccessMessage]: (state, { payload }) => ({
|
[types.updateSuccessMessage]: (state, { payload }) => ({
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { transformers } from '../rechallenge/transformers';
|
import { transformers } from '../rechallenge/transformers';
|
||||||
import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js';
|
import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js';
|
||||||
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||||
|
import createWorker from './worker-executor';
|
||||||
|
import { createTestFramer, runTestInTestFrame } from './frame';
|
||||||
|
|
||||||
const frameRunner = [
|
const frameRunner = [
|
||||||
{
|
{
|
||||||
@ -49,9 +52,62 @@ function checkFilesErrors(files) {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildDOMChallenge(files, meta = {}) {
|
const buildFunctions = {
|
||||||
const { required = [], template = '' } = meta;
|
[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);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testRunners = {
|
||||||
|
[challengeTypes.js]: getJSTestRunner,
|
||||||
|
[challengeTypes.html]: getDOMTestRunner,
|
||||||
|
[challengeTypes.backend]: getDOMTestRunner
|
||||||
|
};
|
||||||
|
export function getTestRunner(buildData, proxyLogger, document) {
|
||||||
|
return testRunners[buildData.challengeType](buildData, proxyLogger, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJSTestRunner({ build, sources }, proxyLogger) {
|
||||||
|
const code = sources && 'index' in sources ? sources['index'] : '';
|
||||||
|
|
||||||
|
const testWorker = createWorker('test-evaluator');
|
||||||
|
|
||||||
|
return async(testString, testTimeout) => {
|
||||||
|
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 = '' }) {
|
||||||
const finalRequires = [...globalRequires, ...required, ...frameRunner];
|
const finalRequires = [...globalRequires, ...required, ...frameRunner];
|
||||||
|
const loadEnzyme = Object.keys(files).some(key => files[key].ext === 'jsx');
|
||||||
const toHtml = [jsToHtml, cssToHtml];
|
const toHtml = [jsToHtml, cssToHtml];
|
||||||
const pipeLine = composeFunctions(...transformers, ...toHtml);
|
const pipeLine = composeFunctions(...transformers, ...toHtml);
|
||||||
const finalFiles = Object.keys(files)
|
const finalFiles = Object.keys(files)
|
||||||
@ -60,12 +116,14 @@ export function buildDOMChallenge(files, meta = {}) {
|
|||||||
return Promise.all(finalFiles)
|
return Promise.all(finalFiles)
|
||||||
.then(checkFilesErrors)
|
.then(checkFilesErrors)
|
||||||
.then(files => ({
|
.then(files => ({
|
||||||
|
challengeType: challengeTypes.html,
|
||||||
build: concatHtml({ required: finalRequires, template, files }),
|
build: concatHtml({ required: finalRequires, template, files }),
|
||||||
sources: buildSourceMap(files)
|
sources: buildSourceMap(files),
|
||||||
|
loadEnzyme
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildJSChallenge(files) {
|
export function buildJSChallenge({ files }) {
|
||||||
const pipeLine = composeFunctions(...transformers);
|
const pipeLine = composeFunctions(...transformers);
|
||||||
const finalFiles = Object.keys(files)
|
const finalFiles = Object.keys(files)
|
||||||
.map(key => files[key])
|
.map(key => files[key])
|
||||||
@ -73,6 +131,7 @@ export function buildJSChallenge(files) {
|
|||||||
return Promise.all(finalFiles)
|
return Promise.all(finalFiles)
|
||||||
.then(checkFilesErrors)
|
.then(checkFilesErrors)
|
||||||
.then(files => ({
|
.then(files => ({
|
||||||
|
challengeType: challengeTypes.js,
|
||||||
build: files
|
build: files
|
||||||
.reduce(
|
.reduce(
|
||||||
(body, file) => [...body, file.head, file.contents, file.tail],
|
(body, file) => [...body, file.head, file.contents, file.tail],
|
||||||
@ -83,11 +142,9 @@ export function buildJSChallenge(files) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildBackendChallenge(formValues) {
|
export function buildBackendChallenge({ url }) {
|
||||||
const {
|
|
||||||
solution: { value: url }
|
|
||||||
} = formValues;
|
|
||||||
return {
|
return {
|
||||||
|
challengeType: challengeTypes.backend,
|
||||||
build: concatHtml({ required: frameRunner }),
|
build: concatHtml({ required: frameRunner }),
|
||||||
sources: { url }
|
sources: { url }
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user