| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | /* eslint-disable no-loop-func */ | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | const path = require('path'); | 
					
						
							|  |  |  |  | const liveServer = require('live-server'); | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | const stringSimilarity = require('string-similarity'); | 
					
						
							| 
									
										
										
										
											2020-10-01 09:53:01 +02:00
										 |  |  |  | const { isAuditedCert } = require('../../utils/is-audited'); | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | const spinner = require('ora')(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | const clientPath = path.resolve(__dirname, '../../client'); | 
					
						
							|  |  |  |  | require('@babel/polyfill'); | 
					
						
							|  |  |  |  | require('@babel/register')({ | 
					
						
							|  |  |  |  |   root: clientPath, | 
					
						
							|  |  |  |  |   babelrc: false, | 
					
						
							| 
									
										
										
										
											2021-05-17 20:53:41 +02:00
										 |  |  |  |   presets: ['@babel/preset-env', '@babel/typescript'], | 
					
						
							| 
									
										
										
										
											2020-01-16 15:47:23 +01:00
										 |  |  |  |   plugins: ['dynamic-import-node'], | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  |   ignore: [/node_modules/], | 
					
						
							|  |  |  |  |   only: [clientPath] | 
					
						
							|  |  |  |  | }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-28 23:11:20 +02:00
										 |  |  |  | const mockRequire = require('mock-require'); | 
					
						
							|  |  |  |  | const lodash = require('lodash'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // lodash-es can't easily be used in node environments, so we just mock it out
 | 
					
						
							|  |  |  |  | // for the original lodash in testing.
 | 
					
						
							|  |  |  |  | mockRequire('lodash-es', lodash); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | const createPseudoWorker = require('./utils/pseudo-worker'); | 
					
						
							|  |  |  |  | const { | 
					
						
							|  |  |  |  |   default: createWorker | 
					
						
							|  |  |  |  | } = require('../../client/src/templates/Challenges/utils/worker-executor'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-01 18:56:15 +03:00
										 |  |  |  | const { assert, AssertionError } = require('chai'); | 
					
						
							|  |  |  |  | const Mocha = require('mocha'); | 
					
						
							| 
									
										
										
										
											2021-04-28 23:11:20 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | const { flatten, isEmpty, cloneDeep, isEqual } = lodash; | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  | const { getLines } = require('../../utils/get-lines'); | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | const jsdom = require('jsdom'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | const vm = require('vm'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-01 02:47:32 +03:00
										 |  |  |  | const puppeteer = require('puppeteer'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  | const { | 
					
						
							|  |  |  |  |   getChallengesForLang, | 
					
						
							|  |  |  |  |   getMetaForBlock, | 
					
						
							|  |  |  |  |   getTranslatableComments | 
					
						
							|  |  |  |  | } = require('../getChallenges'); | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | const MongoIds = require('./utils/mongoIds'); | 
					
						
							|  |  |  |  | const ChallengeTitles = require('./utils/challengeTitles'); | 
					
						
							| 
									
										
										
										
											2018-12-05 20:09:18 +03:00
										 |  |  |  | const { challengeSchemaValidator } = require('../schema/challengeSchema'); | 
					
						
							| 
									
										
										
										
											2020-10-30 20:10:34 +01:00
										 |  |  |  | const { challengeTypes } = require('../../client/utils/challengeTypes'); | 
					
						
							| 
									
										
										
										
											2019-11-15 11:33:08 -06:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-08 16:23:09 +02:00
										 |  |  |  | const { toSortedArray } = require('../../utils/sort-files'); | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  | const { testedLang } = require('../utils'); | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | const { | 
					
						
							|  |  |  |  |   buildDOMChallenge, | 
					
						
							|  |  |  |  |   buildJSChallenge | 
					
						
							|  |  |  |  | } = require('../../client/src/templates/Challenges/utils/build'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 06:33:28 +02:00
										 |  |  |  | const { sortChallenges } = require('./utils/sort-challenges'); | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  | const TRANSLATABLE_COMMENTS = getTranslatableComments( | 
					
						
							|  |  |  |  |   path.resolve(__dirname, '..', 'dictionaries') | 
					
						
							|  |  |  |  | ); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 10:39:57 +01:00
										 |  |  |  | // the config files are created during the build, but not before linting
 | 
					
						
							| 
									
										
										
										
											2021-05-10 08:48:49 -07:00
										 |  |  |  | /* eslint-disable import/no-unresolved */ | 
					
						
							|  |  |  |  | const testEvaluator = | 
					
						
							|  |  |  |  |   require('../../config/client/test-evaluator.json').filename; | 
					
						
							|  |  |  |  | /* eslint-enable import/no-unresolved */ | 
					
						
							| 
									
										
										
										
											2021-03-20 07:29:13 +01:00
										 |  |  |  | const { inspect } = require('util'); | 
					
						
							| 
									
										
										
										
											2019-11-14 21:13:44 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  | const commentExtractors = { | 
					
						
							|  |  |  |  |   html: require('./utils/extract-html-comments'), | 
					
						
							|  |  |  |  |   js: require('./utils/extract-js-comments'), | 
					
						
							|  |  |  |  |   jsx: require('./utils/extract-jsx-comments'), | 
					
						
							| 
									
										
										
										
											2021-03-20 07:29:13 +01:00
										 |  |  |  |   css: require('./utils/extract-css-comments'), | 
					
						
							|  |  |  |  |   scriptJs: require('./utils/extract-script-js-comments') | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-27 12:00:25 +02:00
										 |  |  |  | // rethrow unhandled rejections to make sure the tests exit with -1
 | 
					
						
							| 
									
										
										
										
											2020-08-27 15:31:01 +02:00
										 |  |  |  | process.on('unhandledRejection', err => handleRejection(err)); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | const handleRejection = err => { | 
					
						
							| 
									
										
										
										
											2020-08-27 12:00:25 +02:00
										 |  |  |  |   // setting the error code because node does not (yet) exit with a non-zero
 | 
					
						
							|  |  |  |  |   // code on unhandled exceptions.
 | 
					
						
							|  |  |  |  |   process.exitCode = 1; | 
					
						
							| 
									
										
										
										
											2020-08-27 15:31:01 +02:00
										 |  |  |  |   cleanup(); | 
					
						
							|  |  |  |  |   if (process.env.FULL_OUTPUT === 'true') { | 
					
						
							|  |  |  |  |     // some errors *may* not be reported, since cleanup is triggered by the
 | 
					
						
							|  |  |  |  |     // first error and that starts shutting down the browser and the server.
 | 
					
						
							|  |  |  |  |     console.error(err); | 
					
						
							|  |  |  |  |   } else { | 
					
						
							|  |  |  |  |     throw err; | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | }; | 
					
						
							| 
									
										
										
										
											2020-08-27 12:00:25 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | const dom = new jsdom.JSDOM(''); | 
					
						
							|  |  |  |  | global.document = dom.window.document; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-01 18:56:15 +03:00
										 |  |  |  | const oldRunnerFail = Mocha.Runner.prototype.fail; | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  | Mocha.Runner.prototype.fail = function (test, err) { | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  |   if (err instanceof AssertionError) { | 
					
						
							|  |  |  |  |     const errMessage = String(err.message || ''); | 
					
						
							|  |  |  |  |     const assertIndex = errMessage.indexOf(': expected'); | 
					
						
							| 
									
										
										
										
											2018-12-01 02:47:32 +03:00
										 |  |  |  |     if (assertIndex !== -1) { | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  |       err.message = errMessage.slice(0, assertIndex); | 
					
						
							| 
									
										
										
										
											2018-12-01 02:47:32 +03:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |     // Don't show stacktrace for assertion errors.
 | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  |     if (err.stack) { | 
					
						
							|  |  |  |  |       delete err.stack; | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-01 18:56:15 +03:00
										 |  |  |  |   } | 
					
						
							|  |  |  |  |   return oldRunnerFail.call(this, test, err); | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | async function newPageContext(browser) { | 
					
						
							|  |  |  |  |   const page = await browser.newPage(); | 
					
						
							|  |  |  |  |   // it's needed for workers as context.
 | 
					
						
							|  |  |  |  |   await page.goto('http://127.0.0.1:8080/index.html'); | 
					
						
							|  |  |  |  |   return page; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | spinner.start(); | 
					
						
							|  |  |  |  | spinner.text = 'Populate tests.'; | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | let browser; | 
					
						
							|  |  |  |  | let page; | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | setup() | 
					
						
							|  |  |  |  |   .then(runTests) | 
					
						
							| 
									
										
										
										
											2020-08-27 15:31:01 +02:00
										 |  |  |  |   .catch(err => handleRejection(err)); | 
					
						
							| 
									
										
										
										
											2019-04-01 18:19:25 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | async function setup() { | 
					
						
							|  |  |  |  |   if (process.env.npm_config_superblock && process.env.npm_config_block) { | 
					
						
							|  |  |  |  |     throw new Error(`Please do not use both a block and superblock as input.`); | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-12-07 14:06:07 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |   // liveServer starts synchronously
 | 
					
						
							|  |  |  |  |   liveServer.start({ | 
					
						
							|  |  |  |  |     host: '127.0.0.1', | 
					
						
							|  |  |  |  |     port: '8080', | 
					
						
							|  |  |  |  |     root: path.resolve(__dirname, 'stubs'), | 
					
						
							|  |  |  |  |     mount: [['/js', path.join(clientPath, 'static/js')]], | 
					
						
							|  |  |  |  |     open: false, | 
					
						
							|  |  |  |  |     logLevel: 0 | 
					
						
							|  |  |  |  |   }); | 
					
						
							|  |  |  |  |   browser = await puppeteer.launch({ | 
					
						
							|  |  |  |  |     args: [ | 
					
						
							|  |  |  |  |       // Required for Docker version of Puppeteer
 | 
					
						
							|  |  |  |  |       '--no-sandbox', | 
					
						
							|  |  |  |  |       '--disable-setuid-sandbox', | 
					
						
							|  |  |  |  |       // This will write shared memory files into /tmp instead of /dev/shm,
 | 
					
						
							|  |  |  |  |       // because Docker’s default for /dev/shm is 64MB
 | 
					
						
							|  |  |  |  |       '--disable-dev-shm-usage' | 
					
						
							|  |  |  |  |       // dumpio: true
 | 
					
						
							|  |  |  |  |     ] | 
					
						
							|  |  |  |  |   }); | 
					
						
							|  |  |  |  |   global.Worker = createPseudoWorker(await newPageContext(browser)); | 
					
						
							|  |  |  |  |   page = await newPageContext(browser); | 
					
						
							|  |  |  |  |   await page.setViewport({ width: 300, height: 150 }); | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   const lang = testedLang(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   let challenges = await getChallenges(lang); | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |   // the next few statements create a list of all blocks and superblocks
 | 
					
						
							|  |  |  |  |   // as they appear in the list of challenges
 | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |   const blocks = challenges.map(({ block }) => block); | 
					
						
							|  |  |  |  |   const superBlocks = challenges.map(({ superBlock }) => superBlock); | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |   const targetBlockStrings = [...new Set(blocks)]; | 
					
						
							|  |  |  |  |   const targetSuperBlockStrings = [...new Set(superBlocks)]; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   // the next few statements will filter challenges based on command variables
 | 
					
						
							|  |  |  |  |   if (process.env.npm_config_superblock) { | 
					
						
							|  |  |  |  |     const filter = stringSimilarity.findBestMatch( | 
					
						
							|  |  |  |  |       process.env.npm_config_superblock, | 
					
						
							|  |  |  |  |       targetSuperBlockStrings | 
					
						
							|  |  |  |  |     ).bestMatch.target; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     console.log(`\nsuperBlock being tested: ${filter}`); | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |     challenges = challenges.filter( | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |       challenge => challenge.superBlock === filter | 
					
						
							|  |  |  |  |     ); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |     if (!challenges.length) { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |       throw new Error(`No challenges found with superBlock "${filter}"`); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   if (process.env.npm_config_block) { | 
					
						
							|  |  |  |  |     const filter = stringSimilarity.findBestMatch( | 
					
						
							|  |  |  |  |       process.env.npm_config_block, | 
					
						
							|  |  |  |  |       targetBlockStrings | 
					
						
							|  |  |  |  |     ).bestMatch.target; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     console.log(`\nblock being tested: ${filter}`); | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |     challenges = challenges.filter(challenge => challenge.block === filter); | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |     if (!challenges.length) { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |       throw new Error(`No challenges found with block "${filter}"`); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const meta = {}; | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |   for (const challenge of challenges) { | 
					
						
							| 
									
										
										
										
											2021-02-23 05:22:48 +01:00
										 |  |  |  |     const dashedBlockName = challenge.block; | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |     if (!meta[dashedBlockName]) { | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |       meta[dashedBlockName] = ( | 
					
						
							|  |  |  |  |         await getMetaForBlock(dashedBlockName) | 
					
						
							|  |  |  |  |       ).challengeOrder; | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   return { | 
					
						
							|  |  |  |  |     meta, | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |     challenges, | 
					
						
							|  |  |  |  |     lang | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |   }; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | // cleanup calls some async functions, but it's the last thing that happens, so
 | 
					
						
							|  |  |  |  | // no need to await anything.
 | 
					
						
							|  |  |  |  | function cleanup() { | 
					
						
							|  |  |  |  |   if (browser) { | 
					
						
							|  |  |  |  |     browser.close(); | 
					
						
							|  |  |  |  |   } | 
					
						
							|  |  |  |  |   liveServer.shutdown(); | 
					
						
							|  |  |  |  |   spinner.stop(); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  | function runTests(challengeData) { | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |   describe('Check challenges', function () { | 
					
						
							|  |  |  |  |     after(function () { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |       cleanup(); | 
					
						
							|  |  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |     populateTestsForLang(challengeData); | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |   }); | 
					
						
							|  |  |  |  |   spinner.text = 'Testing'; | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  |   run(); | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | async function getChallenges(lang) { | 
					
						
							|  |  |  |  |   const challenges = await getChallengesForLang(lang).then(curriculum => | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  |     Object.keys(curriculum) | 
					
						
							| 
									
										
										
										
											2018-11-26 10:47:33 +03:00
										 |  |  |  |       .map(key => curriculum[key].blocks) | 
					
						
							|  |  |  |  |       .reduce((challengeArray, superBlock) => { | 
					
						
							|  |  |  |  |         const challengesForBlock = Object.keys(superBlock).map( | 
					
						
							|  |  |  |  |           key => superBlock[key].challenges | 
					
						
							|  |  |  |  |         ); | 
					
						
							|  |  |  |  |         return [...challengeArray, ...flatten(challengesForBlock)]; | 
					
						
							|  |  |  |  |       }, []) | 
					
						
							|  |  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2020-06-30 06:33:28 +02:00
										 |  |  |  |   // This matches the order Gatsby uses (via a GraphQL query). Ideally both
 | 
					
						
							|  |  |  |  |   // should be sourced and sorted using a single query, but we're not there yet.
 | 
					
						
							|  |  |  |  |   return sortChallenges(challenges); | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  | function populateTestsForLang({ lang, challenges, meta }) { | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  |   const mongoIds = new MongoIds(); | 
					
						
							|  |  |  |  |   const challengeTitles = new ChallengeTitles(); | 
					
						
							| 
									
										
										
										
											2020-10-01 15:50:43 +02:00
										 |  |  |  |   const validateChallenge = challengeSchemaValidator(); | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |   describe(`Check challenges (${lang})`, function () { | 
					
						
							| 
									
										
										
										
											2018-11-16 21:16:27 +03:00
										 |  |  |  |     this.timeout(5000); | 
					
						
							| 
									
										
										
										
											2020-06-30 06:33:28 +02:00
										 |  |  |  |     challenges.forEach((challenge, id) => { | 
					
						
							| 
									
										
										
										
											2021-02-23 05:22:48 +01:00
										 |  |  |  |       const dashedBlockName = challenge.block; | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |       describe(challenge.block || 'No block', function () { | 
					
						
							|  |  |  |  |         describe(challenge.title || 'No title', function () { | 
					
						
							| 
									
										
										
										
											2020-10-01 15:50:43 +02:00
										 |  |  |  |           // Note: the title in meta.json are purely for human readability and
 | 
					
						
							|  |  |  |  |           // do not include translations, so we do not validate against them.
 | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |           it('Matches an ID in meta.json', function () { | 
					
						
							| 
									
										
										
										
											2020-08-13 17:05:52 +02:00
										 |  |  |  |             const index = meta[dashedBlockName].findIndex( | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |               arr => arr[0] === challenge.id | 
					
						
							|  |  |  |  |             ); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (index < 0) { | 
					
						
							|  |  |  |  |               throw new AssertionError( | 
					
						
							|  |  |  |  |                 `Cannot find ID "${challenge.id}" in meta.json file` | 
					
						
							|  |  |  |  |               ); | 
					
						
							|  |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  |           }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |           it('Common checks', function () { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             const result = validateChallenge(challenge); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             if (result.error) { | 
					
						
							|  |  |  |  |               throw new AssertionError(result.error); | 
					
						
							|  |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-06-24 16:22:14 +09:00
										 |  |  |  |             const { id, title, block, dashedName } = challenge; | 
					
						
							| 
									
										
										
										
											2021-02-23 05:22:48 +01:00
										 |  |  |  |             const pathAndTitle = `${block}/${dashedName}`; | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             mongoIds.check(id, title); | 
					
						
							| 
									
										
										
										
											2020-06-24 16:22:14 +09:00
										 |  |  |  |             challengeTitles.check(title, pathAndTitle); | 
					
						
							| 
									
										
										
										
											2019-01-15 17:18:56 +03:00
										 |  |  |  |           }); | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  |           it('Has replaced all the English comments', () => { | 
					
						
							|  |  |  |  |             // special cases are where this process breaks for some reason, but
 | 
					
						
							|  |  |  |  |             // we have validated that the challenge gets parsed correctly.
 | 
					
						
							|  |  |  |  |             const specialCases = [ | 
					
						
							|  |  |  |  |               '587d7b84367417b2b2512b36', | 
					
						
							|  |  |  |  |               '587d7b84367417b2b2512b37', | 
					
						
							|  |  |  |  |               '587d7db0367417b2b2512b82', | 
					
						
							|  |  |  |  |               '587d7dbe367417b2b2512bb8', | 
					
						
							|  |  |  |  |               '5a24c314108439a4d4036161', | 
					
						
							|  |  |  |  |               '5a24c314108439a4d4036154', | 
					
						
							|  |  |  |  |               '5a94fe0569fb03452672e45c', | 
					
						
							|  |  |  |  |               '5a94fe7769fb03452672e463', | 
					
						
							|  |  |  |  |               '5a24c314108439a4d4036148' | 
					
						
							|  |  |  |  |             ]; | 
					
						
							|  |  |  |  |             if (specialCases.includes(challenge.id)) return; | 
					
						
							| 
									
										
										
										
											2020-10-01 09:53:01 +02:00
										 |  |  |  |             if ( | 
					
						
							|  |  |  |  |               lang === 'english' || | 
					
						
							|  |  |  |  |               !isAuditedCert(lang, challenge.superBlock) | 
					
						
							|  |  |  |  |             ) { | 
					
						
							|  |  |  |  |               return; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  |             // If no .files, then no seed:
 | 
					
						
							|  |  |  |  |             if (!challenge.files) return; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             // - None of the translatable comments should appear in the
 | 
					
						
							|  |  |  |  |             //   translations. While this is a crude check, no challenges
 | 
					
						
							|  |  |  |  |             //   currently have the text of a comment elsewhere. If that happens
 | 
					
						
							|  |  |  |  |             //   we can handle that challenge separately.
 | 
					
						
							|  |  |  |  |             TRANSLATABLE_COMMENTS.forEach(comment => { | 
					
						
							|  |  |  |  |               Object.values(challenge.files).forEach(file => { | 
					
						
							|  |  |  |  |                 if (file.contents.includes(comment)) | 
					
						
							|  |  |  |  |                   throw Error( | 
					
						
							|  |  |  |  |                     `English comment '${comment}' should be replaced with its translation` | 
					
						
							|  |  |  |  |                   ); | 
					
						
							|  |  |  |  |               }); | 
					
						
							|  |  |  |  |             }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             // - None of the translated comment texts should appear *outside* a
 | 
					
						
							|  |  |  |  |             //   comment
 | 
					
						
							|  |  |  |  |             Object.values(challenge.files).forEach(file => { | 
					
						
							|  |  |  |  |               let comments = {}; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |               // We get all the actual comments using the appropriate parsers
 | 
					
						
							|  |  |  |  |               if (file.ext === 'html') { | 
					
						
							| 
									
										
										
										
											2021-03-20 07:29:13 +01:00
										 |  |  |  |                 const commentTypes = ['css', 'html', 'scriptJs']; | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  |                 for (let type of commentTypes) { | 
					
						
							|  |  |  |  |                   const newComments = commentExtractors[type](file.contents); | 
					
						
							|  |  |  |  |                   for (const [key, value] of Object.entries(newComments)) { | 
					
						
							|  |  |  |  |                     comments[key] = comments[key] | 
					
						
							|  |  |  |  |                       ? comments[key] + value | 
					
						
							|  |  |  |  |                       : value; | 
					
						
							|  |  |  |  |                   } | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |               } else { | 
					
						
							|  |  |  |  |                 comments = commentExtractors[file.ext](file.contents); | 
					
						
							|  |  |  |  |               } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-20 07:29:13 +01:00
										 |  |  |  |               // Then we compare the number of times each comment appears in the
 | 
					
						
							|  |  |  |  |               // translated text (commentMap) with the number of replacements
 | 
					
						
							|  |  |  |  |               // made during translation (challenge.__commentCounts). If they
 | 
					
						
							|  |  |  |  |               // differ, the translation must have gone wrong
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |               const commentMap = new Map(Object.entries(comments)); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |               if (isEmpty(challenge.__commentCounts) && isEmpty(commentMap)) | 
					
						
							|  |  |  |  |                 return; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |               if (!isEqual(commentMap, challenge.__commentCounts)) | 
					
						
							|  |  |  |  |                 throw Error(`Mismatch in ${challenge.title}. Replaced comments:
 | 
					
						
							|  |  |  |  | ${inspect(challenge.__commentCounts)} | 
					
						
							|  |  |  |  | Comments in translated text: | 
					
						
							|  |  |  |  | ${inspect(commentMap)} | 
					
						
							|  |  |  |  | `);
 | 
					
						
							| 
									
										
										
										
											2020-09-23 16:38:20 +02:00
										 |  |  |  |             }); | 
					
						
							|  |  |  |  |           }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |           const { challengeType } = challenge; | 
					
						
							|  |  |  |  |           if ( | 
					
						
							|  |  |  |  |             challengeType !== challengeTypes.html && | 
					
						
							|  |  |  |  |             challengeType !== challengeTypes.js && | 
					
						
							|  |  |  |  |             challengeType !== challengeTypes.bonfire && | 
					
						
							|  |  |  |  |             challengeType !== challengeTypes.modern && | 
					
						
							|  |  |  |  |             challengeType !== challengeTypes.backend | 
					
						
							|  |  |  |  |           ) { | 
					
						
							|  |  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |           } | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |           let { tests = [] } = challenge; | 
					
						
							|  |  |  |  |           tests = tests.filter(test => !!test.testString); | 
					
						
							|  |  |  |  |           if (tests.length === 0) { | 
					
						
							|  |  |  |  |             it('Check tests. No tests.'); | 
					
						
							|  |  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |           } | 
					
						
							| 
									
										
										
										
											2018-10-25 17:23:00 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |           describe('Check tests syntax', function () { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             tests.forEach(test => { | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |               it(`Check for: ${test.text}`, function () { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |                 assert.doesNotThrow(() => new vm.Script(test.testString)); | 
					
						
							|  |  |  |  |               }); | 
					
						
							|  |  |  |  |             }); | 
					
						
							|  |  |  |  |           }); | 
					
						
							| 
									
										
										
										
											2018-10-25 17:23:00 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |           if (challengeType === challengeTypes.backend) { | 
					
						
							|  |  |  |  |             it('Check tests is not implemented.'); | 
					
						
							|  |  |  |  |             return; | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  |           } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           const buildChallenge = | 
					
						
							|  |  |  |  |             challengeType === challengeTypes.js || | 
					
						
							|  |  |  |  |             challengeType === challengeTypes.bonfire | 
					
						
							|  |  |  |  |               ? buildJSChallenge | 
					
						
							|  |  |  |  |               : buildDOMChallenge; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |           it('Test suite must fail on the initial contents', async function () { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             this.timeout(5000 * tests.length + 1000); | 
					
						
							|  |  |  |  |             // suppress errors in the console.
 | 
					
						
							|  |  |  |  |             const oldConsoleError = console.error; | 
					
						
							|  |  |  |  |             console.error = () => {}; | 
					
						
							|  |  |  |  |             let fails = false; | 
					
						
							|  |  |  |  |             let testRunner; | 
					
						
							|  |  |  |  |             try { | 
					
						
							|  |  |  |  |               testRunner = await createTestRunner( | 
					
						
							| 
									
										
										
										
											2020-06-08 15:01:48 +02:00
										 |  |  |  |                 challenge, | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |                 '', | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  |                 buildChallenge | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |               ); | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             } catch { | 
					
						
							|  |  |  |  |               fails = true; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |             if (!fails) { | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |               for (const test of tests) { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |                 try { | 
					
						
							|  |  |  |  |                   await testRunner(test); | 
					
						
							|  |  |  |  |                 } catch (e) { | 
					
						
							|  |  |  |  |                   fails = true; | 
					
						
							|  |  |  |  |                   break; | 
					
						
							|  |  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |               } | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             } | 
					
						
							|  |  |  |  |             console.error = oldConsoleError; | 
					
						
							|  |  |  |  |             assert(fails, 'Test suit does not fail on the initial contents'); | 
					
						
							|  |  |  |  |           }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |           let { solutions = [] } = challenge; | 
					
						
							| 
									
										
										
										
											2020-06-12 14:47:58 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |           // if there's an empty string as solution, this is likely a mistake
 | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  |           // TODO: what does this look like now? (this being detection of empty
 | 
					
						
							|  |  |  |  |           // lines in solutions - rather than entirely missing solutions)
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           // We need to track where the solution came from to give better
 | 
					
						
							|  |  |  |  |           // feedback if the solution is failing.
 | 
					
						
							|  |  |  |  |           let solutionFromNext = false; | 
					
						
							| 
									
										
										
										
											2020-06-12 14:47:58 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 06:33:28 +02:00
										 |  |  |  |           if (isEmpty(solutions)) { | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |             // if there are no solutions in the challenge, it's assumed the next
 | 
					
						
							|  |  |  |  |             // challenge's seed will be a solution to the current challenge.
 | 
					
						
							|  |  |  |  |             // This is expected to happen in the project based curriculum.
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |             const nextChallenge = challenges[id + 1]; | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  |             // TODO: can this be dried out, ideally by removing the redux
 | 
					
						
							|  |  |  |  |             // handler?
 | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |             if (nextChallenge) { | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  |               const solutionFiles = cloneDeep(nextChallenge.files); | 
					
						
							|  |  |  |  |               Object.keys(solutionFiles).forEach(key => { | 
					
						
							|  |  |  |  |                 const file = solutionFiles[key]; | 
					
						
							|  |  |  |  |                 file.editableContents = getLines( | 
					
						
							|  |  |  |  |                   file.contents, | 
					
						
							|  |  |  |  |                   challenge.files[key].editableRegionBoundaries | 
					
						
							|  |  |  |  |                 ); | 
					
						
							|  |  |  |  |               }); | 
					
						
							|  |  |  |  |               solutions = [solutionFiles]; | 
					
						
							|  |  |  |  |               solutionFromNext = true; | 
					
						
							| 
									
										
										
										
											2020-06-12 14:47:58 +02:00
										 |  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |               throw Error('solution omitted'); | 
					
						
							| 
									
										
										
										
											2020-06-30 06:33:28 +02:00
										 |  |  |  |             } | 
					
						
							|  |  |  |  |           } | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |           // TODO: the no-solution filtering is a little convoluted:
 | 
					
						
							|  |  |  |  |           const noSolution = new RegExp('// solution required'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           const solutionsAsArrays = solutions.map(toSortedArray); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           const filteredSolutions = solutionsAsArrays.filter(solution => { | 
					
						
							|  |  |  |  |             return !isEmpty( | 
					
						
							|  |  |  |  |               solution.filter(file => !noSolution.test(file.contents)) | 
					
						
							|  |  |  |  |             ); | 
					
						
							|  |  |  |  |           }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           if (isEmpty(filteredSolutions)) { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             it('Check tests. No solutions'); | 
					
						
							|  |  |  |  |             return; | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |           describe('Check tests against solutions', function () { | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |             solutions.forEach((solution, index) => { | 
					
						
							| 
									
										
										
										
											2021-03-11 00:31:46 +05:30
										 |  |  |  |               it(`Solution ${ | 
					
						
							|  |  |  |  |                 index + 1 | 
					
						
							|  |  |  |  |               } must pass the tests`, async function () {
 | 
					
						
							| 
									
										
										
										
											2021-01-20 16:27:18 +01:00
										 |  |  |  |                 this.timeout(5000 * tests.length + 2000); | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |                 const testRunner = await createTestRunner( | 
					
						
							| 
									
										
										
										
											2020-06-08 15:01:48 +02:00
										 |  |  |  |                   challenge, | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |                   solution, | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  |                   buildChallenge, | 
					
						
							|  |  |  |  |                   solutionFromNext | 
					
						
							| 
									
										
										
										
											2020-04-23 12:01:15 -05:00
										 |  |  |  |                 ); | 
					
						
							|  |  |  |  |                 for (const test of tests) { | 
					
						
							|  |  |  |  |                   await testRunner(test); | 
					
						
							|  |  |  |  |                 } | 
					
						
							|  |  |  |  |               }); | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  |             }); | 
					
						
							|  |  |  |  |           }); | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     }); | 
					
						
							|  |  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2018-12-05 22:04:30 +03:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  | async function createTestRunner( | 
					
						
							|  |  |  |  |   challenge, | 
					
						
							|  |  |  |  |   solution, | 
					
						
							|  |  |  |  |   buildChallenge, | 
					
						
							|  |  |  |  |   solutionFromNext | 
					
						
							|  |  |  |  | ) { | 
					
						
							| 
									
										
										
										
											2021-04-30 21:30:06 +02:00
										 |  |  |  |   const { required = [], template, removeComments } = challenge; | 
					
						
							| 
									
										
										
										
											2020-06-12 14:47:58 +02:00
										 |  |  |  |   // we should avoid modifying challenge, as it gets reused:
 | 
					
						
							|  |  |  |  |   const files = cloneDeep(challenge.files); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |   Object.keys(solution).forEach(key => { | 
					
						
							|  |  |  |  |     files[key].contents = solution[key].contents; | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  |     files[key].editableContents = solution[key].editableContents; | 
					
						
							| 
									
										
										
										
											2020-07-13 18:59:40 +02:00
										 |  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 21:30:06 +02:00
										 |  |  |  |   const { build, sources, loadEnzyme } = await buildChallenge({ | 
					
						
							|  |  |  |  |     files, | 
					
						
							|  |  |  |  |     required, | 
					
						
							|  |  |  |  |     template | 
					
						
							|  |  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2021-04-28 16:18:54 +01:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 11:28:20 +02:00
										 |  |  |  |   const code = { | 
					
						
							|  |  |  |  |     contents: sources.index, | 
					
						
							|  |  |  |  |     editableContents: sources.editableContents | 
					
						
							|  |  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-12-01 02:47:32 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  |   const evaluator = await (buildChallenge === buildDOMChallenge | 
					
						
							|  |  |  |  |     ? getContextEvaluator(build, sources, code, loadEnzyme) | 
					
						
							| 
									
										
										
										
											2021-04-30 21:30:06 +02:00
										 |  |  |  |     : getWorkerEvaluator(build, sources, code, removeComments)); | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-19 01:59:12 +03:00
										 |  |  |  |   return async ({ text, testString }) => { | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  |       const { pass, err } = await evaluator.evaluate(testString, 5000); | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |       if (!pass) { | 
					
						
							| 
									
										
										
										
											2021-02-25 15:39:28 +01:00
										 |  |  |  |         throw err; | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } catch (err) { | 
					
						
							| 
									
										
										
										
											2021-02-25 15:39:28 +01:00
										 |  |  |  |       // add more info to the error so the failing test can be identified.
 | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  |       text = 'Test text: ' + text; | 
					
						
							| 
									
										
										
										
											2021-02-25 15:39:28 +01:00
										 |  |  |  |       const newMessage = solutionFromNext | 
					
						
							| 
									
										
										
										
											2020-08-17 03:57:52 +02:00
										 |  |  |  |         ? 'Check next step for solution!\n' + text | 
					
						
							|  |  |  |  |         : text; | 
					
						
							| 
									
										
										
										
											2021-02-25 15:39:28 +01:00
										 |  |  |  |       // if the stack is missing, the message should be included. Otherwise it
 | 
					
						
							|  |  |  |  |       // is redundant.
 | 
					
						
							|  |  |  |  |       err.message = err.stack | 
					
						
							|  |  |  |  |         ? newMessage | 
					
						
							|  |  |  |  |         : `${newMessage}
 | 
					
						
							|  |  |  |  |       ${err.message}`;
 | 
					
						
							|  |  |  |  |       throw err; | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  | async function getContextEvaluator(build, sources, code, loadEnzyme) { | 
					
						
							|  |  |  |  |   await initializeTestRunner(build, sources, code, loadEnzyme); | 
					
						
							| 
									
										
										
										
											2018-10-25 17:23:00 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  |   return { | 
					
						
							|  |  |  |  |     evaluate: async (testString, timeout) => | 
					
						
							|  |  |  |  |       Promise.race([ | 
					
						
							|  |  |  |  |         new Promise((_, reject) => | 
					
						
							|  |  |  |  |           setTimeout(() => reject('timeout'), timeout) | 
					
						
							|  |  |  |  |         ), | 
					
						
							|  |  |  |  |         await page.evaluate(async testString => { | 
					
						
							|  |  |  |  |           return await document.__runTest(testString); | 
					
						
							|  |  |  |  |         }, testString) | 
					
						
							|  |  |  |  |       ]) | 
					
						
							|  |  |  |  |   }; | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-30 21:30:06 +02:00
										 |  |  |  | async function getWorkerEvaluator(build, sources, code, removeComments) { | 
					
						
							| 
									
										
										
										
											2019-11-14 21:13:44 +01:00
										 |  |  |  |   const testWorker = createWorker(testEvaluator, { terminateWorker: true }); | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  |   return { | 
					
						
							|  |  |  |  |     evaluate: async (testString, timeout) => | 
					
						
							| 
									
										
										
										
											2021-04-30 21:30:06 +02:00
										 |  |  |  |       await testWorker.execute( | 
					
						
							|  |  |  |  |         { testString, build, code, sources, removeComments }, | 
					
						
							|  |  |  |  |         timeout | 
					
						
							|  |  |  |  |       ).done | 
					
						
							| 
									
										
										
										
											2019-01-17 02:50:12 +03:00
										 |  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-10-25 03:34:47 +03:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-10-04 12:16:30 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:27:15 +02:00
										 |  |  |  | async function initializeTestRunner(build, sources, code, loadEnzyme) { | 
					
						
							|  |  |  |  |   await page.reload(); | 
					
						
							|  |  |  |  |   await page.setContent(build); | 
					
						
							|  |  |  |  |   await page.evaluate( | 
					
						
							|  |  |  |  |     async (code, sources, loadEnzyme) => { | 
					
						
							|  |  |  |  |       const getUserInput = fileName => sources[fileName]; | 
					
						
							|  |  |  |  |       await document.__initTestFrame({ code, getUserInput, loadEnzyme }); | 
					
						
							|  |  |  |  |     }, | 
					
						
							|  |  |  |  |     code, | 
					
						
							|  |  |  |  |     sources, | 
					
						
							|  |  |  |  |     loadEnzyme | 
					
						
							|  |  |  |  |   ); | 
					
						
							|  |  |  |  | } |