From 373e808938b270ccfd565e4bd9f1ba2a9bce8c26 Mon Sep 17 00:00:00 2001 From: Tom <20648924+moT01@users.noreply.github.com> Date: Thu, 1 Jun 2023 07:51:57 -0500 Subject: [PATCH] refactor(config): superblocks and map (#50435) --- .eslintignore | 2 +- .gitignore | 4 +- .prettierignore | 4 +- client/i18n/locales.test.ts | 2 +- client/src/assets/icons/index.tsx | 2 +- client/src/components/Map/index.tsx | 115 +-- client/src/components/seo/index.tsx | 2 +- client/src/redux/prop-types.ts | 2 +- client/src/redux/selectors.js | 2 +- client/src/resources/cert-and-project-map.ts | 2 +- .../templates/Challenges/codeally/show.tsx | 2 +- .../Introduction/components/block.tsx | 2 +- .../components/cert-challenge.tsx | 5 +- .../Introduction/components/challenges.tsx | 2 +- .../Introduction/components/legacy-links.tsx | 2 +- .../components/super-block-intro.tsx | 2 +- .../Introduction/super-block-intro.tsx | 2 +- client/src/utils/is-a-cert.ts | 2 +- client/src/utils/superblock-map-titles.ts | 2 +- config/certification-settings.ts | 23 +- config/i18n.ts | 5 +- config/superblock-order.test.ts | 205 ----- config/superblock-order.ts | 707 ------------------ config/superblocks.test.ts | 125 ++++ config/superblocks.ts | 243 ++++++ curriculum/utils.js | 77 +- curriculum/utils.test.ts | 168 ++--- cypress/e2e/default/landing.ts | 19 +- .../e2e/default/learn/challenges/projects.ts | 2 +- cypress/e2e/default/learn/index.ts | 4 +- .../show-cert-from-superblock.ts | 2 +- docs/how-to-enable-new-languages.md | 75 +- docs/language-lead-handbook.md | 2 +- tools/challenge-auditor/index.ts | 3 +- .../create-project.ts | 2 +- .../challenge-helper-scripts/fs-utils.test.ts | 2 +- tools/challenge-helper-scripts/fs-utils.ts | 2 +- .../build-external-curricula-data.test.ts | 2 +- .../build/build-external-curricula-data.ts | 2 +- utils/is-audited.js | 17 +- 40 files changed, 558 insertions(+), 1287 deletions(-) delete mode 100644 config/superblock-order.test.ts delete mode 100644 config/superblock-order.ts create mode 100644 config/superblocks.test.ts create mode 100644 config/superblocks.ts diff --git a/.eslintignore b/.eslintignore index 5ff80c09075..91887d6cc1a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,6 +7,6 @@ config/i18n.js config/misc.js config/certification-settings.js config/donation-settings.js -config/superblock-order.js +config/superblocks.js web/** docs/**/*.md diff --git a/.gitignore b/.gitignore index 043c1a289c8..675f2b9f6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -166,8 +166,8 @@ config/i18n.js config/misc.js config/certification-settings.js config/donation-settings.js -config/superblock-order.js -config/superblock-order.test.js +config/superblocks.js +config/superblocks.test.js ### Generated utils files ### utils/block-nameify.js diff --git a/.prettierignore b/.prettierignore index e99e670db68..0699a7fe9c3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,8 +10,8 @@ config/i18n.js config/misc.js config/certification-settings.js config/donation-settings.js -config/superblock-order.js -config/superblock-order.test.js +config/superblocks.js +config/superblocks.test.js utils/block-nameify.js utils/block-nameify.test.js utils/slugs.js diff --git a/client/i18n/locales.test.ts b/client/i18n/locales.test.ts index 241c7f17200..f8f7239b502 100644 --- a/client/i18n/locales.test.ts +++ b/client/i18n/locales.test.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import { setup } from 'jest-json-schema-extended'; import { availableLangs, LangNames, LangCodes } from '../../config/i18n'; -import { SuperBlocks } from '../../config/certification-settings'; +import { SuperBlocks } from '../../config/superblocks'; import intro from './locales/english/intro.json'; setup(); diff --git a/client/src/assets/icons/index.tsx b/client/src/assets/icons/index.tsx index ff2c4ac8001..33d8d633b25 100644 --- a/client/src/assets/icons/index.tsx +++ b/client/src/assets/icons/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { SuperBlocks } from '../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../config/superblocks'; import APIIcon from './api'; import D3Icon from './d3'; import DatabaseIcon from './database'; diff --git a/client/src/components/Map/index.tsx b/client/src/components/Map/index.tsx index a5ce53cd515..ae7cc112be3 100644 --- a/client/src/components/Map/index.tsx +++ b/client/src/components/Map/index.tsx @@ -1,26 +1,24 @@ import i18next from 'i18next'; import React from 'react'; -import { SuperBlocks } from '../../../../config/certification-settings'; import { - CurriculumMaps, - getAuditedSuperBlocks, - getNotAuditedSuperBlocks, - superBlockOrder -} from '../../../../config/superblock-order'; -import { Languages } from '../../../../config/i18n'; -import envData from '../../../../config/env.json'; + SuperBlocks, + createFlatSuperBlockMap, + getFirstNotAuditedSuperBlock +} from '../../../../config/superblocks'; import { generateIconComponent } from '../../assets/icons'; import LinkButton from '../../assets/icons/link-button'; import { Link, Spacer } from '../helpers'; import { getSuperBlockTitleForMap } from '../../utils/superblock-map-titles'; +import { + curriculumLocale, + showUpcomingChanges, + showNewCurriculum +} from '../../../../config/env.json'; import './map.css'; -const { curriculumLocale, showNewCurriculum, showUpcomingChanges } = envData; - interface MapProps { - currentSuperBlock?: SuperBlocks | null; forLanding?: boolean; } @@ -31,6 +29,16 @@ const linkSpacingStyle = { gap: '15px' }; +const flatSuperBlockMap = createFlatSuperBlockMap({ + showNewCurriculum: showNewCurriculum.toString(), + showUpcomingChanges: showUpcomingChanges.toString() +}); +const firstNotAuditedSuperBlock = getFirstNotAuditedSuperBlock({ + language: curriculumLocale, + showNewCurriculum: showNewCurriculum.toString(), + showUpcomingChanges: showUpcomingChanges.toString() +}); + function MapLi({ superBlock, landing = false @@ -39,62 +47,9 @@ function MapLi({ landing: boolean; }) { return ( -
  • - -
    - {generateIconComponent(superBlock, 'map-icon')} - {getSuperBlockTitleForMap(superBlock)} -
    - {landing && } - -
  • - ); -} - -function renderLandingMap() { - const landingSuperOrder = - superBlockOrder[curriculumLocale as Languages][CurriculumMaps.Landing]; - - return ( - - ); -} - -function renderLearnMap(currentSuperBlock: MapProps['currentSuperBlock']) { - const tempAuditedSuperBlocks = getAuditedSuperBlocks({ - language: curriculumLocale, - showNewCurriculum: showNewCurriculum.toString(), - showUpcomingChanges: showUpcomingChanges.toString() - }); - const tempNotAuditedSuperBlocks = getNotAuditedSuperBlocks({ - language: curriculumLocale, - showNewCurriculum: showNewCurriculum.toString(), - showUpcomingChanges: showUpcomingChanges.toString() - }); - - const auditedSuperBlocks = tempAuditedSuperBlocks.filter( - superBlock => superBlock !== currentSuperBlock - ); - - const notAuditedSuperBlocks = tempNotAuditedSuperBlocks.filter( - superBlock => superBlock !== currentSuperBlock - ); - - return ( - +
  • + +
    + {generateIconComponent(superBlock, 'map-icon')} + {getSuperBlockTitleForMap(superBlock)} +
    + {landing && } + +
  • + ); } -function Map({ - forLanding = false, - currentSuperBlock = null -}: MapProps): React.ReactElement { +function Map({ forLanding = false }: MapProps): React.ReactElement { return ( -
    - {forLanding ? renderLandingMap() : renderLearnMap(currentSuperBlock)} +
    +
    ); } diff --git a/client/src/components/seo/index.tsx b/client/src/components/seo/index.tsx index 363dbaf6a0c..9238c7bba49 100644 --- a/client/src/components/seo/index.tsx +++ b/client/src/components/seo/index.tsx @@ -2,7 +2,7 @@ import { useStaticQuery, graphql } from 'gatsby'; import React from 'react'; import Helmet from 'react-helmet'; import { useTranslation } from 'react-i18next'; -import { SuperBlocks } from '../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../config/superblocks'; interface SEOProps { title?: string; diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 970ab54e7e9..4103cc377af 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -1,5 +1,5 @@ import { HandlerProps } from 'react-reflex'; -import { SuperBlocks } from '../../../config/certification-settings'; +import { SuperBlocks } from '../../../config/superblocks'; import { Themes } from '../components/settings/theme'; import { fullCertMap } from '../resources/cert-and-project-map'; diff --git a/client/src/redux/selectors.js b/client/src/redux/selectors.js index 2c73c4a10a9..e0f05e7a80f 100644 --- a/client/src/redux/selectors.js +++ b/client/src/redux/selectors.js @@ -1,4 +1,4 @@ -import { SuperBlocks } from '../../../config/certification-settings'; +import { SuperBlocks } from '../../../config/superblocks'; import { ns as MainApp } from './action-types'; export const savedChallengesSelector = state => diff --git a/client/src/resources/cert-and-project-map.ts b/client/src/resources/cert-and-project-map.ts index c9d946521db..4587e6030c3 100644 --- a/client/src/resources/cert-and-project-map.ts +++ b/client/src/resources/cert-and-project-map.ts @@ -1,4 +1,4 @@ -import { SuperBlocks } from '../../../config/certification-settings'; +import { SuperBlocks } from '../../../config/superblocks'; import config from '../../../config/env.json'; const { showUpcomingChanges } = config; diff --git a/client/src/templates/Challenges/codeally/show.tsx b/client/src/templates/Challenges/codeally/show.tsx index 292acd75a12..861d13dc1d9 100644 --- a/client/src/templates/Challenges/codeally/show.tsx +++ b/client/src/templates/Challenges/codeally/show.tsx @@ -44,7 +44,7 @@ import { import ProjectToolPanel from '../projects/tool-panel'; import SolutionForm from '../projects/solution-form'; import { FlashMessages } from '../../../components/Flash/redux/flash-messages'; -import { SuperBlocks } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; import { CodeAllyDown } from '../../../components/growth-book/codeally-down'; import './codeally.css'; diff --git a/client/src/templates/Introduction/components/block.tsx b/client/src/templates/Introduction/components/block.tsx index f24eb965bd7..1a64364850b 100644 --- a/client/src/templates/Introduction/components/block.tsx +++ b/client/src/templates/Introduction/components/block.tsx @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import ScrollableAnchor from 'react-scrollable-anchor'; import { bindActionCreators, Dispatch } from 'redux'; import { createSelector } from 'reselect'; -import { SuperBlocks } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; import envData from '../../../../../config/env.json'; import { isAuditedCert } from '../../../../../utils/is-audited'; import Caret from '../../../assets/icons/caret'; diff --git a/client/src/templates/Introduction/components/cert-challenge.tsx b/client/src/templates/Introduction/components/cert-challenge.tsx index 957e2f8c168..ededd7f44cf 100644 --- a/client/src/templates/Introduction/components/cert-challenge.tsx +++ b/client/src/templates/Introduction/components/cert-challenge.tsx @@ -6,9 +6,10 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { certSlugTypeMap, - superBlockCertTypeMap, - SuperBlocks + superBlockCertTypeMap } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; + import { createFlashMessage } from '../../../components/Flash/redux'; import { FlashMessages } from '../../../components/Flash/redux/flash-messages'; import { diff --git a/client/src/templates/Introduction/components/challenges.tsx b/client/src/templates/Introduction/components/challenges.tsx index 0e28ae01529..01217bfe90a 100644 --- a/client/src/templates/Introduction/components/challenges.tsx +++ b/client/src/templates/Introduction/components/challenges.tsx @@ -8,7 +8,7 @@ import type { Dispatch } from 'redux'; import GreenNotCompleted from '../../../assets/icons/green-not-completed'; import GreenPass from '../../../assets/icons/green-pass'; import { executeGA } from '../../../redux/actions'; -import { SuperBlocks } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; import { ChallengeWithCompletedNode } from '../../../redux/prop-types'; import { isNewJsCert, isNewRespCert } from '../../../utils/is-a-cert'; diff --git a/client/src/templates/Introduction/components/legacy-links.tsx b/client/src/templates/Introduction/components/legacy-links.tsx index d068b479c32..ba5dfb2e629 100644 --- a/client/src/templates/Introduction/components/legacy-links.tsx +++ b/client/src/templates/Introduction/components/legacy-links.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Alert } from '@freecodecamp/react-bootstrap'; -import { SuperBlocks } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; import { isOldRespCert, isRelationalDbCert } from '../../../utils/is-a-cert'; import { Link } from '../../../components/helpers'; import { CodeAllyDown } from '../../../components/growth-book/codeally-down'; diff --git a/client/src/templates/Introduction/components/super-block-intro.tsx b/client/src/templates/Introduction/components/super-block-intro.tsx index afa94e92089..9b6a29b1a1a 100644 --- a/client/src/templates/Introduction/components/super-block-intro.tsx +++ b/client/src/templates/Introduction/components/super-block-intro.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { SuperBlocks } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; import { generateIconComponent } from '../../../assets/icons'; import { Spacer } from '../../../components/helpers'; diff --git a/client/src/templates/Introduction/super-block-intro.tsx b/client/src/templates/Introduction/super-block-intro.tsx index 1e0949b0b21..66aead727f7 100644 --- a/client/src/templates/Introduction/super-block-intro.tsx +++ b/client/src/templates/Introduction/super-block-intro.tsx @@ -10,7 +10,7 @@ import { configureAnchors } from 'react-scrollable-anchor'; import { bindActionCreators, Dispatch } from 'redux'; import { createSelector } from 'reselect'; -import { SuperBlocks } from '../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../config/superblocks'; import { getSuperBlockTitleForMap } from '../../utils/superblock-map-titles'; import DonateModal from '../../components/Donation/donation-modal'; import Login from '../../components/Header/components/login'; diff --git a/client/src/utils/is-a-cert.ts b/client/src/utils/is-a-cert.ts index 5c445b9aa17..e5057c98401 100644 --- a/client/src/utils/is-a-cert.ts +++ b/client/src/utils/is-a-cert.ts @@ -1,4 +1,4 @@ -import { SuperBlocks } from '../../../config/certification-settings'; +import { SuperBlocks } from '../../../config/superblocks'; export function isNewRespCert(superBlock: string): boolean { return superBlock === SuperBlocks.RespWebDesignNew; diff --git a/client/src/utils/superblock-map-titles.ts b/client/src/utils/superblock-map-titles.ts index a055a11d3eb..3876e09ee8e 100644 --- a/client/src/utils/superblock-map-titles.ts +++ b/client/src/utils/superblock-map-titles.ts @@ -1,5 +1,5 @@ import i18next from 'i18next'; -import { SuperBlocks } from '../../../config/certification-settings'; +import { SuperBlocks } from '../../../config/superblocks'; // these are keys from i18n translations.json files enum SuperBlockI18nKeys { diff --git a/config/certification-settings.ts b/config/certification-settings.ts index 717ca5fbe25..dcfbde261fc 100644 --- a/config/certification-settings.ts +++ b/config/certification-settings.ts @@ -1,3 +1,5 @@ +import { SuperBlocks } from './superblocks'; + export const certTypes = { frontEnd: 'isFrontEndCert', backEnd: 'isBackEndCert', @@ -18,27 +20,6 @@ export const certTypes = { collegeAlgebraPyV8: 'isCollegeAlgebraPyCertV8' } as const; -export enum SuperBlocks { - RespWebDesignNew = '2022/responsive-web-design', - RespWebDesign = 'responsive-web-design', - JsAlgoDataStruct = 'javascript-algorithms-and-data-structures', - JsAlgoDataStructNew = '2022/javascript-algorithms-and-data-structures', - FrontEndDevLibs = 'front-end-development-libraries', - DataVis = 'data-visualization', - RelationalDb = 'relational-database', - BackEndDevApis = 'back-end-development-and-apis', - QualityAssurance = 'quality-assurance', - SciCompPy = 'scientific-computing-with-python', - DataAnalysisPy = 'data-analysis-with-python', - InfoSec = 'information-security', - MachineLearningPy = 'machine-learning-with-python', - CodingInterviewPrep = 'coding-interview-prep', - TheOdinProject = 'the-odin-project', - ProjectEuler = 'project-euler', - CollegeAlgebraPy = 'college-algebra-with-python', - ExampleCertification = 'example-certification' -} - export const certIds = { legacyFrontEndChallengeId: '561add10cb82ac38a17513be', legacyBackEndChallengeId: '660add10cb82ac38a17513be', diff --git a/config/i18n.ts b/config/i18n.ts index 03a87394e84..e70baa05b09 100644 --- a/config/i18n.ts +++ b/config/i18n.ts @@ -101,12 +101,13 @@ export const LangCodes = { /** * This array contains languages that should NOT appear in the language selector. */ -export const hiddenLangs = ['arabic']; +// German is temporarily disabled until the new RWD is fully translated +export const hiddenLangs = [Languages.Arabic, Languages.German]; /** * This array contains languages that use the RTL layouts. */ -export const rtlLangs = ['arabic']; +export const rtlLangs = [Languages.Arabic]; // locale is sourced from a JSON file, so we use getLangCode to // find the associated enum values diff --git a/config/superblock-order.test.ts b/config/superblock-order.test.ts deleted file mode 100644 index 3a70b970716..00000000000 --- a/config/superblock-order.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -import { Languages } from './i18n'; -import { SuperBlocks } from './certification-settings'; -import { - CurriculumMaps, - defaultSuperBlockOrder, - getAuditedSuperBlocks, - getNotAuditedSuperBlocks, - getLearnSuperBlocks, - numberOfSuperBlocksOnLanding, - superBlockOrder, - SuperBlockStates, - TranslationStates -} from './superblock-order'; - -const superBlocks = Object.values(SuperBlocks); -const translationStates = Object.values(TranslationStates); -const superBlockStates = Object.values(SuperBlockStates); -const superBlockOrderLanguages = Object.keys(superBlockOrder); - -describe("'defaultSuperBlockOrder'", () => { - it("should have a matching item for each value in the 'SuperBlocks' object", () => { - expect(defaultSuperBlockOrder).toEqual(expect.arrayContaining(superBlocks)); - }); - - it('should not have any extra keys', () => { - expect(defaultSuperBlockOrder.length).toEqual(superBlocks.length); - }); -}); - -describe("'superBlockOrder'", () => { - superBlockOrderLanguages.forEach(language => { - describe(`'${language}'`, () => { - describe("'landing'", () => { - const landingSuperBlocks = - superBlockOrder[language as Languages][CurriculumMaps.Landing]; - - it(`should have ${numberOfSuperBlocksOnLanding} items (superBlocks)`, () => { - expect(landingSuperBlocks.length).toEqual( - numberOfSuperBlocksOnLanding - ); - }); - - it('should not have a superBlock out of order', () => { - landingSuperBlocks.forEach((superBlock, index) => { - const defaultIndex = defaultSuperBlockOrder.indexOf(superBlock); - const defaultSbsAfterCurrentSb = defaultSuperBlockOrder.slice( - defaultIndex + 1 - ); - - for (let j = index + 1; j < landingSuperBlocks.length; j++) { - expect(defaultSbsAfterCurrentSb).toContain(landingSuperBlocks[j]); - } - }); - }); - }); - - describe("'learn'", () => { - const learn = - superBlockOrder[language as Languages][CurriculumMaps.Learn]; - const audited = learn[TranslationStates.Audited]; - const notAudited = learn[TranslationStates.NotAudited]; - - describe("'audited'", () => { - superBlockStates.forEach(superBlockState => { - const stateSuperBlocks = audited[superBlockState]; - - describe(`'${superBlockState}'`, () => { - it('should not have a superBlock out of order', () => { - stateSuperBlocks.forEach((superBlock, index) => { - const defaultIndex = - defaultSuperBlockOrder.indexOf(superBlock); - const defaultSbsAfterCurrentSb = defaultSuperBlockOrder.slice( - defaultIndex + 1 - ); - - for (let j = index + 1; j < stateSuperBlocks.length; j++) { - expect(defaultSbsAfterCurrentSb).toContain( - stateSuperBlocks[j] - ); - } - }); - }); - }); - }); - }); - - describe('not audited', () => { - superBlockStates.forEach(superBlockState => { - const stateSuperBlocks = notAudited[superBlockState]; - - describe(`'${superBlockState}'`, () => { - it('should not have a superBlock out of order', () => { - stateSuperBlocks.forEach((superBlock, index) => { - const defaultIndex = - defaultSuperBlockOrder.indexOf(superBlock); - const defaultSbsAfterCurrentSb = defaultSuperBlockOrder.slice( - defaultIndex + 1 - ); - - for (let j = index + 1; j < stateSuperBlocks.length; j++) { - expect(defaultSbsAfterCurrentSb).toContain( - stateSuperBlocks[j] - ); - } - }); - }); - }); - }); - }); - - it("should have exactly one of each 'SuperBlocks' among it's children", () => { - // flatten all ${language}.learn superblocks into one array - const learnSuperBlocks: SuperBlocks[] = []; - - translationStates.forEach(translationState => { - superBlockStates.forEach(superBlockState => { - learnSuperBlocks.push( - ...learn[translationState][superBlockState] - ); - }); - }); - - superBlocks.forEach(superBlock => { - expect(learnSuperBlocks).toContain(superBlock); - }); - }); - }); - }); - }); -}); - -describe("'superBlockOrder' helper functions", () => { - it("'getLearnSuperBlocks('english')' should return the correct array", () => { - const learnSuperBlocks = getLearnSuperBlocks({ - language: 'english', - showNewCurriculum: 'true', - showUpcomingChanges: 'true' - }); - const test = [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler, - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification, - SuperBlocks.RespWebDesign - ]; - expect(learnSuperBlocks).toStrictEqual(test); - expect(learnSuperBlocks.length).toEqual(test.length); - }); - - it("'getAuditedSuperBlocks('german')' should return the correct array", () => { - const auditedSuperBlocks = getAuditedSuperBlocks({ - language: 'german', - showNewCurriculum: 'true', - showUpcomingChanges: 'true' - }); - const test = [ - SuperBlocks.RespWebDesign, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs - ]; - expect(auditedSuperBlocks).toStrictEqual(test); - expect(auditedSuperBlocks.length).toEqual(test.length); - }); - - it("'getNotAuditedSuperBlocks('german')' should return the correct array", () => { - const notAuditedSuperBlocks = getNotAuditedSuperBlocks({ - language: 'german', - showNewCurriculum: 'true', - showUpcomingChanges: 'true' - }); - console.log(notAuditedSuperBlocks); - const test = [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler, - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ]; - expect(notAuditedSuperBlocks).toStrictEqual(test); - expect(notAuditedSuperBlocks.length).toEqual(test.length); - }); -}); diff --git a/config/superblock-order.ts b/config/superblock-order.ts deleted file mode 100644 index aec9812e5e7..00000000000 --- a/config/superblock-order.ts +++ /dev/null @@ -1,707 +0,0 @@ -import { Languages } from './i18n'; -import { SuperBlocks } from './certification-settings'; - -/* - * .env SHOW_NEW_CURRICULUM = SuperBlockStates.New - * 'New' -> shown only on english staging at the moment - * - * .env SHOW_UPCOMING_CHANGES = SuperBlockStates.Upcoming - * 'Upcoming' is for development -> not shown on stag or prod anywhere - * - */ - -export enum CurriculumMaps { - Landing = 'landing', - Learn = 'learn' -} - -export enum TranslationStates { - Audited = 'audited', - NotAudited = 'notAudited' -} - -export enum SuperBlockStates { - Current = 'current', - New = 'new', - Upcoming = 'upcoming', - Legacy = 'legacy' -} - -export const orderedSuperBlockStates = [ - SuperBlockStates.Current, - SuperBlockStates.New, - SuperBlockStates.Upcoming, - SuperBlockStates.Legacy -]; - -type SuperBlockOrder = { - [key in Languages]: { - [CurriculumMaps.Landing]: SuperBlocks[]; - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: SuperBlocks[]; - [SuperBlockStates.New]: SuperBlocks[]; - [SuperBlockStates.Upcoming]: SuperBlocks[]; - [SuperBlockStates.Legacy]: SuperBlocks[]; - }; - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: SuperBlocks[]; - [SuperBlockStates.New]: SuperBlocks[]; - [SuperBlockStates.Upcoming]: SuperBlocks[]; - [SuperBlockStates.Legacy]: SuperBlocks[]; - }; - }; - }; -}; - -// all languages should have this many, one for each current cert -export const numberOfSuperBlocksOnLanding = 12; - -/* - * This is the used for tests to make sure a superBlock isn't out of order - * e.g. so that a RWD button isn't below a JS button. - * It compares each array in `superBlockOrder` to this - those arrays do not - * have to include all these superBlocks, but the ones it does include, have - * to be in this order - */ -export const defaultSuperBlockOrder: SuperBlocks[] = [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.RespWebDesign, - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification -]; - -/* - * The order of superblocks in the arrays below are how they appear on the maps - * - * The 'Landing' map array should contain exactly one superblock for each - * current, non-legacy certification, and only one superblock of each type - - * e.g. only one RWD superblock (button) - * - * The 'Learn' map arrays should contain ALL available SuperBlocks, sorted into - * their various states. These will be used to create the 'superOrder' property. - * - */ -export const superBlockOrder: SuperBlockOrder = { - [Languages.English]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.Espanol]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.Chinese]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.ChineseTraditional]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.Italian]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.Portuguese]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.TheOdinProject], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.Ukrainian]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.TheOdinProject], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.Japanese]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.RespWebDesign, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CodingInterviewPrep - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.German]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesign, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesign, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [] - } - } - }, - [Languages.Arabic]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CollegeAlgebraPy, - SuperBlocks.CodingInterviewPrep, - SuperBlocks.ProjectEuler - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [ - SuperBlocks.JsAlgoDataStructNew, - SuperBlocks.TheOdinProject, - SuperBlocks.ExampleCertification - ], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - } - } - } -}; - -// The client uses the object above to create the map -// Keep this so it can't change -Object.freeze(superBlockOrder); - -function shouldShowSuperblocks({ - superBlockState, - showNewCurriculum = 'false', - showUpcomingChanges = 'false' -}: { - superBlockState: string; - showNewCurriculum: string; - showUpcomingChanges: string; -}) { - if ( - (superBlockState === SuperBlockStates.New && - showNewCurriculum !== 'true') || - (superBlockState === SuperBlockStates.Upcoming && - showUpcomingChanges !== 'true') - ) { - return false; - } - return true; -} - -type Config = { - language: string; - showNewCurriculum?: string; - showUpcomingChanges?: string; -}; - -export function getLearnSuperBlocks({ - language = 'english', - showNewCurriculum = 'false', - showUpcomingChanges = 'false' -}: Config): SuperBlocks[] { - const learnSuperBlocks: SuperBlocks[] = []; - - Object.values(TranslationStates).forEach(translationState => { - Object.values(SuperBlockStates).forEach(superBlockState => { - if ( - shouldShowSuperblocks({ - superBlockState, - showNewCurriculum, - showUpcomingChanges - }) - ) { - learnSuperBlocks.push( - ...superBlockOrder[language as Languages][CurriculumMaps.Learn][ - translationState as TranslationStates - ][superBlockState as SuperBlockStates] - ); - } - }); - }); - - return learnSuperBlocks; -} - -export function getAuditedSuperBlocks({ - language = 'english', - showNewCurriculum = 'false', - showUpcomingChanges = 'false' -}: Config): SuperBlocks[] { - const auditedSuperBlocks: SuperBlocks[] = []; - - Object.values(SuperBlockStates).forEach(superBlockState => { - if ( - shouldShowSuperblocks({ - superBlockState, - showNewCurriculum, - showUpcomingChanges - }) - ) { - auditedSuperBlocks.push( - ...superBlockOrder[language as Languages][CurriculumMaps.Learn][ - TranslationStates.Audited - ][superBlockState as SuperBlockStates] - ); - } - }); - - return auditedSuperBlocks; -} - -export function getNotAuditedSuperBlocks({ - language = 'english', - showNewCurriculum = 'false', - showUpcomingChanges = 'false' -}: Config): SuperBlocks[] { - const notAuditedSuperBlocks: SuperBlocks[] = []; - - Object.values(SuperBlockStates).forEach(superBlockState => { - if ( - shouldShowSuperblocks({ - superBlockState, - showNewCurriculum, - showUpcomingChanges - }) - ) { - notAuditedSuperBlocks.push( - ...superBlockOrder[language as Languages][CurriculumMaps.Learn][ - TranslationStates.NotAudited - ][superBlockState as SuperBlockStates] - ); - } - }); - - return notAuditedSuperBlocks; -} diff --git a/config/superblocks.test.ts b/config/superblocks.test.ts new file mode 100644 index 00000000000..ba76f7388e0 --- /dev/null +++ b/config/superblocks.test.ts @@ -0,0 +1,125 @@ +import { Languages } from './i18n'; +import { + SuperBlocks, + SuperBlockStages, + superBlockOrder, + notAuditedSuperBlocks, + createSuperBlockMap, + createFlatSuperBlockMap, + getAuditedSuperBlocks, + getFirstNotAuditedSuperBlock +} from './superblocks'; + +describe('superBlockOrder', () => { + it('should contain all SuperBlocks', () => { + const allSuperBlocks = Object.values(SuperBlocks); + const superBlockOrderValues = Object.values(superBlockOrder).flat(); + expect(superBlockOrderValues).toHaveLength(allSuperBlocks.length); + expect(superBlockOrderValues).toEqual( + expect.arrayContaining(allSuperBlocks) + ); + }); +}); + +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); + }); + + it('should return an array of SuperBlocks without New and Upcoming when { showNewCurriculum: false, showUpcomingChanges: false }', () => { + const result = createFlatSuperBlockMap({ + showNewCurriculum: 'false', + showUpcomingChanges: 'false' + }); + const tempSuperBlockMap = { ...superBlockOrder }; + tempSuperBlockMap[SuperBlockStages.New] = []; + tempSuperBlockMap[SuperBlockStages.Upcoming] = []; + expect(result).toHaveLength(Object.values(tempSuperBlockMap).flat().length); + }); +}); + +describe('firstNotAuditedSuperBlock', () => { + it("should return 'null' when language is 'english'", () => { + const result = getFirstNotAuditedSuperBlock({ + language: Languages.English, + showNewCurriculum: 'false', + showUpcomingChanges: 'false' + }); + expect(result).toBeNull(); + }); + + it("should return a SuperBlock when language is 'chinese'", () => { + const result = getFirstNotAuditedSuperBlock({ + language: Languages.Chinese, + showNewCurriculum: 'false', + showUpcomingChanges: 'false' + }); + expect(result).toEqual(SuperBlocks.CollegeAlgebraPy); + }); +}); + +describe('Immutability of superBlockOrder, notAuditedSuperBlocks, and flatSuperBlockMap', () => { + it('should not allow modification of superBlockOrder', () => { + expect(() => { + superBlockOrder[SuperBlockStages.FrontEnd] = []; + }).toThrowError(TypeError); + }); + + it('should not allow modification of notAuditedSuperBlocks', () => { + expect(() => { + notAuditedSuperBlocks[Languages.English] = []; + }).toThrowError(TypeError); + }); + + it('should not allow modification of flatSuperBlockMap', () => { + expect(() => { + notAuditedSuperBlocks[Languages.English] = []; + }).toThrowError(TypeError); + }); +}); + +describe('getAuditedSuperBlocks', () => { + Object.keys(notAuditedSuperBlocks).forEach(language => { + it(`should return only audited SuperBlocks for ${language}`, () => { + const auditedSuperBlocks = getAuditedSuperBlocks({ + showNewCurriculum: 'true', + showUpcomingChanges: 'true', + language + }); + + auditedSuperBlocks.forEach(superblock => { + expect(notAuditedSuperBlocks[language as Languages]).not.toContain( + superblock + ); + }); + }); + }); +}); diff --git a/config/superblocks.ts b/config/superblocks.ts new file mode 100644 index 00000000000..66bccf33687 --- /dev/null +++ b/config/superblocks.ts @@ -0,0 +1,243 @@ +import { Languages } from './i18n'; + +// all superblocks +export enum SuperBlocks { + RespWebDesignNew = '2022/responsive-web-design', + RespWebDesign = 'responsive-web-design', + JsAlgoDataStruct = 'javascript-algorithms-and-data-structures', + JsAlgoDataStructNew = '2022/javascript-algorithms-and-data-structures', + FrontEndDevLibs = 'front-end-development-libraries', + DataVis = 'data-visualization', + RelationalDb = 'relational-database', + BackEndDevApis = 'back-end-development-and-apis', + QualityAssurance = 'quality-assurance', + SciCompPy = 'scientific-computing-with-python', + DataAnalysisPy = 'data-analysis-with-python', + InfoSec = 'information-security', + MachineLearningPy = 'machine-learning-with-python', + CodingInterviewPrep = 'coding-interview-prep', + TheOdinProject = 'the-odin-project', + ProjectEuler = 'project-euler', + CollegeAlgebraPy = 'college-algebra-with-python', + ExampleCertification = 'example-certification' +} + +/* + * SuperBlockStages.New = SHOW_NEW_CURRICULUM === 'true' + * 'New' -> shown only on english staging at the moment + * + * SuperBlockStages.Upcoming = SHOW_UPCOMING_CHANGES === 'true' + * 'Upcoming' is for development -> not shown on stag or prod anywhere + */ +export enum SuperBlockStages { + FrontEnd = 'frontend', + Backend = 'backend', + Python = 'python', + Extra = 'extra', + Legacy = 'legacy', + New = 'new', + Upcoming = 'upcoming' +} + +export type SuperBlockOrder = { + [key in SuperBlockStages]: SuperBlocks[]; +}; + +// order of buttons on map, this should include all superblocks +// new and upcoming superblocks are removed below +export const superBlockOrder: SuperBlockOrder = { + [SuperBlockStages.FrontEnd]: [ + SuperBlocks.RespWebDesignNew, + SuperBlocks.JsAlgoDataStruct, + SuperBlocks.FrontEndDevLibs, + SuperBlocks.DataVis + ], + [SuperBlockStages.Backend]: [ + SuperBlocks.RelationalDb, + SuperBlocks.BackEndDevApis, + SuperBlocks.QualityAssurance + ], + [SuperBlockStages.Python]: [ + SuperBlocks.SciCompPy, + SuperBlocks.DataAnalysisPy, + SuperBlocks.InfoSec, + SuperBlocks.MachineLearningPy, + SuperBlocks.CollegeAlgebraPy + ], + [SuperBlockStages.Extra]: [ + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler + ], + [SuperBlockStages.Legacy]: [SuperBlocks.RespWebDesign], + [SuperBlockStages.New]: [], + [SuperBlockStages.Upcoming]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject, + SuperBlocks.ExampleCertification + ] +}; + +Object.freeze(superBlockOrder); + +type NotAuditedSuperBlocks = { + [key in Languages]: SuperBlocks[]; +}; + +// when a superBlock is audited, remove it from its language below +// when adding a new language, add all (not audited) superblocks to the object +export const notAuditedSuperBlocks: NotAuditedSuperBlocks = { + [Languages.English]: [], + [Languages.Espanol]: [ + SuperBlocks.InfoSec, + SuperBlocks.MachineLearningPy, + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], + [Languages.Chinese]: [ + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], + [Languages.ChineseTraditional]: [ + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], + [Languages.Italian]: [ + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], + [Languages.Portuguese]: [ + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew + ], + [Languages.Ukrainian]: [ + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew + ], + [Languages.Japanese]: [ + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], + [Languages.German]: [ + SuperBlocks.RespWebDesignNew, + SuperBlocks.DataVis, + SuperBlocks.RelationalDb, + SuperBlocks.BackEndDevApis, + SuperBlocks.QualityAssurance, + SuperBlocks.SciCompPy, + SuperBlocks.DataAnalysisPy, + SuperBlocks.InfoSec, + SuperBlocks.MachineLearningPy, + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ], + [Languages.Arabic]: [ + SuperBlocks.DataVis, + SuperBlocks.RelationalDb, + SuperBlocks.BackEndDevApis, + SuperBlocks.QualityAssurance, + SuperBlocks.SciCompPy, + SuperBlocks.DataAnalysisPy, + SuperBlocks.InfoSec, + SuperBlocks.MachineLearningPy, + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ] +}; + +Object.freeze(notAuditedSuperBlocks); + +type Config = { + showNewCurriculum: string | undefined; + showUpcomingChanges: string | undefined; +}; + +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 !== 'true') { + superBlockMap[SuperBlockStages.New] = []; + } + if (showUpcomingChanges !== 'true') { + superBlockMap[SuperBlockStages.Upcoming] = []; + } + return superBlockMap; +} + +export function createFlatSuperBlockMap({ + showNewCurriculum, + showUpcomingChanges +}: Config): SuperBlocks[] { + const superBlockMap = { ...superBlockOrder }; + if (showNewCurriculum !== 'true') { + superBlockMap[SuperBlockStages.New] = []; + } + if (showUpcomingChanges !== 'true') { + superBlockMap[SuperBlockStages.Upcoming] = []; + } + return Object.values(superBlockMap).flat(); +} + +// this is so we know where to display the "help us translate" section +export function getFirstNotAuditedSuperBlock({ + language, + showNewCurriculum, + showUpcomingChanges +}: LanguagesConfig): SuperBlocks | null { + const flatSuperBlockMap = createFlatSuperBlockMap({ + showNewCurriculum, + showUpcomingChanges + }); + for (const superBlock of flatSuperBlockMap) { + if (notAuditedSuperBlocks[language as Languages].includes(superBlock)) { + return superBlock; + } + } + return null; +} + +export function getAuditedSuperBlocks({ + language = 'english', + showNewCurriculum = 'false', + showUpcomingChanges = 'false' +}: LanguagesConfig): SuperBlocks[] { + if (!Object.prototype.hasOwnProperty.call(notAuditedSuperBlocks, language)) { + throw Error(`'${language}' key not found in 'notAuditedSuperBlocks'`); + } + + const flatSuperBlockMap = createFlatSuperBlockMap({ + showNewCurriculum, + showUpcomingChanges + }); + const auditedSuperBlocks = flatSuperBlockMap.filter( + superBlock => + !notAuditedSuperBlocks[language as Languages].includes(superBlock) + ); + return auditedSuperBlocks; +} diff --git a/curriculum/utils.js b/curriculum/utils.js index 53f209ca435..4b63be0fca5 100644 --- a/curriculum/utils.js +++ b/curriculum/utils.js @@ -1,11 +1,8 @@ const path = require('path'); const { - CurriculumMaps, - superBlockOrder, - SuperBlockStates, - TranslationStates, - orderedSuperBlockStates -} = require('../config/superblock-order'); + createFlatSuperBlockMap, + SuperBlocks +} = require('../config/superblocks'); require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); @@ -26,72 +23,30 @@ exports.testedLang = function testedLang() { } }; -/* - * creates an object with all the superblocks in - * 'superBlockOrder[lang][learn]' as keys and gives them - * a number (superOrder), starting with 0, as the value - */ -function createSuperOrder({ - language = 'english', - showNewCurriculum = 'false', - showUpcomingChanges = 'false' -}) { - if (!Object.prototype.hasOwnProperty.call(superBlockOrder, language)) { - throw Error(`${language} not found in superblock-order.ts`); +function createSuperOrder(superBlocks) { + if (!Array.isArray(superBlocks)) { + throw Error(`superBlocks must be an Array`); } - - if ( - !Object.prototype.hasOwnProperty.call(superBlockOrder[language], [ - CurriculumMaps.Learn - ]) - ) { - throw Error( - `${language} does not have a 'learn' key in superblock-order.ts` - ); - } - - const audited = - superBlockOrder[language][CurriculumMaps.Learn][TranslationStates.Audited]; - const notAudited = - superBlockOrder[language][CurriculumMaps.Learn][ - TranslationStates.NotAudited - ]; + superBlocks.forEach(superBlock => { + if (!Object.values(SuperBlocks).includes(superBlock)) { + throw Error(`Invalid superBlock: ${superBlock}`); + } + }); const superOrder = {}; - let i = 0; - function addToSuperOrder(superBlocks) { - superBlocks.forEach(key => { - superOrder[key] = i; - i++; - }); - } - - function canAddToSuperOrder(superBlockState) { - if (superBlockState === SuperBlockStates.New) - return showNewCurriculum === 'true'; - if (superBlockState === SuperBlockStates.Upcoming) - return showUpcomingChanges === 'true'; - return true; - } - - function addSuperBlockStates(translationState) { - orderedSuperBlockStates.forEach(state => { - if (canAddToSuperOrder(state)) addToSuperOrder(translationState[state]); - }); - } - - addSuperBlockStates(audited); - addSuperBlockStates(notAudited); + superBlocks.forEach((superBlock, i) => { + superOrder[superBlock] = i; + }); return superOrder; } -const superOrder = createSuperOrder({ - language: process.env.CURRICULUM_LOCALE, +const flatSuperBlockMap = createFlatSuperBlockMap({ showNewCurriculum: process.env.SHOW_NEW_CURRICULUM, showUpcomingChanges: process.env.SHOW_UPCOMING_CHANGES }); +const superOrder = createSuperOrder(flatSuperBlockMap); // gets the superOrder of a superBlock from the object created above function getSuperOrder(superblock) { diff --git a/curriculum/utils.test.ts b/curriculum/utils.test.ts index 281b0a9b013..8b262a83a88 100644 --- a/curriculum/utils.test.ts +++ b/curriculum/utils.test.ts @@ -4,12 +4,33 @@ import fs from 'fs'; import path from 'path'; import { config } from 'dotenv'; -import { SuperBlocks } from '../config/certification-settings'; +import { SuperBlocks } from '../config/superblocks'; import { createSuperOrder, getSuperOrder, getSuperBlockFromDir } from './utils'; config({ path: path.resolve(__dirname, '../.env') }); -const englishTest = { +const mockSuperBlocks = [ + SuperBlocks.RespWebDesignNew, + SuperBlocks.JsAlgoDataStruct, + SuperBlocks.FrontEndDevLibs, + SuperBlocks.DataVis, + SuperBlocks.RelationalDb, + SuperBlocks.BackEndDevApis, + SuperBlocks.QualityAssurance, + SuperBlocks.SciCompPy, + SuperBlocks.DataAnalysisPy, + SuperBlocks.InfoSec, + SuperBlocks.MachineLearningPy, + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.RespWebDesign, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject, + SuperBlocks.ExampleCertification +]; + +const fullSuperOrder = { [SuperBlocks.RespWebDesignNew]: 0, [SuperBlocks.JsAlgoDataStruct]: 1, [SuperBlocks.FrontEndDevLibs]: 2, @@ -24,105 +45,25 @@ const englishTest = { [SuperBlocks.CollegeAlgebraPy]: 11, [SuperBlocks.CodingInterviewPrep]: 12, [SuperBlocks.ProjectEuler]: 13, - [SuperBlocks.RespWebDesign]: 14 -}; - -const upcomingTest = { - [SuperBlocks.RespWebDesignNew]: 0, - [SuperBlocks.JsAlgoDataStruct]: 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.CodingInterviewPrep]: 12, - [SuperBlocks.ProjectEuler]: 13, - [SuperBlocks.JsAlgoDataStructNew]: 14, - [SuperBlocks.TheOdinProject]: 15, - [SuperBlocks.ExampleCertification]: 16, - [SuperBlocks.RespWebDesign]: 17 -}; - -const espanolTest = { - [SuperBlocks.RespWebDesignNew]: 0, - [SuperBlocks.JsAlgoDataStruct]: 1, - [SuperBlocks.FrontEndDevLibs]: 2, - [SuperBlocks.DataVis]: 3, - [SuperBlocks.RelationalDb]: 4, - [SuperBlocks.BackEndDevApis]: 5, - [SuperBlocks.QualityAssurance]: 6, - [SuperBlocks.SciCompPy]: 7, - [SuperBlocks.DataAnalysisPy]: 8, - [SuperBlocks.RespWebDesign]: 9, - [SuperBlocks.InfoSec]: 10, - [SuperBlocks.MachineLearningPy]: 11, - [SuperBlocks.CollegeAlgebraPy]: 12, - [SuperBlocks.CodingInterviewPrep]: 13, - [SuperBlocks.ProjectEuler]: 14 -}; - -const chineseTest = { - [SuperBlocks.RespWebDesignNew]: 0, - [SuperBlocks.JsAlgoDataStruct]: 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.RespWebDesign]: 11, - [SuperBlocks.CollegeAlgebraPy]: 12, - [SuperBlocks.CodingInterviewPrep]: 13, - [SuperBlocks.ProjectEuler]: 14 + [SuperBlocks.RespWebDesign]: 14, + [SuperBlocks.JsAlgoDataStructNew]: 15, + [SuperBlocks.TheOdinProject]: 16, + [SuperBlocks.ExampleCertification]: 17 }; describe('createSuperOrder', () => { - const englishSuperOrder = createSuperOrder({ - language: 'english', - showNewCurriculum: 'false', - showUpcomingChanges: 'false' + const superOrder = createSuperOrder(mockSuperBlocks); + + it('should create the correct object given an array of SuperBlocks', () => { + expect(superOrder).toStrictEqual(fullSuperOrder); }); - const upcomingSuperOrder = createSuperOrder({ - language: 'english', - showNewCurriculum: 'false', - showUpcomingChanges: 'true' - }); - - const espanolSuperOrder = createSuperOrder({ - language: 'espanol', - showNewCurriculum: 'false', - showUpcomingChanges: 'false' - }); - - const chineseSuperOrder = createSuperOrder({ - language: 'chinese', - showNewCurriculum: 'false', - showUpcomingChanges: 'false' - }); - - it("should create the correct object for 'english'", () => { - expect(englishSuperOrder).toStrictEqual(englishTest); - }); - - it('should create the correct object with upcoming changes shown', () => { - expect(upcomingSuperOrder).toStrictEqual(upcomingTest); - }); - - it("should create the correct object for 'espanol'", () => { - expect(espanolSuperOrder).toStrictEqual(espanolTest); - }); - - it("should create the correct object for 'chinese'", () => { - expect(chineseSuperOrder).toStrictEqual(chineseTest); + it('throws when not given an array of SuperBlocks', () => { + expect.assertions(4); + expect(() => getSuperOrder()).toThrow(); + expect(() => getSuperOrder(null)).toThrow(); + expect(() => getSuperOrder('')).toThrow(); + expect(() => getSuperOrder(['respansive-wib-desoin'])).toThrow(); }); }); @@ -146,12 +87,14 @@ describe('getSuperOrder', () => { }); it('returns unique numbers for all current superblocks', () => { - // Skip non-english tests - if (process.env.CURRICULUM_LOCALE !== 'english') { - return; - } - - if (process.env.SHOW_UPCOMING_CHANGES !== 'true') { + if ( + process.env.SHOW_NEW_CURRICULUM !== 'true' && + process.env.SHOW_UPCOMING_CHANGES !== 'true' + ) { + expect.assertions(15); + } else if (process.env.SHOW_NEW_CURRICULUM !== 'true') { + expect.assertions(15); + } else if (process.env.SHOW_UPCOMING_CHANGES !== 'true') { expect.assertions(15); } else { expect.assertions(18); @@ -171,14 +114,21 @@ describe('getSuperOrder', () => { expect(getSuperOrder(SuperBlocks.CollegeAlgebraPy)).toBe(11); expect(getSuperOrder(SuperBlocks.CodingInterviewPrep)).toBe(12); expect(getSuperOrder(SuperBlocks.ProjectEuler)).toBe(13); + expect(getSuperOrder(SuperBlocks.RespWebDesign)).toBe(14); - if (process.env.SHOW_UPCOMING_CHANGES === 'true') { - expect(getSuperOrder(SuperBlocks.JsAlgoDataStructNew)).toBe(14); - expect(getSuperOrder(SuperBlocks.TheOdinProject)).toBe(15); - expect(getSuperOrder(SuperBlocks.ExampleCertification)).toBe(16); - expect(getSuperOrder(SuperBlocks.RespWebDesign)).toBe(17); - } else { - expect(getSuperOrder(SuperBlocks.RespWebDesign)).toBe(14); + if ( + process.env.SHOW_NEW_CURRICULUM === 'true' && + process.env.SHOW_UPCOMING_CHANGES === 'true' + ) { + expect(getSuperOrder(SuperBlocks.JsAlgoDataStructNew)).toBe(15); + expect(getSuperOrder(SuperBlocks.TheOdinProject)).toBe(16); + expect(getSuperOrder(SuperBlocks.ExampleCertification)).toBe(17); + } else if (process.env.SHOW_NEW_CURRICULUM === 'true') { + return; + } else if (process.env.SHOW_UPCOMING_CHANGES === 'true') { + expect(getSuperOrder(SuperBlocks.JsAlgoDataStructNew)).toBe(15); + expect(getSuperOrder(SuperBlocks.TheOdinProject)).toBe(16); + expect(getSuperOrder(SuperBlocks.ExampleCertification)).toBe(17); } }); }); diff --git a/cypress/e2e/default/landing.ts b/cypress/e2e/default/landing.ts index d42996d13da..8c1d85afce3 100644 --- a/cypress/e2e/default/landing.ts +++ b/cypress/e2e/default/landing.ts @@ -2,6 +2,7 @@ const landingPageElements = { heading: "[data-test-label='landing-header']", callToAction: "[data-test-label='landing-big-cta']", certifications: "[data-test-label='certifications']", + curriculumBtns: "[data-test-label='curriculum-map-button']", testimonials: "[data-test-label='testimonial-cards']", landingPageImage: "[data-test-label='landing-page-figure']", faq: "[data-test-label='landing-page-faq']" @@ -11,7 +12,7 @@ type LandingPageTypes = T[keyof T]; type LandingPageLogs = LandingPageTypes; -const certifications = [ +const superBlocks = [ 'Responsive Web Design', 'JavaScript Algorithms and Data Structures', 'Front End Development Libraries', @@ -23,7 +24,10 @@ const certifications = [ 'Data Analysis with Python', 'Information Security', 'Machine Learning with Python', - 'College Algebra with Python' + 'College Algebra with Python', + 'Coding Interview Prep', + 'Project Euler', + 'Legacy Responsive Web Design' ]; describe('Landing page', () => { @@ -67,13 +71,10 @@ describe('Landing page', () => { .should('not.exist'); }); - it('Has links to all the certifications', function () { - cy.get(landingPageElements.certifications) - .children() - .its('length') - .should('eq', 12); - cy.wrap(certifications).each((cert: LandingPageLogs) => { - cy.get(landingPageElements.certifications).contains(cert); + it('Has links to all superblocks', function () { + cy.get(landingPageElements.curriculumBtns).its('length').should('eq', 15); + cy.wrap(superBlocks).each((cert: LandingPageLogs) => { + cy.get(landingPageElements.curriculumBtns).contains(cert); }); }); diff --git a/cypress/e2e/default/learn/challenges/projects.ts b/cypress/e2e/default/learn/challenges/projects.ts index f02fa152c43..0bb29017d16 100644 --- a/cypress/e2e/default/learn/challenges/projects.ts +++ b/cypress/e2e/default/learn/challenges/projects.ts @@ -1,4 +1,4 @@ -import { SuperBlocks } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; interface Meta { challengeOrder: string[][]; diff --git a/cypress/e2e/default/learn/index.ts b/cypress/e2e/default/learn/index.ts index 01158b33a33..f54ba2c5cbd 100644 --- a/cypress/e2e/default/learn/index.ts +++ b/cypress/e2e/default/learn/index.ts @@ -1,5 +1,5 @@ const challengerSelector = { - challengeMap: "[data-test-label='learn-curriculum-map']" + curriculumMap: "[data-test-label='curriculum-map']" } as const; const learnUrl = { @@ -43,7 +43,7 @@ describe('Learn Landing page (not logged in)', () => { it('Should render a curriculum map', () => { cy.document().then(document => { const superBlocks = document.querySelectorAll( - `${challengerSelector.challengeMap} > li > a` + `${challengerSelector.curriculumMap} > ul > li > a` ); expect(superBlocks).to.have.length(15); diff --git a/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.ts b/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.ts index c826abf401f..4d0a3a094a0 100644 --- a/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.ts +++ b/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.ts @@ -1,4 +1,4 @@ -import { SuperBlocks } from '../../../../../config/certification-settings'; +import { SuperBlocks } from '../../../../../config/superblocks'; const projects = { superBlock: SuperBlocks.FrontEndDevLibs, diff --git a/docs/how-to-enable-new-languages.md b/docs/how-to-enable-new-languages.md index bca2b3bd016..cf4738ba0cf 100644 --- a/docs/how-to-enable-new-languages.md +++ b/docs/how-to-enable-new-languages.md @@ -120,69 +120,32 @@ export const rtlLangs = ['']; > [!NOTE] > When a language has been set up in the deployment pipeline AND has a public `/news` instance live, it can be removed from the `hiddenLangs` array and be made available to the public. -### Configure the Language Superblock Order +### Set Translated SuperBlocks -In the [config/superblock-order.ts](https://github.com/freeCodeCamp/freeCodeCamp/blob/main/config/superblock-order.ts) file, you need to set the order and state of all the superblocks for the new language in the `superBlockOrder` object. Copy one of the language keys and all its values, paste it to the bottom of the object (or wherever), and change the key to your new language from the `Languages` enum. +In the [config/superblocks.ts](https://github.com/freeCodeCamp/freeCodeCamp/blob/main/config/superblocks.ts) file, add the new language to the `notAuditedSuperBlocks` object. This lists all the superblocks which are not fully translated. Add an array of superblocks which have not been fully translated to it: ```js -export const superBlockOrder: SuperBlockOrder = { +export const notAuditedSuperBlocks: NotAuditedSuperBlocks = { ... - [Languages.Dothraki]: { - [CurriculumMaps.Landing]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy - ], - [CurriculumMaps.Learn]: { - [TranslationStates.Audited]: { - [SuperBlockStates.Current]: [ - SuperBlocks.RespWebDesignNew, - SuperBlocks.JsAlgoDataStruct, - SuperBlocks.FrontEndDevLibs, - SuperBlocks.DataVis, - SuperBlocks.RelationalDb, - SuperBlocks.BackEndDevApis, - SuperBlocks.QualityAssurance, - SuperBlocks.SciCompPy, - SuperBlocks.DataAnalysisPy, - SuperBlocks.InfoSec, - SuperBlocks.MachineLearningPy, - SuperBlocks.CodingInterviewPrep - ], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [SuperBlocks.JsAlgoDataStructNew], - [SuperBlockStates.Legacy]: [SuperBlocks.RespWebDesign] - }, - [TranslationStates.NotAudited]: { - [SuperBlockStates.Current]: [], - [SuperBlockStates.New]: [], - [SuperBlockStates.Upcoming]: [], - [SuperBlockStates.Legacy]: [] - } - } - } + [Languages.Dothraki]: [ + SuperBlocks.DataVis, + SuperBlocks.RelationalDb, + SuperBlocks.BackEndDevApis, + SuperBlocks.QualityAssurance, + SuperBlocks.SciCompPy, + SuperBlocks.DataAnalysisPy, + SuperBlocks.InfoSec, + SuperBlocks.MachineLearningPy, + SuperBlocks.CollegeAlgebraPy, + SuperBlocks.CodingInterviewPrep, + SuperBlocks.ProjectEuler, + SuperBlocks.JsAlgoDataStructNew, + SuperBlocks.TheOdinProject + ] } ``` -The order of the superblocks in this object is how they appear on the "Landing" page and "Learn" maps. Follow the comments in that file so you know how you are allowed to order the superblocks, then move them to their proper places for the new language. - -> [!ATTENTION] -> Do not change the order of any of the keys in the object, just move the superblocks to the different arrays - -The `CurriculumMaps.Landing` array should contain exactly one superblock for all our current certifications, and the `CurriculumMaps.Learn` object should have all existing superblocks in it. Translated superblocks go in `TranslationStates.Audited` and non-translated superblocks go in `TranslationStates.NotAudited`. Each of those two objects has four different states a superblock can be in. - -- `SuperBlockStates.Current`: Means that the superblock is current, `Responsive Web Design` for example. -- `SuperBlockStates.New`: These only show up when `SHOW_NEW_CURRICULUM` is set to `true` in your `.env` file. It is for displaying new superblocks on a specific build. For example, when we released the new RWD, we only showed it on English to start. -- `SuperBlockStates.Upcoming`: These only show up when `SHOW_UPCOMING_CHANGES` is set to `true` in your `.env` file. It is to show superblocks locally while they are in development. Or, if you just need to hide a superblock from the map for some other reason. -- `SuperBlockStates.Legacy`: A superblock is moved here when a newer version of that superblock has been fully translated and replaced it. +Be sure to only add the superblocks which are **not** fully translated and approved. The translated superblocks will be calculated from this object. When a new superblock is finished being fully translated, remove it from the array for that language. ### Configure Search diff --git a/docs/language-lead-handbook.md b/docs/language-lead-handbook.md index edf002ebe89..e7293b02561 100644 --- a/docs/language-lead-handbook.md +++ b/docs/language-lead-handbook.md @@ -389,4 +389,4 @@ The user is now a proofreader. ## How to Add or Update a Language -Check out the [how to enable new language](how-to-enable-new-languages.md) docs. If you are updating a language the section on [configuring the language superblock order](how-to-enable-new-languages.md#configure-the-language-superblock-order) should be helpful. +Check out the [how to enable new language](how-to-enable-new-languages.md) docs. If you are updating a language the section on [set translated superblocks](how-to-enable-new-languages.md#set-translated-superblocks) should be helpful. diff --git a/tools/challenge-auditor/index.ts b/tools/challenge-auditor/index.ts index d82bebca4da..a73449b9c4f 100644 --- a/tools/challenge-auditor/index.ts +++ b/tools/challenge-auditor/index.ts @@ -9,8 +9,7 @@ config({ path: envPath }); import { availableLangs } from '../../config/i18n'; import { getChallengesForLang } from '../../curriculum/get-challenges'; -import { SuperBlocks } from '../../config/certification-settings'; -import { getAuditedSuperBlocks } from '../../config/superblock-order'; +import { SuperBlocks, getAuditedSuperBlocks } from '../../config/superblocks'; // TODO: re-organise the types to a common 'types' folder that can be shared // between the workspaces so we don't have to declare ChallengeNode here and in diff --git a/tools/challenge-helper-scripts/create-project.ts b/tools/challenge-helper-scripts/create-project.ts index fc9fe46650d..3ba0c898d45 100644 --- a/tools/challenge-helper-scripts/create-project.ts +++ b/tools/challenge-helper-scripts/create-project.ts @@ -5,7 +5,7 @@ import { prompt } from 'inquirer'; import { format } from 'prettier'; import ObjectID from 'bson-objectid'; -import { SuperBlocks } from '../../config/certification-settings'; +import { SuperBlocks } from '../../config/superblocks'; import { createStepFile } from './utils'; import { getSuperBlockSubPath } from './fs-utils'; import { Meta } from './helpers/project-metadata'; diff --git a/tools/challenge-helper-scripts/fs-utils.test.ts b/tools/challenge-helper-scripts/fs-utils.test.ts index efbd484ebfa..4f82c2a823f 100644 --- a/tools/challenge-helper-scripts/fs-utils.test.ts +++ b/tools/challenge-helper-scripts/fs-utils.test.ts @@ -1,6 +1,6 @@ import fs from 'fs/promises'; import path from 'path'; -import { SuperBlocks } from '../../config/certification-settings'; +import { SuperBlocks } from '../../config/superblocks'; import { getSuperBlockSubPath } from './fs-utils'; describe('getSuperBlockSubPath', () => { diff --git a/tools/challenge-helper-scripts/fs-utils.ts b/tools/challenge-helper-scripts/fs-utils.ts index 6a8512b0999..a94410456f2 100644 --- a/tools/challenge-helper-scripts/fs-utils.ts +++ b/tools/challenge-helper-scripts/fs-utils.ts @@ -1,4 +1,4 @@ -import { SuperBlocks } from '../../config/certification-settings'; +import { SuperBlocks } from '../../config/superblocks'; export function getSuperBlockSubPath(superBlock: SuperBlocks): string { const pathMap = { diff --git a/tools/scripts/build/build-external-curricula-data.test.ts b/tools/scripts/build/build-external-curricula-data.test.ts index d4011c9c641..b38824cd89a 100644 --- a/tools/scripts/build/build-external-curricula-data.test.ts +++ b/tools/scripts/build/build-external-curricula-data.test.ts @@ -5,7 +5,7 @@ import readdirp from 'readdirp'; // TODO: remove chai and use jest's assertion errors import { AssertionError } from 'chai'; import envData from '../../../config/env.json'; -import { SuperBlocks } from '../../../config/certification-settings'; +import { SuperBlocks } from '../../../config/superblocks'; import { superblockSchemaValidator, availableSuperBlocksValidator diff --git a/tools/scripts/build/build-external-curricula-data.ts b/tools/scripts/build/build-external-curricula-data.ts index 95a02f83548..9a74bb699c0 100644 --- a/tools/scripts/build/build-external-curricula-data.ts +++ b/tools/scripts/build/build-external-curricula-data.ts @@ -2,7 +2,7 @@ import { mkdirSync, writeFileSync, readFileSync } from 'fs'; import { resolve, dirname } from 'path'; import { submitTypes } from '../../../client/utils/challenge-types'; import { type ChallengeNode } from '../../../client/src/redux/prop-types'; -import { SuperBlocks } from '../../../config/certification-settings'; +import { SuperBlocks } from '../../../config/superblocks'; type Intro = { [keyValue in SuperBlocks]: IntroProps }; export type Curriculum = { diff --git a/utils/is-audited.js b/utils/is-audited.js index f3140a0487a..e4406d5e818 100644 --- a/utils/is-audited.js +++ b/utils/is-audited.js @@ -1,15 +1,18 @@ -const { getAuditedSuperBlocks } = require('../config/superblock-order'); -const { Languages } = require('../config/i18n'); +const { getAuditedSuperBlocks } = require('../config/superblocks'); +const { + showNewCurriculum, + showUpcomingChanges +} = require('../config/env.json'); function isAuditedCert(language, superblock) { if (!language || !superblock) throw Error('Both arguments must be provided for auditing'); - if (language === Languages.English) { - return true; - } - - const auditedSuperBlocks = getAuditedSuperBlocks({ language }); + const auditedSuperBlocks = getAuditedSuperBlocks({ + showNewCurriculum: showNewCurriculum.toString(), + showUpcomingChanges: showUpcomingChanges.toString(), + language + }); return auditedSuperBlocks.includes(superblock); }