From cafbe33cc7abbe67d003179c69e03d2be0adce6d Mon Sep 17 00:00:00 2001 From: Rachel Irene Lunan Date: Sat, 16 Feb 2019 11:41:49 -0600 Subject: [PATCH 1/4] mild proofreading edits (#29854) removed repeated words and unnecessary punctuation --- .../javascript/logical-operators/index.md | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/guide/english/javascript/logical-operators/index.md b/guide/english/javascript/logical-operators/index.md index 7c1fdfe0e6..4de796f9e5 100644 --- a/guide/english/javascript/logical-operators/index.md +++ b/guide/english/javascript/logical-operators/index.md @@ -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) From 98f979f3b47cd677a339141b8d841a49fe82760e Mon Sep 17 00:00:00 2001 From: Valeriy S Date: Fri, 8 Feb 2019 17:33:05 +0300 Subject: [PATCH 2/4] fix(client): disable build on error --- .../redux/execute-challenge-saga.js | 123 +++++------------- .../src/templates/Challenges/redux/index.js | 61 +++++++-- .../src/templates/Challenges/utils/build.js | 73 +++++++++-- 3 files changed, 153 insertions(+), 104 deletions(-) diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js index 1992e18cc7..f7bb97558d 100644 --- a/client/src/templates/Challenges/redux/execute-challenge-saga.js +++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js @@ -10,56 +10,46 @@ 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 -} from '../utils/build'; +import { buildChallenge, getTestRunner } from '../utils/build'; import { challengeTypes } from '../../../../utils/challengeTypes'; -import createWorker from '../utils/worker-executor'; -import { - createMainFramer, - createTestFramer, - runTestInTestFrame -} from '../utils/frame.js'; +import { createMainFramer } 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 buildData = yield buildChallengeData(); + 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 +67,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() { + const challengeData = yield select(challengeDataSelector); 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)); + 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; @@ -168,16 +111,20 @@ function* executeTests(testRunner) { } function* updateMainSaga() { - yield delay(500); + const isBuildEnabled = yield select(isBuildEnabledSelector); + if (!isBuildEnabled) { + return; + } + + yield delay(700); try { + yield put(initConsole('')); const { html, modern } = challengeTypes; - const meta = yield select(challengeMetaSelector); - const { challengeType } = meta; + const { challengeType } = yield select(challengeDataSelector); if (challengeType !== html && challengeType !== modern) { return; } - const files = yield select(challengeFilesSelector); - const ctx = yield call(buildDOMChallenge, files, meta); + const ctx = yield buildChallengeData(); const document = yield getContext('document'); const frameMain = yield call(createMainFramer, document); yield call(frameMain, ctx); diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index 30ab22112a..046a6ca7da 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -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 }) => ({ diff --git a/client/src/templates/Challenges/utils/build.js b/client/src/templates/Challenges/utils/build.js index b74cf7aef4..7faead1bad 100644 --- a/client/src/templates/Challenges/utils/build.js +++ b/client/src/templates/Challenges/utils/build.js @@ -1,5 +1,8 @@ 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 } from './frame'; const frameRunner = [ { @@ -49,9 +52,62 @@ 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); + } + 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 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 +116,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 +131,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,11 +142,9 @@ 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 } }; From 67140a4d337c1ef6b31c9793e5b2c3d13c9d692f Mon Sep 17 00:00:00 2001 From: Valeriy S Date: Wed, 13 Feb 2019 10:07:14 +0300 Subject: [PATCH 3/4] fix(tests): fix broken tests --- curriculum/test/test-challenges.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/curriculum/test/test-challenges.js b/curriculum/test/test-challenges.js index 8697635052..1efa4260b9 100644 --- a/curriculum/test/test-challenges.js +++ b/curriculum/test/test-challenges.js @@ -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'); From 4c252654a4e2f62a84f01b6d912ad7c2f62f1700 Mon Sep 17 00:00:00 2001 From: Valeriy S Date: Wed, 13 Feb 2019 15:47:00 +0300 Subject: [PATCH 4/4] fix(client): refactor a challenge preview saga --- .../redux/execute-challenge-saga.js | 41 ++++++++++--------- .../src/templates/Challenges/utils/build.js | 31 ++++++++++++-- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/client/src/templates/Challenges/redux/execute-challenge-saga.js b/client/src/templates/Challenges/redux/execute-challenge-saga.js index f7bb97558d..b6b7863b1f 100644 --- a/client/src/templates/Challenges/redux/execute-challenge-saga.js +++ b/client/src/templates/Challenges/redux/execute-challenge-saga.js @@ -22,11 +22,12 @@ import { disableBuildOnError } from './'; -import { buildChallenge, getTestRunner } from '../utils/build'; - -import { challengeTypes } from '../../../../utils/challengeTypes'; - -import { createMainFramer } from '../utils/frame.js'; +import { + buildChallenge, + getTestRunner, + challengeHasPreview, + updatePreview +} from '../utils/build'; export function* executeChallengeSaga() { const isBuildEnabled = yield select(isBuildEnabledSelector); @@ -41,7 +42,8 @@ export function* executeChallengeSaga() { yield fork(logToConsole, consoleProxy); const proxyLogger = args => consoleProxy.put(args); - const buildData = yield buildChallengeData(); + const challengeData = yield select(challengeDataSelector); + const buildData = yield buildChallengeData(challengeData); const document = yield getContext('document'); const testRunner = yield call( getTestRunner, @@ -67,13 +69,13 @@ function* logToConsole(channel) { }); } -function* buildChallengeData() { - const challengeData = yield select(challengeDataSelector); +function* buildChallengeData(challengeData) { try { return yield call(buildChallenge, challengeData); } catch (e) { yield put(disableBuildOnError(e)); - throw ['Build failed']; + // eslint-disable-next-line no-throw-literal + throw 'Build failed'; } } @@ -110,24 +112,23 @@ function* executeTests(testRunner) { return testResults; } -function* updateMainSaga() { +function* previewChallengeSaga() { + yield delay(700); + const isBuildEnabled = yield select(isBuildEnabledSelector); if (!isBuildEnabled) { return; } + const challengeData = yield select(challengeDataSelector); + if (!challengeHasPreview(challengeData)) { + return; + } - yield delay(700); try { yield put(initConsole('')); - const { html, modern } = challengeTypes; - const { challengeType } = yield select(challengeDataSelector); - if (challengeType !== html && challengeType !== modern) { - return; - } - const ctx = yield buildChallengeData(); + 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); } @@ -143,7 +144,7 @@ export function createExecuteChallengeSaga(types) { types.challengeMounted, types.resetChallenge ], - updateMainSaga + previewChallengeSaga ) ]; } diff --git a/client/src/templates/Challenges/utils/build.js b/client/src/templates/Challenges/utils/build.js index 7faead1bad..ee5f89f919 100644 --- a/client/src/templates/Challenges/utils/build.js +++ b/client/src/templates/Challenges/utils/build.js @@ -2,7 +2,11 @@ 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 } from './frame'; +import { + createTestFramer, + runTestInTestFrame, + createMainFramer +} from './frame'; const frameRunner = [ { @@ -66,7 +70,7 @@ export async function buildChallenge(challengeData) { if (build) { return build(challengeData); } - return null; + throw new Error(`Cannot build challenge of type ${challengeType}`); } const testRunners = { @@ -75,7 +79,12 @@ const testRunners = { [challengeTypes.backend]: getDOMTestRunner }; export function getTestRunner(buildData, proxyLogger, document) { - return testRunners[buildData.challengeType](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) { @@ -149,3 +158,19 @@ export function buildBackendChallenge({ url }) { 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 + ); +}