mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor: modularize curriculum tooling (#63623)
This commit is contained in:
committed by
GitHub
parent
74dd292cc1
commit
f8dbb50b7e
@@ -19,7 +19,7 @@ const {
|
|||||||
const {
|
const {
|
||||||
transformSuperBlock
|
transformSuperBlock
|
||||||
} = require('../../curriculum/dist/build-superblock.js');
|
} = require('../../curriculum/dist/build-superblock.js');
|
||||||
const { getSuperOrder } = require('../../curriculum/dist/utils.js');
|
const { getSuperOrder } = require('../../curriculum/dist/config.js');
|
||||||
|
|
||||||
const curriculumLocale = process.env.CURRICULUM_LOCALE || 'english';
|
const curriculumLocale = process.env.CURRICULUM_LOCALE || 'english';
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,8 @@ import {
|
|||||||
} from './build-superblock.js';
|
} from './build-superblock.js';
|
||||||
|
|
||||||
import { buildCertification } from './build-certification.js';
|
import { buildCertification } from './build-certification.js';
|
||||||
import {
|
import { getSuperOrder } from './super-order.js';
|
||||||
applyFilters,
|
import { applyFilters, closestFilters, type Filter } from './filter.js';
|
||||||
closestFilters,
|
|
||||||
Filter,
|
|
||||||
getSuperOrder
|
|
||||||
} from './utils.js';
|
|
||||||
import {
|
import {
|
||||||
getContentDir,
|
getContentDir,
|
||||||
getLanguageConfig,
|
getLanguageConfig,
|
||||||
@@ -30,6 +26,7 @@ import {
|
|||||||
getBlockStructureDir,
|
getBlockStructureDir,
|
||||||
type BlockStructure
|
type BlockStructure
|
||||||
} from './file-handler.js';
|
} from './file-handler.js';
|
||||||
|
import { SHOW_UPCOMING_CHANGES } from './config.js';
|
||||||
const log = debug('fcc:build-curriculum');
|
const log = debug('fcc:build-curriculum');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,7 +222,7 @@ export const superBlockToFilename = Object.entries(superBlockNames).reduce(
|
|||||||
*/
|
*/
|
||||||
export function addSuperblockStructure(
|
export function addSuperblockStructure(
|
||||||
superBlockFilenames: string[],
|
superBlockFilenames: string[],
|
||||||
showComingSoon = process.env.SHOW_UPCOMING_CHANGES === 'true'
|
showComingSoon = SHOW_UPCOMING_CHANGES
|
||||||
) {
|
) {
|
||||||
log(`Building structure for ${superBlockFilenames.length} superblocks`);
|
log(`Building structure for ${superBlockFilenames.length} superblocks`);
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import {
|
|||||||
import { SuperBlocks } from '../../shared-dist/config/curriculum';
|
import { SuperBlocks } from '../../shared-dist/config/curriculum';
|
||||||
import type { Chapter } from '../../shared-dist/config/chapters';
|
import type { Chapter } from '../../shared-dist/config/chapters';
|
||||||
import { Certification } from '../../shared-dist/config/certification-settings';
|
import { Certification } from '../../shared-dist/config/certification-settings';
|
||||||
import { getSuperOrder } from './utils.js';
|
import { getSuperOrder } from './super-order.js';
|
||||||
import type {
|
import type {
|
||||||
BlockStructure,
|
BlockStructure,
|
||||||
Challenge,
|
Challenge,
|
||||||
ChallengeFile
|
ChallengeFile
|
||||||
} from './file-handler.js';
|
} from './file-handler.js';
|
||||||
|
import { SHOW_UPCOMING_CHANGES } from './config';
|
||||||
|
|
||||||
const log = debug('fcc:build-superblock');
|
const log = debug('fcc:build-superblock');
|
||||||
|
|
||||||
@@ -372,10 +373,7 @@ export class BlockCreator {
|
|||||||
throw Error(`Block directory not found: ${blockContentDir}`);
|
throw Error(`Block directory not found: ${blockContentDir}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (block.isUpcomingChange && !SHOW_UPCOMING_CHANGES) {
|
||||||
block.isUpcomingChange &&
|
|
||||||
process.env.SHOW_UPCOMING_CHANGES !== 'true'
|
|
||||||
) {
|
|
||||||
log(`Ignoring upcoming block ${blockName}`);
|
log(`Ignoring upcoming block ${blockName}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
|
||||||
|
import { availableLangs } from '../../shared-dist/config/i18n.js';
|
||||||
|
|
||||||
|
config({ path: resolve(__dirname, '../../.env') });
|
||||||
|
|
||||||
|
const curriculumLangs = availableLangs.curriculum;
|
||||||
|
|
||||||
|
// checks that the CURRICULUM_LOCALE exists and is an available language
|
||||||
|
export function testedLang() {
|
||||||
|
if (process.env.CURRICULUM_LOCALE) {
|
||||||
|
if (curriculumLangs.includes(process.env.CURRICULUM_LOCALE)) {
|
||||||
|
return process.env.CURRICULUM_LOCALE;
|
||||||
|
} else {
|
||||||
|
throw Error(`${process.env.CURRICULUM_LOCALE} is not a supported language.
|
||||||
|
Before the site can be built, this language needs to be manually approved`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Error('LOCALE must be set for testing');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SHOW_UPCOMING_CHANGES =
|
||||||
|
process.env.SHOW_UPCOMING_CHANGES === 'true';
|
||||||
|
|
||||||
|
export const FCC_CHALLENGE_ID = process.env.FCC_CHALLENGE_ID
|
||||||
|
? process.env.FCC_CHALLENGE_ID.trim()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
export const curriculumFilter = {
|
||||||
|
block: process.env.FCC_BLOCK ? process.env.FCC_BLOCK.trim() : undefined,
|
||||||
|
challengeId: FCC_CHALLENGE_ID,
|
||||||
|
superBlock: process.env.FCC_SUPERBLOCK
|
||||||
|
? process.env.FCC_SUPERBLOCK.trim()
|
||||||
|
: undefined
|
||||||
|
};
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
closestFilters,
|
||||||
|
closestMatch,
|
||||||
|
filterByBlock,
|
||||||
|
filterByChallengeId,
|
||||||
|
filterBySuperblock
|
||||||
|
} from './filter';
|
||||||
|
|
||||||
|
describe('filterByChallengeId', () => {
|
||||||
|
it('returns the same superblocks if no challengeId is provided', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'superblock-2',
|
||||||
|
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
expect(filterByChallengeId(superblocks)).toEqual(superblocks);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores blocks without the specified challengeId', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [
|
||||||
|
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
||||||
|
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
||||||
|
expect(filtered).toEqual([
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns only the specified challenge and its solution challenge', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
dashedName: 'block-1',
|
||||||
|
challengeOrder: [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||||
|
},
|
||||||
|
{ dashedName: 'block-2', challengeOrder: [{ id: '4' }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const filtered = filterByChallengeId(superblocks, { challengeId: '1' });
|
||||||
|
expect(filtered).toEqual([
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
dashedName: 'block-1',
|
||||||
|
challengeOrder: [{ id: '1' }, { id: '2' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns only superblocks containing the specified challenge', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [
|
||||||
|
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
||||||
|
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'superblock-2',
|
||||||
|
blocks: [{ dashedName: 'block-3', challengeOrder: [{ id: '3' }] }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
||||||
|
expect(filtered).toEqual([
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filterByBlock', () => {
|
||||||
|
it('returns the same superblocks if no block is provided', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
expect(filterByBlock(superblocks)).toEqual(superblocks);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns only the specified block', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const filtered = filterByBlock(superblocks, { block: 'block-1' });
|
||||||
|
expect(filtered).toEqual([
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1' }]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an empty array if no blocks match the specified block', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const filtered = filterByBlock(superblocks, {
|
||||||
|
block: 'nonexistent-block'
|
||||||
|
});
|
||||||
|
expect(filtered).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('filterBySuperblock', () => {
|
||||||
|
it('returns the same superblocks if no superBlock is provided', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
expect(filterBySuperblock(superblocks)).toEqual(superblocks);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns only the specified superblock', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'superblock-2',
|
||||||
|
blocks: [{ dashedName: 'block-3' }]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const filtered = filterBySuperblock(superblocks, {
|
||||||
|
superBlock: 'superblock-1'
|
||||||
|
});
|
||||||
|
expect(filtered).toEqual([
|
||||||
|
{
|
||||||
|
name: 'superblock-1',
|
||||||
|
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('closestMatch', () => {
|
||||||
|
it('returns the closest matching element', () => {
|
||||||
|
const items = [
|
||||||
|
'responsive-web-design',
|
||||||
|
'javascript-algorithms-and-data-structures',
|
||||||
|
'front-end-development-libraries',
|
||||||
|
'data-visualization'
|
||||||
|
];
|
||||||
|
const input = 'responsiv web design';
|
||||||
|
const closest = 'responsive-web-design';
|
||||||
|
expect(closestMatch(input, items)).toBe(closest);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores case when finding the closest match', () => {
|
||||||
|
const items = [
|
||||||
|
'responsive-web-design',
|
||||||
|
'ReSPonSivE-WeB-DeSiGne',
|
||||||
|
'javascript-algorithms-and-data-structures',
|
||||||
|
'front-end-development-libraries',
|
||||||
|
'data-visualization'
|
||||||
|
];
|
||||||
|
const input = 'ReSPonSiv WeB DeSiGn';
|
||||||
|
const closest = 'responsive-web-design';
|
||||||
|
expect(closestMatch(input, items)).toBe(closest);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('closestFilters', () => {
|
||||||
|
it('returns the closest matching superblock filter', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'responsive-web-design',
|
||||||
|
blocks: [
|
||||||
|
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||||
|
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'javascript-algorithms-and-data-structures',
|
||||||
|
blocks: [
|
||||||
|
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||||
|
{ dashedName: 'es6', challengeOrder: [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
closestFilters(superblocks, { superBlock: 'responsiv web design' })
|
||||||
|
).toEqual({ superBlock: 'responsive-web-design' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the closest matching block filter', () => {
|
||||||
|
const superblocks = [
|
||||||
|
{
|
||||||
|
name: 'responsive-web-design',
|
||||||
|
blocks: [
|
||||||
|
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
||||||
|
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'javascript-algorithms-and-data-structures',
|
||||||
|
blocks: [
|
||||||
|
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
||||||
|
{ dashedName: 'es6', challengeOrder: [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(closestFilters(superblocks, { block: 'basic-javascr' })).toEqual({
|
||||||
|
block: 'basic-javascript'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,51 +1,4 @@
|
|||||||
import { resolve } from 'path';
|
|
||||||
|
|
||||||
import comparison from 'string-similarity';
|
import comparison from 'string-similarity';
|
||||||
import { config } from 'dotenv';
|
|
||||||
|
|
||||||
import { generateSuperBlockList } from '../../shared-dist/config/curriculum.js';
|
|
||||||
|
|
||||||
config({ path: resolve(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
import { availableLangs } from '../../shared-dist/config/i18n.js';
|
|
||||||
|
|
||||||
const curriculumLangs = availableLangs.curriculum;
|
|
||||||
|
|
||||||
// checks that the CURRICULUM_LOCALE exists and is an available language
|
|
||||||
export function testedLang() {
|
|
||||||
if (process.env.CURRICULUM_LOCALE) {
|
|
||||||
if (curriculumLangs.includes(process.env.CURRICULUM_LOCALE)) {
|
|
||||||
return process.env.CURRICULUM_LOCALE;
|
|
||||||
} else {
|
|
||||||
throw Error(`${process.env.CURRICULUM_LOCALE} is not a supported language.
|
|
||||||
Before the site can be built, this language needs to be manually approved`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Error('LOCALE must be set for testing');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSuperOrder(superBlocks: string[]) {
|
|
||||||
const superOrder: { [sb: string]: number } = {};
|
|
||||||
|
|
||||||
superBlocks.forEach((superBlock, i) => {
|
|
||||||
superOrder[superBlock] = i;
|
|
||||||
});
|
|
||||||
|
|
||||||
return superOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSuperOrder(
|
|
||||||
superblock: string,
|
|
||||||
showUpcomingChanges = process.env.SHOW_UPCOMING_CHANGES === 'true'
|
|
||||||
) {
|
|
||||||
const flatSuperBlockMap = generateSuperBlockList({
|
|
||||||
showUpcomingChanges
|
|
||||||
});
|
|
||||||
|
|
||||||
const superOrder = createSuperOrder(flatSuperBlockMap);
|
|
||||||
return superOrder[superblock];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the superblocks array to include, at most, a single superblock with the specified block.
|
* Filters the superblocks array to include, at most, a single superblock with the specified block.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
const lint = require('../../tools/scripts/lint');
|
const lint = require('../../tools/scripts/lint');
|
||||||
const { testedLang } = require('./utils');
|
const { testedLang } = require('./config');
|
||||||
|
|
||||||
glob(`challenges/${testedLang()}/**/*.md`, (err, files) => {
|
glob(`challenges/${testedLang()}/**/*.md`, (err, files) => {
|
||||||
if (!files.length) throw Error('No files found');
|
if (!files.length) throw Error('No files found');
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
import { SuperBlocks } from '../../shared-dist/config/curriculum';
|
||||||
|
import { createSuperOrder, getSuperOrder } from './super-order.js';
|
||||||
|
|
||||||
|
const mockSuperBlocks = [
|
||||||
|
SuperBlocks.RespWebDesignNew,
|
||||||
|
SuperBlocks.JsAlgoDataStructNew,
|
||||||
|
SuperBlocks.FrontEndDevLibs,
|
||||||
|
SuperBlocks.DataVis,
|
||||||
|
SuperBlocks.RelationalDb,
|
||||||
|
SuperBlocks.BackEndDevApis,
|
||||||
|
SuperBlocks.QualityAssurance,
|
||||||
|
SuperBlocks.SciCompPy,
|
||||||
|
SuperBlocks.DataAnalysisPy,
|
||||||
|
SuperBlocks.InfoSec,
|
||||||
|
SuperBlocks.MachineLearningPy,
|
||||||
|
SuperBlocks.CollegeAlgebraPy,
|
||||||
|
SuperBlocks.FoundationalCSharp,
|
||||||
|
SuperBlocks.CodingInterviewPrep,
|
||||||
|
SuperBlocks.ProjectEuler,
|
||||||
|
SuperBlocks.RespWebDesign,
|
||||||
|
SuperBlocks.JsAlgoDataStruct,
|
||||||
|
SuperBlocks.TheOdinProject,
|
||||||
|
SuperBlocks.FullStackDeveloper
|
||||||
|
];
|
||||||
|
|
||||||
|
const fullSuperOrder = {
|
||||||
|
[SuperBlocks.RespWebDesignNew]: 0,
|
||||||
|
[SuperBlocks.JsAlgoDataStructNew]: 1,
|
||||||
|
[SuperBlocks.FrontEndDevLibs]: 2,
|
||||||
|
[SuperBlocks.DataVis]: 3,
|
||||||
|
[SuperBlocks.RelationalDb]: 4,
|
||||||
|
[SuperBlocks.BackEndDevApis]: 5,
|
||||||
|
[SuperBlocks.QualityAssurance]: 6,
|
||||||
|
[SuperBlocks.SciCompPy]: 7,
|
||||||
|
[SuperBlocks.DataAnalysisPy]: 8,
|
||||||
|
[SuperBlocks.InfoSec]: 9,
|
||||||
|
[SuperBlocks.MachineLearningPy]: 10,
|
||||||
|
[SuperBlocks.CollegeAlgebraPy]: 11,
|
||||||
|
[SuperBlocks.FoundationalCSharp]: 12,
|
||||||
|
[SuperBlocks.CodingInterviewPrep]: 13,
|
||||||
|
[SuperBlocks.ProjectEuler]: 14,
|
||||||
|
[SuperBlocks.RespWebDesign]: 15,
|
||||||
|
[SuperBlocks.JsAlgoDataStruct]: 16,
|
||||||
|
[SuperBlocks.TheOdinProject]: 17,
|
||||||
|
[SuperBlocks.FullStackDeveloper]: 18
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('createSuperOrder', () => {
|
||||||
|
const superOrder = createSuperOrder(mockSuperBlocks);
|
||||||
|
|
||||||
|
it('should create the correct object given an array of SuperBlocks', () => {
|
||||||
|
expect(superOrder).toStrictEqual(fullSuperOrder);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSuperOrder', () => {
|
||||||
|
it('returns a number for valid curriculum', () => {
|
||||||
|
expect(typeof getSuperOrder('responsive-web-design')).toBe('number');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined for unknown curriculum', () => {
|
||||||
|
expect(getSuperOrder('')).toBeUndefined();
|
||||||
|
expect(getSuperOrder('respansive-wib-desoin')).toBeUndefined();
|
||||||
|
expect(getSuperOrder('certifications')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns numbers for all current curriculum', () => {
|
||||||
|
const superBlocks = Object.values(SuperBlocks);
|
||||||
|
|
||||||
|
const superOrderValues = superBlocks.map(sb => getSuperOrder(sb, true));
|
||||||
|
const definedValues = superOrderValues.filter(v => typeof v === 'number');
|
||||||
|
|
||||||
|
expect(definedValues.length).toBe(superBlocks.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns unique numbers for all current curriculum', () => {
|
||||||
|
const superBlocks = Object.values(SuperBlocks);
|
||||||
|
|
||||||
|
const superOrderValues = superBlocks.map(sb => getSuperOrder(sb, true));
|
||||||
|
const uniqueValues = Array.from(new Set(superOrderValues));
|
||||||
|
|
||||||
|
expect(uniqueValues.length).toBe(superBlocks.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { generateSuperBlockList } from '../../shared-dist/config/curriculum.js';
|
||||||
|
import { SHOW_UPCOMING_CHANGES } from './config.js';
|
||||||
|
|
||||||
|
export function createSuperOrder(superBlocks: string[]) {
|
||||||
|
const superOrder: { [sb: string]: number } = {};
|
||||||
|
|
||||||
|
superBlocks.forEach((superBlock, i) => {
|
||||||
|
superOrder[superBlock] = i;
|
||||||
|
});
|
||||||
|
|
||||||
|
return superOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSuperOrder(
|
||||||
|
superblock: string,
|
||||||
|
showUpcomingChanges = SHOW_UPCOMING_CHANGES
|
||||||
|
) {
|
||||||
|
const flatSuperBlockMap = generateSuperBlockList({
|
||||||
|
showUpcomingChanges
|
||||||
|
});
|
||||||
|
|
||||||
|
const superOrder = createSuperOrder(flatSuperBlockMap);
|
||||||
|
return superOrder[superblock];
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ vi.stubEnv('SHOW_UPCOMING_CHANGES', 'true');
|
|||||||
|
|
||||||
// We need to use dynamic imports here to ensure the environment variable is set
|
// We need to use dynamic imports here to ensure the environment variable is set
|
||||||
// before the module is loaded.
|
// before the module is loaded.
|
||||||
const { testedLang } = await import('../utils.js');
|
const { testedLang } = await import('../config.js');
|
||||||
const { getChallenges } = await import('./test-challenges.js');
|
const { getChallenges } = await import('./test-challenges.js');
|
||||||
|
|
||||||
describe('Daily Coding Challenges', async () => {
|
describe('Daily Coding Challenges', async () => {
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ import { prefixDoctype } from '../../../client/src/templates/Challenges/utils/fr
|
|||||||
|
|
||||||
import { getChallengesForLang } from '../get-challenges.js';
|
import { getChallengesForLang } from '../get-challenges.js';
|
||||||
import { challengeSchemaValidator } from '../../schema/challenge-schema.js';
|
import { challengeSchemaValidator } from '../../schema/challenge-schema.js';
|
||||||
import { testedLang } from '../utils.js';
|
|
||||||
|
|
||||||
import { curriculumSchemaValidator } from '../../schema/curriculum-schema.js';
|
import { curriculumSchemaValidator } from '../../schema/curriculum-schema.js';
|
||||||
import { validateMetaSchema } from '../../schema/meta-schema.js';
|
import { validateMetaSchema } from '../../schema/meta-schema.js';
|
||||||
import { getBlockStructure } from '../file-handler.js';
|
import { getBlockStructure } from '../file-handler.js';
|
||||||
|
import { FCC_CHALLENGE_ID, testedLang } from '../config.js';
|
||||||
import ChallengeTitles from './utils/challenge-titles.js';
|
import ChallengeTitles from './utils/challenge-titles.js';
|
||||||
import MongoIds from './utils/mongo-ids.js';
|
import MongoIds from './utils/mongo-ids.js';
|
||||||
import createPseudoWorker from './utils/pseudo-worker.js';
|
import createPseudoWorker from './utils/pseudo-worker.js';
|
||||||
@@ -135,7 +135,7 @@ function populateTestsForLang({ lang, challenges, meta }) {
|
|||||||
// challenge to test (current challenge) might not have solution.
|
// challenge to test (current challenge) might not have solution.
|
||||||
// Instead seed from next challenge is tested against tests from
|
// Instead seed from next challenge is tested against tests from
|
||||||
// current challenge. Next challenge is skipped from testing.
|
// current challenge. Next challenge is skipped from testing.
|
||||||
if (process.env.FCC_CHALLENGE_ID && id > 0) return;
|
if (FCC_CHALLENGE_ID && id > 0) return;
|
||||||
|
|
||||||
const dashedBlockName = challenge.block;
|
const dashedBlockName = challenge.block;
|
||||||
// TODO: once certifications are not included in the list of challenges,
|
// TODO: once certifications are not included in the list of challenges,
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import path from 'node:path';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { parseCurriculumStructure } from '../../build-curriculum.js';
|
import { parseCurriculumStructure } from '../../build-curriculum.js';
|
||||||
import { Filter } from '../../utils.js';
|
import { type Filter } from '../../filter.js';
|
||||||
|
import { curriculumFilter } from '../../config.js';
|
||||||
|
|
||||||
let __dirnameCompat: string;
|
let __dirnameCompat: string;
|
||||||
|
|
||||||
@@ -16,16 +17,6 @@ if (typeof __dirname !== 'undefined') {
|
|||||||
__dirnameCompat = new Function('return import.meta.dirname')() as string;
|
__dirnameCompat = new Function('return import.meta.dirname')() as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const testFilter: Filter = {
|
|
||||||
block: process.env.FCC_BLOCK ? process.env.FCC_BLOCK.trim() : undefined,
|
|
||||||
challengeId: process.env.FCC_CHALLENGE_ID
|
|
||||||
? process.env.FCC_CHALLENGE_ID.trim()
|
|
||||||
: undefined,
|
|
||||||
superBlock: process.env.FCC_SUPERBLOCK
|
|
||||||
? process.env.FCC_SUPERBLOCK.trim()
|
|
||||||
: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const GENERATED_DIR = path.resolve(__dirnameCompat, '../blocks-generated');
|
const GENERATED_DIR = path.resolve(__dirnameCompat, '../blocks-generated');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -33,7 +24,8 @@ async function main() {
|
|||||||
await fs.promises.rm(GENERATED_DIR, { force: true, recursive: true });
|
await fs.promises.rm(GENERATED_DIR, { force: true, recursive: true });
|
||||||
await fs.promises.mkdir(GENERATED_DIR, { recursive: true });
|
await fs.promises.mkdir(GENERATED_DIR, { recursive: true });
|
||||||
|
|
||||||
const { fullSuperblockList } = await parseCurriculumStructure(testFilter);
|
const { fullSuperblockList } =
|
||||||
|
await parseCurriculumStructure(curriculumFilter);
|
||||||
|
|
||||||
const blocks = _.uniq(
|
const blocks = _.uniq(
|
||||||
fullSuperblockList.flatMap(({ blocks }) => blocks).map(b => b.dashedName)
|
fullSuperblockList.flatMap(({ blocks }) => blocks).map(b => b.dashedName)
|
||||||
@@ -41,17 +33,17 @@ async function main() {
|
|||||||
|
|
||||||
for (const block of blocks) {
|
for (const block of blocks) {
|
||||||
const filePath = path.join(GENERATED_DIR, `${block}.test.js`);
|
const filePath = path.join(GENERATED_DIR, `${block}.test.js`);
|
||||||
const contents = generateSingleBlockFile({ ...testFilter, block });
|
const contents = generateSingleBlockFile({ ...curriculumFilter, block });
|
||||||
await fs.promises.writeFile(filePath, contents, 'utf8');
|
await fs.promises.writeFile(filePath, contents, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Generated ${blocks.length} block test file(s).`);
|
console.log(`Generated ${blocks.length} block test file(s).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSingleBlockFile(testFilter: Filter) {
|
function generateSingleBlockFile(filter: Filter) {
|
||||||
return `import { defineTestsForBlock } from '../test-challenges.js';
|
return `import { defineTestsForBlock } from '../test-challenges.js';
|
||||||
|
|
||||||
await defineTestsForBlock(${JSON.stringify(testFilter)});
|
await defineTestsForBlock(${JSON.stringify(filter)});
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,335 +0,0 @@
|
|||||||
import path from 'path';
|
|
||||||
import { config } from 'dotenv';
|
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
|
|
||||||
import { SuperBlocks } from '../../shared-dist/config/curriculum';
|
|
||||||
import {
|
|
||||||
closestFilters,
|
|
||||||
closestMatch,
|
|
||||||
createSuperOrder,
|
|
||||||
filterByBlock,
|
|
||||||
filterByChallengeId,
|
|
||||||
filterBySuperblock,
|
|
||||||
getSuperOrder
|
|
||||||
} from './utils.js';
|
|
||||||
|
|
||||||
config({ path: path.resolve(__dirname, '../.env') });
|
|
||||||
|
|
||||||
const mockSuperBlocks = [
|
|
||||||
SuperBlocks.RespWebDesignNew,
|
|
||||||
SuperBlocks.JsAlgoDataStructNew,
|
|
||||||
SuperBlocks.FrontEndDevLibs,
|
|
||||||
SuperBlocks.DataVis,
|
|
||||||
SuperBlocks.RelationalDb,
|
|
||||||
SuperBlocks.BackEndDevApis,
|
|
||||||
SuperBlocks.QualityAssurance,
|
|
||||||
SuperBlocks.SciCompPy,
|
|
||||||
SuperBlocks.DataAnalysisPy,
|
|
||||||
SuperBlocks.InfoSec,
|
|
||||||
SuperBlocks.MachineLearningPy,
|
|
||||||
SuperBlocks.CollegeAlgebraPy,
|
|
||||||
SuperBlocks.FoundationalCSharp,
|
|
||||||
SuperBlocks.CodingInterviewPrep,
|
|
||||||
SuperBlocks.ProjectEuler,
|
|
||||||
SuperBlocks.RespWebDesign,
|
|
||||||
SuperBlocks.JsAlgoDataStruct,
|
|
||||||
SuperBlocks.TheOdinProject,
|
|
||||||
SuperBlocks.FullStackDeveloper
|
|
||||||
];
|
|
||||||
|
|
||||||
const fullSuperOrder = {
|
|
||||||
[SuperBlocks.RespWebDesignNew]: 0,
|
|
||||||
[SuperBlocks.JsAlgoDataStructNew]: 1,
|
|
||||||
[SuperBlocks.FrontEndDevLibs]: 2,
|
|
||||||
[SuperBlocks.DataVis]: 3,
|
|
||||||
[SuperBlocks.RelationalDb]: 4,
|
|
||||||
[SuperBlocks.BackEndDevApis]: 5,
|
|
||||||
[SuperBlocks.QualityAssurance]: 6,
|
|
||||||
[SuperBlocks.SciCompPy]: 7,
|
|
||||||
[SuperBlocks.DataAnalysisPy]: 8,
|
|
||||||
[SuperBlocks.InfoSec]: 9,
|
|
||||||
[SuperBlocks.MachineLearningPy]: 10,
|
|
||||||
[SuperBlocks.CollegeAlgebraPy]: 11,
|
|
||||||
[SuperBlocks.FoundationalCSharp]: 12,
|
|
||||||
[SuperBlocks.CodingInterviewPrep]: 13,
|
|
||||||
[SuperBlocks.ProjectEuler]: 14,
|
|
||||||
[SuperBlocks.RespWebDesign]: 15,
|
|
||||||
[SuperBlocks.JsAlgoDataStruct]: 16,
|
|
||||||
[SuperBlocks.TheOdinProject]: 17,
|
|
||||||
[SuperBlocks.FullStackDeveloper]: 18
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('createSuperOrder', () => {
|
|
||||||
const superOrder = createSuperOrder(mockSuperBlocks);
|
|
||||||
|
|
||||||
it('should create the correct object given an array of SuperBlocks', () => {
|
|
||||||
expect(superOrder).toStrictEqual(fullSuperOrder);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getSuperOrder', () => {
|
|
||||||
it('returns a number for valid curriculum', () => {
|
|
||||||
expect(typeof getSuperOrder('responsive-web-design')).toBe('number');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns undefined for unknown curriculum', () => {
|
|
||||||
expect(getSuperOrder('')).toBeUndefined();
|
|
||||||
expect(getSuperOrder('respansive-wib-desoin')).toBeUndefined();
|
|
||||||
expect(getSuperOrder('certifications')).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns numbers for all current curriculum', () => {
|
|
||||||
const superBlocks = Object.values(SuperBlocks);
|
|
||||||
|
|
||||||
const superOrderValues = superBlocks.map(sb => getSuperOrder(sb, true));
|
|
||||||
const definedValues = superOrderValues.filter(v => typeof v === 'number');
|
|
||||||
|
|
||||||
expect(definedValues.length).toBe(superBlocks.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns unique numbers for all current curriculum', () => {
|
|
||||||
const superBlocks = Object.values(SuperBlocks);
|
|
||||||
|
|
||||||
const superOrderValues = superBlocks.map(sb => getSuperOrder(sb, true));
|
|
||||||
const uniqueValues = Array.from(new Set(superOrderValues));
|
|
||||||
|
|
||||||
expect(uniqueValues.length).toBe(superBlocks.length);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('filter utils', () => {
|
|
||||||
describe('filterByChallengeId', () => {
|
|
||||||
it('returns the same superblocks if no challengeId is provided', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'superblock-2',
|
|
||||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
expect(filterByChallengeId(superblocks)).toEqual(superblocks);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ignores blocks without the specified challengeId', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [
|
|
||||||
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
|
||||||
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
|
||||||
expect(filtered).toEqual([
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only the specified challenge and its solution challenge', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [
|
|
||||||
{
|
|
||||||
dashedName: 'block-1',
|
|
||||||
challengeOrder: [{ id: '1' }, { id: '2' }, { id: '3' }]
|
|
||||||
},
|
|
||||||
{ dashedName: 'block-2', challengeOrder: [{ id: '4' }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const filtered = filterByChallengeId(superblocks, { challengeId: '1' });
|
|
||||||
expect(filtered).toEqual([
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [
|
|
||||||
{
|
|
||||||
dashedName: 'block-1',
|
|
||||||
challengeOrder: [{ id: '1' }, { id: '2' }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only superblocks containing the specified challenge', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [
|
|
||||||
{ dashedName: 'block-1', challengeOrder: [{ id: '1' }] },
|
|
||||||
{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'superblock-2',
|
|
||||||
blocks: [{ dashedName: 'block-3', challengeOrder: [{ id: '3' }] }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const filtered = filterByChallengeId(superblocks, { challengeId: '2' });
|
|
||||||
expect(filtered).toEqual([
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-2', challengeOrder: [{ id: '2' }] }]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('filterByBlock', () => {
|
|
||||||
it('returns the same superblocks if no block is provided', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
expect(filterByBlock(superblocks)).toEqual(superblocks);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only the specified block', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const filtered = filterByBlock(superblocks, { block: 'block-1' });
|
|
||||||
expect(filtered).toEqual([
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1' }]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns an empty array if no blocks match the specified block', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const filtered = filterByBlock(superblocks, {
|
|
||||||
block: 'nonexistent-block'
|
|
||||||
});
|
|
||||||
expect(filtered).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('filterBySuperblock', () => {
|
|
||||||
it('returns the same superblocks if no superBlock is provided', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
expect(filterBySuperblock(superblocks)).toEqual(superblocks);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only the specified superblock', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'superblock-2',
|
|
||||||
blocks: [{ dashedName: 'block-3' }]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const filtered = filterBySuperblock(superblocks, {
|
|
||||||
superBlock: 'superblock-1'
|
|
||||||
});
|
|
||||||
expect(filtered).toEqual([
|
|
||||||
{
|
|
||||||
name: 'superblock-1',
|
|
||||||
blocks: [{ dashedName: 'block-1' }, { dashedName: 'block-2' }]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('closestMatch', () => {
|
|
||||||
it('returns the closest matching element', () => {
|
|
||||||
const items = [
|
|
||||||
'responsive-web-design',
|
|
||||||
'javascript-algorithms-and-data-structures',
|
|
||||||
'front-end-development-libraries',
|
|
||||||
'data-visualization'
|
|
||||||
];
|
|
||||||
const input = 'responsiv web design';
|
|
||||||
const closest = 'responsive-web-design';
|
|
||||||
expect(closestMatch(input, items)).toBe(closest);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ignores case when finding the closest match', () => {
|
|
||||||
const items = [
|
|
||||||
'responsive-web-design',
|
|
||||||
'ReSPonSivE-WeB-DeSiGne',
|
|
||||||
'javascript-algorithms-and-data-structures',
|
|
||||||
'front-end-development-libraries',
|
|
||||||
'data-visualization'
|
|
||||||
];
|
|
||||||
const input = 'ReSPonSiv WeB DeSiGn';
|
|
||||||
const closest = 'responsive-web-design';
|
|
||||||
expect(closestMatch(input, items)).toBe(closest);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('closestFilters', () => {
|
|
||||||
it('returns the closest matching superblock filter', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'responsive-web-design',
|
|
||||||
blocks: [
|
|
||||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
|
||||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'javascript-algorithms-and-data-structures',
|
|
||||||
blocks: [
|
|
||||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
|
||||||
{ dashedName: 'es6', challengeOrder: [] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(
|
|
||||||
closestFilters(superblocks, { superBlock: 'responsiv web design' })
|
|
||||||
).toEqual({ superBlock: 'responsive-web-design' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the closest matching block filter', () => {
|
|
||||||
const superblocks = [
|
|
||||||
{
|
|
||||||
name: 'responsive-web-design',
|
|
||||||
blocks: [
|
|
||||||
{ dashedName: 'basic-html-and-html5', challengeOrder: [] },
|
|
||||||
{ dashedName: 'css-flexbox', challengeOrder: [] }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'javascript-algorithms-and-data-structures',
|
|
||||||
blocks: [
|
|
||||||
{ dashedName: 'basic-javascript', challengeOrder: [] },
|
|
||||||
{ dashedName: 'es6', challengeOrder: [] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(closestFilters(superblocks, { block: 'basic-javascr' })).toEqual({
|
|
||||||
block: 'basic-javascript'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user