Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.
+DisplayMessages
component should render an empty div
element.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })(), 'The DisplayMessages
component should render an empty div
element.');
- text: The DisplayMessages
constructor should be called properly with super
, passing in props
.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,'); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'The DisplayMessages
constructor should be called properly with super
, passing in props
.');
+ testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'The DisplayMessages
constructor should be called properly with super
, passing in props
.');
- text: 'The DisplayMessages
component should have an initial state equal to {input: "", messages: []}
.'
- testString: 'assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return typeof initialState === ''object'' && initialState.input === '''' && Array.isArray(initialState.messages) && initialState.messages.length === 0; })(), ''The DisplayMessages
component should have an initial state equal to {input: "", messages: []}
.'');'
+ testString: "assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); const initialState = mockedComponent.state(); return typeof initialState === 'object' && initialState.input === '' && Array.isArray(initialState.messages) && initialState.messages.length === 0; })(), 'The DisplayMessages
component should have an initial state equal to {input: \"\", messages: []}
.');"
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md b/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md
index e8cb5bbe21..8afecb47fe 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react-and-redux/use-provider-to-connect-redux-to-react.english.md
@@ -26,7 +26,7 @@ tests:
- text: The AppWrapper
should render.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'The AppWrapper
should render.');
- text: The Provider
wrapper component should have a prop of store
passed to it, equal to the Redux store.
- testString: getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/\s/g,').includes('Provider
wrapper component should have a prop of store
passed to it, equal to the Redux store.');
+ testString: getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return getUserInput('index').replace(/\s/g,'').includes('Provider
wrapper component should have a prop of store
passed to it, equal to the Redux store.');
- text: DisplayMessages
should render as a child of AppWrapper
.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })(), 'DisplayMessages
should render as a child of AppWrapper
.');
- text: The DisplayMessages
component should render an h2, input, button, and ul
element.
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/override-default-props.english.md b/curriculum/challenges/english/03-front-end-libraries/react/override-default-props.english.md
index 22b6c9b8e9..e19e51ee57 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/override-default-props.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/override-default-props.english.md
@@ -25,8 +25,8 @@ tests:
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('ShoppingCart').length === 1; })(), 'The component ShoppingCart
should render.');
- text: The component Items
should render.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('Items').length === 1; })(), 'The component Items
should render.');
- - text: 'The Items
component should have a prop of { quantity: 10 }
passed from the ShoppingCart
component.'
- testString: 'getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find(''Items'').props().quantity == 10 && getUserInput(''index'').replace(/ /g,'').includes(''Items
component should have a prop of { quantity: 10 }
passed from the ShoppingCart
component.'');'
+ - text: "The Items
component should have a prop of { quantity: 10 }
passed from the ShoppingCart
component."
+ testString: "getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('Items').props().quantity == 10 && getUserInput('index').replace(/ /g,'').includes('Items
component should have a prop of { quantity: 10 }
passed from the ShoppingCart
component.');"
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/render-react-on-the-server-with-rendertostring.english.md b/curriculum/challenges/english/03-front-end-libraries/react/render-react-on-the-server-with-rendertostring.english.md
index 4b8385d188..885482d0ac 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/render-react-on-the-server-with-rendertostring.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/render-react-on-the-server-with-rendertostring.english.md
@@ -22,7 +22,7 @@ The renderToString()
method is provided on ReactDOMServerApp
component should render to a string using ReactDOMServer.renderToString
.
- testString: getUserInput => assert(getUserInput('index').replace(/ /g,').includes('ReactDOMServer.renderToString(App
component should render to a string using ReactDOMServer.renderToString
.');
+ testString: getUserInput => assert(getUserInput('index').replace(/ /g,'').includes('ReactDOMServer.renderToString(App
component should render to a string using ReactDOMServer.renderToString
.');
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md b/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md
index eaa11f5a7f..fdf4016cf4 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/review-using-props-with-stateless-functional-components.english.md
@@ -27,9 +27,9 @@ tests:
- text: The Camper
component should render.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('Camper').length === 1; })(), 'The Camper
component should render.');
- text: The Camper
component should include default props which assign the string CamperBot
to the key name
.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g, '); const verify1 = 'Camper.defaultProps={name:\'CamperBot\'}'; const verify2 = 'Camper.defaultProps={name:"CamperBot"}'; return (noWhiteSpace.includes(verify1) || noWhiteSpace.includes(verify2)); })(), 'The Camper
component should include default props which assign the string CamperBot
to the key name
.');
+ testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g, ''); const verify1 = 'Camper.defaultProps={name:\'CamperBot\'}'; const verify2 = 'Camper.defaultProps={name:"CamperBot"}'; return (noWhiteSpace.includes(verify1) || noWhiteSpace.includes(verify2)); })(), 'The Camper
component should include default props which assign the string CamperBot
to the key name
.');
- text: The Camper
component should include prop types which require the name
prop to be of type string
.
- testString: getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); const noWhiteSpace = getUserInput('index').replace(/\s/g, '); const verifyDefaultProps = 'Camper.propTypes={name:PropTypes.string.isRequired}'; return noWhiteSpace.includes(verifyDefaultProps); })(), 'The Camper
component should include prop types which require the name
prop to be of type string
.');
+ testString: getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); const noWhiteSpace = getUserInput('index').replace(/\s/g, ''); const verifyDefaultProps = 'Camper.propTypes={name:PropTypes.string.isRequired}'; return noWhiteSpace.includes(verifyDefaultProps); })(), 'The Camper
component should include prop types which require the name
prop to be of type string
.');
- text: The Camper
component should contain a p
element with only the text from the name
prop.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('p').text() === mockedComponent.find('Camper').props().name; })(), 'The Camper
component should contain a p
element with only the text from the name
prop.');
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md b/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md
index 5593f53ef6..152a9302de 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/use-proptypes-to-define-the-props-you-expect.english.md
@@ -30,7 +30,7 @@ tests:
- text: The Items
component should render.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('Items').length === 1; })(), 'The Items
component should render.');
- text: The Items
component should include a propTypes
check that requires quantity
to be a number
.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g, '); return noWhiteSpace.includes('quantity:PropTypes.number.isRequired') && noWhiteSpace.includes('Items.propTypes='); })(), 'The Items
component should include a propTypes
check that requires quantity
to be a number
.');
+ testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g, ''); return noWhiteSpace.includes('quantity:PropTypes.number.isRequired') && noWhiteSpace.includes('Items.propTypes='); })(), 'The Items
component should include a propTypes
check that requires quantity
to be a number
.');
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md b/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md
index a61c0f46be..ebef357540 100644
--- a/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/react/write-a-react-component-from-scratch.english.md
@@ -22,7 +22,7 @@ Render this component to the DOM using ReactDOM.render()
. There is
```yml
tests:
- text: There should be a React component called MyComponent
.
- testString: getUserInput => assert(getUserInput('index').replace(/\s/g, ').includes('classMyComponentextendsReact.Component{'), 'There should be a React component called MyComponent
.');
+ testString: getUserInput => assert(getUserInput('index').replace(/\s/g, '').includes('classMyComponentextendsReact.Component{'), 'There should be a React component called MyComponent
.');
- text: MyComponent
should contain an h1
tag with text My First React Component!
Case and punctuation matter.
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return mockedComponent.find('h1').text() === 'My First React Component!'; })(), 'MyComponent
should contain an h1
tag with text My First React Component!
Case and punctuation matter.');
- text: MyComponent
should render to the DOM.
diff --git a/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md b/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md
index 636b621578..bce57794b2 100644
--- a/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/redux/combine-multiple-reducers.english.md
@@ -29,9 +29,9 @@ tests:
- text: The authReducer
should toggle the state
of authenticated
between true
and false
.
testString: 'assert((function() { store.dispatch({type: LOGIN}); const loggedIn = store.getState().auth.authenticated; store.dispatch({type: LOGOUT}); const loggedOut = store.getState().auth.authenticated; return loggedIn === true && loggedOut === false })(), ''The authReducer
should toggle the state
of authenticated
between true
and false
.'');'
- text: 'The store state
should have two keys: count
, which holds a number, and auth
, which holds an object. The auth
object should have a property of authenticated
, which holds a boolean.'
- testString: 'assert((function() { const state = store.getState(); return typeof state.auth === ''object'' && typeof state.auth.authenticated === ''boolean'' && typeof state.count === ''number'' })(), ''The store state
should have two keys: count
, which holds a number, and auth
, which holds an object. The auth
object should have a property of authenticated
, which holds a boolean.'');'
+ testString: "assert((function() { const state = store.getState(); return typeof state.auth === 'object' && typeof state.auth.authenticated === 'boolean' && typeof state.count === 'number' })(), 'The store state
should have two keys: count
, which holds a number, and auth
, which holds an object. The auth
object should have a property of authenticated
, which holds a boolean.');"
- text: The rootReducer
should be a function that combines the counterReducer
and the authReducer
.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,'); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })(), 'The rootReducer
should be a function that combines the counterReducer
and the authReducer
.');
+ testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })(), 'The rootReducer
should be a function that combines the counterReducer
and the authReducer
.');
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/redux/dispatch-an-action-event.english.md b/curriculum/challenges/english/03-front-end-libraries/redux/dispatch-an-action-event.english.md
index efe04a2e5e..fca66d9d03 100644
--- a/curriculum/challenges/english/03-front-end-libraries/redux/dispatch-an-action-event.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/redux/dispatch-an-action-event.english.md
@@ -27,7 +27,7 @@ tests:
- text: The store should be initialized with an object with property login
set to false
.
testString: assert(store.getState().login === false, 'The store should be initialized with an object with property login
set to false
.');
- text: The store.dispatch()
method should be used to dispatch an action of type LOGIN
.
- testString: 'getUserInput => assert((function() { let noWhiteSpace = getUserInput(''index'').replace(/\s/g,''); return noWhiteSpace.includes(''store.dispatch(loginAction())'') || noWhiteSpace.includes(''store.dispatch({type: \''LOGIN\''})'') === true })(), ''The store.dispatch()
method should be used to dispatch an action of type LOGIN
.'');'
+ testString: "getUserInput => assert((function() { let noWhiteSpace = getUserInput('index').replace(/\\s/g,''); return noWhiteSpace.includes('store.dispatch(loginAction())') || noWhiteSpace.includes('store.dispatch({type: \\'LOGIN\\'})') === true })(), 'The store.dispatch()
method should be used to dispatch an action of type LOGIN
.');"
```
diff --git a/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md b/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md
index e0ff5c6561..bc3ff0c977 100644
--- a/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md
+++ b/curriculum/challenges/english/03-front-end-libraries/redux/use-const-for-action-types.english.md
@@ -34,9 +34,9 @@ tests:
- text: The authReducer
function should handle multiple action types with a switch statement.
testString: getUserInput => assert((function() { return typeof authReducer === 'function' && getUserInput('index').toString().includes('switch') && getUserInput('index').toString().includes('case') && getUserInput('index').toString().includes('default') })(), 'The authReducer
function should handle multiple action types with a switch statement.');
- text: LOGIN
and LOGOUT
should be declared as const
values and should be assigned strings of LOGIN
and LOGOUT
.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\s/g,'); return (noWhiteSpace.includes('constLOGIN=\'LOGIN\') || noWhiteSpace.includes('constLOGIN="LOGIN"')) && (noWhiteSpace.includes('constLOGOUT=\'LOGOUT\') || noWhiteSpace.includes('constLOGOUT="LOGOUT"')) })(), 'LOGIN
and LOGOUT
should be declared as const
values and should be assigned strings of LOGIN
and LOGOUT
.');
+ testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\s/g,''); return (noWhiteSpace.includes('constLOGIN=\'LOGIN\'') || noWhiteSpace.includes('constLOGIN="LOGIN"')) && (noWhiteSpace.includes('constLOGOUT=\'LOGOUT\'') || noWhiteSpace.includes('constLOGOUT="LOGOUT"')) })(), 'LOGIN
and LOGOUT
should be declared as const
values and should be assigned strings of LOGIN
and LOGOUT
.');
- text: The action creators and the reducer should reference the LOGIN
and LOGOUT
constants.
- testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\s/g,'); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })(), 'The action creators and the reducer should reference the LOGIN
and LOGOUT
constants.');
+ testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').toString().replace(/\s/g,''); return noWhiteSpace.includes('caseLOGIN:') && noWhiteSpace.includes('caseLOGOUT:') && noWhiteSpace.includes('type:LOGIN') && noWhiteSpace.includes('type:LOGOUT') })(), 'The action creators and the reducer should reference the LOGIN
and LOGOUT
constants.');
```
diff --git a/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/announce-new-users.english.md b/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/announce-new-users.english.md
index 8e434b46e1..30491df9ab 100644
--- a/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/announce-new-users.english.md
+++ b/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/announce-new-users.english.md
@@ -36,7 +36,7 @@ tests:
- text: Event 'user' is emitted with name, currentUsers, and connected
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /io.emit.*('|")user('|").*name.*currentUsers.*connected/gi, 'You should have an event emitted named user sending name, currentUsers, and connected'); }, xhr => { throw new Error(xhr.statusText); })
- text: Client properly handling and displaying the new data from event 'user'
- testString: getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.on.*('|")user('|")[^]*num-users/gi, 'You should change the text of #num-users within on your client within the "user" even listener to show the current users connected'); assert.match(data, /socket.on.*('|")user('|")[^]*messages.*li/gi, 'You should append a list item to #messages on your client within the "user" event listener to annouce a user came or went'); }, xhr => { throw new Error(xhr.statusText); })
+ testString: "getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.on.*('|\")user('|\")[^]*num-users/gi, 'You should change the text of #num-users within on your client within the \"user\" even listener to show the current users connected'); assert.match(data, /socket.on.*('|\")user('|\")[^]*messages.*li/gi, 'You should append a list item to #messages on your client within the \"user\" event listener to annouce a user came or went'); }, xhr => { throw new Error(xhr.statusText); })"
```
diff --git a/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.english.md b/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.english.md
index f23017b443..209bbc41b9 100644
--- a/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.english.md
+++ b/curriculum/challenges/english/06-information-security-and-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.english.md
@@ -27,7 +27,7 @@ tests:
- text: Server listens for 'chat message' then emits it properly
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*name.*message/gi, 'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object'); }, xhr => { throw new Error(xhr.statusText); })
- text: Client properly handling and displaying the new data from event 'chat message'
- testString: getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.on.*('|")chat message('|")[^]*messages.*li/gi, 'You should append a list item to #messages on your client within the "chat message" event listener to display the new message'); }, xhr => { throw new Error(xhr.statusText); })
+ testString: "getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.on.*('|\")chat message('|\")[^]*messages.*li/gi, 'You should append a list item to #messages on your client within the \"chat message\" event listener to display the new message'); }, xhr => { throw new Error(xhr.statusText); })"
```
diff --git a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-162-hexadecimal-numbers.english.md b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-162-hexadecimal-numbers.english.md
index bd2e9c751a..690b237e51 100644
--- a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-162-hexadecimal-numbers.english.md
+++ b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-162-hexadecimal-numbers.english.md
@@ -27,7 +27,7 @@ Give your answer as a hexadecimal number.
```yml
tests:
- text: euler162()
should return 3D58725572C62302.
- testString: assert.strictEqual(euler162(), 3D58725572C62302, 'euler162()
should return 3D58725572C62302.');
+ testString: assert.strictEqual(euler162(), '3D58725572C62302', 'euler162()
should return 3D58725572C62302.');
```
diff --git a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-284-steady-squares.english.md b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-284-steady-squares.english.md
index 7e2b54ff63..798f0e1369 100644
--- a/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-284-steady-squares.english.md
+++ b/curriculum/challenges/english/08-coding-interview-prep/project-euler/problem-284-steady-squares.english.md
@@ -27,7 +27,7 @@ Find the sum of the digits of all the n-digit steady squares in the base 14 numb
```yml
tests:
- text: euler284()
should return 5a411d7b.
- testString: assert.strictEqual(euler284(), 5a411d7b, 'euler284()
should return 5a411d7b.');
+ testString: assert.strictEqual(euler284(), '5a411d7b', 'euler284()
should return 5a411d7b.');
```
diff --git a/curriculum/mongoIds.js b/curriculum/mongoIds.js
index 034a0b48cf..03d96b578b 100644
--- a/curriculum/mongoIds.js
+++ b/curriculum/mongoIds.js
@@ -1,5 +1,5 @@
-import _ from 'lodash';
-import { isMongoId } from 'validator';
+const _ = require('lodash');
+const { isMongoId } = require('validator');
class MongoIds {
constructor() {
@@ -21,4 +21,4 @@ class MongoIds {
}
}
-export default MongoIds;
+module.exports = MongoIds;
diff --git a/curriculum/package.json b/curriculum/package.json
index 78773da345..f4b46b404c 100644
--- a/curriculum/package.json
+++ b/curriculum/package.json
@@ -21,17 +21,17 @@
"prepare": "npm run build",
"repack": "babel-node ./repack.js",
"semantic-release": "semantic-release",
- "test": "babel-node ./test-challenges.js | tap-spec",
+ "test": "node ./test-challenges.js | tap-spec",
"unpack": "babel-node ./unpack.js"
},
"dependencies": {
- "@freecodecamp/challenge-md-parser": "^1.0.0",
"invariant": "^2.2.4"
},
"devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.1",
"@commitlint/travis-cli": "^7.0.0",
+ "@freecodecamp/challenge-md-parser": "^1.0.0",
"@semantic-release/changelog": "^2.0.2",
"@semantic-release/git": "^5.0.0",
"babel-cli": "^6.3.17",
diff --git a/curriculum/schema/challengeSchema.js b/curriculum/schema/challengeSchema.js
index d7fa28c123..2bcc621dab 100644
--- a/curriculum/schema/challengeSchema.js
+++ b/curriculum/schema/challengeSchema.js
@@ -4,18 +4,16 @@ Joi.objectId = require('joi-objectid')(Joi);
const schema = Joi.object().keys({
block: Joi.string(),
blockId: Joi.objectId(),
+ challengeOrder: Joi.number(),
challengeType: Joi.number()
.min(0)
.max(9)
.required(),
checksum: Joi.number(),
dashedName: Joi.string(),
- description: Joi.array()
- .items(Joi.string().allow(''))
- .required(),
+ description: Joi.string().required(),
fileName: Joi.string(),
- files: Joi.object().pattern(
- /(jsx?|html|css|sass)$/,
+ files: Joi.array().items(
Joi.object().keys({
key: Joi.string(),
ext: Joi.string(),
@@ -32,6 +30,7 @@ const schema = Joi.object().keys({
videoUrl: Joi.string().allow(''),
helpRoom: Joi.string(),
id: Joi.objectId().required(),
+ instructions: Joi.string().required(),
isBeta: Joi.bool(),
isComingSoon: Joi.bool(),
isLocked: Joi.bool(),
diff --git a/curriculum/test-challenges.js b/curriculum/test-challenges.js
index 4b084778b4..46ea00c90e 100644
--- a/curriculum/test-challenges.js
+++ b/curriculum/test-challenges.js
@@ -1,39 +1,43 @@
-/* eslint-disable no-eval, no-process-exit, no-unused-vars */
+/* eslint-disable no-process-exit, no-unused-vars */
-import { Observable } from 'rx';
-import tape from 'tape';
+const { Observable } = require('rx');
+const tape = require('tape');
+const { flatten } = require('lodash');
+const vm = require('vm');
+const path = require('path');
+require('dotenv').config({ path: path.resolve(__dirname, '../.env') });
-import getChallenges from './getChallenges';
+const { getChallengesForLang } = require('./getChallenges');
-import MongoIds from './mongoIds';
-import ChallengeTitles from './challengeTitles';
-import addAssertsToTapTest from './addAssertsToTapTest';
-import { validateChallenge } from './schema/challengeSchema';
+const MongoIds = require('./mongoIds');
+const ChallengeTitles = require('./challengeTitles');
+const addAssertsToTapTest = require('./addAssertsToTapTest');
+const { validateChallenge } = require('./schema/challengeSchema');
-// modern challengeType
-const modern = 6;
+const { LOCALE: lang } = process.env;
+
+const { challengeTypes } = require('../client/utils/challengeTypes');
let mongoIds = new MongoIds();
let challengeTitles = new ChallengeTitles();
-function evaluateTest(
+function checkSyntax(test, tapTest) {
+ try {
+ // eslint-disable-next-line
+ new vm.Script(test.testString);
+ tapTest.pass(test.text);
+ } catch (e) {
+ tapTest.fail(e);
+ }
+}
+
+function evaluateHtmlJsTest(
solution,
assert,
- react,
- redux,
- reactRedux,
- head,
- tail,
+ files,
test,
tapTest
) {
- let code = solution;
-
- /* NOTE: Provide dependencies for React/Redux challenges
- * and configure testing environment
- */
- let React, ReactDOM, Redux, ReduxThunk, ReactRedux, Enzyme, document;
-
// Fake Deep Equal dependency
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -53,173 +57,250 @@ function evaluateTest(
return o;
};
- if (react || redux || reactRedux) {
- // Provide dependencies, just provide all of them
- React = require('react');
- ReactDOM = require('react-dom');
- Redux = require('redux');
- ReduxThunk = require('redux-thunk');
- ReactRedux = require('react-redux');
- Enzyme = require('enzyme');
- const Adapter15 = require('enzyme-adapter-react-15');
- Enzyme.configure({ adapter: new Adapter15() });
-
- /* Transpile ALL the code
- * (we may use JSX in head or tail or tests, too): */
- const transform = require('babel-standalone').transform;
- const options = { presets: ['es2015', 'react'] };
-
- head = transform(head, options).code;
- solution = transform(solution, options).code;
- tail = transform(tail, options).code;
- test = transform(test, options).code;
+ let sandbox = {
+ assert,
+ code: solution,
+ DeepEqual,
+ DeepFreeze,
+ test: test.testString
+ };
+ if (files.html) {
+ const { head, tail } = files.html;
const { JSDOM } = require('jsdom');
- // Mock DOM document for ReactDOM.render method
- const jsdom = new JSDOM(`
-
-
-
-
-
- `);
- const { window } = jsdom;
-
- // Mock DOM for ReactDOM tests
- document = window.document;
- global.window = window;
- global.document = window.document;
+ const jsdom = new JSDOM(`
+
+
+ ${head}
+ ${solution}
+ ${tail}
+
+ `);
+ const jQuery = require('jquery')(jsdom.window);
+ sandbox = {
+ ...sandbox,
+ window: jsdom.window,
+ document: jsdom.window.document,
+ $: jQuery
+ };
}
+ let scriptString = '';
+ if (files.js) {
+ const { head, tail } = files.js;
+ scriptString = head + '\n' + solution + '\n' + tail + '\n';
+ }
+
+ try {
+ const context = vm.createContext(sandbox);
+ scriptString += `
+ const testResult = eval(test);
+ if (typeof testResult === 'function') {
+ testResult(() => code);
+ }`;
+ const script = new vm.Script(scriptString);
+ script.runInContext(context);
+ } catch (e) {
+ // console.log(scriptString);
+ // console.log(e);
+ tapTest.fail(e);
+ // process.exit(1);
+ }
+}
+
+function evaluateReactReduxTest() {
+ /* NOTE: Provide dependencies for React/Redux challenges
+ * and configure testing environment
+ */
+ // let React, ReactDOM, Redux, ReduxThunk, ReactRedux, Enzyme, document;
+
+ // if (react || redux || reactRedux) {
+ // // Provide dependencies, just provide all of them
+ // React = require('react');
+ // ReactDOM = require('react-dom');
+ // Redux = require('redux');
+ // ReduxThunk = require('redux-thunk');
+ // ReactRedux = require('react-redux');
+ // Enzyme = require('enzyme');
+ // const Adapter15 = require('enzyme-adapter-react-15');
+ // Enzyme.configure({ adapter: new Adapter15() });
+
+ // /* Transpile ALL the code
+ // * (we may use JSX in head or tail or tests, too): */
+ // const transform = require('babel-standalone').transform;
+ // const options = { presets: ['es2015', 'react'] };
+
+ // head = transform(head, options).code;
+ // solution = transform(solution, options).code;
+ // tail = transform(tail, options).code;
+ // test = transform(test, options).code;
+
+ // const { JSDOM } = require('jsdom');
+ // // Mock DOM document for ReactDOM.render method
+ // const jsdom = new JSDOM(`
+ //
+ //
+ //
+ //
+ //
+ // `);
+ // const { window } = jsdom;
+
+ // // Mock DOM for ReactDOM tests
+ // document = window.document;
+ // global.window = window;
+ // global.document = window.document;
+ // }
+
/* eslint-enable no-unused-vars */
- try {
- (() => {
- return eval(
- head + '\n' + solution + '\n' + tail + '\n' + test.testString
- );
- })();
- } catch (e) {
- console.log(head + '\n' + solution + '\n' + tail + '\n' + test.testString);
- console.log(e);
- tapTest.fail(e);
- process.exit(1);
- }
+
+ // No support for async tests
+ // const isAsync = s => s.includes('(async () => ');
+
+ // try {
+ // if (!isAsync(test.testString)) {
+ // const context = vm.createContext(sandbox);
+ // const scriptString =
+ // head + '\n' + solution + '\n' + tail + '\n' + `
+ // const testResult = eval(test);
+ // if (typeof testResult === 'function') {
+ // testResult(() => code);
+ // }`;
+ // const script = new vm.Script(scriptString);
+ // script.runInContext(context);
+ // } else {
+ // // For async tests only check syntax
+ // // eslint-disable-next-line
+ // new vm.Script(test.testString);
+ // tapTest.pass(test.text);
+ // }
+ // } catch (e) {
+ // console.log(head + '\n' + solution + '\n' + tail + '\n' + test.testString);
+ // // console.log(e);
+ // tapTest.fail(e);
+ // // process.exit(1);
+ // }
}
function createTest({
title,
id = '',
+ challengeType,
tests = [],
solutions = [],
- files = [],
- react = false,
- redux = false,
- reactRedux = false
+ files = []
}) {
mongoIds.check(id, title);
challengeTitles.check(title);
- solutions = solutions.filter(solution => !!solution);
- tests = tests.filter(test => !!test);
-
- // No support for async tests
- const isAsync = s => s.includes('(async () => ');
- if (isAsync(tests.join(''))) {
- console.log(`Replacing Async Tests for Challenge ${title}`);
- tests = tests.map(
- challengeTestSource =>
- isAsync(challengeTestSource)
- ? "assert(true, 'message: great');"
- : challengeTestSource
- );
+ // if title starts with [word] [number], for example `Problem 5`,
+ // tap-spec does not recognize it as test suite.
+ const titleRe = new RegExp('^([a-z]+\\s+)(\\d+.*)$', 'i');
+ const match = titleRe.exec(title);
+ if (match) {
+ title = `${match[1]}#${match[2]}`;
}
- const { head, tail } = Object.keys(files)
- .map(key => files[key])
- .reduce(
- (result, file) => ({
- head: result.head + ';' + file.head.join('\n'),
- tail: result.tail + ';' + file.tail.join('\n')
- }),
- { head: '', tail: '' }
- );
- const plan = tests.length;
- if (!plan) {
- return Observable.just({
- title,
- type: 'missing'
+
+ const testSuite = Observable.fromCallback(tape)(title);
+
+ tests = tests.filter(test => !!test.testString);
+ if (tests.length === 0) {
+ return testSuite.flatMap(tapTest => {
+ tapTest.end();
+ return Observable.just(title);
});
}
- return Observable.fromCallback(tape)(title)
- .doOnNext(
- tapTest => (solutions.length ? tapTest.plan(plan) : tapTest.end())
- )
- .flatMap(tapTest => {
- if (solutions.length <= 0) {
- return Observable.just({
- title,
- type: 'missing'
- });
- }
+ const noSolution = new RegExp('// solution required');
+ solutions = solutions.filter(solution => (
+ !!solution && !noSolution.test(solution)
+ ));
+ const skipTests = challengeType !== challengeTypes.html &&
+ challengeType !== challengeTypes.js &&
+ challengeType !== challengeTypes.bonfire &&
+ challengeType !== challengeTypes.zipline;
+
+ // For problems without a solution, check only the syntax of the tests.
+ if (solutions.length === 0 || skipTests) {
+ return testSuite.flatMap(tapTest => {
+ tapTest.plan(tests.length);
+ tests.forEach(test => {
+ checkSyntax(test, tapTest);
+ });
+ return Observable.just(title);
+ });
+ }
+
+ const exts = Array.from(new Set(files.map(({ ext }) => ext)));
+ const groupedFiles = exts.reduce((result, ext) => {
+ const file = files.filter(file => file.ext === ext ).reduce(
+ (result, file) => ({
+ head: result.head + ';' + file.head,
+ tail: result.tail + ';' + file.tail
+ }),
+ { head: '', tail: '' }
+ );
+ return {
+ ...result,
+ [ext]: file
+ };
+ }, {});
+
+ const plan = tests.length * solutions.length;
+ return testSuite
+ .flatMap(tapTest => {
+ tapTest.plan(plan);
return (
Observable.just(tapTest)
.map(addAssertsToTapTest)
- /* eslint-disable no-unused-vars */
- // assert and code used within the eval
.doOnNext(assert => {
solutions.forEach(solution => {
tests.forEach(test => {
- evaluateTest(
+ evaluateHtmlJsTest(
solution,
assert,
- react,
- redux,
- reactRedux,
- head,
- tail,
+ groupedFiles,
test,
tapTest
);
});
});
})
- .map(() => ({ title }))
+ .ignoreElements()
);
});
}
-Observable.from(getChallenges())
- .do(({ challenges }) => {
- challenges.forEach(challenge => {
- const result = validateChallenge(challenge);
- if (result.error) {
- console.log(result.value);
- throw new Error(result.error);
- }
- });
+Observable.fromPromise(getChallengesForLang(lang || 'english'))
+ .flatMap(curriculum => {
+ const allChallenges = Object.keys(curriculum)
+ .map(key => curriculum[key].blocks)
+ .reduce((challengeArray, superBlock) => {
+ const challengesForBlock = Object.keys(superBlock).map(
+ key => superBlock[key].challenges
+ );
+ return [...challengeArray, ...flatten(challengesForBlock)];
+ }, []);
+ return Observable.from(allChallenges);
})
- .flatMap(challengeSpec => {
- return Observable.from(challengeSpec.challenges);
+ .do(challenge => {
+ const result = validateChallenge(challenge);
+ if (result.error) {
+ console.log(result.value);
+ throw new Error(result.error);
+ }
})
- .filter(({ challengeType }) => challengeType !== modern)
.flatMap(challenge => {
return createTest(challenge);
})
- .map(({ title, type }) => {
- if (type === 'missing') {
- return title;
- }
- return false;
- })
- .filter(title => !!title)
.toArray()
.subscribe(
noSolutions => {
if (noSolutions) {
console.log(
- '# These challenges have no solutions\n- [ ] ' +
- noSolutions.join('\n- [ ] ')
+ `# These challenges have no solutions (${noSolutions.length})\n` +
+ '- [ ] ' + noSolutions.join('\n- [ ] ')
);
}
},
diff --git a/package.json b/package.json
index d473dfc279..22f6bac204 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"test": "npm-run-all -p test:*",
"test-ci": "npm test",
"test:client": "cd ./client && npm test && cd ../",
- "test:curriculum": "echo 'Warning: TODO - Define Testing.'",
+ "test:curriculum": "cd ./curriculum && npm test && cd ../",
"test:guide-directories": "node ./tools/scripts/ci/ensure-guide-page-naming.js",
"test:server": "echo 'Warning: TODO - Define Testing.'",
"test:tools": "jest ./tools"
diff --git a/tools/challenge-md-parser/index.js b/tools/challenge-md-parser/index.js
index 6e08341fa5..0f9fc92ea2 100644
--- a/tools/challenge-md-parser/index.js
+++ b/tools/challenge-md-parser/index.js
@@ -10,6 +10,7 @@ const frontmatterToData = require('./frontmatter-to-data');
const textToData = require('./text-to-data');
const testsToData = require('./tests-to-data');
const challengeSeedToData = require('./challengeSeed-to-data');
+const solutionsToData = require('./solution-to-data');
const processor = unified()
.use(markdown)
@@ -18,6 +19,7 @@ const processor = unified()
.use(testsToData)
.use(remark2rehype, { allowDangerousHTML: true })
.use(raw)
+ .use(solutionsToData)
.use(textToData, ['description', 'instructions'])
.use(challengeSeedToData)
// the plugins below are just to stop the processor from throwing
diff --git a/tools/challenge-md-parser/solution-to-data.js b/tools/challenge-md-parser/solution-to-data.js
index 1b63733f0c..66a742b17b 100644
--- a/tools/challenge-md-parser/solution-to-data.js
+++ b/tools/challenge-md-parser/solution-to-data.js
@@ -10,7 +10,10 @@ function createPlugin() {
const solutions = selectAll('code', node).map(
element => element.children[0].value
);
- file.data.solutions = solutions;
+ file.data = {
+ ...file.data,
+ solutions
+ };
}
}
visit(tree, 'element', visitor);