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:
Berkeley Martinez
2017-12-13 15:24:36 -08:00
committed by Quincy Larson
parent 375442d365
commit d3bbf27dab
5 changed files with 91 additions and 65 deletions

View File

@ -73,31 +73,18 @@ document.addEventListener('DOMContentLoaded', function() {
/* eslint-enable no-eval */
if (typeof test === 'function') {
// we know that the test eval'ed to a function
// the function could expect a callback
// 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);
} 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);
}
// all async tests must return a promise or observable
// sync tests can return Any type
__result = test(getUserInput);
if (helpers.isPromise(__result)) {
// turn promise into an observable
__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);
}
} catch (e) {

View File

@ -5,11 +5,7 @@ import presetEs2015 from 'babel-preset-es2015';
import presetReact from 'babel-preset-react';
import { Observable } from 'rx';
import {
transformHeadTailAndContents,
setContent,
setExt
} from '../../../../utils/polyvinyl.js';
import * as vinyl from '../../../../utils/polyvinyl.js';
import castToObservable from '../../../utils/cast-to-observable.js';
const babelOptions = { presets: [ presetEs2015, presetReact ] };
@ -35,43 +31,47 @@ const testJS$JSX = _.overSome(isJS, _.matchesProperty('ext', 'jsx'));
// to `window.__console.log`
// this let's us tap into logging into the console.
// currently we only do this to the main window and not the test window
export function proxyLoggerTransformer(file) {
return transformHeadTailAndContents(
(source) => (
export const proxyLoggerTransformer = _.partial(
vinyl.transformHeadTailAndContents,
source => (
source.replace(console$logReg, (match, methodCall) => {
return 'window.__console' + methodCall;
})),
file
);
}
})),
);
export const addLoopProtect = _.cond([
const addLoopProtect = _.partial(
vinyl.transformContents,
contents => {
/* eslint-disable import/no-unresolved */
const loopProtect = require('loop-protect');
/* eslint-enable import/no-unresolved */
loopProtect.hit = loopProtectHit;
return loopProtect(contents);
}
);
export const addLoopProtectHtmlJsJsx = _.cond([
[
testHTMLJS,
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 */
const loopProtect = require('loop-protect');
/* eslint-enable import/no-unresolved */
loopProtect.hit = loopProtectHit;
return setContent(loopProtect(file.contents), file);
}
_.overEvery(
testHTMLJS,
_.partial(
vinyl.testContents,
contents => contents.toLowerCase().contians('<script>')
)
),
addLoopProtect
],
[ testJS$JSX, addLoopProtect ],
[ _.stubTrue, _.identity ]
]);
export const replaceNBSP = _.cond([
[
testHTMLJS,
function(file) {
return setContent(
file.contents.replace(NBSPReg, ' '),
file
);
}
_.partial(
vinyl.transformContents,
contents => contents.replace(NBSPReg, ' ')
)
],
[ _.stubTrue, _.identity ]
]);
@ -79,19 +79,19 @@ export const replaceNBSP = _.cond([
export const babelTransformer = _.cond([
[
testJS$JSX,
function(file) {
const result = babel.transform(file.contents, babelOptions);
return _.flow(
_.partial(setContent, result.code),
_.partial(setExt, 'js')
)(file);
}
_.flow(
_.partial(
vinyl.transformContents,
contents => babel.transform(contents, babelOptions).code
),
_.partial(vinyl.setExt, 'js')
)
],
[ _.stubTrue, _.identity ]
]);
export const _transformers = [
addLoopProtect,
addLoopProtectHtmlJsJsx,
replaceNBSP,
babelTransformer
];

View File

@ -101,7 +101,8 @@ const writeTestDepsToDocument = frameReady => ctx => {
// default for classic challenges
// should not be used for modern
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.__frameReady = frameReady;
return ctx;

View File

@ -168,8 +168,8 @@ export function clearHeadTail(poly) {
// compileHeadTail(contents: String, poly: PolyVinyl) => PolyVinyl
export function compileHeadTail(padding = '', poly) {
return clearHeadTail(setContent(
[ poly.head, poly.contents, poly.tail ].join(padding),
return clearHeadTail(transformContents(
() => [ poly.head, poly.contents, poly.tail ].join(padding),
poly
));
}
@ -188,7 +188,7 @@ export function transformContents(wrap, poly) {
poly
);
// if no source exist, set the original contents as source
newPoly.source = poly.contents || poly.contents;
newPoly.source = poly.source || poly.contents;
return newPoly;
}
@ -206,3 +206,7 @@ export function transformHeadTailAndContents(wrap, poly) {
tail: wrap(poly.tail)
};
}
export function testContents(predicate, poly) {
return !!predicate(poly.contents);
}

View File

@ -27,7 +27,7 @@
"name": "index",
"contents": [
"",
"var jsx = <div></div>;",
"const jsx = <div></div>;",
""
]
}
@ -39,6 +39,40 @@
"type": "modern",
"isRequired": false,
"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
}
]
}