fix: unify single and multifile testing
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
d7dc1acb4a
commit
0f3f27287d
@ -72,13 +72,15 @@ function getSchemaForLang(lang) {
|
|||||||
crossDomain: Joi.bool()
|
crossDomain: Joi.bool()
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
solutions: Joi.array().items(Joi.string().optional()),
|
solutions: Joi.array().items(
|
||||||
solutionFiles: Joi.object().keys({
|
Joi.object().keys({
|
||||||
indexcss: fileJoi,
|
indexcss: fileJoi,
|
||||||
indexhtml: fileJoi,
|
indexhtml: fileJoi,
|
||||||
indexjs: fileJoi,
|
indexjs: fileJoi,
|
||||||
indexjsx: fileJoi
|
indexjsx: fileJoi,
|
||||||
}),
|
indexpy: fileJoi
|
||||||
|
})
|
||||||
|
),
|
||||||
superBlock: Joi.string(),
|
superBlock: Joi.string(),
|
||||||
superOrder: Joi.number(),
|
superOrder: Joi.number(),
|
||||||
suborder: Joi.number(),
|
suborder: Joi.number(),
|
||||||
|
@ -360,31 +360,39 @@ 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 = [], solutionFiles = {} } = challenge;
|
let { solutions = [] } = challenge;
|
||||||
|
|
||||||
const noSolution = new RegExp('// solution required');
|
// if there's an empty string as solution, this is likely a mistake
|
||||||
solutions = solutions.filter(
|
// TODO: what does this look like now?
|
||||||
solution => !!solution && !noSolution.test(solution)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
if (isEmpty(solutions)) {
|
||||||
// if there are no solutions in the challenge, it's assumed the next
|
// if there are no solutions in the challenge, it's assumed the next
|
||||||
// challenge's seed will be a solution to the current challenge.
|
// challenge's seed will be a solution to the current challenge.
|
||||||
// This is expected to happen in the project based curriculum.
|
// 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];
|
const nextChallenge = challenges[id + 1];
|
||||||
|
// TODO: check this actually works...
|
||||||
if (nextChallenge) {
|
if (nextChallenge) {
|
||||||
solutions = [nextChallenge.files[0].contents];
|
solutions = [nextChallenge.files];
|
||||||
}
|
} else {
|
||||||
|
throw Error('solution omitted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (solutions.length === 0 && isEmpty(solutionFiles)) {
|
// 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))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log('filteredSolutions', filteredSolutions);
|
||||||
|
|
||||||
|
if (isEmpty(filteredSolutions)) {
|
||||||
it('Check tests. No solutions');
|
it('Check tests. No solutions');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -415,20 +423,9 @@ async function createTestRunner(challenge, solution, buildChallenge) {
|
|||||||
// we should avoid modifying challenge, as it gets reused:
|
// we should avoid modifying challenge, as it gets reused:
|
||||||
const files = cloneDeep(challenge.files);
|
const files = cloneDeep(challenge.files);
|
||||||
|
|
||||||
// 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 => {
|
Object.keys(solution).forEach(key => {
|
||||||
files[key].contents = solution[key].contents;
|
files[key].contents = solution[key].contents;
|
||||||
});
|
});
|
||||||
} else if (solution) {
|
|
||||||
// fallback for single solution
|
|
||||||
const sortedFiles = toSortedArray(files);
|
|
||||||
|
|
||||||
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({
|
||||||
files,
|
files,
|
||||||
|
@ -19,22 +19,23 @@ function defaultFile(lang) {
|
|||||||
tail: ''
|
tail: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function createCodeGetter(key, regEx, seeds) {
|
function createCodeGetter(codeKey, regEx, seeds) {
|
||||||
return container => {
|
return container => {
|
||||||
const {
|
const {
|
||||||
properties: { id }
|
properties: { id }
|
||||||
} = container;
|
} = container;
|
||||||
const lang = id.match(regEx)[1];
|
const lang = id.match(regEx)[1];
|
||||||
|
const key = `index${lang}`;
|
||||||
const code = select('code', container).children[0].value;
|
const code = select('code', container).children[0].value;
|
||||||
if (lang in seeds) {
|
if (key in seeds) {
|
||||||
seeds[lang] = {
|
seeds[key] = {
|
||||||
...seeds[lang],
|
...seeds[key],
|
||||||
[key]: code
|
[codeKey]: code
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
seeds[lang] = {
|
seeds[key] = {
|
||||||
...defaultFile(lang),
|
...defaultFile(lang),
|
||||||
[key]: code
|
[codeKey]: code
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -89,13 +90,13 @@ function createPlugin() {
|
|||||||
|
|
||||||
file.data = {
|
file.data = {
|
||||||
...file.data,
|
...file.data,
|
||||||
files: Object.keys(seeds).map(lang => seeds[lang])
|
files: seeds
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: make this readable.
|
// TODO: make this readable.
|
||||||
|
|
||||||
if (file.data.files) {
|
Object.keys(seeds).forEach(key => {
|
||||||
file.data.files.forEach(fileData => {
|
const fileData = seeds[key];
|
||||||
const editRegionMarkers = findRegionMarkers(fileData);
|
const editRegionMarkers = findRegionMarkers(fileData);
|
||||||
if (editRegionMarkers) {
|
if (editRegionMarkers) {
|
||||||
fileData.contents = removeLines(
|
fileData.contents = removeLines(
|
||||||
@ -111,8 +112,6 @@ function createPlugin() {
|
|||||||
fileData.editableRegionBoundaries = [];
|
fileData.editableRegionBoundaries = [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: TESTS!
|
// TODO: TESTS!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,3 +121,4 @@ function createPlugin() {
|
|||||||
|
|
||||||
exports.challengeSeedToData = createPlugin;
|
exports.challengeSeedToData = createPlugin;
|
||||||
exports.createCodeGetter = createCodeGetter;
|
exports.createCodeGetter = createCodeGetter;
|
||||||
|
exports.defaultFile = defaultFile;
|
||||||
|
@ -2,23 +2,27 @@ const visit = require('unist-util-visit');
|
|||||||
const { selectAll } = require('hast-util-select');
|
const { selectAll } = require('hast-util-select');
|
||||||
|
|
||||||
const { sectionFilter } = require('./utils');
|
const { sectionFilter } = require('./utils');
|
||||||
const { createCodeGetter } = require('./challengeSeed-to-data');
|
const { createCodeGetter, defaultFile } = require('./challengeSeed-to-data');
|
||||||
|
const { isEmpty } = require('lodash');
|
||||||
|
|
||||||
const solutionRE = /(.+)-solution$/;
|
const solutionRE = /(.+)-solution$/;
|
||||||
|
|
||||||
|
function indexByKey(obj) {
|
||||||
|
return { [obj.key]: { ...obj } };
|
||||||
|
}
|
||||||
|
|
||||||
function createPlugin() {
|
function createPlugin() {
|
||||||
return function transformer(tree, file) {
|
return function transformer(tree, file) {
|
||||||
function visitor(node) {
|
function visitor(node) {
|
||||||
if (sectionFilter(node, 'solution')) {
|
if (sectionFilter(node, 'solution')) {
|
||||||
// fallback for single-file challenges
|
// fallback for single-file challenges
|
||||||
const solutions = selectAll('code', node).map(
|
const rawSolutions = selectAll('code', node).map(element => ({
|
||||||
element => element.children[0].value
|
lang: element.properties.className[0].split('-')[1],
|
||||||
);
|
contents: element.children[0].value
|
||||||
file.data = {
|
}));
|
||||||
...file.data,
|
|
||||||
solutions
|
|
||||||
};
|
|
||||||
const solutionFiles = {};
|
const solutionFiles = {};
|
||||||
|
|
||||||
const codeDivs = selectAll('div', node);
|
const codeDivs = selectAll('div', node);
|
||||||
const solutionContainers = codeDivs.filter(({ properties: { id } }) =>
|
const solutionContainers = codeDivs.filter(({ properties: { id } }) =>
|
||||||
solutionRE.test(id)
|
solutionRE.test(id)
|
||||||
@ -27,11 +31,20 @@ function createPlugin() {
|
|||||||
createCodeGetter('contents', solutionRE, solutionFiles)
|
createCodeGetter('contents', solutionRE, solutionFiles)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const solutionsAsFiles = rawSolutions
|
||||||
|
.map(({ lang, contents }) => ({
|
||||||
|
...defaultFile(lang),
|
||||||
|
contents
|
||||||
|
}))
|
||||||
|
.map(indexByKey);
|
||||||
|
|
||||||
|
const solutions = isEmpty(solutionFiles)
|
||||||
|
? solutionsAsFiles
|
||||||
|
: [solutionFiles];
|
||||||
|
|
||||||
file.data = {
|
file.data = {
|
||||||
...file.data,
|
...file.data,
|
||||||
solutionFiles: Object.keys(solutionFiles).map(
|
solutions
|
||||||
lang => solutionFiles[lang]
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user