Merge branch 'ValeraS-fix/disable-build-on-error'
This commit is contained in:
@ -10,56 +10,48 @@ import {
|
||||
import { delay, channel } from 'redux-saga';
|
||||
|
||||
import {
|
||||
backendFormValuesSelector,
|
||||
challengeFilesSelector,
|
||||
challengeMetaSelector,
|
||||
challengeDataSelector,
|
||||
challengeTestsSelector,
|
||||
initConsole,
|
||||
updateConsole,
|
||||
initLogs,
|
||||
updateLogs,
|
||||
logsToConsole,
|
||||
updateTests
|
||||
updateTests,
|
||||
isBuildEnabledSelector,
|
||||
disableBuildOnError
|
||||
} from './';
|
||||
|
||||
import {
|
||||
buildJSChallenge,
|
||||
buildDOMChallenge,
|
||||
buildBackendChallenge
|
||||
buildChallenge,
|
||||
getTestRunner,
|
||||
challengeHasPreview,
|
||||
updatePreview
|
||||
} from '../utils/build';
|
||||
|
||||
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||
|
||||
import createWorker from '../utils/worker-executor';
|
||||
import {
|
||||
createMainFramer,
|
||||
createTestFramer,
|
||||
runTestInTestFrame
|
||||
} from '../utils/frame.js';
|
||||
|
||||
export function* executeChallengeSaga() {
|
||||
const isBuildEnabled = yield select(isBuildEnabledSelector);
|
||||
if (!isBuildEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const consoleProxy = yield channel();
|
||||
try {
|
||||
const { js, bonfire, backend } = challengeTypes;
|
||||
const { challengeType } = yield select(challengeMetaSelector);
|
||||
|
||||
yield put(initLogs());
|
||||
yield put(initConsole('// running tests'));
|
||||
yield fork(logToConsole, consoleProxy);
|
||||
const proxyLogger = args => consoleProxy.put(args);
|
||||
|
||||
let testResults;
|
||||
switch (challengeType) {
|
||||
case js:
|
||||
case bonfire:
|
||||
testResults = yield executeJSChallengeSaga(proxyLogger);
|
||||
break;
|
||||
case backend:
|
||||
testResults = yield executeBackendChallengeSaga(proxyLogger);
|
||||
break;
|
||||
default:
|
||||
testResults = yield executeDOMChallengeSaga(proxyLogger);
|
||||
}
|
||||
const challengeData = yield select(challengeDataSelector);
|
||||
const buildData = yield buildChallengeData(challengeData);
|
||||
const document = yield getContext('document');
|
||||
const testRunner = yield call(
|
||||
getTestRunner,
|
||||
buildData,
|
||||
proxyLogger,
|
||||
document
|
||||
);
|
||||
const testResults = yield executeTests(testRunner);
|
||||
|
||||
yield put(updateTests(testResults));
|
||||
yield put(updateConsole('// tests completed'));
|
||||
@ -77,63 +69,16 @@ function* logToConsole(channel) {
|
||||
});
|
||||
}
|
||||
|
||||
function* executeJSChallengeSaga(proxyLogger) {
|
||||
const files = yield select(challengeFilesSelector);
|
||||
const { build, sources } = yield call(buildJSChallenge, files);
|
||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
||||
|
||||
const testWorker = createWorker('test-evaluator');
|
||||
testWorker.on('LOG', proxyLogger);
|
||||
|
||||
function* buildChallengeData(challengeData) {
|
||||
try {
|
||||
return yield call(executeTests, async(testString, testTimeout) => {
|
||||
try {
|
||||
return await testWorker.execute(
|
||||
{ build, testString, code, sources },
|
||||
testTimeout
|
||||
);
|
||||
} finally {
|
||||
testWorker.killWorker();
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
testWorker.remove('LOG', proxyLogger);
|
||||
return yield call(buildChallenge, challengeData);
|
||||
} catch (e) {
|
||||
yield put(disableBuildOnError(e));
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw 'Build failed';
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
const tests = yield select(challengeTestsSelector);
|
||||
const testTimeout = 5000;
|
||||
@ -167,20 +112,23 @@ function* executeTests(testRunner) {
|
||||
return testResults;
|
||||
}
|
||||
|
||||
function* updateMainSaga() {
|
||||
yield delay(500);
|
||||
function* previewChallengeSaga() {
|
||||
yield delay(700);
|
||||
|
||||
const isBuildEnabled = yield select(isBuildEnabledSelector);
|
||||
if (!isBuildEnabled) {
|
||||
return;
|
||||
}
|
||||
const challengeData = yield select(challengeDataSelector);
|
||||
if (!challengeHasPreview(challengeData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { html, modern } = challengeTypes;
|
||||
const meta = yield select(challengeMetaSelector);
|
||||
const { challengeType } = meta;
|
||||
if (challengeType !== html && challengeType !== modern) {
|
||||
return;
|
||||
}
|
||||
const files = yield select(challengeFilesSelector);
|
||||
const ctx = yield call(buildDOMChallenge, files, meta);
|
||||
yield put(initConsole(''));
|
||||
const ctx = yield buildChallengeData(challengeData);
|
||||
const document = yield getContext('document');
|
||||
const frameMain = yield call(createMainFramer, document);
|
||||
yield call(frameMain, ctx);
|
||||
yield call(updatePreview, ctx, document);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@ -196,7 +144,7 @@ export function createExecuteChallengeSaga(types) {
|
||||
types.challengeMounted,
|
||||
types.resetChallenge
|
||||
],
|
||||
updateMainSaga
|
||||
previewChallengeSaga
|
||||
)
|
||||
];
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import codeStorageEpic from './code-storage-epic';
|
||||
import { createIdToNameMapSaga } from './id-to-name-map-saga';
|
||||
import { createExecuteChallengeSaga } from './execute-challenge-saga';
|
||||
import { createCurrentChallengeSaga } from './current-challenge-saga';
|
||||
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||
|
||||
export const ns = 'challenge';
|
||||
export const backendNS = 'backendChallenge';
|
||||
@ -23,12 +24,14 @@ const initialState = {
|
||||
challengeIdToNameMap: {},
|
||||
challengeMeta: {
|
||||
id: '',
|
||||
nextChallengePath: '/'
|
||||
nextChallengePath: '/',
|
||||
introPath: '',
|
||||
challengeType: -1
|
||||
},
|
||||
challengeTests: [],
|
||||
consoleOut: '',
|
||||
isCodeLocked: false,
|
||||
isJSEnabled: true,
|
||||
isBuildEnabled: true,
|
||||
modal: {
|
||||
completion: false,
|
||||
help: false,
|
||||
@ -59,7 +62,7 @@ export const types = createTypes(
|
||||
|
||||
'lockCode',
|
||||
'unlockCode',
|
||||
'disableJSOnError',
|
||||
'disableBuildOnError',
|
||||
'storedCodeFound',
|
||||
'noStoredCodeFound',
|
||||
|
||||
@ -136,7 +139,7 @@ export const logsToConsole = createAction(types.logsToConsole);
|
||||
|
||||
export const lockCode = createAction(types.lockCode);
|
||||
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 noStoredCodeFound = createAction(types.noStoredCodeFound);
|
||||
|
||||
@ -165,13 +168,55 @@ export const isCompletionModalOpenSelector = state =>
|
||||
export const isHelpModalOpenSelector = state => state[ns].modal.help;
|
||||
export const isVideoModalOpenSelector = state => state[ns].modal.video;
|
||||
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 backendFormValuesSelector = state => state.form[backendNS];
|
||||
export const projectFormValuesSelector = state =>
|
||||
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(
|
||||
{
|
||||
[types.fetchIdToNameMapComplete]: (state, { payload }) => ({
|
||||
@ -269,13 +314,13 @@ export const reducer = handleActions(
|
||||
}),
|
||||
[types.unlockCode]: state => ({
|
||||
...state,
|
||||
isJSEnabled: true,
|
||||
isBuildEnabled: true,
|
||||
isCodeLocked: false
|
||||
}),
|
||||
[types.disableJSOnError]: (state, { payload }) => ({
|
||||
[types.disableBuildOnError]: (state, { payload }) => ({
|
||||
...state,
|
||||
consoleOut: state.consoleOut + ' \n' + payload,
|
||||
isJSEnabled: false
|
||||
isBuildEnabled: false
|
||||
}),
|
||||
|
||||
[types.updateSuccessMessage]: (state, { payload }) => ({
|
||||
|
@ -1,5 +1,12 @@
|
||||
import { transformers } from '../rechallenge/transformers';
|
||||
import { cssToHtml, jsToHtml, concatHtml } from '../rechallenge/builders.js';
|
||||
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||
import createWorker from './worker-executor';
|
||||
import {
|
||||
createTestFramer,
|
||||
runTestInTestFrame,
|
||||
createMainFramer
|
||||
} from './frame';
|
||||
|
||||
const frameRunner = [
|
||||
{
|
||||
@ -49,9 +56,67 @@ function checkFilesErrors(files) {
|
||||
return files;
|
||||
}
|
||||
|
||||
export function buildDOMChallenge(files, meta = {}) {
|
||||
const { required = [], template = '' } = meta;
|
||||
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);
|
||||
}
|
||||
throw new Error(`Cannot build challenge of type ${challengeType}`);
|
||||
}
|
||||
|
||||
const testRunners = {
|
||||
[challengeTypes.js]: getJSTestRunner,
|
||||
[challengeTypes.html]: getDOMTestRunner,
|
||||
[challengeTypes.backend]: getDOMTestRunner
|
||||
};
|
||||
export function getTestRunner(buildData, proxyLogger, document) {
|
||||
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}`);
|
||||
}
|
||||
|
||||
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 loadEnzyme = Object.keys(files).some(key => files[key].ext === 'jsx');
|
||||
const toHtml = [jsToHtml, cssToHtml];
|
||||
const pipeLine = composeFunctions(...transformers, ...toHtml);
|
||||
const finalFiles = Object.keys(files)
|
||||
@ -60,12 +125,14 @@ export function buildDOMChallenge(files, meta = {}) {
|
||||
return Promise.all(finalFiles)
|
||||
.then(checkFilesErrors)
|
||||
.then(files => ({
|
||||
challengeType: challengeTypes.html,
|
||||
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 finalFiles = Object.keys(files)
|
||||
.map(key => files[key])
|
||||
@ -73,6 +140,7 @@ export function buildJSChallenge(files) {
|
||||
return Promise.all(finalFiles)
|
||||
.then(checkFilesErrors)
|
||||
.then(files => ({
|
||||
challengeType: challengeTypes.js,
|
||||
build: files
|
||||
.reduce(
|
||||
(body, file) => [...body, file.head, file.contents, file.tail],
|
||||
@ -83,12 +151,26 @@ export function buildJSChallenge(files) {
|
||||
}));
|
||||
}
|
||||
|
||||
export function buildBackendChallenge(formValues) {
|
||||
const {
|
||||
solution: { value: url }
|
||||
} = formValues;
|
||||
export function buildBackendChallenge({ url }) {
|
||||
return {
|
||||
challengeType: challengeTypes.backend,
|
||||
build: concatHtml({ required: frameRunner }),
|
||||
sources: { url }
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
@ -280,9 +280,8 @@ async function createTestRunnerForDOMChallenge(
|
||||
files[0].contents = solution;
|
||||
}
|
||||
|
||||
const loadEnzyme = files[0].ext === 'jsx';
|
||||
|
||||
const { build, sources } = await buildDOMChallenge(files, {
|
||||
const { build, sources, loadEnzyme } = await buildDOMChallenge({
|
||||
files,
|
||||
required,
|
||||
template
|
||||
});
|
||||
@ -324,7 +323,7 @@ async function createTestRunnerForJSChallenge({ files }, solution) {
|
||||
files[0].contents = solution;
|
||||
}
|
||||
|
||||
const { build, sources } = await buildJSChallenge(files);
|
||||
const { build, sources } = await buildJSChallenge({ files });
|
||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
||||
|
||||
const testWorker = createWorker('test-evaluator');
|
||||
|
@ -3,10 +3,9 @@ title: Logical Operators
|
||||
---
|
||||
|
||||
# Logical Operators
|
||||
Logical operators compare Boolean values and return a Boolean response. There are two types of logical operators - Logical AND and Logical OR. These operators are often written as && for AND, and || for OR.
|
||||
|
||||
Logical operators compare Boolean values and return a Boolean response. There are two types of logical operators: Logical AND, and Logical OR. These operators are often written as && for AND, and || for OR.
|
||||
|
||||
#### Logical AND ( && )
|
||||
## Logical AND ( && )
|
||||
|
||||
The AND operator compares two expressions. If the first evaluates as ["truthy"](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), the statement will return the value of the second expression.
|
||||
If the first evaluates as ["falsy"](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), the statement will return the value of the first expression.
|
||||
@ -22,9 +21,9 @@ undefined && 'abc' //returns the first value, undefined
|
||||
0 && false //returns the first value, 0
|
||||
```
|
||||
|
||||
#### Logical OR ( || )
|
||||
## Logical OR ( || )
|
||||
|
||||
The OR operator compares two expressions. If the first evaluates as "falsy", the statement will return the value of the second second expression. If the first evaluates as "truthy", the statement will return the value of the first expression.
|
||||
The OR operator compares two expressions. If the first evaluates as "falsy", the statement will return the value of the second expression. If the first evaluates as "truthy", the statement will return the value of the first expression.
|
||||
|
||||
When only involving boolean values (either `true` or `false`), it returns true if either expression is true. Both expressions can be true, but only one is needed to get true as a result.
|
||||
```js
|
||||
@ -36,28 +35,15 @@ false || false //returns the second value, false
|
||||
undefined || 'abc' //returns the second value, 'abc'
|
||||
0 || false //returns the second value, false
|
||||
```
|
||||
#### Short-circuit evaluation
|
||||
&& and || behave as a short-circuit operators.
|
||||
|
||||
In case of the logical AND, if the first operand returns false, the second operand is never evaluated and first operand is returned.
|
||||
|
||||
In case of the logical OR, if the first value returns true, the second value is never evaluated and the first operand is returned.
|
||||
|
||||
```js
|
||||
false && (anything) // short-circuit evaluated to false.
|
||||
true || (anything) // short-circuit evaluated to true.
|
||||
```
|
||||
|
||||
#### Logical NOT (!)
|
||||
|
||||
## Logical NOT (!)
|
||||
The NOT operator does not do any comparison like the AND and OR operators.Moreover it is operated only on 1 operand.
|
||||
|
||||
An '!' (exclamation) mark is used for representing the NOT operator.
|
||||
|
||||
###### Use of NOT operators
|
||||
|
||||
1. conversion of the expression into boolean.
|
||||
2. returns the inverse of the boolean value obtained in last step.
|
||||
### Use of NOT operators
|
||||
- conversion of the expression into boolean.
|
||||
- returns the inverse of the boolean value obtained in last step.
|
||||
|
||||
```js
|
||||
var spam = 'rinki'; //spam may be equal to any non empty string
|
||||
@ -74,10 +60,20 @@ var booSpam2 = !spam2;
|
||||
inverse of which is evaluated to true.
|
||||
*/
|
||||
```
|
||||
#### Tips:
|
||||
Both logical operators will return the value of the last evaluated expression. For example:
|
||||
|
||||
## Short-circuit evaluation
|
||||
`&&` and `||` behave as a short-circuit operators.
|
||||
|
||||
In case of the logical AND, if the first operand returns false, the second operand is never evaluated and first operand is returned.
|
||||
|
||||
In case of the logical OR, if the first value returns true, the second value is never evaluated and the first operand is returned.
|
||||
|
||||
Some Examples:
|
||||
```js
|
||||
false && (anything) // short-circuit evaluated to false.
|
||||
true && (anything) // short-circuit evaluated to the result of the expression (anything)
|
||||
true || (anything) // short-circuit evaluated to true.
|
||||
|
||||
"cat" && "dog" //returns "dog"
|
||||
"cat" && false //returns false
|
||||
0 && "cat" //returns 0 (which is a falsy value)
|
||||
@ -89,8 +85,6 @@ Both logical operators will return the value of the last evaluated expression. F
|
||||
|
||||
Note that where `&&` returns the first value, `||` returns the second value and vice versa.
|
||||
|
||||
#### More information:
|
||||
|
||||
* [Javascript Truth Table](https://guide.freecodecamp.org/javascript/truth-table)
|
||||
|
||||
* [MDN](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Logical_Operators)
|
||||
## Additional Resources
|
||||
- [Javascript Truth Table](https://guide.freecodecamp.org/javascript/truth-table)
|
||||
- [MDN](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Logical_Operators)
|
||||
|
Reference in New Issue
Block a user