feat: handle multi-file solutions
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
54630cbfca
commit
301212e194
@ -185,24 +185,30 @@ Trying to parse ${fullPath}`);
|
|||||||
return prepareChallenge(challenge);
|
return prepareChallenge(challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: tests and more descriptive name.
|
||||||
|
function filesToObject(files) {
|
||||||
|
return reduce(
|
||||||
|
files,
|
||||||
|
(map, file) => {
|
||||||
|
map[file.key] = {
|
||||||
|
...file,
|
||||||
|
head: arrToString(file.head),
|
||||||
|
contents: arrToString(file.contents),
|
||||||
|
tail: arrToString(file.tail)
|
||||||
|
};
|
||||||
|
return map;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// gets the challenge ready for sourcing into Gatsby
|
// gets the challenge ready for sourcing into Gatsby
|
||||||
function prepareChallenge(challenge) {
|
function prepareChallenge(challenge) {
|
||||||
challenge.name = nameify(challenge.title);
|
challenge.name = nameify(challenge.title);
|
||||||
if (challenge.files) {
|
if (challenge.files) {
|
||||||
challenge.files = reduce(
|
challenge.files = filesToObject(challenge.files);
|
||||||
challenge.files,
|
|
||||||
(map, file) => {
|
|
||||||
map[file.key] = {
|
|
||||||
...file,
|
|
||||||
head: arrToString(file.head),
|
|
||||||
contents: arrToString(file.contents),
|
|
||||||
tail: arrToString(file.tail)
|
|
||||||
};
|
|
||||||
return map;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
// TODO: This should be something that can be folded into the above reduce
|
// TODO: This should be something that can be folded into the above reduce
|
||||||
|
// EDIT: maybe not, now that we're doing the same for solutionFiles.
|
||||||
challenge.files = Object.keys(challenge.files)
|
challenge.files = Object.keys(challenge.files)
|
||||||
.filter(key => challenge.files[key])
|
.filter(key => challenge.files[key])
|
||||||
.map(key => challenge.files[key])
|
.map(key => challenge.files[key])
|
||||||
@ -217,6 +223,10 @@ function prepareChallenge(challenge) {
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (challenge.solutionFiles) {
|
||||||
|
challenge.solutionFiles = filesToObject(challenge.solutionFiles);
|
||||||
|
}
|
||||||
challenge.block = dasherize(challenge.block);
|
challenge.block = dasherize(challenge.block);
|
||||||
challenge.superBlock = blockNameify(challenge.superBlock);
|
challenge.superBlock = blockNameify(challenge.superBlock);
|
||||||
return challenge;
|
return challenge;
|
||||||
|
@ -73,7 +73,12 @@ function getSchemaForLang(lang) {
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
solutions: Joi.array().items(Joi.string().optional()),
|
solutions: Joi.array().items(Joi.string().optional()),
|
||||||
solutionFiles: Joi.array().items(fileJoi),
|
solutionFiles: Joi.object().keys({
|
||||||
|
indexcss: fileJoi,
|
||||||
|
indexhtml: fileJoi,
|
||||||
|
indexjs: fileJoi,
|
||||||
|
indexjsx: fileJoi
|
||||||
|
}),
|
||||||
superBlock: Joi.string(),
|
superBlock: Joi.string(),
|
||||||
superOrder: Joi.number(),
|
superOrder: Joi.number(),
|
||||||
suborder: Joi.number(),
|
suborder: Joi.number(),
|
||||||
|
@ -23,7 +23,7 @@ const {
|
|||||||
|
|
||||||
const { assert, AssertionError } = require('chai');
|
const { assert, AssertionError } = require('chai');
|
||||||
const Mocha = require('mocha');
|
const Mocha = require('mocha');
|
||||||
const { flatten, isEmpty } = require('lodash');
|
const { flatten, isEmpty, cloneDeep } = require('lodash');
|
||||||
|
|
||||||
const jsdom = require('jsdom');
|
const jsdom = require('jsdom');
|
||||||
|
|
||||||
@ -330,9 +330,6 @@ function populateTestsForLang({ lang, challenges, meta }) {
|
|||||||
? buildJSChallenge
|
? buildJSChallenge
|
||||||
: buildDOMChallenge;
|
: buildDOMChallenge;
|
||||||
|
|
||||||
// TODO: create more sophisticated validation now we allow for more
|
|
||||||
// than one seed/solution file.
|
|
||||||
|
|
||||||
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);
|
||||||
// suppress errors in the console.
|
// suppress errors in the console.
|
||||||
@ -363,22 +360,31 @@ function populateTestsForLang({ lang, challenges, meta }) {
|
|||||||
assert(fails, 'Test suit does not fail on the initial contents');
|
assert(fails, 'Test suit does not fail on the initial contents');
|
||||||
});
|
});
|
||||||
|
|
||||||
let { solutions = [] } = challenge;
|
let { solutions = [], solutionFiles = {} } = challenge;
|
||||||
// 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.
|
|
||||||
if (isEmpty(solutions)) {
|
|
||||||
const nextChallenge = challenges[id + 1];
|
|
||||||
if (nextChallenge) {
|
|
||||||
solutions = [nextChallenge.files[0].contents];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const noSolution = new RegExp('// solution required');
|
const noSolution = new RegExp('// solution required');
|
||||||
solutions = solutions.filter(
|
solutions = solutions.filter(
|
||||||
solution => !!solution && !noSolution.test(solution)
|
solution => !!solution && !noSolution.test(solution)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (solutions.length === 0) {
|
// 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.
|
||||||
|
|
||||||
|
if (isEmpty(solutions)) {
|
||||||
|
if (!isEmpty(solutionFiles)) {
|
||||||
|
solutions = [solutionFiles];
|
||||||
|
// TODO: there needs to be a way of telling that a challenge uses
|
||||||
|
// multiple files when it doesn't have anything in solutionFiles!
|
||||||
|
} else {
|
||||||
|
const nextChallenge = challenges[id + 1];
|
||||||
|
if (nextChallenge) {
|
||||||
|
solutions = [nextChallenge.files[0].contents];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (solutions.length === 0 && isEmpty(solutionFiles)) {
|
||||||
it('Check tests. No solutions');
|
it('Check tests. No solutions');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -404,22 +410,24 @@ function populateTestsForLang({ lang, challenges, meta }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: solutions will need to be multi-file, too, with a fallback when there
|
async function createTestRunner(challenge, solution, buildChallenge) {
|
||||||
// is only one file.
|
const { required = [], template } = challenge;
|
||||||
// we cannot simply use the solution instead of files, because the are not
|
// we should avoid modifying challenge, as it gets reused:
|
||||||
// just the seed(s), they contain the head and tail code. The best approach
|
const files = cloneDeep(challenge.files);
|
||||||
// is probably to separate out the head and tail from the files. Then the
|
|
||||||
// files can be entirely replaced by the solution.
|
// TODO: there must be a better way of handling both single and multi-file
|
||||||
|
// solutions
|
||||||
|
if (typeof solution === 'object' && !isEmpty(solution)) {
|
||||||
|
Object.keys(solution).forEach(key => {
|
||||||
|
files[key].contents = solution[key].contents;
|
||||||
|
});
|
||||||
|
} else if (solution) {
|
||||||
|
// fallback for single solution
|
||||||
|
const sortedFiles = sortFiles(files);
|
||||||
|
|
||||||
async function createTestRunner(
|
|
||||||
{ required = [], template, files },
|
|
||||||
solution,
|
|
||||||
buildChallenge
|
|
||||||
) {
|
|
||||||
// fallback for single solution
|
|
||||||
const sortedFiles = sortFiles(files);
|
|
||||||
if (solution) {
|
|
||||||
files[sortedFiles[0].key].contents = solution;
|
files[sortedFiles[0].key].contents = solution;
|
||||||
|
} else {
|
||||||
|
throw Error('Tried to create test runner without a solution.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { build, sources, loadEnzyme } = await buildChallenge({
|
const { build, sources, loadEnzyme } = await buildChallenge({
|
||||||
|
Reference in New Issue
Block a user