From 0eebe3ee2ede423ddfdd4fe3b8dea6a354dceb6a Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Thu, 25 Jul 2024 17:37:24 +0200 Subject: [PATCH] refactor: simplify curriculum ordering (#55657) --- client/src/components/Map/index.tsx | 129 +++++++--------------------- e2e/map.spec.ts | 14 ++- shared/config/curriculum.test.ts | 41 ++------- shared/config/curriculum.ts | 90 +++++++++---------- 4 files changed, 82 insertions(+), 192 deletions(-) diff --git a/client/src/components/Map/index.tsx b/client/src/components/Map/index.tsx index 0dbe5faec2d..6154b86dc18 100644 --- a/client/src/components/Map/index.tsx +++ b/client/src/components/Map/index.tsx @@ -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 (
-

- {t('landing.core-certs-heading')} -

- - -

- {t('landing.learn-english-heading')} -

-
    - {superBlockOrder[SuperBlockStages.English].map((superBlock, i) => ( - - ))} -
- -

- {t('landing.professional-certs-heading')} -

-
    - {superBlockOrder[SuperBlockStages.Professional].map((superBlock, i) => ( - - ))} -
- -

- {t('landing.interview-prep-heading')} -

-
    - {superBlockOrder[SuperBlockStages.Extra].map((superBlock, i) => ( - - ))} -
- -

- {t('landing.legacy-curriculum-heading')} -

-
    - {superBlockOrder[SuperBlockStages.Legacy].map((superBlock, i) => ( - - ))} -
- {showUpcomingChanges && ( + {getStageOrder({ showNewCurriculum, showUpcomingChanges }).map(stage => ( <> -

- {t('landing.upcoming-heading')} + {t(superBlockHeadings[stage])}

-
    - {superBlockOrder[SuperBlockStages.Upcoming].map((superBlock, i) => ( +
      + {superBlockStages[stage].map((superblock, i) => ( ))}
    + - )} + ))}
); } diff --git a/e2e/map.spec.ts b/e2e/map.spec.ts index 339f5abad77..5d2d00ef4f5 100644 --- a/e2e/map.spec.ts +++ b/e2e/map.spec.ts @@ -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 = { diff --git a/shared/config/curriculum.test.ts b/shared/config/curriculum.test.ts index c059c6fece6..4c88eae26f5 100644 --- a/shared/config/curriculum.test.ts +++ b/shared/config/curriculum.test.ts @@ -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); }); diff --git a/shared/config/curriculum.ts b/shared/config/curriculum.ts index 00785150ca0..d7f12098a0e 100644 --- a/shared/config/curriculum.ts +++ b/shared/config/curriculum.ts @@ -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({