diff --git a/api/src/routes/protected/certificate.test.ts b/api/src/routes/protected/certificate.test.ts
index 253a6e26049..451d2d68d47 100644
--- a/api/src/routes/protected/certificate.test.ts
+++ b/api/src/routes/protected/certificate.test.ts
@@ -52,6 +52,8 @@ describe('certificate routes', () => {
isMachineLearningPyCertV7: false,
isCollegeAlgebraPyCertV8: false,
isFoundationalCSharpCertV8: false,
+ // isJavascriptCertV9: false,
+ // isRespWebDesignCertV9: false,
username: 'fcc'
}
});
@@ -241,6 +243,8 @@ describe('certificate routes', () => {
isMachineLearningPyCertV7: true,
isCollegeAlgebraPyCertV8: true,
isFoundationalCSharpCertV8: true,
+ // isJavascriptCertV9: true,
+ // isRespWebDesignCertV9: true,
isA2EnglishCert: true
}
});
diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json
index 424e3b3a624..98270192884 100644
--- a/client/i18n/locales/english/intro.json
+++ b/client/i18n/locales/english/intro.json
@@ -5087,6 +5087,7 @@
},
"javascript-v9": {
"title": "JavaScript Certification",
+ "note": "This certification is currently in development and will be available soon. We recommend completing the available courses below to prepare for the certification exam once it is released.",
"intro": [
"This course teaches you core JavaScript programming concepts such as working with variables, functions, objects, arrays, and control flow. You'll also learn how to manipulate the DOM, handle events, and apply techniques like asynchronous programming, functional programming, and accessibility best practices.",
"To qualify for the exam, you must complete the following projects:",
@@ -5101,6 +5102,12 @@
"javascript": "JavaScript",
"javascript-certification-exam": "JavaScript Certification Exam"
},
+ "module-intros": {
+ "javascript-certification-exam": {
+ "note": "Coming Winter 2025",
+ "intro": ["Pass this exam to earn your JavaScript Certification."]
+ }
+ },
"modules": {
"javascript-variables-and-strings": "Variables and Strings",
"javascript-booleans-and-numbers": "Booleans and Numbers",
@@ -7483,6 +7490,12 @@
"intro": [
"Learn the fundamentals of how web communication works through the HTTP request-response model, explore different types of web assets and responses, and understand how forms handle data submission using various HTTP methods."
]
+ },
+ "exam-back-end-development-and-apis-certification": {
+ "title": "Back End Development and APIs Certification Exam",
+ "intro": [
+ "Pass this exam to earn your Back End Development and APIs Certification"
+ ]
}
}
},
@@ -7491,14 +7504,7 @@
"note": "If you were previously working through our full stack curriculum, don't worry - you're progress is saved. We split it into smaller certifications for you to earn along your journey. This certification is currently in development and will be available soon. Start earning the required certifications so you're ready when it launches.",
"intro": [
"This certification represents the culmination of your full stack developer journey. It demonstrates your ability to build complete, modern web applications from start to finish.",
- "To qualify for the exam, you must earn the following certifications:",
- "- Responsive Web Design Certification",
- "- JavaScript Certification",
- "- Front End Development Libraries Certification",
- "- Python Certification",
- "- Relational Databases Certification",
- "- Back End Development and APIs Certification",
- "Pass the exam to earn your Full Stack Developer Certification."
+ "To qualify for the exam, you must earn the certifications below. Pass the exam to earn your Full Stack Developer Certification."
],
"chapters": {
"certified-full-stack-developer-exam": "Certified Full Stack Developer Exam"
@@ -7510,7 +7516,7 @@
"certified-full-stack-developer-exam": {
"note": "Coming Late 2026",
"intro": [
- "This will be a 90 question exam testing what you have learned throughout this certification."
+ "This exam will test what you have learned throughout the previous six certifications."
]
}
},
@@ -7658,6 +7664,7 @@
},
"responsive-web-design-v9": {
"title": "Responsive Web Design Certification",
+ "note": "This certification is currently in development and will be available soon. We recommend completing the available courses below to prepare for the certification exam once it is released.",
"intro": [
"This course teaches the fundamentals of HTML and CSS, including modern layout, design, accessibility, and responsive web development. You'll build practical projects and gain the skills to create professional, user-friendly webpages.",
"To qualify for the exam, you must complete the following projects:",
@@ -7673,6 +7680,14 @@
"css": "CSS",
"responsive-web-design-certification-exam": "Responsive Web Design Certification Exam"
},
+ "module-intros": {
+ "responsive-web-design-certification-exam": {
+ "note": "Coming Winter 2025",
+ "intro": [
+ "Pass this exam to earn your Responsive Web Design Certification."
+ ]
+ }
+ },
"modules": {
"basic-html": "Basic HTML",
"semantic-html": "Semantic HTML",
@@ -9023,6 +9038,7 @@
"misc-text": {
"browse-other": "Browse our other free certifications",
"courses": "Courses",
+ "requirements": "Requirements",
"steps": "Steps",
"expand": "Expand course",
"collapse": "Collapse course",
diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json
index d02b1e796a1..db182c47096 100644
--- a/client/i18n/locales/english/translations.json
+++ b/client/i18n/locales/english/translations.json
@@ -213,6 +213,7 @@
"next-heading": "Try our beta curriculum:",
"upcoming-heading": "Upcoming curriculum:",
"catalog-heading": "Explore our Catalog:",
+ "fsd-restructure-note": "If you were previously working through our Certified Full Stack Developer curriculum, don't worry - your progress is saved. We've split it into smaller certifications you can earn along your journey.",
"archive-link": "Looking for older coursework? Check out <0>our archive page0>.",
"faq": "Frequently asked questions:",
"faqs": [
@@ -1227,6 +1228,18 @@
"a2-english-for-developers-cert": "A2 English for Developers Certification",
"b1-english-for-developers": "B1 English for Developers",
"b1-english-for-developers-cert": "B1 English for Developers Certification",
+ "responsive-web-design-v9": "Responsive Web Design",
+ "responsive-web-design-v9-cert": "Responsive Web Design Certification",
+ "javascript-v9": "JavaScript",
+ "javascript-v9-cert": "JavaScript Certification",
+ "front-end-development-libraries-v9": "Front End Development Libraries",
+ "front-end-development-libraries-v9-cert": "Front End Development Libraries Certification",
+ "python-v9": "Python",
+ "python-v9-cert": "Python Certification",
+ "relational-databases-v9": "Relational Database",
+ "relational-databases-v9-cert": "Relational Database Certification",
+ "back-end-development-and-apis-v9": "Back End Development and APIs",
+ "back-end-development-and-apis-v9-cert": "Back End Development and APIs Certification",
"full-stack-developer-v9": "Full Stack Developer",
"full-stack-developer-v9-cert": "Full Stack Developer Certification",
"a1-professional-spanish": "A1 Professional Spanish",
diff --git a/client/src/components/Map/index.tsx b/client/src/components/Map/index.tsx
index b68b52d60a2..113ee6655e0 100644
--- a/client/src/components/Map/index.tsx
+++ b/client/src/components/Map/index.tsx
@@ -113,6 +113,9 @@ function Map({ forLanding = false }: MapProps) {
{t(superBlockHeadings[stage])}
+ {stage === SuperBlockStage.Core && (
+ {t('landing.fsd-restructure-note')}
+ )}
{superblocks.map(superblock => (
{chapterLabel}
+ {isLinkChapter && examSlug && (
+
+ )}
- {!comingSoon && (
+ {!comingSoon && !isLinkChapter && (
{t('learn.steps-completed', {
totalSteps,
diff --git a/client/src/templates/Introduction/components/super-block-map.tsx b/client/src/templates/Introduction/components/super-block-map.tsx
new file mode 100644
index 00000000000..3859a4683fe
--- /dev/null
+++ b/client/src/templates/Introduction/components/super-block-map.tsx
@@ -0,0 +1,221 @@
+import React, { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Spacer } from '@freecodecamp/ui';
+
+import {
+ certificationCollectionSuperBlocks,
+ chapterBasedSuperBlocks,
+ SuperBlocks
+} from '../../../../../shared-dist/config/curriculum';
+import type { CertTitle } from '../../../../config/cert-and-project-map';
+import type {
+ ChapterBasedSuperBlockStructure,
+ ClaimedCertifications,
+ User
+} from '../../../redux/prop-types';
+import type {
+ BlockLabel,
+ BlockLayouts
+} from '../../../../../shared-dist/config/blocks';
+import { SuperBlockIcon } from '../../../assets/superblock-icon';
+import { Link } from '../../../components/helpers';
+import {
+ certSlugTypeMap,
+ certificationRequirements,
+ superBlockToCertMap
+} from '../../../../../shared-dist/config/certification-settings';
+import CheckMark from './check-mark';
+
+import Block from './block';
+import CertChallenge from './cert-challenge';
+import { SuperBlockAccordion } from './super-block-accordion';
+import './super-block-accordion.css';
+
+type Challenge = {
+ block: string;
+ blockLabel: BlockLabel;
+ blockLayout: BlockLayouts;
+ challengeType: number;
+ dashedName: string;
+ fields: { slug: string };
+ id: string;
+ module: string;
+ order: number;
+ superBlock: SuperBlocks;
+ title: string;
+};
+
+type SuperBlockMapProps = {
+ certification: string;
+ completedChallengeIds: string[];
+ disabledBlocks: string[];
+ initialExpandedBlock: string;
+ showCertification: boolean;
+ structure?: ChapterBasedSuperBlockStructure;
+ superBlock: SuperBlocks;
+ superBlockChallenges: Challenge[];
+ title: CertTitle;
+ user: User | null;
+};
+
+const BlockList = ({
+ certification,
+ disabledBlocks,
+ showCertification,
+ superBlock,
+ superBlockChallenges,
+ title,
+ user
+}: {
+ certification: string;
+ disabledBlocks: string[];
+ showCertification: boolean;
+ superBlock: SuperBlocks;
+ superBlockChallenges: Challenge[];
+ title: CertTitle;
+ user: User | null;
+}) => {
+ const visibleBlocks = useMemo(() => {
+ const uniqueBlocks = Array.from(
+ new Set(superBlockChallenges.map(({ block }) => block))
+ );
+
+ return uniqueBlocks.filter(block => !disabledBlocks.includes(block));
+ }, [disabledBlocks, superBlockChallenges]);
+
+ return (
+
+ {visibleBlocks.map(block => {
+ const blockChallenges = superBlockChallenges.filter(
+ challenge => challenge.block === block
+ );
+ const blockLabel = blockChallenges[0]?.blockLabel ?? null;
+
+ if (!blockChallenges.length) return null;
+
+ return (
+
+ );
+ })}
+ {showCertification && !!user && (
+
+ )}
+
+ );
+};
+
+export const SuperBlockMap = ({
+ certification,
+ completedChallengeIds,
+ disabledBlocks,
+ initialExpandedBlock,
+ showCertification,
+ structure,
+ superBlock,
+ superBlockChallenges,
+ title,
+ user
+}: SuperBlockMapProps) => {
+ const { t } = useTranslation();
+ if (chapterBasedSuperBlocks.includes(superBlock)) {
+ if (!structure) return null;
+
+ const getRequirementItems = () => {
+ const certificationForSuperBlock = superBlockToCertMap[superBlock];
+ const requirementsLookup = certificationRequirements as Partial<
+ Record
+ >;
+ const requirements: SuperBlocks[] =
+ (certificationForSuperBlock &&
+ requirementsLookup[certificationForSuperBlock]) ??
+ [];
+
+ const requirementItems = requirements.map((requirement: SuperBlocks) => {
+ const requirementTitle = t(`intro:${requirement}.title`);
+ const requirementLink = `/learn/${requirement}/`;
+
+ const certSlug = superBlockToCertMap[requirement];
+ const certFlagLookup = certSlugTypeMap as Record<
+ string,
+ keyof ClaimedCertifications
+ >;
+ const certFlagKey = certSlug ? certFlagLookup[certSlug] : undefined;
+ const isRequirementComplete = Boolean(
+ certFlagKey && user?.[certFlagKey]
+ );
+
+ return (
+
+
+
+
+
+
+
+ {requirementTitle}
+
+
+
+ );
+ });
+
+ return requirementItems;
+ };
+
+ return (
+ <>
+ {certificationCollectionSuperBlocks.includes(superBlock) && (
+ <>
+
+ {getRequirementItems()}
+
+
+
+ {t(`intro:misc-text.courses`)}
+
+
+ >
+ )}
+
+
+ >
+ );
+ }
+
+ return (
+
+ );
+};
+
+SuperBlockMap.displayName = 'SuperBlockMap';
+
+export default SuperBlockMap;
diff --git a/client/src/templates/Introduction/super-block-intro.tsx b/client/src/templates/Introduction/super-block-intro.tsx
index 7caf822e541..8c1f90b1c66 100644
--- a/client/src/templates/Introduction/super-block-intro.tsx
+++ b/client/src/templates/Introduction/super-block-intro.tsx
@@ -1,7 +1,7 @@
import i18next from 'i18next';
import { WindowLocation } from '@gatsbyjs/reach-router';
import { graphql } from 'gatsby';
-import { uniq, isEmpty, last } from 'lodash-es';
+import { isEmpty, last } from 'lodash-es';
import React, { useEffect, memo, useMemo } from 'react';
import Helmet from 'react-helmet';
import { useTranslation, withTranslation } from 'react-i18next';
@@ -13,8 +13,8 @@ import { Container, Col, Row, Spacer } from '@freecodecamp/ui';
import { useFeatureValue } from '@growthbook/growthbook-react';
import {
- chapterBasedSuperBlocks,
- SuperBlocks
+ SuperBlocks,
+ certificationCollectionSuperBlocks
} from '../../../../shared-dist/config/curriculum';
import DonateModal from '../../components/Donation/donation-modal';
import Login from '../../components/Header/components/login';
@@ -39,12 +39,10 @@ import {
BlockLayouts,
BlockLabel
} from '../../../../shared-dist/config/blocks';
-import Block from './components/block';
-import CertChallenge from './components/cert-challenge';
import LegacyLinks from './components/legacy-links';
import HelpTranslate from './components/help-translate';
import SuperBlockIntro from './components/super-block-intro';
-import { SuperBlockAccordion } from './components/super-block-accordion';
+import SuperBlockMap from './components/super-block-map';
import { resetExpansion, toggleBlock } from './redux';
import './intro.css';
@@ -178,8 +176,6 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
() => allChallenges.filter(c => c.superBlock === superBlock),
[allChallenges, superBlock]
);
- const blocks = uniq(superBlockChallenges.map(({ block }) => block));
-
const completedChallenges = useMemo(
() =>
(user?.completedChallenges ?? []).filter(completedChallenge =>
@@ -239,7 +235,9 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
}
}
- return blocks[0];
+ const fallbackBlock = superBlockChallenges[0]?.block;
+
+ return fallbackBlock ?? '';
};
const initializeExpandedState = () => {
@@ -279,51 +277,25 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
- {t(`intro:misc-text.courses`)}
+ {certificationCollectionSuperBlocks.includes(superBlock)
+ ? t(`intro:misc-text.requirements`)
+ : t(`intro:misc-text.courses`)}
- {chapterBasedSuperBlocks.includes(superBlock) ? (
- c.id)}
- />
- ) : (
-
- {blocks
- .filter(block => {
- return !disabledBlocksFeature.includes(block);
- })
- .map(block => {
- const blockChallenges = superBlockChallenges.filter(
- c => c.block === block
- );
- const blockLabel = blockChallenges[0].blockLabel;
-
- return (
-
- );
- })}
- {showCertification && !!user && (
-
- )}
-
- )}
+ c.id)}
+ disabledBlocks={disabledBlocksFeature}
+ initialExpandedBlock={initialExpandedBlock}
+ showCertification={showCertification}
+ structure={
+ currentSuperBlockStructure as ChapterBasedSuperBlockStructure
+ }
+ superBlock={superBlock}
+ superBlockChallenges={superBlockChallenges}
+ title={title}
+ user={user}
+ />
{!isSignedIn && !signInLoading && (
<>
diff --git a/curriculum/structure/superblocks/javascript-v9.json b/curriculum/structure/superblocks/javascript-v9.json
index e5a2fb63b8b..51e0878ec8f 100644
--- a/curriculum/structure/superblocks/javascript-v9.json
+++ b/curriculum/structure/superblocks/javascript-v9.json
@@ -327,9 +327,11 @@
},
{
"chapterType": "exam",
+ "comingSoon": true,
"dashedName": "javascript-certification-exam",
"modules": [
{
+ "comingSoon": true,
"dashedName": "javascript-certification-exam",
"blocks": ["exam-javascript-certification"]
}
diff --git a/curriculum/structure/superblocks/responsive-web-design-v9.json b/curriculum/structure/superblocks/responsive-web-design-v9.json
index b200392653e..c012f4497a6 100644
--- a/curriculum/structure/superblocks/responsive-web-design-v9.json
+++ b/curriculum/structure/superblocks/responsive-web-design-v9.json
@@ -296,9 +296,11 @@
},
{
"chapterType": "exam",
+ "comingSoon": true,
"dashedName": "responsive-web-design-certification-exam",
"modules": [
{
+ "comingSoon": true,
"dashedName": "responsive-web-design-certification-exam",
"blocks": ["exam-responsive-web-design-certification"]
}
diff --git a/e2e/cert-username-case-navigation.spec.ts b/e2e/cert-username-case-navigation.spec.ts
index aac7cf7ca3d..628cae9c691 100644
--- a/e2e/cert-username-case-navigation.spec.ts
+++ b/e2e/cert-username-case-navigation.spec.ts
@@ -16,7 +16,7 @@ test.describe('Public profile certifications', () => {
await expect(
page.getByRole('link', { name: /View.+Certification/ })
- ).toHaveCount(20);
+ ).toHaveCount(22);
});
test('Should show claimed certifications if the username includes uppercase characters', async ({
@@ -48,7 +48,7 @@ test.describe('Public profile certifications', () => {
await page.waitForURL('/certifiedboozer');
await expect(
page.getByRole('link', { name: /View.+Certification/ })
- ).toHaveCount(20);
+ ).toHaveCount(22);
});
test.afterAll(() => {
diff --git a/e2e/donation-modal.spec.ts b/e2e/donation-modal.spec.ts
index 5ec7ecd9d4a..22feefc033e 100644
--- a/e2e/donation-modal.spec.ts
+++ b/e2e/donation-modal.spec.ts
@@ -318,7 +318,7 @@ test.describe('Donation modal appearance logic - Certified user claiming a new b
page
}) => {
await page.goto(
- '/learn/full-stack-developer/review-basic-html/basic-html-review'
+ '/learn/responsive-web-design-v9/review-basic-html/basic-html-review'
);
await page.getByRole('checkbox', { name: /Review/ }).click();
@@ -334,7 +334,7 @@ test.describe('Donation modal appearance logic - Certified user claiming a new b
test('should not appear if FSD review module is completed', async ({
page
}) => {
- await page.goto('/learn/full-stack-developer/review-html/review-html');
+ await page.goto('/learn/responsive-web-design-v9/review-html/review-html');
await page.getByRole('checkbox', { name: /Review/ }).click();
await page.getByRole('button', { name: 'Submit', exact: true }).click();
await page.getByRole('button', { name: /Submit and go/ }).click();
@@ -361,7 +361,7 @@ test.describe('Donation modal appearance logic - Certified user claiming a new m
// This lecture is not added to the seed data, so it is not completed.
// By completing this lecture, we claim both the block and its module.
await page.goto(
- '/learn/full-stack-developer/lecture-working-with-code-editors-and-ides/what-are-some-good-vs-code-extensions-you-can-use-in-your-editor'
+ '/learn/relational-databases-v9/lecture-working-with-code-editors-and-ides/what-are-some-good-vs-code-extensions-you-can-use-in-your-editor'
);
// Wait for the page content to render
diff --git a/e2e/full-stack-page.spec.ts b/e2e/full-stack-page.spec.ts
new file mode 100644
index 00000000000..7bdeff2eea1
--- /dev/null
+++ b/e2e/full-stack-page.spec.ts
@@ -0,0 +1,82 @@
+import { test, expect } from '@playwright/test';
+
+const requiredCerts = [
+ {
+ text: 'Responsive Web Design Certification',
+ slug: '/learn/responsive-web-design-v9/'
+ },
+ {
+ text: 'JavaScript Certification',
+ slug: '/learn/javascript-v9/'
+ },
+ {
+ text: 'Front End Development Libraries Certification',
+ slug: '/learn/front-end-development-libraries-v9/'
+ },
+ {
+ text: 'Python Certification',
+ slug: '/learn/python-v9/'
+ },
+ {
+ text: 'Relational Databases Certification',
+ slug: '/learn/relational-databases-v9/'
+ },
+ {
+ text: 'Back End Development and APIs Certification',
+ slug: '/learn/back-end-development-and-apis-v9/'
+ }
+];
+
+test.describe('Full Stack Developer V9 superBlock page', () => {
+ test('lists and links to requirements', async ({ page }) => {
+ await page.goto('/learn/full-stack-developer-v9/');
+
+ const reqList = page.locator('.requirement-list');
+ await expect(reqList).toBeVisible();
+
+ const reqLinks = reqList.locator('.chapter.requirement .chapter-button');
+ await expect(reqLinks).toHaveCount(requiredCerts.length);
+
+ for (let i = 0; i < requiredCerts.length; i++) {
+ const reqLink = reqLinks.nth(i);
+ await expect(reqLink).toBeVisible();
+ await expect(reqLink).toContainText(requiredCerts[i].text);
+ await expect(reqLink).toHaveAttribute('href', requiredCerts[i].slug);
+ }
+ });
+
+ if (process.env.SHOW_UPCOMING_CHANGES === 'true') {
+ test('shows the exam', async ({ page }) => {
+ await page.goto('/learn/full-stack-developer-v9/');
+ const examChapterButton = page.locator('.chapter .chapter-button', {
+ hasText: /certified full stack developer exam/i
+ });
+
+ await expect(examChapterButton).toBeVisible();
+ await expect(examChapterButton).toHaveAttribute(
+ 'href',
+ '/learn/full-stack-developer-v9/exam-certified-full-stack-developer/exam-certified-full-stack-developer'
+ );
+ });
+ } else {
+ test('shows the exam module and coming soon text', async ({ page }) => {
+ await page.goto('/learn/full-stack-developer-v9/');
+ const examChapterButton = page.locator('.chapter .chapter-button', {
+ hasText: /certified full stack developer exam/i
+ });
+ await expect(examChapterButton).toBeVisible();
+
+ const examModuleButton = page.locator('.module-button', {
+ hasText: /certified full stack developer exam/i
+ });
+ await examModuleButton.click();
+
+ const moduleIntro = page.locator('.module-intro');
+ await expect(moduleIntro).toBeVisible();
+ await expect(moduleIntro).toContainText('Coming Late 2026');
+ await expect(moduleIntro).toContainText(
+ 'This exam will test what you have learned throughout the previous six certifications.'
+ );
+ });
+ }
+});
diff --git a/e2e/hotkeys.spec.ts b/e2e/hotkeys.spec.ts
index 606e9e72d7a..fda92424aa3 100644
--- a/e2e/hotkeys.spec.ts
+++ b/e2e/hotkeys.spec.ts
@@ -25,7 +25,7 @@ const links = {
multipleChoiceQuestion:
'/learn/a2-english-for-developers/learn-greetings-in-your-first-day-at-the-office/task-7',
assignment:
- '/learn/full-stack-developer/review-semantic-html/review-semantic-html'
+ '/learn/responsive-web-design-v9/review-semantic-html/review-semantic-html'
};
const titles = {
diff --git a/e2e/interactive-editor.spec.ts b/e2e/interactive-editor.spec.ts
index 95a4d57e1ae..02f94ee81ef 100644
--- a/e2e/interactive-editor.spec.ts
+++ b/e2e/interactive-editor.spec.ts
@@ -26,7 +26,7 @@ interface PageData {
}
const challengePath =
- '/learn/full-stack-developer/lecture-what-is-css/what-are-some-default-browser-styles-applied-to-html';
+ '/learn/responsive-web-design-v9/lecture-what-is-css/what-are-some-default-browser-styles-applied-to-html';
const challengeTitle = 'Test Challenge Title';
diff --git a/e2e/landing.spec.ts b/e2e/landing.spec.ts
index a8d2881033a..ee4f6a1598b 100644
--- a/e2e/landing.spec.ts
+++ b/e2e/landing.spec.ts
@@ -18,7 +18,13 @@ const landingPageElements = {
} as const;
const nonArchivedSuperBlocks = [
- intro[SuperBlocks.FullStackDeveloper].title,
+ intro[SuperBlocks.RespWebDesignV9].title,
+ intro[SuperBlocks.JsV9].title,
+ intro[SuperBlocks.FrontEndDevLibsV9].title,
+ intro[SuperBlocks.PythonV9].title,
+ intro[SuperBlocks.RelationalDbV9].title,
+ intro[SuperBlocks.BackEndDevApisV9].title,
+ intro[SuperBlocks.FullStackDeveloperV9].title,
intro[SuperBlocks.A2English].title,
intro[SuperBlocks.B1English].title,
intro[SuperBlocks.TheOdinProject].title,
diff --git a/e2e/map.spec.ts b/e2e/map.spec.ts
index 8cfb0d9042f..3d44ca70cc4 100644
--- a/e2e/map.spec.ts
+++ b/e2e/map.spec.ts
@@ -7,7 +7,31 @@ test.beforeEach(async ({ page }) => {
const LANDING_PAGE_LINKS = [
{
- slug: 'full-stack-developer',
+ slug: 'responsive-web-design-v9',
+ name: 'Responsive Web Design Certification'
+ },
+ {
+ slug: 'javascript-v9',
+ name: 'JavaScript Certification'
+ },
+ {
+ slug: 'front-end-development-libraries-v9',
+ name: 'Front End Development Libraries Certification'
+ },
+ {
+ slug: 'python-v9',
+ name: 'Python Certification'
+ },
+ {
+ slug: 'relational-databases-v9',
+ name: 'Relational Databases Certification'
+ },
+ {
+ slug: 'back-end-development-and-apis-v9',
+ name: 'Back End Development and APIs Certification'
+ },
+ {
+ slug: 'full-stack-developer-v9',
name: 'Certified Full Stack Developer Curriculum'
},
{
@@ -40,7 +64,7 @@ test.describe('Map Component', () => {
page.getByText(translations.landing['interview-prep-heading'])
).toBeVisible();
const curriculumBtns = page.getByTestId('curriculum-map-button');
- await expect(curriculumBtns).toHaveCount(8);
+ await expect(curriculumBtns).toHaveCount(14);
for (const { name, slug } of LANDING_PAGE_LINKS) {
const superblockLink = page.getByRole('link', {
diff --git a/e2e/multiple-choice-question-challenge.spec.ts b/e2e/multiple-choice-question-challenge.spec.ts
index d5a3cd4eab5..06756737517 100644
--- a/e2e/multiple-choice-question-challenge.spec.ts
+++ b/e2e/multiple-choice-question-challenge.spec.ts
@@ -4,7 +4,7 @@ import translations from '../client/i18n/locales/english/translations.json';
const pageWithSpeaking =
'/learn/b1-english-for-developers/learn-about-adverbial-phrases/task-19';
const pageWithoutSpeaking =
- '/learn/full-stack-developer/lecture-what-is-css/what-is-the-basic-anatomy-of-a-css-rule';
+ '/learn/responsive-web-design-v9/lecture-what-is-css/what-is-the-basic-anatomy-of-a-css-rule';
test.describe('Multiple Choice Question Challenge - With Speaking Modal', () => {
test.beforeEach(async ({ page }) => {
diff --git a/e2e/project-preview-modal.spec.ts b/e2e/project-preview-modal.spec.ts
index 005e3d8cb69..365985a15e0 100644
--- a/e2e/project-preview-modal.spec.ts
+++ b/e2e/project-preview-modal.spec.ts
@@ -52,7 +52,7 @@ test.describe('Should be shown automatically', () => {
test.describe('Should be shown manually', () => {
test.beforeEach(async ({ page }) => {
const urlWithProjectPreview =
- '/learn/full-stack-developer/lab-drum-machine/build-drum-machine';
+ '/learn/javascript-v9/lab-drum-machine/build-drum-machine';
await page.goto(urlWithProjectPreview);
});
diff --git a/e2e/quiz-challenge.spec.ts b/e2e/quiz-challenge.spec.ts
index 1d3dd51bc23..95060804417 100644
--- a/e2e/quiz-challenge.spec.ts
+++ b/e2e/quiz-challenge.spec.ts
@@ -23,7 +23,8 @@ interface PageData {
};
}
-const quizPath = '/learn/full-stack-developer/quiz-basic-html/quiz-basic-html';
+const quizPath =
+ '/learn/responsive-web-design-v9/quiz-basic-html/quiz-basic-html';
test.describe('Quiz challenge', () => {
test.beforeEach(async ({ page }) => {
@@ -205,7 +206,7 @@ test.describe('Quiz challenge', () => {
// The navigation should be blocked, the user should stay on the same page
await expect(page).toHaveURL(
allowTrailingSlash(
- '/learn/full-stack-developer/quiz-basic-html/quiz-basic-html'
+ '/learn/responsive-web-design-v9/quiz-basic-html/quiz-basic-html'
)
);
@@ -223,7 +224,7 @@ test.describe('Quiz challenge', () => {
.getByRole('button', { name: 'Yes, I want to leave the quiz' })
.click();
- await page.waitForURL('/learn/full-stack-developer/#quiz-basic-html');
+ await page.waitForURL('/learn/responsive-web-design-v9/#quiz-basic-html');
await expect(
page.getByRole('heading', { level: 3, name: 'Basic HTML Quiz' })
).toBeVisible();
@@ -242,7 +243,7 @@ test.describe('Quiz challenge', () => {
.getByRole('button', { name: 'Yes, I want to leave the quiz' })
.click();
- await page.waitForURL('/learn/full-stack-developer/#quiz-basic-html');
+ await page.waitForURL('/learn/responsive-web-design-v9/#quiz-basic-html');
await expect(
page.getByRole('heading', { level: 3, name: 'Basic HTML Quiz' })
).toBeVisible();
diff --git a/e2e/super-block-page.spec.ts b/e2e/super-block-page.spec.ts
index aaf6d08c41d..5b9c65ffabb 100644
--- a/e2e/super-block-page.spec.ts
+++ b/e2e/super-block-page.spec.ts
@@ -106,7 +106,9 @@ test.describe('Super Block Page - Authenticated User', () => {
test('should expand the correct block when user goes to the page from breadcrumb click', async ({
page
}) => {
- await page.goto(`/learn/full-stack-developer/workshop-cafe-menu/step-2`);
+ await page.goto(
+ `/learn/responsive-web-design-v9/workshop-cafe-menu/step-2`
+ );
await page
.getByRole('link', {
@@ -114,7 +116,9 @@ test.describe('Super Block Page - Authenticated User', () => {
})
.click();
- await page.waitForURL('/learn/full-stack-developer/#workshop-cafe-menu');
+ await page.waitForURL(
+ '/learn/responsive-web-design-v9/#workshop-cafe-menu'
+ );
// Chapter
await expect(
@@ -146,7 +150,7 @@ test.describe('Super Block Page - Authenticated User', () => {
);
});
- await page.goto('/learn/full-stack-developer');
+ await page.goto('/learn/responsive-web-design-v9');
// HTML chapter
await expect(
@@ -173,7 +177,7 @@ test.describe('Super Block Page - Authenticated User', () => {
}) => {
test.setTimeout(20000);
- await page.goto('/learn/full-stack-developer');
+ await page.goto('/learn/responsive-web-design-v9');
// HTML chapter
await expect(
@@ -194,7 +198,9 @@ test.describe('Super Block Page - Authenticated User', () => {
})
).toHaveAttribute('aria-expanded', 'true');
- await page.goto('/learn/full-stack-developer/workshop-blog-page/step-2');
+ await page.goto(
+ '/learn/responsive-web-design-v9/workshop-blog-page/step-2'
+ );
// Wait for the page to finish loading so that the current challenge ID can be registered.
await expect(
@@ -202,7 +208,7 @@ test.describe('Super Block Page - Authenticated User', () => {
).toBeVisible();
// Go back to the super block page
- await page.goto('/learn/full-stack-developer');
+ await page.goto('/learn/responsive-web-design-v9');
// Semantic HTML module
await expect(
@@ -248,7 +254,7 @@ test.describe('Super Block Page - Unauthenticated User', () => {
test('should expand the first block of the super block', async ({
page
}) => {
- await page.goto('/learn/full-stack-developer');
+ await page.goto('/learn/responsive-web-design-v9');
// First chapter
await expect(
diff --git a/shared/config/certification-settings.ts b/shared/config/certification-settings.ts
index 3f45a5155db..c4fba928817 100644
--- a/shared/config/certification-settings.ts
+++ b/shared/config/certification-settings.ts
@@ -339,6 +339,19 @@ export const superBlockToCertMap: {
[SuperBlocks.FullStackDeveloper]: null
};
+export const certificationRequirements: Partial<
+ Record
+> = {
+ [Certification.FullStackDeveloperV9]: [
+ SuperBlocks.RespWebDesignV9,
+ SuperBlocks.JsV9,
+ SuperBlocks.FrontEndDevLibsV9,
+ SuperBlocks.PythonV9,
+ SuperBlocks.RelationalDbV9,
+ SuperBlocks.BackEndDevApisV9
+ ]
+};
+
export type CertSlug = (typeof Certification)[keyof typeof Certification];
export const linkedInCredentialIds = {
diff --git a/shared/config/curriculum.ts b/shared/config/curriculum.ts
index 3da5664495a..34d1bf34b2f 100644
--- a/shared/config/curriculum.ts
+++ b/shared/config/curriculum.ts
@@ -104,7 +104,15 @@ export type StageMap = {
// Groups of superblocks in learn map. This should include all superblocks.
export const superBlockStages: StageMap = {
- [SuperBlockStage.Core]: [SuperBlocks.FullStackDeveloper],
+ [SuperBlockStage.Core]: [
+ SuperBlocks.RespWebDesignV9,
+ SuperBlocks.JsV9,
+ SuperBlocks.FrontEndDevLibsV9,
+ SuperBlocks.PythonV9,
+ SuperBlocks.RelationalDbV9,
+ SuperBlocks.BackEndDevApisV9,
+ SuperBlocks.FullStackDeveloperV9
+ ],
[SuperBlockStage.English]: [SuperBlocks.A2English, SuperBlocks.B1English],
[SuperBlockStage.Professional]: [SuperBlocks.FoundationalCSharp],
[SuperBlockStage.Extra]: [
@@ -133,18 +141,12 @@ export const superBlockStages: StageMap = {
[SuperBlockStage.Next]: [],
[SuperBlockStage.Upcoming]: [
SuperBlocks.FullStackOpen,
- SuperBlocks.RespWebDesignV9,
- SuperBlocks.JsV9,
- SuperBlocks.FrontEndDevLibsV9,
- SuperBlocks.PythonV9,
- SuperBlocks.RelationalDbV9,
- SuperBlocks.BackEndDevApisV9,
- SuperBlocks.FullStackDeveloperV9,
SuperBlocks.A1Spanish,
SuperBlocks.A2Spanish,
SuperBlocks.A2Chinese,
SuperBlocks.A1Chinese,
- SuperBlocks.DevPlayground
+ SuperBlocks.DevPlayground,
+ SuperBlocks.FullStackDeveloper
],
// Catalog is treated like upcoming for now
// Add catalog superBlocks to catalog.ts when adding new superBlocks
@@ -428,6 +430,11 @@ export const chapterBasedSuperBlocks = [
];
Object.freeze(chapterBasedSuperBlocks);
+export const certificationCollectionSuperBlocks = [
+ SuperBlocks.FullStackDeveloperV9
+];
+Object.freeze(certificationCollectionSuperBlocks);
+
type Config = {
showUpcomingChanges: boolean;
};
diff --git a/tools/scripts/build/build-external-curricula-data-v1.ts b/tools/scripts/build/build-external-curricula-data-v1.ts
index 39a8436d4d4..762aa7632f0 100644
--- a/tools/scripts/build/build-external-curricula-data-v1.ts
+++ b/tools/scripts/build/build-external-curricula-data-v1.ts
@@ -40,6 +40,13 @@ interface Block {
const ver = 'v1';
export const orderedSuperBlockInfo = [
+ { dashedName: SuperBlocks.RespWebDesignV9, public: false },
+ { dashedName: SuperBlocks.JsV9, public: false },
+ { dashedName: SuperBlocks.FrontEndDevLibsV9, public: false },
+ { dashedName: SuperBlocks.PythonV9, public: false },
+ { dashedName: SuperBlocks.RelationalDbV9, public: false },
+ { dashedName: SuperBlocks.BackEndDevApisV9, public: false },
+ { dashedName: SuperBlocks.FullStackDeveloperV9, public: false },
{ dashedName: SuperBlocks.RespWebDesignNew, public: true },
{ dashedName: SuperBlocks.DataAnalysisPy, public: true },
{ dashedName: SuperBlocks.MachineLearningPy, public: true },
@@ -49,7 +56,6 @@ export const orderedSuperBlockInfo = [
{ dashedName: SuperBlocks.TheOdinProject, public: true },
{ dashedName: SuperBlocks.RespWebDesign, public: true },
{ dashedName: SuperBlocks.PythonForEverybody, public: true },
- { dashedName: SuperBlocks.FullStackDeveloper, public: false },
{ dashedName: SuperBlocks.JsAlgoDataStructNew, public: false },
{ dashedName: SuperBlocks.FrontEndDevLibs, public: false },
{ dashedName: SuperBlocks.DataVis, public: false },
diff --git a/tools/scripts/build/build-external-curricula-data-v2.ts b/tools/scripts/build/build-external-curricula-data-v2.ts
index 8ae11926b43..799d694570b 100644
--- a/tools/scripts/build/build-external-curricula-data-v2.ts
+++ b/tools/scripts/build/build-external-curricula-data-v2.ts
@@ -3,7 +3,10 @@ import { resolve, dirname } from 'path';
import { omit } from 'lodash';
import { submitTypes } from '../../../shared-dist/config/challenge-types';
import { type ChallengeNode } from '../../../client/src/redux/prop-types';
-import { SuperBlocks } from '../../../shared-dist/config/curriculum';
+import {
+ SuperBlocks,
+ chapterBasedSuperBlocks
+} from '../../../shared-dist/config/curriculum';
import type { Chapter } from '../../../shared-dist/config/chapters';
import { getSuperblockStructure } from '../../../curriculum/src/file-handler';
import { patchBlock } from './patches';
@@ -114,9 +117,39 @@ const intros = JSON.parse(
export const orderedSuperBlockInfo: OrderedSuperBlocks = {
[SuperBlockStage.Core]: [
{
- dashedName: SuperBlocks.FullStackDeveloper,
+ dashedName: SuperBlocks.RespWebDesignV9,
public: false,
- title: intros[SuperBlocks.FullStackDeveloper].title
+ title: intros[SuperBlocks.RespWebDesignV9].title
+ },
+ {
+ dashedName: SuperBlocks.JsV9,
+ public: false,
+ title: intros[SuperBlocks.JsV9].title
+ },
+ {
+ dashedName: SuperBlocks.FrontEndDevLibsV9,
+ public: false,
+ title: intros[SuperBlocks.FrontEndDevLibsV9].title
+ },
+ {
+ dashedName: SuperBlocks.PythonV9,
+ public: false,
+ title: intros[SuperBlocks.PythonV9].title
+ },
+ {
+ dashedName: SuperBlocks.RelationalDbV9,
+ public: false,
+ title: intros[SuperBlocks.RelationalDbV9].title
+ },
+ {
+ dashedName: SuperBlocks.BackEndDevApisV9,
+ public: false,
+ title: intros[SuperBlocks.BackEndDevApisV9].title
+ },
+ {
+ dashedName: SuperBlocks.FullStackDeveloperV9,
+ public: false,
+ title: intros[SuperBlocks.FullStackDeveloperV9].title
}
],
@@ -273,7 +306,7 @@ export function buildExtCurriculumDataV2(
});
for (const superBlockKey of superBlockKeys) {
- if (superBlockKey === SuperBlocks.FullStackDeveloper) {
+ if (chapterBasedSuperBlocks.includes(superBlockKey)) {
buildChapterBasedCurriculum(superBlockKey);
} else {
buildBlockBasedCurriculum(superBlockKey);
@@ -284,7 +317,7 @@ export function buildExtCurriculumDataV2(
}
function buildChapterBasedCurriculum(superBlockKey: SuperBlocks) {
- const { chapters } = getSuperblockStructure('full-stack-developer') as {
+ const { chapters } = getSuperblockStructure(superBlockKey) as {
chapters: Chapter[];
};
const blocksWithData = curriculum[superBlockKey].blocks;
diff --git a/tools/scripts/build/external-data-schema-v2.js b/tools/scripts/build/external-data-schema-v2.js
index b8328861a99..fcafc0042b7 100644
--- a/tools/scripts/build/external-data-schema-v2.js
+++ b/tools/scripts/build/external-data-schema-v2.js
@@ -91,7 +91,11 @@ const chapterBasedCurriculumSchema = Joi.object().pattern(
modules: Joi.array()
.items(
Joi.object().keys({
- moduleType: Joi.valid('review', 'exam').optional(),
+ moduleType: Joi.valid(
+ 'review',
+ 'exam',
+ 'cert-project'
+ ).optional(),
name: Joi.string().required(),
comingSoon: Joi.boolean().optional(),
dashedName: Joi.string().regex(slugRE).required(),