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

This commit is contained in:
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; 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; document.__initTestFrame = initTestFrame;
async function initTestFrame() { async function initTestFrame(e = {}) {
const code = (document.__source || '').slice(0); const code = (e.code || '').slice(0);
if (!document.__getUserInput) { if (!e.getUserInput) {
document.__getUserInput = () => code; e.getUserInput = () => code;
} }
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
@ -43,7 +37,7 @@ async function initTestFrame() {
/* eslint-enable no-unused-vars */ /* eslint-enable no-unused-vars */
let Enzyme; let Enzyme;
if (document.__loadEnzyme) { if (e.loadEnzyme) {
let Adapter16; let Adapter16;
/* eslint-disable no-inline-comments */ /* eslint-disable no-inline-comments */
[{ default: Enzyme }, { default: Adapter16 }] = await Promise.all([ [{ default: Enzyme }, { default: Adapter16 }] = await Promise.all([
@ -66,7 +60,7 @@ async function initTestFrame() {
// eslint-disable-next-line no-eval // eslint-disable-next-line no-eval
const test = eval(testString); const test = eval(testString);
if (typeof test === 'function') { if (typeof test === 'function') {
await test(document.__getUserInput); await test(e.getUserInput);
} }
return { pass: true }; return { pass: true };
} catch (err) { } 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 // work around for SASS error in Edge
importScripts('/js/sass.sync.js'); // 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 => { self.onmessage = e => {
const data = e.data; 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 { 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() { render() {
const { const {
close, close,
@ -67,22 +105,11 @@ export class CompletionModal extends Component {
submitChallenge, submitChallenge,
handleKeypress, handleKeypress,
message, message,
files = {},
title title
} = this.props; } = this.props;
if (isOpen) { if (isOpen) {
ga.modalview('/completion-modal'); 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); const dashedName = dasherize(title);
return ( return (
<Modal <Modal
@ -112,18 +139,17 @@ export class CompletionModal extends Component {
bsStyle='primary' bsStyle='primary'
onClick={submitChallenge} 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> </Button>
{showDownloadButton ? ( {this.state.downloadURL ? (
<Button <Button
block={true} block={true}
bsSize='lg' bsSize='lg'
bsStyle='primary' bsStyle='primary'
className='btn-primary-invert' className='btn-primary-invert'
download={`${dashedName}.json`} download={`${dashedName}.json`}
href={`data:text/json;charset=utf-8,${encodeURIComponent( href={this.state.downloadURL}
JSON.stringify(filesForDownload)
)}`}
> >
Download my solution Download my solution
</Button> </Button>
@ -137,4 +163,7 @@ export class CompletionModal extends Component {
CompletionModal.displayName = 'CompletionModal'; CompletionModal.displayName = 'CompletionModal';
CompletionModal.propTypes = propTypes; 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; return ctx;
}; };
const writeTestDepsToDocument = frameReady => ctx => { const initTestFrame = frameReady => ctx => {
const { sources, loadEnzyme } = ctx; const contentLoaded = new Promise(resolve => {
// default for classic challenges if (ctx.document.readyState === 'loading') {
// should not be used for modern ctx.document.addEventListener('DOMContentLoaded', resolve);
ctx.document.__source = sources && 'index' in sources ? sources['index'] : ''; } else {
// provide the file name and get the original source resolve();
ctx.document.__getUserInput = fileName => toString(sources[fileName]); }
ctx.document.__frameReady = frameReady; });
ctx.document.__loadEnzyme = loadEnzyme; 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; return ctx;
}; };
@ -107,7 +116,7 @@ export const createTestFramer = (document, frameReady, proxyConsole) =>
flow( flow(
createFrame(document, testId), createFrame(document, testId),
mountFrame(document), mountFrame(document),
writeTestDepsToDocument(frameReady), writeContentToFrame,
buildProxyConsole(proxyConsole), buildProxyConsole(proxyConsole),
writeContentToFrame initTestFrame(frameReady)
); );

View File

@ -10,9 +10,17 @@ class WorkerExecutor {
this.getWorker = this.getWorker.bind(this); this.getWorker = this.getWorker.bind(this);
} }
getWorker() { async getWorker() {
if (this.worker === null) { 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; return this.worker;
@ -25,8 +33,8 @@ class WorkerExecutor {
} }
} }
execute(data, timeout = 1000) { async execute(data, timeout = 1000) {
const worker = this.getWorker(); const worker = await this.getWorker();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Handle timeout // Handle timeout
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {

View File

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