Fix(Challenges): get user code (#16187)
* fix(Challenges.): Prevent source from being overwritten * fix(Challenges): Tests should use name * fix(seed/react): Namespace tests for now
This commit is contained in:
committed by
Quincy Larson
parent
375442d365
commit
d3bbf27dab
@ -73,31 +73,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
/* eslint-enable no-eval */
|
/* eslint-enable no-eval */
|
||||||
if (typeof test === 'function') {
|
if (typeof test === 'function') {
|
||||||
|
|
||||||
// we know that the test eval'ed to a function
|
// all async tests must return a promise or observable
|
||||||
// the function could expect a callback
|
// sync tests can return Any type
|
||||||
// or it could return a promise/observable
|
|
||||||
// or it could still be sync
|
|
||||||
if (test.length === 1) {
|
|
||||||
// a function with length 0 means it expects 0 args
|
|
||||||
// We call it and store the result
|
|
||||||
// This result may be a promise or an observable or undefined
|
|
||||||
__result = test(getUserInput);
|
__result = test(getUserInput);
|
||||||
} else {
|
|
||||||
// if function takes arguments
|
|
||||||
// we expect it to be of the form
|
|
||||||
// function(cb) { /* ... */ }
|
|
||||||
// and callback has the following signature
|
|
||||||
// function(err) { /* ... */ }
|
|
||||||
__result = Rx.Observable.fromNodeCallback(test)(getUserInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (helpers.isPromise(__result)) {
|
if (helpers.isPromise(__result)) {
|
||||||
// turn promise into an observable
|
// turn promise into an observable
|
||||||
__result = Rx.Observable.fromPromise(__result);
|
__result = Rx.Observable.fromPromise(__result);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// test is not a function
|
|
||||||
// fill result with for compatibility
|
if (!__result || typeof __result.subscribe !== 'function') {
|
||||||
|
// make sure result is an observable
|
||||||
__result = Rx.Observable.of(null);
|
__result = Rx.Observable.of(null);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -5,11 +5,7 @@ import presetEs2015 from 'babel-preset-es2015';
|
|||||||
import presetReact from 'babel-preset-react';
|
import presetReact from 'babel-preset-react';
|
||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
|
|
||||||
import {
|
import * as vinyl from '../../../../utils/polyvinyl.js';
|
||||||
transformHeadTailAndContents,
|
|
||||||
setContent,
|
|
||||||
setExt
|
|
||||||
} from '../../../../utils/polyvinyl.js';
|
|
||||||
import castToObservable from '../../../utils/cast-to-observable.js';
|
import castToObservable from '../../../utils/cast-to-observable.js';
|
||||||
|
|
||||||
const babelOptions = { presets: [ presetEs2015, presetReact ] };
|
const babelOptions = { presets: [ presetEs2015, presetReact ] };
|
||||||
@ -35,43 +31,47 @@ const testJS$JSX = _.overSome(isJS, _.matchesProperty('ext', 'jsx'));
|
|||||||
// to `window.__console.log`
|
// to `window.__console.log`
|
||||||
// this let's us tap into logging into the console.
|
// this let's us tap into logging into the console.
|
||||||
// currently we only do this to the main window and not the test window
|
// currently we only do this to the main window and not the test window
|
||||||
export function proxyLoggerTransformer(file) {
|
export const proxyLoggerTransformer = _.partial(
|
||||||
return transformHeadTailAndContents(
|
vinyl.transformHeadTailAndContents,
|
||||||
(source) => (
|
source => (
|
||||||
source.replace(console$logReg, (match, methodCall) => {
|
source.replace(console$logReg, (match, methodCall) => {
|
||||||
return 'window.__console' + methodCall;
|
return 'window.__console' + methodCall;
|
||||||
})),
|
})),
|
||||||
file
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export const addLoopProtect = _.cond([
|
const addLoopProtect = _.partial(
|
||||||
[
|
vinyl.transformContents,
|
||||||
testHTMLJS,
|
contents => {
|
||||||
function(file) {
|
|
||||||
const _contents = file.contents.toLowerCase();
|
|
||||||
if (file.ext === 'html' && !_contents.indexOf('<script>') !== -1) {
|
|
||||||
// No JavaScript in user code, so no need for loopProtect
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
/* eslint-disable import/no-unresolved */
|
/* eslint-disable import/no-unresolved */
|
||||||
const loopProtect = require('loop-protect');
|
const loopProtect = require('loop-protect');
|
||||||
/* eslint-enable import/no-unresolved */
|
/* eslint-enable import/no-unresolved */
|
||||||
loopProtect.hit = loopProtectHit;
|
loopProtect.hit = loopProtectHit;
|
||||||
return setContent(loopProtect(file.contents), file);
|
return loopProtect(contents);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const addLoopProtectHtmlJsJsx = _.cond([
|
||||||
|
[
|
||||||
|
_.overEvery(
|
||||||
|
testHTMLJS,
|
||||||
|
_.partial(
|
||||||
|
vinyl.testContents,
|
||||||
|
contents => contents.toLowerCase().contians('<script>')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
addLoopProtect
|
||||||
],
|
],
|
||||||
|
[ testJS$JSX, addLoopProtect ],
|
||||||
[ _.stubTrue, _.identity ]
|
[ _.stubTrue, _.identity ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const replaceNBSP = _.cond([
|
export const replaceNBSP = _.cond([
|
||||||
[
|
[
|
||||||
testHTMLJS,
|
testHTMLJS,
|
||||||
function(file) {
|
_.partial(
|
||||||
return setContent(
|
vinyl.transformContents,
|
||||||
file.contents.replace(NBSPReg, ' '),
|
contents => contents.replace(NBSPReg, ' ')
|
||||||
file
|
)
|
||||||
);
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
[ _.stubTrue, _.identity ]
|
[ _.stubTrue, _.identity ]
|
||||||
]);
|
]);
|
||||||
@ -79,19 +79,19 @@ export const replaceNBSP = _.cond([
|
|||||||
export const babelTransformer = _.cond([
|
export const babelTransformer = _.cond([
|
||||||
[
|
[
|
||||||
testJS$JSX,
|
testJS$JSX,
|
||||||
function(file) {
|
_.flow(
|
||||||
const result = babel.transform(file.contents, babelOptions);
|
_.partial(
|
||||||
return _.flow(
|
vinyl.transformContents,
|
||||||
_.partial(setContent, result.code),
|
contents => babel.transform(contents, babelOptions).code
|
||||||
_.partial(setExt, 'js')
|
),
|
||||||
)(file);
|
_.partial(vinyl.setExt, 'js')
|
||||||
}
|
)
|
||||||
],
|
],
|
||||||
[ _.stubTrue, _.identity ]
|
[ _.stubTrue, _.identity ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const _transformers = [
|
export const _transformers = [
|
||||||
addLoopProtect,
|
addLoopProtectHtmlJsJsx,
|
||||||
replaceNBSP,
|
replaceNBSP,
|
||||||
babelTransformer
|
babelTransformer
|
||||||
];
|
];
|
||||||
|
@ -101,7 +101,8 @@ const writeTestDepsToDocument = frameReady => ctx => {
|
|||||||
// default for classic challenges
|
// default for classic challenges
|
||||||
// should not be used for modern
|
// should not be used for modern
|
||||||
tests.__source = sources['index'] || '';
|
tests.__source = sources['index'] || '';
|
||||||
tests.__getUserInput = key => sources[key];
|
// provide the file name and get the original source
|
||||||
|
tests.__getUserInput = fileName => _.toString(sources[fileName]);
|
||||||
tests.__checkChallengePayload = checkChallengePayload;
|
tests.__checkChallengePayload = checkChallengePayload;
|
||||||
tests.__frameReady = frameReady;
|
tests.__frameReady = frameReady;
|
||||||
return ctx;
|
return ctx;
|
||||||
|
@ -168,8 +168,8 @@ export function clearHeadTail(poly) {
|
|||||||
|
|
||||||
// compileHeadTail(contents: String, poly: PolyVinyl) => PolyVinyl
|
// compileHeadTail(contents: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function compileHeadTail(padding = '', poly) {
|
export function compileHeadTail(padding = '', poly) {
|
||||||
return clearHeadTail(setContent(
|
return clearHeadTail(transformContents(
|
||||||
[ poly.head, poly.contents, poly.tail ].join(padding),
|
() => [ poly.head, poly.contents, poly.tail ].join(padding),
|
||||||
poly
|
poly
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ export function transformContents(wrap, poly) {
|
|||||||
poly
|
poly
|
||||||
);
|
);
|
||||||
// if no source exist, set the original contents as source
|
// if no source exist, set the original contents as source
|
||||||
newPoly.source = poly.contents || poly.contents;
|
newPoly.source = poly.source || poly.contents;
|
||||||
return newPoly;
|
return newPoly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,3 +206,7 @@ export function transformHeadTailAndContents(wrap, poly) {
|
|||||||
tail: wrap(poly.tail)
|
tail: wrap(poly.tail)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function testContents(predicate, poly) {
|
||||||
|
return !!predicate(poly.contents);
|
||||||
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
"name": "index",
|
"name": "index",
|
||||||
"contents": [
|
"contents": [
|
||||||
"",
|
"",
|
||||||
"var jsx = <div></div>;",
|
"const jsx = <div></div>;",
|
||||||
""
|
""
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -39,6 +39,40 @@
|
|||||||
"type": "modern",
|
"type": "modern",
|
||||||
"isRequired": false,
|
"isRequired": false,
|
||||||
"translations": {}
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5a24c314108439a4d4036168",
|
||||||
|
"title": "Write a React Component from Scratch",
|
||||||
|
"releasedOn": "December 25, 2017",
|
||||||
|
"description": [
|
||||||
|
"Now that you've learned the basics of JSX and React components, it's time to write a component on your own. React components are the core building blocks of React applications so it's important to become very familiar with writing them. Remember, a typical React component is an ES6 <code>class</code> which extends <code>React.Component</code>. It has a render method that returns HTML (from JSX) or <code>null</code>. This is the basic form of a React component. Once you understand this well, you will be prepared to start building more complex React projects.",
|
||||||
|
"<hr>",
|
||||||
|
"Define a class <code>MyComponent</code> that extends <code>React.Component</code>. Its render method should return a <code>div</code> that contains an <code>h1</code> tag with the text: <code>My First React Component!</code> in it. Use this text exactly, the case and punctuation matter. Make sure to call the constructor for your component, too.",
|
||||||
|
"Render this component to the DOM using <code>ReactDOM.render()</code>. There is a <code>div</code> with <code>id='challenge-node'</code> available for you to use."
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"indexjsx": {
|
||||||
|
"key": "indexjsx",
|
||||||
|
"ext": "jsx",
|
||||||
|
"name": "index",
|
||||||
|
"contents": [
|
||||||
|
"// change code below this line",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"backup-tests": [
|
||||||
|
"(getUserInput) => assert(getUserInput('index').replace(/\\s/g, '').includes('classMyComponentextendsReact.Component{'), 'message: There should be a React component called MyComponent')"
|
||||||
|
],
|
||||||
|
"head": [],
|
||||||
|
"tail": [],
|
||||||
|
"solutions": [
|
||||||
|
"// change code below this line\nclass MyComponent extends React.Component {\n constructor(props) {\n super(props);\n }\n render() {\n return (\n <div>\n <h1>My First React Component!</h1>\n </div>\n );\n }\n};\n\nReactDOM.render(<MyComponent />, document.getElementById('challenge-node'));"
|
||||||
|
],
|
||||||
|
"type": "modern",
|
||||||
|
"isRequired": false,
|
||||||
|
"translations": {},
|
||||||
|
"react": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user