diff --git a/curriculum/getChallenges.js b/curriculum/getChallenges.js index 7c8ce4d675..97d418a1a9 100644 --- a/curriculum/getChallenges.js +++ b/curriculum/getChallenges.js @@ -177,10 +177,15 @@ async function buildBlocks({ basename: blockName }, curriculum, superBlock) { __dirname, `./challenges/_meta/${blockName}/meta.json` ); - let blockMeta; - try { - blockMeta = require(metaPath); + + if (fs.existsSync(metaPath)) { + // try to read the file, if the meta path does not exist it should be a certification. + // As they do not have meta files. + + const blockMeta = JSON.parse(fs.readFileSync(metaPath)); + const { isUpcomingChange } = blockMeta; + if (typeof isUpcomingChange !== 'boolean') { throw Error( `meta file at ${metaPath} is missing 'isUpcomingChange', it must be 'true' or 'false'` @@ -192,10 +197,7 @@ async function buildBlocks({ basename: blockName }, curriculum, superBlock) { const blockInfo = { meta: blockMeta, challenges: [] }; curriculum[superBlock].blocks[blockName] = blockInfo; } - } catch (e) { - if (e.code !== 'MODULE_NOT_FOUND') { - throw e; - } + } else { curriculum['certifications'].blocks[blockName] = { challenges: [] }; } } diff --git a/tools/scripts/build/build-mobile-curriculum.js b/tools/scripts/build/build-mobile-curriculum.js index 970a5ffa7d..bdacb71e5f 100644 --- a/tools/scripts/build/build-mobile-curriculum.js +++ b/tools/scripts/build/build-mobile-curriculum.js @@ -3,6 +3,10 @@ const path = require('path'); exports.buildMobileCurriculum = function buildMobileCurriculum(json) { const mobileStaticPath = path.resolve(__dirname, '../../../client/static'); + const blockIntroPath = path.resolve( + __dirname, + '../../../client/i18n/locales/english/intro.json' + ); fs.mkdirSync(`${mobileStaticPath}/mobile`, { recursive: true }); writeAndParseCurriculumJson(json); @@ -12,7 +16,14 @@ exports.buildMobileCurriculum = function buildMobileCurriculum(json) { key => key !== 'certifications' ); - writeToFile('availableSuperblocks', { superblocks: superBlockKeys }); + writeToFile('availableSuperblocks', { + // removing "/" as it will create an extra sub-path when accessed via an endpoint + + superblocks: [ + superBlockKeys.map(key => key.replace(/\//, '-')), + getSuperBlockNames(superBlockKeys) + ] + }); for (let i = 0; i < superBlockKeys.length; i++) { const superBlock = {}; @@ -25,16 +36,32 @@ exports.buildMobileCurriculum = function buildMobileCurriculum(json) { for (let j = 0; j < blockNames.length; j++) { superBlock[superBlockKeys[i]]['blocks'][blockNames[j]] = {}; + + superBlock[superBlockKeys[i]]['blocks'][blockNames[j]]['desc'] = + getBlockDescription(superBlockKeys[i], blockNames[j]); + superBlock[superBlockKeys[i]]['blocks'][blockNames[j]]['challenges'] = curriculum[superBlockKeys[i]]['blocks'][blockNames[j]]['meta']; } - writeToFile(superBlockKeys[i], superBlock); + writeToFile(superBlockKeys[i].replace(/\//, '-'), superBlock); } } - function writeToFile(filename, json) { - const fullPath = `${mobileStaticPath}/mobile/${filename}.json`; + function getBlockDescription(superBlockKey, blockKey) { + const intros = JSON.parse(fs.readFileSync(blockIntroPath)); + + return intros[superBlockKey]['blocks'][blockKey]['intro']; + } + + function getSuperBlockNames(superBlockKeys) { + const superBlocks = JSON.parse(fs.readFileSync(blockIntroPath)); + + return superBlockKeys.map(key => superBlocks[key].title); + } + + function writeToFile(fileName, json) { + const fullPath = `${mobileStaticPath}/mobile/${fileName}.json`; fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, JSON.stringify(json, null, 2)); } diff --git a/tools/scripts/build/mobile-curriculum.test.js b/tools/scripts/build/mobile-curriculum.test.js new file mode 100644 index 0000000000..bebe5de3ef --- /dev/null +++ b/tools/scripts/build/mobile-curriculum.test.js @@ -0,0 +1,53 @@ +const path = require('path'); +const fs = require('fs'); +const { AssertionError } = require('chai'); +const { getChallengesForLang } = require('../../../curriculum/getChallenges'); +const { buildMobileCurriculum } = require('./build-mobile-curriculum'); +const { mobileSchemaValidator } = require('./mobileSchema'); + +describe('mobile curriculum build', () => { + const mobileStaticPath = path.resolve(__dirname, '../../../client/static'); + const blockIntroPath = path.resolve( + __dirname, + '../../../client/i18n/locales/english/intro.json' + ); + + const validateMobileSuperBlock = mobileSchemaValidator(); + + let curriculum; + + beforeAll(async () => { + curriculum = await getChallengesForLang('english'); + await buildMobileCurriculum(curriculum); + }, 20000); + + test('the mobile curriculum should have a static folder with multiple files', () => { + expect(fs.existsSync(`${mobileStaticPath}/mobile`)).toBe(true); + + expect(fs.readdirSync(`${mobileStaticPath}/mobile`).length).toBeGreaterThan( + 0 + ); + }); + + test('the mobile curriculum should have access to the intro.json file', () => { + expect(fs.existsSync(blockIntroPath)).toBe(true); + }); + + test('the files generated should have the correct schema', () => { + const fileArray = fs.readdirSync(`${mobileStaticPath}/mobile`); + + fileArray + .filter(fileInArray => fileInArray !== 'availableSuperblocks.json') + .forEach(fileInArray => { + const fileContent = fs.readFileSync( + `${mobileStaticPath}/mobile/${fileInArray}` + ); + + const result = validateMobileSuperBlock(JSON.parse(fileContent)); + + if (result.error) { + throw new AssertionError(result.error, `file: ${fileInArray}`); + } + }); + }); +}); diff --git a/tools/scripts/build/mobileSchema.js b/tools/scripts/build/mobileSchema.js new file mode 100644 index 0000000000..d51eae977e --- /dev/null +++ b/tools/scripts/build/mobileSchema.js @@ -0,0 +1,32 @@ +const Joi = require('joi'); + +const blockSchema = Joi.object({}).keys({ + desc: Joi.array().min(1), + challenges: Joi.object({}).keys({ + name: Joi.string(), + isUpcomingChange: Joi.bool(), + usesMultifileEditor: Joi.bool().optional(), + hasEditableBoundaries: Joi.bool().optional(), + isBeta: Joi.bool().optional(), + dashedName: Joi.string(), + order: Joi.number(), + time: Joi.string().allow(''), + template: Joi.string().allow(''), + required: Joi.array(), + superBlock: Joi.string(), + challengeOrder: Joi.array().items(Joi.array().min(1)) + }) +}); + +const subSchema = Joi.object({}).keys({ + blocks: Joi.object({}).pattern(Joi.string(), Joi.object().concat(blockSchema)) +}); + +const schema = Joi.object({}).pattern( + Joi.string(), + Joi.object().concat(subSchema) +); + +exports.mobileSchemaValidator = () => { + return superblock => schema.validate(superblock); +};