chore: refactor and simplify testing (#39050)
This commit is contained in:
committed by
GitHub
parent
5934984064
commit
b4926052f4
@ -1,206 +0,0 @@
|
|||||||
// originally based off of https://github.com/gulpjs/vinyl
|
|
||||||
import invariant from 'invariant';
|
|
||||||
import { Observable } from 'rx';
|
|
||||||
import castToObservable from '../../server/utils/cast-to-observable';
|
|
||||||
|
|
||||||
// createFileStream(
|
|
||||||
// files: [...PolyVinyl]
|
|
||||||
// ) => Observable[...Observable[...PolyVinyl]]
|
|
||||||
export function createFileStream(files = []) {
|
|
||||||
return Observable.of(Observable.from(files));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observable::pipe(
|
|
||||||
// project(
|
|
||||||
// file: PolyVinyl
|
|
||||||
// ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl]
|
|
||||||
// ) => Observable[...Observable[...PolyVinyl]]
|
|
||||||
export function pipe(project) {
|
|
||||||
const source = this;
|
|
||||||
return source.map(files =>
|
|
||||||
files.flatMap(file => castToObservable(project(file)))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// interface PolyVinyl {
|
|
||||||
// source: String,
|
|
||||||
// contents: String,
|
|
||||||
// name: String,
|
|
||||||
// ext: String,
|
|
||||||
// path: String,
|
|
||||||
// key: String,
|
|
||||||
// head: String,
|
|
||||||
// tail: String,
|
|
||||||
// history: [...String],
|
|
||||||
// error: Null|Object|Error
|
|
||||||
// }
|
|
||||||
|
|
||||||
// createPoly({
|
|
||||||
// name: String,
|
|
||||||
// ext: String,
|
|
||||||
// contents: String,
|
|
||||||
// history?: [...String],
|
|
||||||
// }) => PolyVinyl, throws
|
|
||||||
export function createPoly({ name, ext, contents, history, ...rest } = {}) {
|
|
||||||
invariant(typeof name === 'string', 'name must be a string but got %s', name);
|
|
||||||
|
|
||||||
invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext);
|
|
||||||
|
|
||||||
invariant(
|
|
||||||
typeof contents === 'string',
|
|
||||||
'contents must be a string but got %s',
|
|
||||||
contents
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...rest,
|
|
||||||
history: Array.isArray(history) ? history : [name + ext],
|
|
||||||
name,
|
|
||||||
ext,
|
|
||||||
path: name + '.' + ext,
|
|
||||||
key: name + ext,
|
|
||||||
contents,
|
|
||||||
error: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// isPoly(poly: Any) => Boolean
|
|
||||||
export function isPoly(poly) {
|
|
||||||
return (
|
|
||||||
poly &&
|
|
||||||
typeof poly.contents === 'string' &&
|
|
||||||
typeof poly.name === 'string' &&
|
|
||||||
typeof poly.ext === 'string' &&
|
|
||||||
Array.isArray(poly.history)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPoly(poly: Any) => Void, throws
|
|
||||||
export function checkPoly(poly) {
|
|
||||||
invariant(
|
|
||||||
isPoly(poly),
|
|
||||||
'function should receive a PolyVinyl, but got %s',
|
|
||||||
poly
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// isEmpty(poly: PolyVinyl) => Boolean, throws
|
|
||||||
export function isEmpty(poly) {
|
|
||||||
checkPoly(poly);
|
|
||||||
return !!poly.contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setContent(contents: String, poly: PolyVinyl) => PolyVinyl
|
|
||||||
// setContent will loose source if set
|
|
||||||
export function setContent(contents, poly) {
|
|
||||||
checkPoly(poly);
|
|
||||||
return {
|
|
||||||
...poly,
|
|
||||||
contents,
|
|
||||||
source: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl
|
|
||||||
export function setExt(ext, poly) {
|
|
||||||
checkPoly(poly);
|
|
||||||
const newPoly = {
|
|
||||||
...poly,
|
|
||||||
ext,
|
|
||||||
path: poly.name + '.' + ext,
|
|
||||||
key: poly.name + ext
|
|
||||||
};
|
|
||||||
newPoly.history = [...poly.history, newPoly.path];
|
|
||||||
return newPoly;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setName(name: String, poly: PolyVinyl) => PolyVinyl
|
|
||||||
export function setName(name, poly) {
|
|
||||||
checkPoly(poly);
|
|
||||||
const newPoly = {
|
|
||||||
...poly,
|
|
||||||
name,
|
|
||||||
path: name + '.' + poly.ext,
|
|
||||||
key: name + poly.ext
|
|
||||||
};
|
|
||||||
newPoly.history = [...poly.history, newPoly.path];
|
|
||||||
return newPoly;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setError(error: Object, poly: PolyVinyl) => PolyVinyl
|
|
||||||
export function setError(error, poly) {
|
|
||||||
invariant(
|
|
||||||
typeof error === 'object',
|
|
||||||
'error must be an object or null, but got %',
|
|
||||||
error
|
|
||||||
);
|
|
||||||
checkPoly(poly);
|
|
||||||
return {
|
|
||||||
...poly,
|
|
||||||
error
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
|
|
||||||
export function clearHeadTail(poly) {
|
|
||||||
checkPoly(poly);
|
|
||||||
return {
|
|
||||||
...poly,
|
|
||||||
head: '',
|
|
||||||
tail: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
|
|
||||||
export function appendToTail(tail, poly) {
|
|
||||||
checkPoly(poly);
|
|
||||||
return {
|
|
||||||
...poly,
|
|
||||||
tail: poly.tail.concat(tail)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
|
|
||||||
export function compileHeadTail(padding = '', poly) {
|
|
||||||
return clearHeadTail(
|
|
||||||
transformContents(
|
|
||||||
() => [poly.head, poly.contents, poly.tail].join(padding),
|
|
||||||
poly
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformContents(
|
|
||||||
// wrap: (contents: String) => String,
|
|
||||||
// poly: PolyVinyl
|
|
||||||
// ) => PolyVinyl
|
|
||||||
// transformContents will keep a copy of the original
|
|
||||||
// code in the `source` property. If the original polyvinyl
|
|
||||||
// already contains a source, this version will continue as
|
|
||||||
// the source property
|
|
||||||
export function transformContents(wrap, poly) {
|
|
||||||
const newPoly = setContent(wrap(poly.contents), poly);
|
|
||||||
// if no source exist, set the original contents as source
|
|
||||||
newPoly.source = poly.source || poly.contents;
|
|
||||||
return newPoly;
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformHeadTailAndContents(
|
|
||||||
// wrap: (source: String) => String,
|
|
||||||
// poly: PolyVinyl
|
|
||||||
// ) => PolyVinyl
|
|
||||||
export function transformHeadTailAndContents(wrap, poly) {
|
|
||||||
return {
|
|
||||||
...transformContents(wrap, poly),
|
|
||||||
head: wrap(poly.head),
|
|
||||||
tail: wrap(poly.tail)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function testContents(predicate, poly) {
|
|
||||||
return !!predicate(poly.contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateFileFromSpec(spec, poly) {
|
|
||||||
return setContent(poly.contents, createPoly(spec));
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import validator from 'express-validator';
|
import validator from 'express-validator';
|
||||||
import { isPoly } from '../../common/utils/polyvinyl';
|
import { isPoly } from '../../../utils/polyvinyl';
|
||||||
|
|
||||||
const isObject = val => !!val && typeof val === 'object';
|
const isObject = val => !!val && typeof val === 'object';
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ const env = require('../config/env');
|
|||||||
const { createFilePath } = require('gatsby-source-filesystem');
|
const { createFilePath } = require('gatsby-source-filesystem');
|
||||||
|
|
||||||
const { dasherize } = require('../utils/slugs');
|
const { dasherize } = require('../utils/slugs');
|
||||||
const { blockNameify } = require('./utils/blockNameify');
|
const { blockNameify } = require('../utils/block-nameify');
|
||||||
const {
|
const {
|
||||||
createChallengePages,
|
createChallengePages,
|
||||||
createBlockIntroPages,
|
createBlockIntroPages,
|
||||||
|
@ -6,7 +6,7 @@ import { connect } from 'react-redux';
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
import { Modal, Button, Col, Row } from '@freecodecamp/react-bootstrap';
|
||||||
import { Spacer } from '../helpers';
|
import { Spacer } from '../helpers';
|
||||||
import { blockNameify } from '../../../utils/blockNameify';
|
import { blockNameify } from '../../../../utils/block-nameify';
|
||||||
import Heart from '../../assets/icons/Heart';
|
import Heart from '../../assets/icons/Heart';
|
||||||
import Cup from '../../assets/icons/Cup';
|
import Cup from '../../assets/icons/Cup';
|
||||||
import MinimalDonateForm from './MinimalDonateForm';
|
import MinimalDonateForm from './MinimalDonateForm';
|
||||||
|
@ -8,7 +8,7 @@ import { Link } from 'gatsby';
|
|||||||
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
import { makeExpandedBlockSelector, toggleBlock } from '../redux';
|
||||||
import { completedChallengesSelector, executeGA } from '../../../redux';
|
import { completedChallengesSelector, executeGA } from '../../../redux';
|
||||||
import Caret from '../../../assets/icons/Caret';
|
import Caret from '../../../assets/icons/Caret';
|
||||||
import { blockNameify } from '../../../../utils/blockNameify';
|
import { blockNameify } from '../../../../../utils/block-nameify';
|
||||||
import GreenPass from '../../../assets/icons/GreenPass';
|
import GreenPass from '../../../assets/icons/GreenPass';
|
||||||
import GreenNotCompleted from '../../../assets/icons/GreenNotCompleted';
|
import GreenNotCompleted from '../../../assets/icons/GreenNotCompleted';
|
||||||
import IntroInformation from '../../../assets/icons/IntroInformation';
|
import IntroInformation from '../../../assets/icons/IntroInformation';
|
||||||
|
@ -8,7 +8,11 @@ import {
|
|||||||
template as _template
|
template as _template
|
||||||
} from 'lodash';
|
} from 'lodash';
|
||||||
|
|
||||||
import { compileHeadTail, setExt, transformContents } from '../utils/polyvinyl';
|
import {
|
||||||
|
compileHeadTail,
|
||||||
|
setExt,
|
||||||
|
transformContents
|
||||||
|
} from '../../../../../utils/polyvinyl';
|
||||||
|
|
||||||
const htmlCatch = '\n<!--fcc-->\n';
|
const htmlCatch = '\n<!--fcc-->\n';
|
||||||
const jsCatch = '\n;/*fcc*/\n';
|
const jsCatch = '\n;/*fcc*/\n';
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
|
|
||||||
import protect from '@freecodecamp/loop-protect';
|
import protect from '@freecodecamp/loop-protect';
|
||||||
|
|
||||||
import * as vinyl from '../utils/polyvinyl.js';
|
import * as vinyl from '../../../../../utils/polyvinyl.js';
|
||||||
import createWorker from '../utils/worker-executor';
|
import createWorker from '../utils/worker-executor';
|
||||||
|
|
||||||
// the config files are created during the build, but not before linting
|
// the config files are created during the build, but not before linting
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
|
|
||||||
import { types as appTypes } from '../../../redux';
|
import { types as appTypes } from '../../../redux';
|
||||||
|
|
||||||
import { setContent, isPoly } from '../utils/polyvinyl';
|
import { setContent, isPoly } from '../../../../../utils/polyvinyl';
|
||||||
|
|
||||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { createAction, handleActions } from 'redux-actions';
|
|||||||
|
|
||||||
import { createTypes } from '../../../../utils/stateManagement';
|
import { createTypes } from '../../../../utils/stateManagement';
|
||||||
|
|
||||||
import { createPoly } from '../utils/polyvinyl';
|
import { createPoly } from '../../../../../utils/polyvinyl';
|
||||||
import challengeModalEpic from './challenge-modal-epic';
|
import challengeModalEpic from './challenge-modal-epic';
|
||||||
import completionEpic from './completion-epic';
|
import completionEpic from './completion-epic';
|
||||||
import codeLockEpic from './code-lock-epic';
|
import codeLockEpic from './code-lock-epic';
|
||||||
|
@ -7,7 +7,7 @@ const {
|
|||||||
} = require('../../curriculum/getChallenges');
|
} = require('../../curriculum/getChallenges');
|
||||||
const { dasherize, nameify } = require('../../utils/slugs');
|
const { dasherize, nameify } = require('../../utils/slugs');
|
||||||
const { locale } = require('../config/env.json');
|
const { locale } = require('../config/env.json');
|
||||||
const { blockNameify } = require('./blockNameify');
|
const { blockNameify } = require('../../utils/block-nameify');
|
||||||
|
|
||||||
const arrToString = arr =>
|
const arrToString = arr =>
|
||||||
Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
|
Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
|
||||||
|
@ -6,8 +6,6 @@ const fs = require('fs');
|
|||||||
|
|
||||||
const { dasherize } = require('../utils/slugs');
|
const { dasherize } = require('../utils/slugs');
|
||||||
|
|
||||||
const { challengeSchemaValidator } = require('./schema/challengeSchema');
|
|
||||||
|
|
||||||
const challengesDir = path.resolve(__dirname, './challenges');
|
const challengesDir = path.resolve(__dirname, './challenges');
|
||||||
const metaDir = path.resolve(challengesDir, '_meta');
|
const metaDir = path.resolve(challengesDir, '_meta');
|
||||||
exports.challengesDir = challengesDir;
|
exports.challengesDir = challengesDir;
|
||||||
@ -38,13 +36,13 @@ exports.getChallengesForLang = function getChallengesForLang(lang) {
|
|||||||
readDirP({ root: getChallengesDirForLang(lang) })
|
readDirP({ root: getChallengesDirForLang(lang) })
|
||||||
.on('data', file => {
|
.on('data', file => {
|
||||||
running++;
|
running++;
|
||||||
buildCurriculum(file, curriculum, lang).then(done);
|
buildCurriculum(file, curriculum).then(done);
|
||||||
})
|
})
|
||||||
.on('end', done);
|
.on('end', done);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async function buildCurriculum(file, curriculum, lang) {
|
async function buildCurriculum(file, curriculum) {
|
||||||
const { name, depth, path: filePath, fullPath, stat } = file;
|
const { name, depth, path: filePath, fullPath, stat } = file;
|
||||||
if (depth === 1 && stat.isDirectory()) {
|
if (depth === 1 && stat.isDirectory()) {
|
||||||
// extract the superBlock info
|
// extract the superBlock info
|
||||||
@ -80,12 +78,12 @@ async function buildCurriculum(file, curriculum, lang) {
|
|||||||
}
|
}
|
||||||
const { meta } = challengeBlock;
|
const { meta } = challengeBlock;
|
||||||
|
|
||||||
const challenge = await createChallenge(fullPath, meta, lang);
|
const challenge = await createChallenge(fullPath, meta);
|
||||||
|
|
||||||
challengeBlock.challenges = [...challengeBlock.challenges, challenge];
|
challengeBlock.challenges = [...challengeBlock.challenges, challenge];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createChallenge(fullPath, maybeMeta, lang) {
|
async function createChallenge(fullPath, maybeMeta) {
|
||||||
let meta;
|
let meta;
|
||||||
if (maybeMeta) {
|
if (maybeMeta) {
|
||||||
meta = maybeMeta;
|
meta = maybeMeta;
|
||||||
@ -98,11 +96,6 @@ async function createChallenge(fullPath, maybeMeta, lang) {
|
|||||||
}
|
}
|
||||||
const { name: superBlock } = superBlockInfoFromFullPath(fullPath);
|
const { name: superBlock } = superBlockInfoFromFullPath(fullPath);
|
||||||
const challenge = await parseMarkdown(fullPath);
|
const challenge = await parseMarkdown(fullPath);
|
||||||
const result = challengeSchemaValidator(lang)(challenge);
|
|
||||||
if (result.error) {
|
|
||||||
console.log(result.value);
|
|
||||||
throw new Error(result.error);
|
|
||||||
}
|
|
||||||
const challengeOrder = findIndex(
|
const challengeOrder = findIndex(
|
||||||
meta.challengeOrder,
|
meta.challengeOrder,
|
||||||
([id]) => id === challenge.id
|
([id]) => id === challenge.id
|
||||||
|
@ -53,9 +53,7 @@ const {
|
|||||||
buildJSChallenge
|
buildJSChallenge
|
||||||
} = require('../../client/src/templates/Challenges/utils/build');
|
} = require('../../client/src/templates/Challenges/utils/build');
|
||||||
|
|
||||||
const {
|
const { createPoly } = require('../../utils/polyvinyl');
|
||||||
createPoly
|
|
||||||
} = require('../../client/src/templates/Challenges/utils/polyvinyl');
|
|
||||||
|
|
||||||
const testEvaluator = require('../../client/config/test-evaluator').filename;
|
const testEvaluator = require('../../client/config/test-evaluator').filename;
|
||||||
|
|
||||||
@ -323,24 +321,24 @@ function populateTestsForLang({ lang, challenges }, meta) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let { files = [] } = challenge;
|
let { files = [] } = challenge;
|
||||||
let createTestRunner;
|
|
||||||
if (challengeType === challengeTypes.backend) {
|
if (challengeType === challengeTypes.backend) {
|
||||||
it('Check tests is not implemented.');
|
it('Check tests is not implemented.');
|
||||||
return;
|
return;
|
||||||
} else if (
|
}
|
||||||
challengeType === challengeTypes.js ||
|
|
||||||
challengeType === challengeTypes.bonfire
|
if (files.length > 1) {
|
||||||
) {
|
|
||||||
createTestRunner = createTestRunnerForJSChallenge;
|
|
||||||
} else if (files.length === 1) {
|
|
||||||
createTestRunner = createTestRunnerForDOMChallenge;
|
|
||||||
} else {
|
|
||||||
it('Check tests.', () => {
|
it('Check tests.', () => {
|
||||||
throw new Error('Seed file should be only the one.');
|
throw new Error('Seed file should be only the one.');
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const buildChallenge =
|
||||||
|
challengeType === challengeTypes.js ||
|
||||||
|
challengeType === challengeTypes.bonfire
|
||||||
|
? buildJSChallenge
|
||||||
|
: buildDOMChallenge;
|
||||||
|
|
||||||
files = files.map(createPoly);
|
files = files.map(createPoly);
|
||||||
it('Test suite must fail on the initial contents', async function() {
|
it('Test suite must fail on the initial contents', async function() {
|
||||||
this.timeout(5000 * tests.length + 1000);
|
this.timeout(5000 * tests.length + 1000);
|
||||||
@ -353,7 +351,7 @@ function populateTestsForLang({ lang, challenges }, meta) {
|
|||||||
testRunner = await createTestRunner(
|
testRunner = await createTestRunner(
|
||||||
{ ...challenge, files },
|
{ ...challenge, files },
|
||||||
'',
|
'',
|
||||||
page
|
buildChallenge
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
fails = true;
|
fails = true;
|
||||||
@ -390,7 +388,7 @@ function populateTestsForLang({ lang, challenges }, meta) {
|
|||||||
const testRunner = await createTestRunner(
|
const testRunner = await createTestRunner(
|
||||||
{ ...challenge, files },
|
{ ...challenge, files },
|
||||||
solution,
|
solution,
|
||||||
page
|
buildChallenge
|
||||||
);
|
);
|
||||||
for (const test of tests) {
|
for (const test of tests) {
|
||||||
await testRunner(test);
|
await testRunner(test);
|
||||||
@ -404,72 +402,75 @@ function populateTestsForLang({ lang, challenges }, meta) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTestRunnerForDOMChallenge(
|
async function createTestRunner(
|
||||||
{ required = [], template, files },
|
{ required = [], template, files },
|
||||||
solution,
|
solution,
|
||||||
context
|
buildChallenge
|
||||||
) {
|
) {
|
||||||
if (solution) {
|
if (solution) {
|
||||||
files[0].contents = solution;
|
files[0].contents = solution;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { build, sources, loadEnzyme } = await buildDOMChallenge({
|
const { build, sources, loadEnzyme } = await buildChallenge({
|
||||||
files,
|
files,
|
||||||
required,
|
required,
|
||||||
template
|
template
|
||||||
});
|
});
|
||||||
|
|
||||||
await context.reload();
|
|
||||||
await context.setContent(build);
|
|
||||||
await context.evaluate(
|
|
||||||
async (sources, loadEnzyme) => {
|
|
||||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
const code = sources && 'index' in sources ? sources['index'] : '';
|
||||||
|
|
||||||
|
const evaluator = await (buildChallenge === buildDOMChallenge
|
||||||
|
? getContextEvaluator(build, sources, code, loadEnzyme)
|
||||||
|
: getWorkerEvaluator(build, sources, code));
|
||||||
|
|
||||||
|
return async ({ text, testString }) => {
|
||||||
|
try {
|
||||||
|
const { pass, err } = await evaluator.evaluate(testString, 5000);
|
||||||
|
if (!pass) {
|
||||||
|
throw new AssertionError(err.message);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reThrow(err, text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getContextEvaluator(build, sources, code, loadEnzyme) {
|
||||||
|
await initializeTestRunner(build, sources, code, loadEnzyme);
|
||||||
|
|
||||||
|
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)
|
||||||
|
])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWorkerEvaluator(build, sources, code) {
|
||||||
|
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
|
||||||
|
return {
|
||||||
|
evaluate: async (testString, timeout) =>
|
||||||
|
await testWorker.execute({ testString, build, code, sources }, timeout)
|
||||||
|
.done
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
const getUserInput = fileName => sources[fileName];
|
||||||
await document.__initTestFrame({ code, getUserInput, loadEnzyme });
|
await document.__initTestFrame({ code, getUserInput, loadEnzyme });
|
||||||
},
|
},
|
||||||
|
code,
|
||||||
sources,
|
sources,
|
||||||
loadEnzyme
|
loadEnzyme
|
||||||
);
|
);
|
||||||
|
|
||||||
return async ({ text, testString }) => {
|
|
||||||
try {
|
|
||||||
const { pass, err } = await Promise.race([
|
|
||||||
new Promise((_, reject) => setTimeout(() => reject('timeout'), 5000)),
|
|
||||||
await context.evaluate(async testString => {
|
|
||||||
return await document.__runTest(testString);
|
|
||||||
}, testString)
|
|
||||||
]);
|
|
||||||
if (!pass) {
|
|
||||||
throw new AssertionError(err.message);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
reThrow(err, text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createTestRunnerForJSChallenge({ files }, solution) {
|
|
||||||
if (solution) {
|
|
||||||
files[0].contents = solution;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { build, sources } = await buildJSChallenge({ files });
|
|
||||||
const code = sources && 'index' in sources ? sources['index'] : '';
|
|
||||||
|
|
||||||
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
|
|
||||||
return async ({ text, testString }) => {
|
|
||||||
try {
|
|
||||||
const { pass, err } = await testWorker.execute(
|
|
||||||
{ testString, build, code, sources },
|
|
||||||
5000
|
|
||||||
).done;
|
|
||||||
if (!pass) {
|
|
||||||
throw new AssertionError(err.message);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
reThrow(err, text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reThrow(err, text) {
|
function reThrow(err, text) {
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
// originally based off of https://github.com/gulpjs/vinyl
|
// originally based off of https://github.com/gulpjs/vinyl
|
||||||
import invariant from 'invariant';
|
const invariant = require('invariant');
|
||||||
import { of, from, isObservable } from 'rxjs';
|
const { of, from, isObservable } = require('rxjs');
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
const { map, switchMap } = require('rxjs/operators');
|
||||||
|
|
||||||
export const isPromise = value =>
|
function isPromise(value) {
|
||||||
|
return (
|
||||||
value &&
|
value &&
|
||||||
typeof value.subscribe !== 'function' &&
|
typeof value.subscribe !== 'function' &&
|
||||||
typeof value.then === 'function';
|
typeof value.then === 'function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function castToObservable(maybe) {
|
function castToObservable(maybe) {
|
||||||
if (isObservable(maybe)) {
|
if (isObservable(maybe)) {
|
||||||
return maybe;
|
return maybe;
|
||||||
}
|
}
|
||||||
@ -21,7 +24,7 @@ export function castToObservable(maybe) {
|
|||||||
// createFileStream(
|
// createFileStream(
|
||||||
// files: [...PolyVinyl]
|
// files: [...PolyVinyl]
|
||||||
// ) => Observable[...Observable[...PolyVinyl]]
|
// ) => Observable[...Observable[...PolyVinyl]]
|
||||||
export function createFileStream(files = []) {
|
function createFileStream(files = []) {
|
||||||
return of(from(files));
|
return of(from(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +33,7 @@ export function createFileStream(files = []) {
|
|||||||
// file: PolyVinyl
|
// file: PolyVinyl
|
||||||
// ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl]
|
// ) => PolyVinyl|Observable[PolyVinyl]|Promise[PolyVinyl]
|
||||||
// ) => Observable[...Observable[...PolyVinyl]]
|
// ) => Observable[...Observable[...PolyVinyl]]
|
||||||
export function pipe(project) {
|
function pipe(project) {
|
||||||
const source = this;
|
const source = this;
|
||||||
return source.pipe(
|
return source.pipe(
|
||||||
map(files => {
|
map(files => {
|
||||||
@ -58,7 +61,7 @@ export function pipe(project) {
|
|||||||
// contents: String,
|
// contents: String,
|
||||||
// history?: [...String],
|
// history?: [...String],
|
||||||
// }) => PolyVinyl, throws
|
// }) => PolyVinyl, throws
|
||||||
export function createPoly({ name, ext, contents, history, ...rest } = {}) {
|
function createPoly({ name, ext, contents, history, ...rest } = {}) {
|
||||||
invariant(typeof name === 'string', 'name must be a string but got %s', name);
|
invariant(typeof name === 'string', 'name must be a string but got %s', name);
|
||||||
|
|
||||||
invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext);
|
invariant(typeof ext === 'string', 'ext must be a string, but was %s', ext);
|
||||||
@ -82,7 +85,7 @@ export function createPoly({ name, ext, contents, history, ...rest } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isPoly(poly: Any) => Boolean
|
// isPoly(poly: Any) => Boolean
|
||||||
export function isPoly(poly) {
|
function isPoly(poly) {
|
||||||
return (
|
return (
|
||||||
poly &&
|
poly &&
|
||||||
typeof poly.contents === 'string' &&
|
typeof poly.contents === 'string' &&
|
||||||
@ -93,7 +96,7 @@ export function isPoly(poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkPoly(poly: Any) => Void, throws
|
// checkPoly(poly: Any) => Void, throws
|
||||||
export function checkPoly(poly) {
|
function checkPoly(poly) {
|
||||||
invariant(
|
invariant(
|
||||||
isPoly(poly),
|
isPoly(poly),
|
||||||
'function should receive a PolyVinyl, but got %s',
|
'function should receive a PolyVinyl, but got %s',
|
||||||
@ -102,14 +105,14 @@ export function checkPoly(poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isEmpty(poly: PolyVinyl) => Boolean, throws
|
// isEmpty(poly: PolyVinyl) => Boolean, throws
|
||||||
export function isEmpty(poly) {
|
function isEmpty(poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
return !!poly.contents;
|
return !!poly.contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setContent(contents: String, poly: PolyVinyl) => PolyVinyl
|
// setContent(contents: String, poly: PolyVinyl) => PolyVinyl
|
||||||
// setContent will loose source if set
|
// setContent will loose source if set
|
||||||
export function setContent(contents, poly) {
|
function setContent(contents, poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
return {
|
return {
|
||||||
...poly,
|
...poly,
|
||||||
@ -119,7 +122,7 @@ export function setContent(contents, poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl
|
// setExt(ext: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function setExt(ext, poly) {
|
function setExt(ext, poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
const newPoly = {
|
const newPoly = {
|
||||||
...poly,
|
...poly,
|
||||||
@ -132,7 +135,7 @@ export function setExt(ext, poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setName(name: String, poly: PolyVinyl) => PolyVinyl
|
// setName(name: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function setName(name, poly) {
|
function setName(name, poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
const newPoly = {
|
const newPoly = {
|
||||||
...poly,
|
...poly,
|
||||||
@ -145,7 +148,7 @@ export function setName(name, poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setError(error: Object, poly: PolyVinyl) => PolyVinyl
|
// setError(error: Object, poly: PolyVinyl) => PolyVinyl
|
||||||
export function setError(error, poly) {
|
function setError(error, poly) {
|
||||||
invariant(
|
invariant(
|
||||||
typeof error === 'object',
|
typeof error === 'object',
|
||||||
'error must be an object or null, but got %',
|
'error must be an object or null, but got %',
|
||||||
@ -159,7 +162,7 @@ export function setError(error, poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
|
// clearHeadTail(poly: PolyVinyl) => PolyVinyl
|
||||||
export function clearHeadTail(poly) {
|
function clearHeadTail(poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
return {
|
return {
|
||||||
...poly,
|
...poly,
|
||||||
@ -169,7 +172,7 @@ export function clearHeadTail(poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
|
// appendToTail (tail: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function appendToTail(tail, poly) {
|
function appendToTail(tail, poly) {
|
||||||
checkPoly(poly);
|
checkPoly(poly);
|
||||||
return {
|
return {
|
||||||
...poly,
|
...poly,
|
||||||
@ -178,7 +181,7 @@ export function appendToTail(tail, poly) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
|
// compileHeadTail(padding: String, poly: PolyVinyl) => PolyVinyl
|
||||||
export function compileHeadTail(padding = '', poly) {
|
function compileHeadTail(padding = '', poly) {
|
||||||
return clearHeadTail(
|
return clearHeadTail(
|
||||||
transformContents(
|
transformContents(
|
||||||
() => [poly.head, poly.contents, poly.tail].join(padding),
|
() => [poly.head, poly.contents, poly.tail].join(padding),
|
||||||
@ -195,7 +198,7 @@ export function compileHeadTail(padding = '', poly) {
|
|||||||
// code in the `source` property. If the original polyvinyl
|
// code in the `source` property. If the original polyvinyl
|
||||||
// already contains a source, this version will continue as
|
// already contains a source, this version will continue as
|
||||||
// the source property
|
// the source property
|
||||||
export function transformContents(wrap, poly) {
|
function transformContents(wrap, poly) {
|
||||||
const newPoly = setContent(wrap(poly.contents), poly);
|
const newPoly = setContent(wrap(poly.contents), poly);
|
||||||
// if no source exist, set the original contents as source
|
// if no source exist, set the original contents as source
|
||||||
newPoly.source = poly.source || poly.contents;
|
newPoly.source = poly.source || poly.contents;
|
||||||
@ -206,7 +209,7 @@ export function transformContents(wrap, poly) {
|
|||||||
// wrap: (source: String) => String,
|
// wrap: (source: String) => String,
|
||||||
// poly: PolyVinyl
|
// poly: PolyVinyl
|
||||||
// ) => PolyVinyl
|
// ) => PolyVinyl
|
||||||
export function transformHeadTailAndContents(wrap, poly) {
|
function transformHeadTailAndContents(wrap, poly) {
|
||||||
return {
|
return {
|
||||||
...transformContents(wrap, poly),
|
...transformContents(wrap, poly),
|
||||||
head: wrap(poly.head),
|
head: wrap(poly.head),
|
||||||
@ -214,10 +217,32 @@ export function transformHeadTailAndContents(wrap, poly) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function testContents(predicate, poly) {
|
function testContents(predicate, poly) {
|
||||||
return !!predicate(poly.contents);
|
return !!predicate(poly.contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateFileFromSpec(spec, poly) {
|
function updateFileFromSpec(spec, poly) {
|
||||||
return setContent(poly.contents, createPoly(spec));
|
return setContent(poly.contents, createPoly(spec));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isPromise,
|
||||||
|
castToObservable,
|
||||||
|
createFileStream,
|
||||||
|
pipe,
|
||||||
|
createPoly,
|
||||||
|
isPoly,
|
||||||
|
checkPoly,
|
||||||
|
isEmpty,
|
||||||
|
setContent,
|
||||||
|
setExt,
|
||||||
|
setName,
|
||||||
|
setError,
|
||||||
|
clearHeadTail,
|
||||||
|
appendToTail,
|
||||||
|
compileHeadTail,
|
||||||
|
transformContents,
|
||||||
|
transformHeadTailAndContents,
|
||||||
|
testContents,
|
||||||
|
updateFileFromSpec
|
||||||
|
};
|
Reference in New Issue
Block a user