diff --git a/client/package-lock.json b/client/package-lock.json index 6ce8a0cfbe..b90ef6d654 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -5013,6 +5013,12 @@ "@types/responselike": "*" } }, + "@types/chai": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", + "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", + "dev": true + }, "@types/common-tags": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz", diff --git a/client/package.json b/client/package.json index c3fca36d48..27f3c9fc09 100644 --- a/client/package.json +++ b/client/package.json @@ -131,6 +131,7 @@ "@codesee/tracker": "0.112.1", "@testing-library/jest-dom": "5.14.1", "@testing-library/react": "12.1.2", + "@types/chai": "^4.2.21", "@types/jest": "26.0.24", "@types/loadable__component": "5.13.4", "@types/lodash-es": "4.17.5", diff --git a/client/src/client/workers/sass-compile.js b/client/src/client/workers/sass-compile.js deleted file mode 100644 index c20cb38895..0000000000 --- a/client/src/client/workers/sass-compile.js +++ /dev/null @@ -1,27 +0,0 @@ -// 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; - self.Sass.compile(data, result => { - if (result.status === 0) { - self.postMessage(result.text); - } else { - self.postMessage({ type: 'error', data: { message: result.formatted } }); - } - }); -}; - -self.postMessage({ type: 'contentLoaded' }); diff --git a/client/src/client/workers/sass-compile.ts b/client/src/client/workers/sass-compile.ts new file mode 100644 index 0000000000..181a20634b --- /dev/null +++ b/client/src/client/workers/sass-compile.ts @@ -0,0 +1,40 @@ +/* eslint-disable import/unambiguous */ +// work around for SASS error in Edge +// https://github.com/medialize/sass.js/issues/96#issuecomment-424386171 +interface WorkerWithSass extends Worker { + Sass: { + compile(data: unknown, callback: unknown): void; + }; +} + +const ctx: WorkerWithSass & typeof globalThis = + self as unknown as WorkerWithSass & typeof globalThis; + +if (!ctx.crypto) { + (ctx.crypto as unknown) = { + getRandomValues: function (array: number[]) { + for (let i = 0, l = array.length; i < l; i++) { + array[i] = Math.floor(Math.random() * 256); + } + return array; + } + }; +} + +ctx.importScripts('/js/sass.sync.js'); + +ctx.onmessage = e => { + const data: unknown = e.data; + ctx.Sass.compile(data, (result: Record) => { + if (result.status === 0) { + ctx.postMessage(result.text); + } else { + ctx.postMessage({ + type: 'error', + data: { message: result.formatted } + }); + } + }); +}; + +ctx.postMessage({ type: 'contentLoaded' }); diff --git a/client/src/client/workers/test-evaluator.js b/client/src/client/workers/test-evaluator.ts similarity index 63% rename from client/src/client/workers/test-evaluator.js rename to client/src/client/workers/test-evaluator.ts index bc1189cbe7..13ea866e24 100644 --- a/client/src/client/workers/test-evaluator.js +++ b/client/src/client/workers/test-evaluator.ts @@ -6,13 +6,16 @@ import curriculumHelpers, { } from '../../utils/curriculum-helpers'; import { format as __format } from '../../utils/format'; +const ctx: Worker & typeof globalThis = self as unknown as Worker & + typeof globalThis; + const __utils = (() => { const MAX_LOGS_SIZE = 64 * 1024; - let logs = []; + let logs: string[] = []; function flushLogs() { if (logs.length) { - self.postMessage({ + ctx.postMessage({ type: 'LOG', data: logs.join('\n') }); @@ -20,8 +23,8 @@ const __utils = (() => { } } - const oldLog = self.console.log.bind(self.console); - function proxyLog(...args) { + const oldLog = ctx.console.log.bind(ctx.console); + function proxyLog(...args: string[]) { logs.push(args.map(arg => __format(arg)).join(' ')); if (logs.join('\n').length > MAX_LOGS_SIZE) { flushLogs(); @@ -30,12 +33,12 @@ const __utils = (() => { } // unless data.type is truthy, this sends data out to the testRunner - function postResult(data) { + function postResult(data: unknown) { flushLogs(); - self.postMessage(data); + ctx.postMessage(data); } - function log(...msgs) { + function log(...msgs: Error[]) { if (msgs && msgs[0] && !(msgs[0] instanceof chai.AssertionError)) { // discards the stack trace via toString as it only useful to debug the // site, not a specific challenge. @@ -43,8 +46,8 @@ const __utils = (() => { } } - const toggleProxyLogger = on => { - self.console.log = on ? proxyLog : oldLog; + const toggleProxyLogger = (on: unknown) => { + ctx.console.log = on ? proxyLog : oldLog; }; return { @@ -55,9 +58,25 @@ const __utils = (() => { }; })(); +interface TestEvaluatorEvent extends MessageEvent { + data: { + code: { + contents: string; + editableContents: string; + }; + removeComments: boolean; + firstTest: unknown; + testString: string; + build: string; + sources: { + [fileName: string]: unknown; + }; + }; +} + /* Run the test if there is one. If not just evaluate the user code */ -self.onmessage = async e => { - /* eslint-disable no-unused-vars */ +ctx.onmessage = async (e: TestEvaluatorEvent) => { + /* eslint-disable @typescript-eslint/no-unused-vars */ let code = (e.data?.code?.contents || '').slice(); code = e.data?.removeComments ? removeJSComments(code) : code; let editableContents = (e.data?.code?.editableContents || '').slice(); @@ -68,13 +87,16 @@ self.onmessage = async e => { const assert = chai.assert; const __helpers = curriculumHelpers; // Fake Deep Equal dependency - const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b); + const DeepEqual = (a: unknown, b: unknown) => + JSON.stringify(a) === JSON.stringify(b); // Build errors should be reported, but only once: __utils.toggleProxyLogger(e.data.firstTest); - /* eslint-enable no-unused-vars */ + /* eslint-enable @typescript-eslint/no-unused-vars */ try { let testResult; + // This can be reassigned by the eval inside the try block, so it should be declared as a let + // eslint-disable-next-line prefer-const let __userCodeWasExecuted = false; /* eslint-disable no-eval */ try { @@ -86,7 +108,7 @@ self.onmessage = async e => { __utils.flushLogs(); __userCodeWasExecuted = true; __utils.toggleProxyLogger(true); -${e.data.testString}`); +${e.data.testString}`) as unknown; } catch (err) { if (__userCodeWasExecuted) { // rethrow error, since test failed. @@ -95,19 +117,21 @@ ${e.data.testString}`); // log build errors unless they're related to import/export/require (there // are challenges that use them and they should not trigger warnings) if ( - err.name !== 'ReferenceError' || - (err.message !== 'require is not defined' && - err.message !== 'exports is not defined') + (err as Error).name !== 'ReferenceError' || + ((err as Error).message !== 'require is not defined' && + (err as Error).message !== 'exports is not defined') ) { - __utils.log(err); + __utils.log(err as Error); } // the tests may not require working code, so they are evaluated even if // the user code does not get executed. - testResult = eval(e.data.testString); + testResult = eval(e.data.testString) as unknown; } /* eslint-enable no-eval */ if (typeof testResult === 'function') { - await testResult(fileName => __toString(e.data.sources[fileName])); + await testResult((fileName: string) => + __toString(e.data.sources[fileName]) + ); } __utils.postResult({ pass: true @@ -117,15 +141,15 @@ ${e.data.testString}`); __utils.toggleProxyLogger(false); // Report execution errors in case user code has errors that are only // uncovered during testing. - __utils.log(err); + __utils.log(err as Error); // postResult flushes the logs and must be called after logging is finished. __utils.postResult({ err: { - message: err.message, - stack: err.stack + message: (err as Error).message, + stack: (err as Error).stack } }); } }; -self.postMessage({ type: 'contentLoaded' }); +ctx.postMessage({ type: 'contentLoaded' }); diff --git a/client/tsconfig.json b/client/tsconfig.json index 689bc83179..7dff098671 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,6 +1,11 @@ { "include": ["./i18n/**/*", "./plugins/**/*", "./src/**/*", "./utils/**/*"], "compilerOptions": { + "lib": [ + "WebWorker", + "DOM", + "DOM.Iterable" + ], "target": "es2020", "module": "es2020", "moduleResolution": "node", diff --git a/client/webpack-workers.js b/client/webpack-workers.js index a4e7f70792..4e7cc53375 100644 --- a/client/webpack-workers.js +++ b/client/webpack-workers.js @@ -12,8 +12,8 @@ module.exports = (env = {}) => { mode: __DEV__ ? 'development' : 'production', entry: { 'frame-runner': './src/client/frame-runner.js', - 'sass-compile': './src/client/workers/sass-compile.js', - 'test-evaluator': './src/client/workers/test-evaluator.js' + 'sass-compile': './src/client/workers/sass-compile.ts', + 'test-evaluator': './src/client/workers/test-evaluator.ts' }, devtool: __DEV__ ? 'inline-source-map' : 'source-map', output: {