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) {
|
function addAssertsToTapTest(tapTest) {
|
||||||
const assert = tapTest.assert;
|
const assert = tapTest.assert;
|
||||||
|
|
||||||
|
assert.isTrue = createIsAssert(tapTest, v => v === true);
|
||||||
assert.isArray = createIsAssert(tapTest, _.isArray);
|
assert.isArray = createIsAssert(tapTest, _.isArray);
|
||||||
assert.isBoolean = createIsAssert(tapTest, _.isBoolean);
|
assert.isBoolean = createIsAssert(tapTest, _.isBoolean);
|
||||||
assert.isString = createIsAssert(tapTest, _.isString);
|
assert.isString = createIsAssert(tapTest, _.isString);
|
||||||
assert.isNumber = createIsAssert(tapTest, _.isNumber);
|
assert.isNumber = createIsAssert(tapTest, _.isNumber);
|
||||||
assert.isUndefined = createIsAssert(tapTest, _.isUndefined);
|
assert.isUndefined = createIsAssert(tapTest, _.isUndefined);
|
||||||
|
assert.isNaN = createIsAssert(tapTest, _.isNaN);
|
||||||
|
|
||||||
assert.deepEqual = tapTest.deepEqual;
|
assert.deepEqual = tapTest.deepEqual;
|
||||||
assert.equal = tapTest.equal;
|
assert.equal = tapTest.equal;
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
class ChallengeTitles {
|
class ChallengeTitles {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.knownTitles = [];
|
this.knownTitles = [];
|
||||||
}
|
}
|
||||||
check(title) {
|
check(title) {
|
||||||
if (typeof title !== 'string') {
|
if (typeof title !== 'string') {
|
||||||
throw new Error(`Expected a valid string for ${title}, but got a(n) ${typeof title}`);
|
throw new Error(
|
||||||
} else if (title.length === 0) {
|
`Expected a valid string for ${title}, but got a(n) ${typeof title}`
|
||||||
throw new Error(`Expected a title length greater than 0`);
|
);
|
||||||
|
}
|
||||||
|
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);
|
const isKnown = this.knownTitles.includes(titleToCheck);
|
||||||
if (isKnown) {
|
if (isKnown) {
|
||||||
throw new Error(`
|
throw new Error(`
|
||||||
@ -23,4 +24,4 @@ class ChallengeTitles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChallengeTitles;
|
module.exports = ChallengeTitles;
|
||||||
|
@ -79,9 +79,41 @@ tests:
|
|||||||
## Solution
|
## Solution
|
||||||
<section id='solution'>
|
<section id='solution'>
|
||||||
|
|
||||||
|
```html
|
||||||
```js
|
<style>
|
||||||
var code = ".fullCard {\nwidth: 245px; border: 1px solid #ccc; border-radius: 5px; margin: 10px 5px; padding: 4px;}"
|
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>
|
</section>
|
||||||
|
@ -90,9 +90,42 @@ tests:
|
|||||||
## Solution
|
## Solution
|
||||||
<section id='solution'>
|
<section id='solution'>
|
||||||
|
|
||||||
|
```html
|
||||||
```js
|
<style>
|
||||||
var code = ".heart {transform: rotate(-45deg);} .heart::after {background-color: pink; border-radius: 50%;} .heart::before {content: \"\"; border-radius: 50%;}"
|
.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>
|
</section>
|
||||||
|
@ -84,7 +84,44 @@ tests:
|
|||||||
## Solution
|
## Solution
|
||||||
<section id='solution'>
|
<section id='solution'>
|
||||||
|
|
||||||
```js
|
```html
|
||||||
// solution required
|
<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>
|
</section>
|
||||||
|
@ -54,7 +54,7 @@ tests:
|
|||||||
## Solution
|
## Solution
|
||||||
<section id='solution'>
|
<section id='solution'>
|
||||||
|
|
||||||
```js
|
```html
|
||||||
// solution required
|
<h1>Hello World</h1>
|
||||||
```
|
```
|
||||||
</section>
|
</section>
|
||||||
|
@ -81,9 +81,38 @@ tests:
|
|||||||
## Solution
|
## Solution
|
||||||
<section id='solution'>
|
<section id='solution'>
|
||||||
|
|
||||||
|
```html
|
||||||
|
<style>
|
||||||
|
.item1{background:LightSkyBlue;}
|
||||||
|
.item2{background:LightSalmon;}
|
||||||
|
.item3{background:PaleTurquoise;}
|
||||||
|
.item4{background:LightPink;}
|
||||||
|
.item5{background:PaleGreen;}
|
||||||
|
|
||||||
```js
|
.container {
|
||||||
var code = ".container {grid-template-areas: \"header header header\" \". content content\" \"footer footer footer\";}"
|
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>
|
</section>
|
||||||
|
@ -113,9 +113,75 @@ tests:
|
|||||||
## Solution
|
## Solution
|
||||||
<section id='solution'>
|
<section id='solution'>
|
||||||
|
|
||||||
|
```html
|
||||||
|
<style>
|
||||||
|
.item1 {
|
||||||
|
background: LightSkyBlue;
|
||||||
|
grid-area: header;
|
||||||
|
}
|
||||||
|
|
||||||
```js
|
.item2 {
|
||||||
var code = "@media (min-width: 400px){.container{ grid-template-areas: \"header header\" \"advert content\" \"footer footer\";}}"
|
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>
|
</section>
|
||||||
|
@ -25,9 +25,9 @@ tests:
|
|||||||
- text: The <code>DisplayMessages</code> component should render an empty <code>div</code> element.
|
- 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.');
|
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>.
|
- 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>.'
|
- 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.
|
- 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.');
|
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.
|
- 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>.
|
- 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>.');
|
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.
|
- 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.');
|
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.
|
- 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.');
|
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.'
|
- 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.'');'
|
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
|
```yml
|
||||||
tests:
|
tests:
|
||||||
- text: The <code>App</code> component should render to a string using <code>ReactDOMServer.renderToString</code>.
|
- 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.
|
- 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.');
|
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>.
|
- 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>.
|
- 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.
|
- 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.');
|
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.
|
- 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.');
|
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>.
|
- 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
|
```yml
|
||||||
tests:
|
tests:
|
||||||
- text: There should be a React component called <code>MyComponent</code>.
|
- 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.
|
- 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.');
|
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.
|
- 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>.
|
- 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>.'');'
|
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.'
|
- 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>.
|
- 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>.
|
- 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>.');
|
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>.
|
- 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.
|
- 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.');
|
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>.
|
- 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.
|
- 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
|
- 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); })
|
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'
|
- 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
|
- 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); })
|
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'
|
- 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
|
```yml
|
||||||
tests:
|
tests:
|
||||||
- text: <code>euler162()</code> should return 3D58725572C62302.
|
- 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
|
```yml
|
||||||
tests:
|
tests:
|
||||||
- text: <code>euler284()</code> should return 5a411d7b.
|
- 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';
|
const _ = require('lodash');
|
||||||
import { isMongoId } from 'validator';
|
const { isMongoId } = require('validator');
|
||||||
|
|
||||||
class MongoIds {
|
class MongoIds {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -21,4 +21,4 @@ class MongoIds {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MongoIds;
|
module.exports = MongoIds;
|
||||||
|
@ -21,17 +21,17 @@
|
|||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"repack": "babel-node ./repack.js",
|
"repack": "babel-node ./repack.js",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
"test": "babel-node ./test-challenges.js | tap-spec",
|
"test": "node ./test-challenges.js | tap-spec",
|
||||||
"unpack": "babel-node ./unpack.js"
|
"unpack": "babel-node ./unpack.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@freecodecamp/challenge-md-parser": "^1.0.0",
|
|
||||||
"invariant": "^2.2.4"
|
"invariant": "^2.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^7.0.0",
|
"@commitlint/cli": "^7.0.0",
|
||||||
"@commitlint/config-conventional": "^7.0.1",
|
"@commitlint/config-conventional": "^7.0.1",
|
||||||
"@commitlint/travis-cli": "^7.0.0",
|
"@commitlint/travis-cli": "^7.0.0",
|
||||||
|
"@freecodecamp/challenge-md-parser": "^1.0.0",
|
||||||
"@semantic-release/changelog": "^2.0.2",
|
"@semantic-release/changelog": "^2.0.2",
|
||||||
"@semantic-release/git": "^5.0.0",
|
"@semantic-release/git": "^5.0.0",
|
||||||
"babel-cli": "^6.3.17",
|
"babel-cli": "^6.3.17",
|
||||||
|
@ -4,18 +4,16 @@ Joi.objectId = require('joi-objectid')(Joi);
|
|||||||
const schema = Joi.object().keys({
|
const schema = Joi.object().keys({
|
||||||
block: Joi.string(),
|
block: Joi.string(),
|
||||||
blockId: Joi.objectId(),
|
blockId: Joi.objectId(),
|
||||||
|
challengeOrder: Joi.number(),
|
||||||
challengeType: Joi.number()
|
challengeType: Joi.number()
|
||||||
.min(0)
|
.min(0)
|
||||||
.max(9)
|
.max(9)
|
||||||
.required(),
|
.required(),
|
||||||
checksum: Joi.number(),
|
checksum: Joi.number(),
|
||||||
dashedName: Joi.string(),
|
dashedName: Joi.string(),
|
||||||
description: Joi.array()
|
description: Joi.string().required(),
|
||||||
.items(Joi.string().allow(''))
|
|
||||||
.required(),
|
|
||||||
fileName: Joi.string(),
|
fileName: Joi.string(),
|
||||||
files: Joi.object().pattern(
|
files: Joi.array().items(
|
||||||
/(jsx?|html|css|sass)$/,
|
|
||||||
Joi.object().keys({
|
Joi.object().keys({
|
||||||
key: Joi.string(),
|
key: Joi.string(),
|
||||||
ext: Joi.string(),
|
ext: Joi.string(),
|
||||||
@ -32,6 +30,7 @@ const schema = Joi.object().keys({
|
|||||||
videoUrl: Joi.string().allow(''),
|
videoUrl: Joi.string().allow(''),
|
||||||
helpRoom: Joi.string(),
|
helpRoom: Joi.string(),
|
||||||
id: Joi.objectId().required(),
|
id: Joi.objectId().required(),
|
||||||
|
instructions: Joi.string().required(),
|
||||||
isBeta: Joi.bool(),
|
isBeta: Joi.bool(),
|
||||||
isComingSoon: Joi.bool(),
|
isComingSoon: Joi.bool(),
|
||||||
isLocked: 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';
|
const { Observable } = require('rx');
|
||||||
import tape from 'tape';
|
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';
|
const MongoIds = require('./mongoIds');
|
||||||
import ChallengeTitles from './challengeTitles';
|
const ChallengeTitles = require('./challengeTitles');
|
||||||
import addAssertsToTapTest from './addAssertsToTapTest';
|
const addAssertsToTapTest = require('./addAssertsToTapTest');
|
||||||
import { validateChallenge } from './schema/challengeSchema';
|
const { validateChallenge } = require('./schema/challengeSchema');
|
||||||
|
|
||||||
// modern challengeType
|
const { LOCALE: lang } = process.env;
|
||||||
const modern = 6;
|
|
||||||
|
const { challengeTypes } = require('../client/utils/challengeTypes');
|
||||||
|
|
||||||
let mongoIds = new MongoIds();
|
let mongoIds = new MongoIds();
|
||||||
let challengeTitles = new ChallengeTitles();
|
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,
|
solution,
|
||||||
assert,
|
assert,
|
||||||
react,
|
files,
|
||||||
redux,
|
|
||||||
reactRedux,
|
|
||||||
head,
|
|
||||||
tail,
|
|
||||||
test,
|
test,
|
||||||
tapTest
|
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
|
// Fake Deep Equal dependency
|
||||||
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
||||||
|
|
||||||
@ -53,173 +57,250 @@ function evaluateTest(
|
|||||||
return o;
|
return o;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (react || redux || reactRedux) {
|
let sandbox = {
|
||||||
// Provide dependencies, just provide all of them
|
assert,
|
||||||
React = require('react');
|
code: solution,
|
||||||
ReactDOM = require('react-dom');
|
DeepEqual,
|
||||||
Redux = require('redux');
|
DeepFreeze,
|
||||||
ReduxThunk = require('redux-thunk');
|
test: test.testString
|
||||||
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;
|
|
||||||
|
|
||||||
|
if (files.html) {
|
||||||
|
const { head, tail } = files.html;
|
||||||
const { JSDOM } = require('jsdom');
|
const { JSDOM } = require('jsdom');
|
||||||
// Mock DOM document for ReactDOM.render method
|
const jsdom = new JSDOM(`
|
||||||
const jsdom = new JSDOM(`<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<body>
|
${head}
|
||||||
<div id="challenge-node"></div>
|
${solution}
|
||||||
</body>
|
${tail}
|
||||||
</html>
|
</html>
|
||||||
`);
|
`);
|
||||||
const { window } = jsdom;
|
const jQuery = require('jquery')(jsdom.window);
|
||||||
|
sandbox = {
|
||||||
// Mock DOM for ReactDOM tests
|
...sandbox,
|
||||||
document = window.document;
|
window: jsdom.window,
|
||||||
global.window = window;
|
document: jsdom.window.document,
|
||||||
global.document = 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 */
|
/* eslint-enable no-unused-vars */
|
||||||
try {
|
|
||||||
(() => {
|
// No support for async tests
|
||||||
return eval(
|
// const isAsync = s => s.includes('(async () => ');
|
||||||
head + '\n' + solution + '\n' + tail + '\n' + test.testString
|
|
||||||
);
|
// try {
|
||||||
})();
|
// if (!isAsync(test.testString)) {
|
||||||
} catch (e) {
|
// const context = vm.createContext(sandbox);
|
||||||
console.log(head + '\n' + solution + '\n' + tail + '\n' + test.testString);
|
// const scriptString =
|
||||||
console.log(e);
|
// head + '\n' + solution + '\n' + tail + '\n' + `
|
||||||
tapTest.fail(e);
|
// const testResult = eval(test);
|
||||||
process.exit(1);
|
// 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({
|
function createTest({
|
||||||
title,
|
title,
|
||||||
id = '',
|
id = '',
|
||||||
|
challengeType,
|
||||||
tests = [],
|
tests = [],
|
||||||
solutions = [],
|
solutions = [],
|
||||||
files = [],
|
files = []
|
||||||
react = false,
|
|
||||||
redux = false,
|
|
||||||
reactRedux = false
|
|
||||||
}) {
|
}) {
|
||||||
mongoIds.check(id, title);
|
mongoIds.check(id, title);
|
||||||
challengeTitles.check(title);
|
challengeTitles.check(title);
|
||||||
|
|
||||||
solutions = solutions.filter(solution => !!solution);
|
// if title starts with [word] [number], for example `Problem 5`,
|
||||||
tests = tests.filter(test => !!test);
|
// tap-spec does not recognize it as test suite.
|
||||||
|
const titleRe = new RegExp('^([a-z]+\\s+)(\\d+.*)$', 'i');
|
||||||
// No support for async tests
|
const match = titleRe.exec(title);
|
||||||
const isAsync = s => s.includes('(async () => ');
|
if (match) {
|
||||||
if (isAsync(tests.join(''))) {
|
title = `${match[1]}#${match[2]}`;
|
||||||
console.log(`Replacing Async Tests for Challenge ${title}`);
|
|
||||||
tests = tests.map(
|
|
||||||
challengeTestSource =>
|
|
||||||
isAsync(challengeTestSource)
|
|
||||||
? "assert(true, 'message: great');"
|
|
||||||
: challengeTestSource
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const { head, tail } = Object.keys(files)
|
|
||||||
.map(key => files[key])
|
const testSuite = Observable.fromCallback(tape)(title);
|
||||||
.reduce(
|
|
||||||
|
tests = tests.filter(test => !!test.testString);
|
||||||
|
if (tests.length === 0) {
|
||||||
|
return testSuite.flatMap(tapTest => {
|
||||||
|
tapTest.end();
|
||||||
|
return Observable.just(title);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => ({
|
(result, file) => ({
|
||||||
head: result.head + ';' + file.head.join('\n'),
|
head: result.head + ';' + file.head,
|
||||||
tail: result.tail + ';' + file.tail.join('\n')
|
tail: result.tail + ';' + file.tail
|
||||||
}),
|
}),
|
||||||
{ head: '', tail: '' }
|
{ head: '', tail: '' }
|
||||||
);
|
);
|
||||||
const plan = tests.length;
|
return {
|
||||||
if (!plan) {
|
...result,
|
||||||
return Observable.just({
|
[ext]: file
|
||||||
title,
|
};
|
||||||
type: 'missing'
|
}, {});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Observable.fromCallback(tape)(title)
|
const plan = tests.length * solutions.length;
|
||||||
.doOnNext(
|
return testSuite
|
||||||
tapTest => (solutions.length ? tapTest.plan(plan) : tapTest.end())
|
|
||||||
)
|
|
||||||
.flatMap(tapTest => {
|
.flatMap(tapTest => {
|
||||||
if (solutions.length <= 0) {
|
tapTest.plan(plan);
|
||||||
return Observable.just({
|
|
||||||
title,
|
|
||||||
type: 'missing'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Observable.just(tapTest)
|
Observable.just(tapTest)
|
||||||
.map(addAssertsToTapTest)
|
.map(addAssertsToTapTest)
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
// assert and code used within the eval
|
|
||||||
.doOnNext(assert => {
|
.doOnNext(assert => {
|
||||||
solutions.forEach(solution => {
|
solutions.forEach(solution => {
|
||||||
tests.forEach(test => {
|
tests.forEach(test => {
|
||||||
evaluateTest(
|
evaluateHtmlJsTest(
|
||||||
solution,
|
solution,
|
||||||
assert,
|
assert,
|
||||||
react,
|
groupedFiles,
|
||||||
redux,
|
|
||||||
reactRedux,
|
|
||||||
head,
|
|
||||||
tail,
|
|
||||||
test,
|
test,
|
||||||
tapTest
|
tapTest
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.map(() => ({ title }))
|
.ignoreElements()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable.from(getChallenges())
|
Observable.fromPromise(getChallengesForLang(lang || 'english'))
|
||||||
.do(({ challenges }) => {
|
.flatMap(curriculum => {
|
||||||
challenges.forEach(challenge => {
|
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);
|
||||||
|
})
|
||||||
|
.do(challenge => {
|
||||||
const result = validateChallenge(challenge);
|
const result = validateChallenge(challenge);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
console.log(result.value);
|
console.log(result.value);
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.flatMap(challengeSpec => {
|
|
||||||
return Observable.from(challengeSpec.challenges);
|
|
||||||
})
|
|
||||||
.filter(({ challengeType }) => challengeType !== modern)
|
|
||||||
.flatMap(challenge => {
|
.flatMap(challenge => {
|
||||||
return createTest(challenge);
|
return createTest(challenge);
|
||||||
})
|
})
|
||||||
.map(({ title, type }) => {
|
|
||||||
if (type === 'missing') {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.filter(title => !!title)
|
|
||||||
.toArray()
|
.toArray()
|
||||||
.subscribe(
|
.subscribe(
|
||||||
noSolutions => {
|
noSolutions => {
|
||||||
if (noSolutions) {
|
if (noSolutions) {
|
||||||
console.log(
|
console.log(
|
||||||
'# These challenges have no solutions\n- [ ] ' +
|
`# These challenges have no solutions (${noSolutions.length})\n` +
|
||||||
noSolutions.join('\n- [ ] ')
|
'- [ ] ' + noSolutions.join('\n- [ ] ')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"test": "npm-run-all -p test:*",
|
"test": "npm-run-all -p test:*",
|
||||||
"test-ci": "npm test",
|
"test-ci": "npm test",
|
||||||
"test:client": "cd ./client && npm test && cd ../",
|
"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:guide-directories": "node ./tools/scripts/ci/ensure-guide-page-naming.js",
|
||||||
"test:server": "echo 'Warning: TODO - Define Testing.'",
|
"test:server": "echo 'Warning: TODO - Define Testing.'",
|
||||||
"test:tools": "jest ./tools"
|
"test:tools": "jest ./tools"
|
||||||
|
@ -10,6 +10,7 @@ const frontmatterToData = require('./frontmatter-to-data');
|
|||||||
const textToData = require('./text-to-data');
|
const textToData = require('./text-to-data');
|
||||||
const testsToData = require('./tests-to-data');
|
const testsToData = require('./tests-to-data');
|
||||||
const challengeSeedToData = require('./challengeSeed-to-data');
|
const challengeSeedToData = require('./challengeSeed-to-data');
|
||||||
|
const solutionsToData = require('./solution-to-data');
|
||||||
|
|
||||||
const processor = unified()
|
const processor = unified()
|
||||||
.use(markdown)
|
.use(markdown)
|
||||||
@ -18,6 +19,7 @@ const processor = unified()
|
|||||||
.use(testsToData)
|
.use(testsToData)
|
||||||
.use(remark2rehype, { allowDangerousHTML: true })
|
.use(remark2rehype, { allowDangerousHTML: true })
|
||||||
.use(raw)
|
.use(raw)
|
||||||
|
.use(solutionsToData)
|
||||||
.use(textToData, ['description', 'instructions'])
|
.use(textToData, ['description', 'instructions'])
|
||||||
.use(challengeSeedToData)
|
.use(challengeSeedToData)
|
||||||
// the plugins below are just to stop the processor from throwing
|
// the plugins below are just to stop the processor from throwing
|
||||||
|
@ -10,7 +10,10 @@ function createPlugin() {
|
|||||||
const solutions = selectAll('code', node).map(
|
const solutions = selectAll('code', node).map(
|
||||||
element => element.children[0].value
|
element => element.children[0].value
|
||||||
);
|
);
|
||||||
file.data.solutions = solutions;
|
file.data = {
|
||||||
|
...file.data,
|
||||||
|
solutions
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visit(tree, 'element', visitor);
|
visit(tree, 'element', visitor);
|
||||||
|
Reference in New Issue
Block a user