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 */ /* 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) {

View File

@ -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
]; ];

View File

@ -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;

View File

@ -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);
}

View File

@ -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
} }
] ]
} }