Files
freeCodeCamp/common/app/routes/Challenges/utils/frame.js

138 lines
3.9 KiB
JavaScript
Raw Normal View History

Feat(Challenges): no js preview (#16149) * fix(files): Decouple files from challenges * feat(server/react): Remove action logger use redux remote devtools instead! * feat(Challenges): Disable js on edit, enable on execute * feat(Challenge/Preview): Show message when js is disabled * refactor(frameEpic): Reduce code by using lodash * feat(frameEpic): Disable js in preview by state * feat(frameEpic): Colocate epic in Challenges/redux * refactor(ExecuteChallengeEpic): CoLocated with Challenges * refactor(executeChallengesEpic): Separate tests from main logic * feat(Challenge/Preview): Update main on edit * feat(frameEpuc): Replace frame on edit/execute This allows for sandbox to work properly * fix(Challenges/Utils): Require utisl * revert(frameEpic): Hoist function to mount code in frame * fix(frameEpic): Ensure new frame is given classname * feat(executeChallenge): Update main on code unlocked * fix(frameEpic): Filter out empty test message * fix(Challenge/Preview): Remove unnessary quote in classname * feat(codeStorageEpic): Separate localstorage from solutions loading * fix(fetchUser): Merge user actions into one prefer many effects from one action over one action to one effect * fix(themes): Centralize theme utils and defs * fix(entities.user): Fix user reducer namespacing * feat(frame): Refactor frameEpic to util * feat(Challenges.redux): Should not attempt to update main from storage * fix(loadPreviousChallengeEpic): Refactor for RFR * fix(Challenges.Modern): Show preview plane
2017-12-07 16:13:19 -08:00
import _ from 'lodash';
import Rx, { Observable } from 'rx';
import { ShallowWrapper, ReactWrapper } from 'enzyme';
import Adapter15 from 'enzyme-adapter-react-15';
import { isJSEnabledSelector } from '../redux';
// we use two different frames to make them all essentially pure functions
// main iframe is responsible rendering the preview and is where we proxy the
// console.log
const mainId = 'fcc-main-frame';
// the test frame is responsible for running the assert tests
const testId = 'fcc-test-frame';
const createHeader = (id = mainId) => `
<script>
window.__frameId = '${id}';
window.onerror = function(msg, url, ln, col, err) {
window.__err = err;
return true;
};
</script>
`;
export const runTestsInTestFrame = (document, tests) => Observable.defer(() => {
const { contentDocument: frame } = document.getElementById(testId);
return frame.__runTests(tests);
});
const createFrame = (document, getState, id) => ctx => {
const isJSEnabled = isJSEnabledSelector(getState());
const frame = document.createElement('iframe');
frame.id = id;
if (!isJSEnabled) {
frame.sandbox = 'allow-same-origin';
}
return {
...ctx,
element: frame
};
};
const hiddenFrameClassname = 'hide-test-frame';
const mountFrame = document => ({ element, ...rest })=> {
const oldFrame = document.getElementById(element.id);
if (oldFrame) {
element.className = oldFrame.className || hiddenFrameClassname;
oldFrame.parentNode.replaceChild(element, oldFrame);
} else {
element.className = hiddenFrameClassname;
document.body.appendChild(element);
}
return {
...rest,
element,
document: element.contentDocument,
window: element.contentWindow
};
};
const addDepsToDocument = ctx => {
ctx.document.Rx = Rx;
// using require here prevents nodejs issues as loop-protect
// is added to the window object by webpack and not available to
// us server side.
/* eslint-disable import/no-unresolved */
ctx.document.loopProtect = require('loop-protect');
/* eslint-enable import/no-unresolved */
return ctx;
};
const buildProxyConsole = proxyLogger => ctx => {
const oldLog = ctx.window.console.log.bind(ctx.window.console);
ctx.window.__console = {};
ctx.window.__console.log = function proxyConsole(...args) {
proxyLogger.onNext(args);
return oldLog(...args);
};
return ctx;
};
const writeTestDepsToDocument = frameReady => ctx => {
const {
document: tests,
sources,
checkChallengePayload
} = ctx;
// add enzyme
// TODO: do programatically
// TODO: webpack lazyload this
tests.Enzyme = {
shallow: (node, options) => new ShallowWrapper(node, null, {
...options,
adapter: new Adapter15()
}),
mount: (node, options) => new ReactWrapper(node, null, {
...options,
adapter: new Adapter15()
})
};
// default for classic challenges
// should not be used for modern
tests.__source = (sources && 'index' in sources) ? sources['index'] : '';
// provide the file name and get the original source
tests.__getUserInput = fileName => _.toString(sources[fileName]);
Feat(Challenges): no js preview (#16149) * fix(files): Decouple files from challenges * feat(server/react): Remove action logger use redux remote devtools instead! * feat(Challenges): Disable js on edit, enable on execute * feat(Challenge/Preview): Show message when js is disabled * refactor(frameEpic): Reduce code by using lodash * feat(frameEpic): Disable js in preview by state * feat(frameEpic): Colocate epic in Challenges/redux * refactor(ExecuteChallengeEpic): CoLocated with Challenges * refactor(executeChallengesEpic): Separate tests from main logic * feat(Challenge/Preview): Update main on edit * feat(frameEpuc): Replace frame on edit/execute This allows for sandbox to work properly * fix(Challenges/Utils): Require utisl * revert(frameEpic): Hoist function to mount code in frame * fix(frameEpic): Ensure new frame is given classname * feat(executeChallenge): Update main on code unlocked * fix(frameEpic): Filter out empty test message * fix(Challenge/Preview): Remove unnessary quote in classname * feat(codeStorageEpic): Separate localstorage from solutions loading * fix(fetchUser): Merge user actions into one prefer many effects from one action over one action to one effect * fix(themes): Centralize theme utils and defs * fix(entities.user): Fix user reducer namespacing * feat(frame): Refactor frameEpic to util * feat(Challenges.redux): Should not attempt to update main from storage * fix(loadPreviousChallengeEpic): Refactor for RFR * fix(Challenges.Modern): Show preview plane
2017-12-07 16:13:19 -08:00
tests.__checkChallengePayload = checkChallengePayload;
tests.__frameReady = frameReady;
return ctx;
};
function writeToFrame(content, frame) {
frame.open();
frame.write(content);
frame.close();
return frame;
}
const writeContentToFrame = ctx => {
writeToFrame(createHeader(ctx.element.id) + ctx.build, ctx.document);
return ctx;
};
export const createMainFramer = (document, getState, proxyLogger) => _.flow(
createFrame(document, getState, mainId),
mountFrame(document),
addDepsToDocument,
buildProxyConsole(proxyLogger),
writeContentToFrame,
);
export const createTestFramer = (document, getState, frameReady) => _.flow(
createFrame(document, getState, testId),
mountFrame(document),
addDepsToDocument,
writeTestDepsToDocument(frameReady),
writeContentToFrame,
);