fix: test curriculum challenges (#24180)
This commit is contained in:
committed by
mrugesh mohapatra
parent
7da04a348b
commit
e099d6486d
@ -12,11 +12,13 @@ function createIsAssert(tapTest, isThing) {
|
||||
function addAssertsToTapTest(tapTest) {
|
||||
const assert = tapTest.assert;
|
||||
|
||||
assert.isTrue = createIsAssert(tapTest, v => v === true);
|
||||
assert.isArray = createIsAssert(tapTest, _.isArray);
|
||||
assert.isBoolean = createIsAssert(tapTest, _.isBoolean);
|
||||
assert.isString = createIsAssert(tapTest, _.isString);
|
||||
assert.isNumber = createIsAssert(tapTest, _.isNumber);
|
||||
assert.isUndefined = createIsAssert(tapTest, _.isUndefined);
|
||||
assert.isNaN = createIsAssert(tapTest, _.isNaN);
|
||||
|
||||
assert.deepEqual = tapTest.deepEqual;
|
||||
assert.equal = tapTest.equal;
|
||||
|
@ -1,16 +1,17 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
class ChallengeTitles {
|
||||
constructor() {
|
||||
this.knownTitles = [];
|
||||
}
|
||||
check(title) {
|
||||
if (typeof title !== 'string') {
|
||||
throw new Error(`Expected a valid string for ${title}, but got a(n) ${typeof title}`);
|
||||
} else if (title.length === 0) {
|
||||
throw new Error(`Expected a title length greater than 0`);
|
||||
throw new Error(
|
||||
`Expected a valid string for ${title}, but got a(n) ${typeof title}`
|
||||
);
|
||||
}
|
||||
const titleToCheck = title.replace(/\s+/g, '').toLowerCase();
|
||||
if (titleToCheck.length === 0) {
|
||||
throw new Error('Expected a title length greater than 0');
|
||||
}
|
||||
const titleToCheck = title.toLowerCase().replace(/\s+/g, '');
|
||||
const isKnown = this.knownTitles.includes(titleToCheck);
|
||||
if (isKnown) {
|
||||
throw new Error(`
|
||||
@ -23,4 +24,4 @@ class ChallengeTitles {
|
||||
}
|
||||
}
|
||||
|
||||
export default ChallengeTitles;
|
||||
module.exports = ChallengeTitles;
|
||||
|
@ -79,9 +79,41 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
var code = ".fullCard {\nwidth: 245px; border: 1px solid #ccc; border-radius: 5px; margin: 10px 5px; padding: 4px;}"
|
||||
```html
|
||||
<style>
|
||||
h4 {
|
||||
text-align: center;
|
||||
}
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
.links {
|
||||
margin-right: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.fullCard {
|
||||
width: 245px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 10px 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
.cardContent {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="fullCard">
|
||||
<div class="cardContent">
|
||||
<div class="cardText">
|
||||
<h4>Google</h4>
|
||||
<p>Google was founded by Larry Page and Sergey Brin while they were Ph.D. students at Stanford University.</p>
|
||||
</div>
|
||||
<div class="cardLinks">
|
||||
<a href="https://en.wikipedia.org/wiki/Larry_Page" target="_blank" class="links">Larry Page</a>
|
||||
<a href="https://en.wikipedia.org/wiki/Sergey_Brin" target="_blank" class="links">Sergey Brin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@ -90,9 +90,42 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
|
||||
```js
|
||||
var code = ".heart {transform: rotate(-45deg);} .heart::after {background-color: pink; border-radius: 50%;} .heart::before {content: \"\"; border-radius: 50%;}"
|
||||
```html
|
||||
<style>
|
||||
.heart {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: pink;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
.heart::after {
|
||||
background-color: pink;
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: 0px;
|
||||
left: 25px;
|
||||
}
|
||||
.heart::before {
|
||||
content: "";
|
||||
background-color: pink;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: -25px;
|
||||
left: 0px;
|
||||
}
|
||||
</style>
|
||||
<div class = "heart"></div>
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@ -84,7 +84,44 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```js
|
||||
// solution required
|
||||
```html
|
||||
<style>
|
||||
.injected-text {
|
||||
margin-bottom: -25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box {
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
border-width: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.yellow-box {
|
||||
background-color: yellow;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.red-box {
|
||||
background-color: crimson;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
margin: -15px;
|
||||
}
|
||||
|
||||
.blue-box {
|
||||
background-color: blue;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
margin-top: -15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="box yellow-box">
|
||||
<h5 class="box red-box">padding</h5>
|
||||
<h5 class="box blue-box">padding</h5>
|
||||
</div>
|
||||
```
|
||||
</section>
|
||||
|
@ -54,7 +54,7 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```js
|
||||
// solution required
|
||||
```html
|
||||
<h1>Hello World</h1>
|
||||
```
|
||||
</section>
|
||||
|
@ -81,9 +81,38 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```html
|
||||
<style>
|
||||
.item1{background:LightSkyBlue;}
|
||||
.item2{background:LightSalmon;}
|
||||
.item3{background:PaleTurquoise;}
|
||||
.item4{background:LightPink;}
|
||||
.item5{background:PaleGreen;}
|
||||
|
||||
```js
|
||||
var code = ".container {grid-template-areas: \"header header header\" \". content content\" \"footer footer footer\";}"
|
||||
.container {
|
||||
font-size: 40px;
|
||||
min-height: 300px;
|
||||
width: 100%;
|
||||
background: LightGray;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
|
||||
grid-template-areas:
|
||||
"header header header"
|
||||
". content content"
|
||||
"footer footer footer";
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<div class="item1">1</div>
|
||||
<div class="item2">2</div>
|
||||
<div class="item3">3</div>
|
||||
<div class="item4">4</div>
|
||||
<div class="item5">5</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@ -113,9 +113,75 @@ tests:
|
||||
## Solution
|
||||
<section id='solution'>
|
||||
|
||||
```html
|
||||
<style>
|
||||
.item1 {
|
||||
background: LightSkyBlue;
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
```js
|
||||
var code = "@media (min-width: 400px){.container{ grid-template-areas: \"header header\" \"advert content\" \"footer footer\";}}"
|
||||
.item2 {
|
||||
background: LightSalmon;
|
||||
grid-area: advert;
|
||||
}
|
||||
|
||||
.item3 {
|
||||
background: PaleTurquoise;
|
||||
grid-area: content;
|
||||
}
|
||||
|
||||
.item4 {
|
||||
background: lightpink;
|
||||
grid-area: footer;
|
||||
}
|
||||
|
||||
.container {
|
||||
font-size: 1.5em;
|
||||
min-height: 300px;
|
||||
width: 100%;
|
||||
background: LightGray;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 50px auto 1fr auto;
|
||||
grid-gap: 10px;
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"advert"
|
||||
"content"
|
||||
"footer";
|
||||
}
|
||||
|
||||
@media (min-width: 300px){
|
||||
.container{
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
grid-template-areas:
|
||||
"advert header"
|
||||
"advert content"
|
||||
"advert footer";
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 400px){
|
||||
.container{
|
||||
/* change the code below this line */
|
||||
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"advert content"
|
||||
"footer footer";
|
||||
|
||||
/* change the code above this line */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<div class="item1">header</div>
|
||||
<div class="item2">advert</div>
|
||||
<div class="item3">content</div>
|
||||
<div class="item4">footer</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
</section>
|
||||
|
@ -25,9 +25,9 @@ tests:
|
||||
- text: The <code>DisplayMessages</code> component should render an empty <code>div</code> element.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(DisplayMessages)); return mockedComponent.find('div').text() === '' })(), 'The <code>DisplayMessages</code> component should render an empty <code>div</code> element.');
|
||||
- text: The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.
|
||||
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,'); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.');
|
||||
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return noWhiteSpace.includes('constructor(props)') && noWhiteSpace.includes('super(props'); })(), 'The <code>DisplayMessages</code> constructor should be called properly with <code>super</code>, passing in <code>props</code>.');
|
||||
- text: 'The <code>DisplayMessages</code> component should have an initial state equal to <code>{input: "", messages: []}</code>.'
|
||||
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 <code>DisplayMessages</code> component should have an initial state equal to <code>{input: "", messages: []}</code>.'');'
|
||||
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 <code>DisplayMessages</code> component should have an initial state equal to <code>{input: \"\", messages: []}</code>.');"
|
||||
|
||||
```
|
||||
|
||||
|
@ -26,7 +26,7 @@ tests:
|
||||
- text: The <code>AppWrapper</code> should render.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').length === 1; })(), 'The <code>AppWrapper</code> should render.');
|
||||
- text: The <code>Provider</code> wrapper component should have a prop of <code>store</code> 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('<Providerstore={store}>'); })(), 'The <code>Provider</code> wrapper component should have a prop of <code>store</code> 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('<Providerstore={store}>'); })(), 'The <code>Provider</code> wrapper component should have a prop of <code>store</code> passed to it, equal to the Redux store.');
|
||||
- text: <code>DisplayMessages</code> should render as a child of <code>AppWrapper</code>.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(AppWrapper)); return mockedComponent.find('AppWrapper').find('DisplayMessages').length === 1; })(), '<code>DisplayMessages</code> should render as a child of <code>AppWrapper</code>.');
|
||||
- text: The <code>DisplayMessages</code> component should render an h2, input, button, and <code>ul</code> element.
|
||||
|
@ -25,8 +25,8 @@ tests:
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('ShoppingCart').length === 1; })(), 'The component <code>ShoppingCart</code> should render.');
|
||||
- text: The component <code>Items</code> should render.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('Items').length === 1; })(), 'The component <code>Items</code> should render.');
|
||||
- text: 'The <code>Items</code> component should have a prop of <code>{ quantity: 10 }</code> passed from the <code>ShoppingCart</code> component.'
|
||||
testString: 'getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find(''Items'').props().quantity == 10 && getUserInput(''index'').replace(/ /g,'').includes(''<Itemsquantity={10}/>''); })(), ''The <code>Items</code> component should have a prop of <code>{ quantity: 10 }</code> passed from the <code>ShoppingCart</code> component.'');'
|
||||
- text: "The <code>Items</code> component should have a prop of <code>{ quantity: 10 }</code> passed from the <code>ShoppingCart</code> component."
|
||||
testString: "getUserInput => assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('Items').props().quantity == 10 && getUserInput('index').replace(/ /g,'').includes('<Itemsquantity={10}/>'); })(), 'The <code>Items</code> component should have a prop of <code>{ quantity: 10 }</code> passed from the <code>ShoppingCart</code> component.');"
|
||||
|
||||
```
|
||||
|
||||
|
@ -22,7 +22,7 @@ The <code>renderToString()</code> method is provided on <code>ReactDOMServer</co
|
||||
```yml
|
||||
tests:
|
||||
- text: The <code>App</code> component should render to a string using <code>ReactDOMServer.renderToString</code>.
|
||||
testString: getUserInput => assert(getUserInput('index').replace(/ /g,').includes('ReactDOMServer.renderToString(<App/>)') && Enzyme.mount(React.createElement(App)).children().name() === 'div', 'The <code>App</code> component should render to a string using <code>ReactDOMServer.renderToString</code>.');
|
||||
testString: getUserInput => assert(getUserInput('index').replace(/ /g,'').includes('ReactDOMServer.renderToString(<App/>)') && Enzyme.mount(React.createElement(App)).children().name() === 'div', 'The <code>App</code> component should render to a string using <code>ReactDOMServer.renderToString</code>.');
|
||||
|
||||
```
|
||||
|
||||
|
@ -27,9 +27,9 @@ tests:
|
||||
- text: The <code>Camper</code> component should render.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('Camper').length === 1; })(), 'The <code>Camper</code> component should render.');
|
||||
- text: The <code>Camper</code> component should include default props which assign the string <code>CamperBot</code> to the key <code>name</code>.
|
||||
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 <code>Camper</code> component should include default props which assign the string <code>CamperBot</code> to the key <code>name</code>.');
|
||||
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 <code>Camper</code> component should include default props which assign the string <code>CamperBot</code> to the key <code>name</code>.');
|
||||
- text: The <code>Camper</code> component should include prop types which require the <code>name</code> prop to be of type <code>string</code>.
|
||||
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 <code>Camper</code> component should include prop types which require the <code>name</code> prop to be of type <code>string</code>.');
|
||||
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 <code>Camper</code> component should include prop types which require the <code>name</code> prop to be of type <code>string</code>.');
|
||||
- text: The <code>Camper</code> component should contain a <code>p</code> element with only the text from the <code>name</code> prop.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(CampSite)); return mockedComponent.find('p').text() === mockedComponent.find('Camper').props().name; })(), 'The <code>Camper</code> component should contain a <code>p</code> element with only the text from the <code>name</code> prop.');
|
||||
|
||||
|
@ -30,7 +30,7 @@ tests:
|
||||
- text: The <code>Items</code> component should render.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(ShoppingCart)); return mockedComponent.find('Items').length === 1; })(), 'The <code>Items</code> component should render.');
|
||||
- text: The <code>Items</code> component should include a <code>propTypes</code> check that requires <code>quantity</code> to be a <code>number</code>.
|
||||
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g, '); return noWhiteSpace.includes('quantity:PropTypes.number.isRequired') && noWhiteSpace.includes('Items.propTypes='); })(), 'The <code>Items</code> component should include a <code>propTypes</code> check that requires <code>quantity</code> to be a <code>number</code>.');
|
||||
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/ /g, ''); return noWhiteSpace.includes('quantity:PropTypes.number.isRequired') && noWhiteSpace.includes('Items.propTypes='); })(), 'The <code>Items</code> component should include a <code>propTypes</code> check that requires <code>quantity</code> to be a <code>number</code>.');
|
||||
|
||||
```
|
||||
|
||||
|
@ -22,7 +22,7 @@ Render this component to the DOM using <code>ReactDOM.render()</code>. There is
|
||||
```yml
|
||||
tests:
|
||||
- text: There should be a React component called <code>MyComponent</code>.
|
||||
testString: getUserInput => assert(getUserInput('index').replace(/\s/g, ').includes('classMyComponentextendsReact.Component{'), 'There should be a React component called <code>MyComponent</code>.');
|
||||
testString: getUserInput => assert(getUserInput('index').replace(/\s/g, '').includes('classMyComponentextendsReact.Component{'), 'There should be a React component called <code>MyComponent</code>.');
|
||||
- text: <code>MyComponent</code> should contain an <code>h1</code> tag with text <code>My First React Component!</code> Case and punctuation matter.
|
||||
testString: assert((function() { const mockedComponent = Enzyme.mount(React.createElement(MyComponent)); return mockedComponent.find('h1').text() === 'My First React Component!'; })(), '<code>MyComponent</code> should contain an <code>h1</code> tag with text <code>My First React Component!</code> Case and punctuation matter.');
|
||||
- text: <code>MyComponent</code> should render to the DOM.
|
||||
|
@ -29,9 +29,9 @@ tests:
|
||||
- text: The <code>authReducer</code> should toggle the <code>state</code> of <code>authenticated</code> between <code>true</code> and <code>false</code>.
|
||||
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 <code>authReducer</code> should toggle the <code>state</code> of <code>authenticated</code> between <code>true</code> and <code>false</code>.'');'
|
||||
- text: 'The store <code>state</code> should have two keys: <code>count</code>, which holds a number, and <code>auth</code>, which holds an object. The <code>auth</code> object should have a property of <code>authenticated</code>, 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 <code>state</code> should have two keys: <code>count</code>, which holds a number, and <code>auth</code>, which holds an object. The <code>auth</code> object should have a property of <code>authenticated</code>, 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 <code>state</code> should have two keys: <code>count</code>, which holds a number, and <code>auth</code>, which holds an object. The <code>auth</code> object should have a property of <code>authenticated</code>, which holds a boolean.');"
|
||||
- text: The <code>rootReducer</code> should be a function that combines the <code>counterReducer</code> and the <code>authReducer</code>.
|
||||
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,'); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })(), 'The <code>rootReducer</code> should be a function that combines the <code>counterReducer</code> and the <code>authReducer</code>.');
|
||||
testString: getUserInput => assert((function() { const noWhiteSpace = getUserInput('index').replace(/\s/g,''); return typeof rootReducer === 'function' && noWhiteSpace.includes('Redux.combineReducers') })(), 'The <code>rootReducer</code> should be a function that combines the <code>counterReducer</code> and the <code>authReducer</code>.');
|
||||
|
||||
```
|
||||
|
||||
|
@ -27,7 +27,7 @@ tests:
|
||||
- text: The store should be initialized with an object with property <code>login</code> set to <code>false</code>.
|
||||
testString: assert(store.getState().login === false, 'The store should be initialized with an object with property <code>login</code> set to <code>false</code>.');
|
||||
- text: The <code>store.dispatch()</code> method should be used to dispatch an action of type <code>LOGIN</code>.
|
||||
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 <code>store.dispatch()</code> method should be used to dispatch an action of type <code>LOGIN</code>.'');'
|
||||
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 <code>store.dispatch()</code> method should be used to dispatch an action of type <code>LOGIN</code>.');"
|
||||
|
||||
```
|
||||
|
||||
|
@ -34,9 +34,9 @@ tests:
|
||||
- text: The <code>authReducer</code> 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 <code>authReducer</code> function should handle multiple action types with a switch statement.');
|
||||
- text: <code>LOGIN</code> and <code>LOGOUT</code> should be declared as <code>const</code> values and should be assigned strings of <code>LOGIN</code>and <code>LOGOUT</code>.
|
||||
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"')) })(), '<code>LOGIN</code> and <code>LOGOUT</code> should be declared as <code>const</code> values and should be assigned strings of <code>LOGIN</code>and <code>LOGOUT</code>.');
|
||||
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"')) })(), '<code>LOGIN</code> and <code>LOGOUT</code> should be declared as <code>const</code> values and should be assigned strings of <code>LOGIN</code>and <code>LOGOUT</code>.');
|
||||
- text: The action creators and the reducer should reference the <code>LOGIN</code> and <code>LOGOUT</code> 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 <code>LOGIN</code> and <code>LOGOUT</code> 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 <code>LOGIN</code> and <code>LOGOUT</code> constants.');
|
||||
|
||||
```
|
||||
|
||||
|
@ -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); })"
|
||||
|
||||
```
|
||||
|
||||
|
@ -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); })"
|
||||
|
||||
```
|
||||
|
||||
|
@ -27,7 +27,7 @@ Give your answer as a hexadecimal number.
|
||||
```yml
|
||||
tests:
|
||||
- text: <code>euler162()</code> should return 3D58725572C62302.
|
||||
testString: assert.strictEqual(euler162(), 3D58725572C62302, '<code>euler162()</code> should return 3D58725572C62302.');
|
||||
testString: assert.strictEqual(euler162(), '3D58725572C62302', '<code>euler162()</code> should return 3D58725572C62302.');
|
||||
|
||||
```
|
||||
|
||||
|
@ -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: <code>euler284()</code> should return 5a411d7b.
|
||||
testString: assert.strictEqual(euler284(), 5a411d7b, '<code>euler284()</code> should return 5a411d7b.');
|
||||
testString: assert.strictEqual(euler284(), '5a411d7b', '<code>euler284()</code> should return 5a411d7b.');
|
||||
|
||||
```
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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(),
|
||||
|
@ -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(`<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<div id="challenge-node"></div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
const { window } = jsdom;
|
||||
|
||||
// Mock DOM for ReactDOM tests
|
||||
document = window.document;
|
||||
global.window = window;
|
||||
global.document = window.document;
|
||||
const jsdom = new JSDOM(`
|
||||
<!doctype html>
|
||||
<html>
|
||||
${head}
|
||||
${solution}
|
||||
${tail}
|
||||
</html>
|
||||
`);
|
||||
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(`<!doctype html>
|
||||
// <html>
|
||||
// <body>
|
||||
// <div id="challenge-node"></div>
|
||||
// </body>
|
||||
// </html>
|
||||
// `);
|
||||
// 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- [ ] ')
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user