fix(edge): get client app to work on edge (#35040)

pull/29904/head^2
Valeriy 2019-02-03 19:49:27 +03:00 committed by mrugesh mohapatra
parent e71b82c7a4
commit cc6e1fdbf4
7 changed files with 105 additions and 54 deletions

View File

@ -3,18 +3,12 @@ import jQuery from 'jquery';
window.$ = jQuery;
const testId = 'fcc-test-frame';
if (window.frameElement && window.frameElement.id === testId) {
document.addEventListener('DOMContentLoaded', initTestFrame);
}
// For tests in CI.
document.__initTestFrame = initTestFrame;
async function initTestFrame() {
const code = (document.__source || '').slice(0);
if (!document.__getUserInput) {
document.__getUserInput = () => code;
async function initTestFrame(e = {}) {
const code = (e.code || '').slice(0);
if (!e.getUserInput) {
e.getUserInput = () => code;
}
/* eslint-disable no-unused-vars */
@ -43,7 +37,7 @@ async function initTestFrame() {
/* eslint-enable no-unused-vars */
let Enzyme;
if (document.__loadEnzyme) {
if (e.loadEnzyme) {
let Adapter16;
/* eslint-disable no-inline-comments */
[{ default: Enzyme }, { default: Adapter16 }] = await Promise.all([
@ -66,7 +60,7 @@ async function initTestFrame() {
// eslint-disable-next-line no-eval
const test = eval(testString);
if (typeof test === 'function') {
await test(document.__getUserInput);
await test(e.getUserInput);
}
return { pass: true };
} catch (err) {
@ -81,7 +75,4 @@ async function initTestFrame() {
};
}
};
// notify that the window methods are ready to run
document.__frameReady();
}

View File

@ -1,5 +1,17 @@
// eslint-disable-next-line no-undef
importScripts('/js/sass.sync.js');
// work around for SASS error in Edge
// https://github.com/medialize/sass.js/issues/96#issuecomment-424386171
if (!self.crypto) {
self.crypto = {
getRandomValues: function(array) {
for (var i = 0, l = array.length; i < l; i++) {
array[i] = Math.floor(Math.random() * 256);
}
return array;
}
};
}
self.importScripts('/js/sass.sync.js');
self.onmessage = e => {
const data = e.data;
@ -11,3 +23,5 @@ self.onmessage = e => {
}
});
};
self.postMessage({ type: 'contentLoaded' });

View File

@ -48,3 +48,5 @@ self.onmessage = async e => {
}
}
};
self.postMessage({ type: 'contentLoaded' });

View File

@ -60,6 +60,44 @@ const propTypes = {
};
export class CompletionModal extends Component {
state = {
downloadURL: null
};
static getDerivedStateFromProps(props, state) {
const { files, isOpen } = props;
if (!isOpen) {
return null;
}
const { downloadURL } = state;
if (downloadURL) {
URL.revokeObjectURL(downloadURL);
}
let newURL = null;
if (Object.keys(files).length) {
const filesForDownload = Object.keys(files)
.map(key => files[key])
.reduce(
(allFiles, { path, contents }) => ({
...allFiles,
[path]: contents
}),
{}
);
const blob = new Blob([JSON.stringify(filesForDownload, null, 2)], {
type: 'text/json'
});
newURL = URL.createObjectURL(blob);
}
return { downloadURL: newURL };
}
componentWillUnmount() {
if (this.state.downloadURL) {
URL.revokeObjectURL(this.state.downloadURL);
}
}
render() {
const {
close,
@ -67,22 +105,11 @@ export class CompletionModal extends Component {
submitChallenge,
handleKeypress,
message,
files = {},
title
} = this.props;
if (isOpen) {
ga.modalview('/completion-modal');
}
const showDownloadButton = Object.keys(files).length;
const filesForDownload = Object.keys(files)
.map(key => files[key])
.reduce(
(allFiles, { path, contents }) => ({
...allFiles,
[path]: contents
}),
{}
);
const dashedName = dasherize(title);
return (
<Modal
@ -112,18 +139,17 @@ export class CompletionModal extends Component {
bsStyle='primary'
onClick={submitChallenge}
>
Submit and go to next challenge <span className='hidden-xs'>(Ctrl + Enter)</span>
Submit and go to next challenge{' '}
<span className='hidden-xs'>(Ctrl + Enter)</span>
</Button>
{showDownloadButton ? (
{this.state.downloadURL ? (
<Button
block={true}
bsSize='lg'
bsStyle='primary'
className='btn-primary-invert'
download={`${dashedName}.json`}
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(filesForDownload)
)}`}
href={this.state.downloadURL}
>
Download my solution
</Button>
@ -137,4 +163,7 @@ export class CompletionModal extends Component {
CompletionModal.displayName = 'CompletionModal';
CompletionModal.propTypes = propTypes;
export default connect(mapStateToProps, mapDispatchToProps)(CompletionModal);
export default connect(
mapStateToProps,
mapDispatchToProps
)(CompletionModal);

View File

@ -72,15 +72,24 @@ const buildProxyConsole = proxyLogger => ctx => {
return ctx;
};
const writeTestDepsToDocument = frameReady => ctx => {
const { sources, loadEnzyme } = ctx;
// default for classic challenges
// should not be used for modern
ctx.document.__source = sources && 'index' in sources ? sources['index'] : '';
// provide the file name and get the original source
ctx.document.__getUserInput = fileName => toString(sources[fileName]);
ctx.document.__frameReady = frameReady;
ctx.document.__loadEnzyme = loadEnzyme;
const initTestFrame = frameReady => ctx => {
const contentLoaded = new Promise(resolve => {
if (ctx.document.readyState === 'loading') {
ctx.document.addEventListener('DOMContentLoaded', resolve);
} else {
resolve();
}
});
contentLoaded.then(async() => {
const { sources, loadEnzyme } = ctx;
// default for classic challenges
// should not be used for modern
const code = sources && 'index' in sources ? sources['index'] : '';
// provide the file name and get the original source
const getUserInput = fileName => toString(sources[fileName]);
await ctx.document.__initTestFrame({ code, getUserInput, loadEnzyme });
frameReady();
});
return ctx;
};
@ -107,7 +116,7 @@ export const createTestFramer = (document, frameReady, proxyConsole) =>
flow(
createFrame(document, testId),
mountFrame(document),
writeTestDepsToDocument(frameReady),
writeContentToFrame,
buildProxyConsole(proxyConsole),
writeContentToFrame
initTestFrame(frameReady)
);

View File

@ -10,9 +10,17 @@ class WorkerExecutor {
this.getWorker = this.getWorker.bind(this);
}
getWorker() {
async getWorker() {
if (this.worker === null) {
this.worker = new Worker(`${this.location}${this.workerName}.js`);
this.worker = await new Promise((resolve, reject) => {
const worker = new Worker(`${this.location}${this.workerName}.js`);
worker.onmessage = e => {
if (e.data && e.data.type && e.data.type === 'contentLoaded') {
resolve(worker);
}
};
worker.onerror = e => reject(e.message);
});
}
return this.worker;
@ -25,8 +33,8 @@ class WorkerExecutor {
}
}
execute(data, timeout = 1000) {
const worker = this.getWorker();
async execute(data, timeout = 1000) {
const worker = await this.getWorker();
return new Promise((resolve, reject) => {
// Handle timeout
const timeoutId = setTimeout(() => {

View File

@ -291,11 +291,9 @@ async function createTestRunnerForDOMChallenge(
await context.setContent(build);
await context.evaluate(
async(sources, loadEnzyme) => {
document.__source = sources && 'index' in sources ? sources['index'] : '';
document.__getUserInput = fileName => sources[fileName];
document.__frameReady = () => {};
document.__loadEnzyme = loadEnzyme;
await document.__initTestFrame();
const code = sources && 'index' in sources ? sources['index'] : '';
const getUserInput = fileName => sources[fileName];
await document.__initTestFrame({ code, getUserInput, loadEnzyme });
},
sources,
loadEnzyme