mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix(external curricula): use strings for superblock stages (#60813)
This commit is contained in:
@@ -20,7 +20,8 @@ import {
|
|||||||
type GeneratedBlockBasedCurriculumProps,
|
type GeneratedBlockBasedCurriculumProps,
|
||||||
type GeneratedChapterBasedCurriculumProps,
|
type GeneratedChapterBasedCurriculumProps,
|
||||||
type ChapterBasedCurriculumIntros,
|
type ChapterBasedCurriculumIntros,
|
||||||
orderedSuperBlockInfo
|
orderedSuperBlockInfo,
|
||||||
|
OrderedSuperBlocks
|
||||||
} from './build-external-curricula-data-v2';
|
} from './build-external-curricula-data-v2';
|
||||||
|
|
||||||
const VERSION = 'v2';
|
const VERSION = 'v2';
|
||||||
@@ -55,16 +56,29 @@ describe('external curriculum data build', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('the available-superblocks file should have the correct structure', async () => {
|
test('the available-superblocks file should have the correct structure', async () => {
|
||||||
|
const filteredSuperBlockStages: string[] = Object.keys(SuperBlockStage)
|
||||||
|
.filter(key => isNaN(Number(key))) // Filter out numeric keys to get only the names
|
||||||
|
.filter(name => name !== 'Upcoming' && name !== 'Next') // Filter out 'Upcoming' and 'Next'
|
||||||
|
.map(name => name.toLowerCase());
|
||||||
|
|
||||||
const validateAvailableSuperBlocks = availableSuperBlocksValidator();
|
const validateAvailableSuperBlocks = availableSuperBlocksValidator();
|
||||||
const availableSuperblocks: unknown = JSON.parse(
|
const availableSuperblocks = JSON.parse(
|
||||||
await fs.promises.readFile(
|
await fs.promises.readFile(
|
||||||
`${clientStaticPath}/curriculum-data/${VERSION}/available-superblocks.json`,
|
`${clientStaticPath}/curriculum-data/${VERSION}/available-superblocks.json`,
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
);
|
) as { superblocks: OrderedSuperBlocks };
|
||||||
|
|
||||||
const result = validateAvailableSuperBlocks(availableSuperblocks);
|
const result = validateAvailableSuperBlocks(availableSuperblocks);
|
||||||
|
|
||||||
|
expect(Object.keys(availableSuperblocks.superblocks)).toHaveLength(
|
||||||
|
filteredSuperBlockStages.length
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(Object.keys(availableSuperblocks.superblocks)).toEqual(
|
||||||
|
expect.arrayContaining(filteredSuperBlockStages)
|
||||||
|
);
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw Error(
|
throw Error(
|
||||||
`file: available-superblocks.json
|
`file: available-superblocks.json
|
||||||
@@ -263,23 +277,35 @@ ${result.error.message}`);
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('All public SuperBlocks should be present in the SuperBlock object', () => {
|
test('All public SuperBlocks should be present in the SuperBlock object', () => {
|
||||||
const stages = Object.keys(orderedSuperBlockInfo).map(
|
// Create a mapping from string to shared/config SuperBlockStage enum value
|
||||||
key => Number(key) as SuperBlockStage
|
// so we can look up the enum value by string.
|
||||||
);
|
const superBlockStageStringMap: Record<string, SuperBlockStage> = {
|
||||||
|
core: SuperBlockStage.Core,
|
||||||
|
english: SuperBlockStage.English,
|
||||||
|
professional: SuperBlockStage.Professional,
|
||||||
|
extra: SuperBlockStage.Extra,
|
||||||
|
legacy: SuperBlockStage.Legacy,
|
||||||
|
upcoming: SuperBlockStage.Upcoming,
|
||||||
|
next: SuperBlockStage.Next
|
||||||
|
};
|
||||||
|
|
||||||
expect(stages).not.toContain(SuperBlockStage.Next);
|
const stages = Object.keys(orderedSuperBlockInfo);
|
||||||
expect(stages).not.toContain(SuperBlockStage.Upcoming);
|
|
||||||
|
expect(stages).not.toContain('next');
|
||||||
|
expect(stages).not.toContain('upcoming');
|
||||||
|
|
||||||
for (const stage of stages) {
|
for (const stage of stages) {
|
||||||
const superBlockDashedNames = orderedSuperBlockInfo[stage].map(
|
const superBlockDashedNames = orderedSuperBlockInfo[stage].map(
|
||||||
superBlock => superBlock.dashedName
|
superBlock => superBlock.dashedName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const stageValueInNum = superBlockStageStringMap[stage];
|
||||||
|
|
||||||
expect(superBlockDashedNames).toEqual(
|
expect(superBlockDashedNames).toEqual(
|
||||||
expect.arrayContaining(superBlockStages[stage])
|
expect.arrayContaining(superBlockStages[stageValueInNum])
|
||||||
);
|
);
|
||||||
expect(superBlockDashedNames).toHaveLength(
|
expect(superBlockDashedNames).toHaveLength(
|
||||||
superBlockStages[stage].length
|
superBlockStages[stageValueInNum].length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ import { mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|||||||
import { resolve, dirname } from 'path';
|
import { resolve, dirname } from 'path';
|
||||||
import { submitTypes } from '../../../shared/config/challenge-types';
|
import { submitTypes } from '../../../shared/config/challenge-types';
|
||||||
import { type ChallengeNode } from '../../../client/src/redux/prop-types';
|
import { type ChallengeNode } from '../../../client/src/redux/prop-types';
|
||||||
import {
|
import { SuperBlocks } from '../../../shared/config/curriculum';
|
||||||
SuperBlocks,
|
|
||||||
SuperBlockStage
|
|
||||||
} from '../../../shared/config/curriculum';
|
|
||||||
import fullStackSuperBlockStructure from '../../../curriculum/superblock-structure/full-stack.json';
|
import fullStackSuperBlockStructure from '../../../curriculum/superblock-structure/full-stack.json';
|
||||||
import type { Chapter } from '../../../shared/config/chapters';
|
import type { Chapter } from '../../../shared/config/chapters';
|
||||||
|
|
||||||
@@ -85,6 +82,21 @@ interface GeneratedBlock {
|
|||||||
meta: Record<string, unknown>;
|
meta: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This enum is based on the `SuperBlockStage` enum in shared/config,
|
||||||
|
// but with string value instead of number.
|
||||||
|
enum SuperBlockStage {
|
||||||
|
Core = 'core',
|
||||||
|
English = 'english',
|
||||||
|
Professional = 'professional',
|
||||||
|
Extra = 'extra',
|
||||||
|
Legacy = 'legacy'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrderedSuperBlocks = Record<
|
||||||
|
string,
|
||||||
|
Array<{ dashedName: SuperBlocks; public: boolean; title: string }>
|
||||||
|
>;
|
||||||
|
|
||||||
const ver = 'v2';
|
const ver = 'v2';
|
||||||
|
|
||||||
const staticFolderPath = resolve(__dirname, '../../../client/static');
|
const staticFolderPath = resolve(__dirname, '../../../client/static');
|
||||||
@@ -97,10 +109,7 @@ const intros = JSON.parse(
|
|||||||
readFileSync(blockIntroPath, 'utf-8')
|
readFileSync(blockIntroPath, 'utf-8')
|
||||||
) as CurriculumIntros;
|
) as CurriculumIntros;
|
||||||
|
|
||||||
export const orderedSuperBlockInfo: Record<
|
export const orderedSuperBlockInfo: OrderedSuperBlocks = {
|
||||||
string,
|
|
||||||
Array<{ dashedName: SuperBlocks; public: boolean; title: string }>
|
|
||||||
> = {
|
|
||||||
[SuperBlockStage.Core]: [
|
[SuperBlockStage.Core]: [
|
||||||
{
|
{
|
||||||
dashedName: SuperBlocks.FullStackDeveloper,
|
dashedName: SuperBlocks.FullStackDeveloper,
|
||||||
|
|||||||
Reference in New Issue
Block a user