mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-02-25 14:01:29 -05:00
refactor: simplify curriculum ordering (#55657)
This commit is contained in:
committed by
GitHub
parent
cb861ca971
commit
0eebe3ee2e
@@ -4,15 +4,19 @@ import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
SuperBlockStages,
|
||||
SuperBlocks,
|
||||
superBlockOrder
|
||||
type SuperBlocks,
|
||||
SuperBlockStage,
|
||||
getStageOrder,
|
||||
superBlockStages
|
||||
} from '../../../../shared/config/curriculum';
|
||||
import { SuperBlockIcon } from '../../assets/icons/superblock-icon';
|
||||
import LinkButton from '../../assets/icons/link-button';
|
||||
import { Spacer, ButtonLink } from '../helpers';
|
||||
import { getSuperBlockTitleForMap } from '../../utils/superblock-map-titles';
|
||||
import { showUpcomingChanges } from '../../../config/env.json';
|
||||
import {
|
||||
showUpcomingChanges,
|
||||
showNewCurriculum
|
||||
} from '../../../config/env.json';
|
||||
|
||||
import './map.css';
|
||||
|
||||
@@ -49,11 +53,15 @@ const linkSpacingStyle = {
|
||||
gap: '15px'
|
||||
};
|
||||
|
||||
const coreCurriculum = [
|
||||
...superBlockOrder[SuperBlockStages.FrontEnd],
|
||||
...superBlockOrder[SuperBlockStages.Backend],
|
||||
...superBlockOrder[SuperBlockStages.Python]
|
||||
];
|
||||
const superBlockHeadings: { [key in SuperBlockStage]: string } = {
|
||||
[SuperBlockStage.Core]: 'landing.core-certs-heading',
|
||||
[SuperBlockStage.English]: 'landing.learn-english-heading',
|
||||
[SuperBlockStage.Professional]: 'landing.professional-certs-heading',
|
||||
[SuperBlockStage.Extra]: 'landing.interview-prep-heading',
|
||||
[SuperBlockStage.Legacy]: 'landing.legacy-curriculum-heading',
|
||||
[SuperBlockStage.New]: '', // TODO: add translation
|
||||
[SuperBlockStage.Upcoming]: 'landing.upcoming-heading'
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isSignedInSelector,
|
||||
@@ -173,107 +181,28 @@ function Map({
|
||||
|
||||
return (
|
||||
<div className='map-ui' data-test-label='curriculum-map'>
|
||||
<h2 className={forLanding ? 'big-heading' : ''}>
|
||||
{t('landing.core-certs-heading')}
|
||||
</h2>
|
||||
<ul>
|
||||
{coreCurriculum.map((superBlock, i) => (
|
||||
<MapLi
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
index={i}
|
||||
claimed={isClaimed(superBlock)}
|
||||
showProgressionLines={true}
|
||||
showNumbers={true}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Spacer size='medium' />
|
||||
<h2 className={forLanding ? 'big-heading' : ''}>
|
||||
{t('landing.learn-english-heading')}
|
||||
</h2>
|
||||
<ul>
|
||||
{superBlockOrder[SuperBlockStages.English].map((superBlock, i) => (
|
||||
<MapLi
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Spacer size='medium' />
|
||||
<h2 className={forLanding ? 'big-heading' : ''}>
|
||||
{t('landing.professional-certs-heading')}
|
||||
</h2>
|
||||
<ul>
|
||||
{superBlockOrder[SuperBlockStages.Professional].map((superBlock, i) => (
|
||||
<MapLi
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Spacer size='medium' />
|
||||
<h2 className={forLanding ? 'big-heading' : ''}>
|
||||
{t('landing.interview-prep-heading')}
|
||||
</h2>
|
||||
<ul>
|
||||
{superBlockOrder[SuperBlockStages.Extra].map((superBlock, i) => (
|
||||
<MapLi
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Spacer size='medium' />
|
||||
<h2 className={forLanding ? 'big-heading' : ''}>
|
||||
{t('landing.legacy-curriculum-heading')}
|
||||
</h2>
|
||||
<ul>
|
||||
{superBlockOrder[SuperBlockStages.Legacy].map((superBlock, i) => (
|
||||
<MapLi
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
landing={forLanding}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
claimed={isClaimed(superBlock)}
|
||||
index={i}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{showUpcomingChanges && (
|
||||
{getStageOrder({ showNewCurriculum, showUpcomingChanges }).map(stage => (
|
||||
<>
|
||||
<Spacer size='medium' />
|
||||
<h2 className={forLanding ? 'big-heading' : ''}>
|
||||
{t('landing.upcoming-heading')}
|
||||
{t(superBlockHeadings[stage])}
|
||||
</h2>
|
||||
<ul>
|
||||
{superBlockOrder[SuperBlockStages.Upcoming].map((superBlock, i) => (
|
||||
<ul key={stage}>
|
||||
{superBlockStages[stage].map((superblock, i) => (
|
||||
<MapLi
|
||||
key={i}
|
||||
superBlock={superBlock}
|
||||
key={superblock}
|
||||
superBlock={superblock}
|
||||
landing={forLanding}
|
||||
completed={allSuperblockChallengesCompleted(superBlock)}
|
||||
index={i}
|
||||
claimed={isClaimed(superBlock)}
|
||||
claimed={isClaimed(superblock)}
|
||||
showProgressionLines={stage === SuperBlockStage.Core}
|
||||
showNumbers={stage === SuperBlockStage.Core}
|
||||
completed={allSuperblockChallengesCompleted(superblock)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<Spacer size='medium' />
|
||||
</>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,18 @@ import { test, expect } from '@playwright/test';
|
||||
import translations from '../client/i18n/locales/english/translations.json';
|
||||
import intro from '../client/i18n/locales/english/intro.json';
|
||||
|
||||
import { SuperBlockStages, superBlockOrder } from '../shared/config/curriculum';
|
||||
import { SuperBlockStage, superBlockStages } from '../shared/config/curriculum';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/learn');
|
||||
});
|
||||
|
||||
const superBlocksWithLinks = [
|
||||
...superBlockOrder[SuperBlockStages.FrontEnd],
|
||||
...superBlockOrder[SuperBlockStages.Backend],
|
||||
...superBlockOrder[SuperBlockStages.Python],
|
||||
...superBlockOrder[SuperBlockStages.English],
|
||||
...superBlockOrder[SuperBlockStages.Professional],
|
||||
...superBlockOrder[SuperBlockStages.Extra],
|
||||
...superBlockOrder[SuperBlockStages.Legacy]
|
||||
...superBlockStages[SuperBlockStage.Core],
|
||||
...superBlockStages[SuperBlockStage.English],
|
||||
...superBlockStages[SuperBlockStage.Professional],
|
||||
...superBlockStages[SuperBlockStage.Extra],
|
||||
...superBlockStages[SuperBlockStage.Legacy]
|
||||
];
|
||||
|
||||
const superBlockTitleOverride: Record<string, string> = {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Languages } from './i18n';
|
||||
import {
|
||||
SuperBlocks,
|
||||
SuperBlockStages,
|
||||
superBlockOrder,
|
||||
SuperBlockStage,
|
||||
superBlockStages,
|
||||
notAuditedSuperBlocks,
|
||||
createSuperBlockMap,
|
||||
createFlatSuperBlockMap,
|
||||
getAuditedSuperBlocks
|
||||
} from './curriculum';
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
describe('superBlockOrder', () => {
|
||||
it('should contain all SuperBlocks', () => {
|
||||
const allSuperBlocks = Object.values(SuperBlocks);
|
||||
const superBlockOrderValues = Object.values(superBlockOrder).flat();
|
||||
const superBlockOrderValues = Object.values(superBlockStages).flat();
|
||||
expect(superBlockOrderValues).toHaveLength(allSuperBlocks.length);
|
||||
expect(superBlockOrderValues).toEqual(
|
||||
expect.arrayContaining(allSuperBlocks)
|
||||
@@ -20,37 +19,13 @@ describe('superBlockOrder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSuperBlockMap', () => {
|
||||
it('should return an object with New and Upcoming when { showNewCurriculum: true, showUpcomingChanges: true }', () => {
|
||||
const result = createSuperBlockMap({
|
||||
showNewCurriculum: true,
|
||||
showUpcomingChanges: true
|
||||
});
|
||||
expect(result[SuperBlockStages.New]).toHaveLength(
|
||||
superBlockOrder[SuperBlockStages.New].length
|
||||
);
|
||||
expect(result[SuperBlockStages.Upcoming]).toHaveLength(
|
||||
superBlockOrder[SuperBlockStages.Upcoming].length
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an object without New and Upcoming when { showNewCurriculum: false, showUpcomingChanges: false }', () => {
|
||||
const result = createSuperBlockMap({
|
||||
showNewCurriculum: false,
|
||||
showUpcomingChanges: false
|
||||
});
|
||||
expect(result[SuperBlockStages.New]).toHaveLength(0);
|
||||
expect(result[SuperBlockStages.Upcoming]).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFlatSuperBlockMap', () => {
|
||||
it('should return an array of SuperBlocks object with New and Upcoming when { showNewCurriculum: true, showUpcomingChanges: true }', () => {
|
||||
const result = createFlatSuperBlockMap({
|
||||
showNewCurriculum: true,
|
||||
showUpcomingChanges: true
|
||||
});
|
||||
expect(result).toHaveLength(Object.values(superBlockOrder).flat().length);
|
||||
expect(result).toHaveLength(Object.values(superBlockStages).flat().length);
|
||||
});
|
||||
|
||||
it('should return an array of SuperBlocks without New and Upcoming when { showNewCurriculum: false, showUpcomingChanges: false }', () => {
|
||||
@@ -58,9 +33,9 @@ describe('createFlatSuperBlockMap', () => {
|
||||
showNewCurriculum: false,
|
||||
showUpcomingChanges: false
|
||||
});
|
||||
const tempSuperBlockMap = { ...superBlockOrder };
|
||||
tempSuperBlockMap[SuperBlockStages.New] = [];
|
||||
tempSuperBlockMap[SuperBlockStages.Upcoming] = [];
|
||||
const tempSuperBlockMap = { ...superBlockStages };
|
||||
tempSuperBlockMap[SuperBlockStage.New] = [];
|
||||
tempSuperBlockMap[SuperBlockStage.Upcoming] = [];
|
||||
expect(result).toHaveLength(Object.values(tempSuperBlockMap).flat().length);
|
||||
});
|
||||
});
|
||||
@@ -68,7 +43,7 @@ describe('createFlatSuperBlockMap', () => {
|
||||
describe('Immutability of superBlockOrder, notAuditedSuperBlocks, and flatSuperBlockMap', () => {
|
||||
it('should not allow modification of superBlockOrder', () => {
|
||||
expect(() => {
|
||||
superBlockOrder[SuperBlockStages.FrontEnd] = [];
|
||||
superBlockStages[SuperBlockStage.Core] = [];
|
||||
}).toThrowError(TypeError);
|
||||
});
|
||||
|
||||
|
||||
@@ -37,10 +37,8 @@ export enum SuperBlocks {
|
||||
* SuperBlockStages.Upcoming = SHOW_UPCOMING_CHANGES === 'true'
|
||||
* 'Upcoming' is for development -> not shown on stag or prod anywhere
|
||||
*/
|
||||
export enum SuperBlockStages {
|
||||
FrontEnd,
|
||||
Backend,
|
||||
Python,
|
||||
export enum SuperBlockStage {
|
||||
Core,
|
||||
English,
|
||||
Professional,
|
||||
Extra,
|
||||
@@ -49,53 +47,67 @@ export enum SuperBlockStages {
|
||||
Upcoming
|
||||
}
|
||||
|
||||
export type SuperBlockOrder = {
|
||||
[key in SuperBlockStages]: SuperBlocks[];
|
||||
const defaultStageOrder = [
|
||||
SuperBlockStage.Core,
|
||||
SuperBlockStage.English,
|
||||
SuperBlockStage.Professional,
|
||||
SuperBlockStage.Extra,
|
||||
SuperBlockStage.Legacy
|
||||
];
|
||||
|
||||
export function getStageOrder({
|
||||
showNewCurriculum,
|
||||
showUpcomingChanges
|
||||
}: Config): SuperBlockStage[] {
|
||||
const stageOrder = [...defaultStageOrder];
|
||||
|
||||
if (showNewCurriculum) stageOrder.push(SuperBlockStage.New);
|
||||
if (showUpcomingChanges) stageOrder.push(SuperBlockStage.Upcoming);
|
||||
return stageOrder;
|
||||
}
|
||||
|
||||
export type StageMap = {
|
||||
[key in SuperBlockStage]: SuperBlocks[];
|
||||
};
|
||||
|
||||
// order of buttons on map, this should include all superblocks
|
||||
// new and upcoming superblocks are removed below
|
||||
export const superBlockOrder: SuperBlockOrder = {
|
||||
[SuperBlockStages.FrontEnd]: [
|
||||
// Groups of superblocks in learn map. This should include all superblocks.
|
||||
export const superBlockStages: StageMap = {
|
||||
[SuperBlockStage.Core]: [
|
||||
SuperBlocks.RespWebDesignNew,
|
||||
SuperBlocks.JsAlgoDataStructNew,
|
||||
SuperBlocks.FrontEndDevLibs,
|
||||
SuperBlocks.DataVis
|
||||
],
|
||||
[SuperBlockStages.Backend]: [
|
||||
SuperBlocks.DataVis,
|
||||
SuperBlocks.RelationalDb,
|
||||
SuperBlocks.BackEndDevApis,
|
||||
SuperBlocks.QualityAssurance
|
||||
],
|
||||
[SuperBlockStages.Python]: [
|
||||
SuperBlocks.QualityAssurance,
|
||||
SuperBlocks.SciCompPy,
|
||||
SuperBlocks.DataAnalysisPy,
|
||||
SuperBlocks.InfoSec,
|
||||
SuperBlocks.MachineLearningPy,
|
||||
SuperBlocks.CollegeAlgebraPy
|
||||
],
|
||||
[SuperBlockStages.English]: [SuperBlocks.A2English],
|
||||
[SuperBlockStages.Professional]: [SuperBlocks.FoundationalCSharp],
|
||||
[SuperBlockStages.Extra]: [
|
||||
[SuperBlockStage.English]: [SuperBlocks.A2English],
|
||||
[SuperBlockStage.Professional]: [SuperBlocks.FoundationalCSharp],
|
||||
[SuperBlockStage.Extra]: [
|
||||
SuperBlocks.TheOdinProject,
|
||||
SuperBlocks.CodingInterviewPrep,
|
||||
SuperBlocks.ProjectEuler,
|
||||
SuperBlocks.RosettaCode
|
||||
],
|
||||
[SuperBlockStages.Legacy]: [
|
||||
[SuperBlockStage.Legacy]: [
|
||||
SuperBlocks.RespWebDesign,
|
||||
SuperBlocks.JsAlgoDataStruct,
|
||||
SuperBlocks.PythonForEverybody
|
||||
],
|
||||
[SuperBlockStages.New]: [],
|
||||
[SuperBlockStages.Upcoming]: [
|
||||
[SuperBlockStage.New]: [],
|
||||
[SuperBlockStage.Upcoming]: [
|
||||
SuperBlocks.B1English,
|
||||
SuperBlocks.FrontEndDevelopment,
|
||||
SuperBlocks.UpcomingPython
|
||||
]
|
||||
};
|
||||
|
||||
Object.freeze(superBlockOrder);
|
||||
Object.freeze(superBlockStages);
|
||||
|
||||
type NotAuditedSuperBlocks = {
|
||||
[key in Languages]: SuperBlocks[];
|
||||
@@ -277,34 +289,10 @@ type LanguagesConfig = Config & {
|
||||
language: string;
|
||||
};
|
||||
|
||||
// removes new and upcoming from superBlockOrder
|
||||
// not used yet, will be used when adding progress indicators to map
|
||||
export function createSuperBlockMap({
|
||||
showNewCurriculum,
|
||||
showUpcomingChanges
|
||||
}: Config): SuperBlockOrder {
|
||||
const superBlockMap = { ...superBlockOrder };
|
||||
if (!showNewCurriculum) {
|
||||
superBlockMap[SuperBlockStages.New] = [];
|
||||
}
|
||||
if (!showUpcomingChanges) {
|
||||
superBlockMap[SuperBlockStages.Upcoming] = [];
|
||||
}
|
||||
return superBlockMap;
|
||||
}
|
||||
|
||||
export function createFlatSuperBlockMap({
|
||||
showNewCurriculum,
|
||||
showUpcomingChanges
|
||||
}: Config): SuperBlocks[] {
|
||||
const superBlockMap = { ...superBlockOrder };
|
||||
if (!showNewCurriculum) {
|
||||
superBlockMap[SuperBlockStages.New] = [];
|
||||
}
|
||||
if (!showUpcomingChanges) {
|
||||
superBlockMap[SuperBlockStages.Upcoming] = [];
|
||||
}
|
||||
return Object.values(superBlockMap).flat();
|
||||
export function createFlatSuperBlockMap(config: Config): SuperBlocks[] {
|
||||
return getStageOrder(config)
|
||||
.map(stage => superBlockStages[stage])
|
||||
.flat();
|
||||
}
|
||||
|
||||
export function getAuditedSuperBlocks({
|
||||
|
||||
Reference in New Issue
Block a user