mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2025-12-19 10:07:46 -05:00
feat(client/curriculum): release new superblocks, certs coming soon (#63574)
Co-authored-by: ahmad abdolsaheb <ahmad.abdolsaheb@gmail.com>
This commit is contained in:
@@ -52,6 +52,8 @@ describe('certificate routes', () => {
|
|||||||
isMachineLearningPyCertV7: false,
|
isMachineLearningPyCertV7: false,
|
||||||
isCollegeAlgebraPyCertV8: false,
|
isCollegeAlgebraPyCertV8: false,
|
||||||
isFoundationalCSharpCertV8: false,
|
isFoundationalCSharpCertV8: false,
|
||||||
|
// isJavascriptCertV9: false,
|
||||||
|
// isRespWebDesignCertV9: false,
|
||||||
username: 'fcc'
|
username: 'fcc'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -241,6 +243,8 @@ describe('certificate routes', () => {
|
|||||||
isMachineLearningPyCertV7: true,
|
isMachineLearningPyCertV7: true,
|
||||||
isCollegeAlgebraPyCertV8: true,
|
isCollegeAlgebraPyCertV8: true,
|
||||||
isFoundationalCSharpCertV8: true,
|
isFoundationalCSharpCertV8: true,
|
||||||
|
// isJavascriptCertV9: true,
|
||||||
|
// isRespWebDesignCertV9: true,
|
||||||
isA2EnglishCert: true
|
isA2EnglishCert: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5087,6 +5087,7 @@
|
|||||||
},
|
},
|
||||||
"javascript-v9": {
|
"javascript-v9": {
|
||||||
"title": "JavaScript Certification",
|
"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": [
|
"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.",
|
"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:",
|
"To qualify for the exam, you must complete the following projects:",
|
||||||
@@ -5101,6 +5102,12 @@
|
|||||||
"javascript": "JavaScript",
|
"javascript": "JavaScript",
|
||||||
"javascript-certification-exam": "JavaScript Certification Exam"
|
"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": {
|
"modules": {
|
||||||
"javascript-variables-and-strings": "Variables and Strings",
|
"javascript-variables-and-strings": "Variables and Strings",
|
||||||
"javascript-booleans-and-numbers": "Booleans and Numbers",
|
"javascript-booleans-and-numbers": "Booleans and Numbers",
|
||||||
@@ -7483,6 +7490,12 @@
|
|||||||
"intro": [
|
"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."
|
"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.",
|
"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": [
|
"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.",
|
"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:",
|
"To qualify for the exam, you must earn the certifications below. Pass the exam to earn your Full Stack Developer Certification."
|
||||||
"- 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."
|
|
||||||
],
|
],
|
||||||
"chapters": {
|
"chapters": {
|
||||||
"certified-full-stack-developer-exam": "Certified Full Stack Developer Exam"
|
"certified-full-stack-developer-exam": "Certified Full Stack Developer Exam"
|
||||||
@@ -7510,7 +7516,7 @@
|
|||||||
"certified-full-stack-developer-exam": {
|
"certified-full-stack-developer-exam": {
|
||||||
"note": "Coming Late 2026",
|
"note": "Coming Late 2026",
|
||||||
"intro": [
|
"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": {
|
"responsive-web-design-v9": {
|
||||||
"title": "Responsive Web Design Certification",
|
"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": [
|
"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.",
|
"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:",
|
"To qualify for the exam, you must complete the following projects:",
|
||||||
@@ -7673,6 +7680,14 @@
|
|||||||
"css": "CSS",
|
"css": "CSS",
|
||||||
"responsive-web-design-certification-exam": "Responsive Web Design Certification Exam"
|
"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": {
|
"modules": {
|
||||||
"basic-html": "Basic HTML",
|
"basic-html": "Basic HTML",
|
||||||
"semantic-html": "Semantic HTML",
|
"semantic-html": "Semantic HTML",
|
||||||
@@ -9023,6 +9038,7 @@
|
|||||||
"misc-text": {
|
"misc-text": {
|
||||||
"browse-other": "Browse our other free certifications",
|
"browse-other": "Browse our other free certifications",
|
||||||
"courses": "Courses",
|
"courses": "Courses",
|
||||||
|
"requirements": "Requirements",
|
||||||
"steps": "Steps",
|
"steps": "Steps",
|
||||||
"expand": "Expand course",
|
"expand": "Expand course",
|
||||||
"collapse": "Collapse course",
|
"collapse": "Collapse course",
|
||||||
|
|||||||
@@ -213,6 +213,7 @@
|
|||||||
"next-heading": "Try our beta curriculum:",
|
"next-heading": "Try our beta curriculum:",
|
||||||
"upcoming-heading": "Upcoming curriculum:",
|
"upcoming-heading": "Upcoming curriculum:",
|
||||||
"catalog-heading": "Explore our Catalog:",
|
"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 page</0>.",
|
"archive-link": "Looking for older coursework? Check out <0>our archive page</0>.",
|
||||||
"faq": "Frequently asked questions:",
|
"faq": "Frequently asked questions:",
|
||||||
"faqs": [
|
"faqs": [
|
||||||
@@ -1227,6 +1228,18 @@
|
|||||||
"a2-english-for-developers-cert": "A2 English for Developers Certification",
|
"a2-english-for-developers-cert": "A2 English for Developers Certification",
|
||||||
"b1-english-for-developers": "B1 English for Developers",
|
"b1-english-for-developers": "B1 English for Developers",
|
||||||
"b1-english-for-developers-cert": "B1 English for Developers Certification",
|
"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": "Full Stack Developer",
|
||||||
"full-stack-developer-v9-cert": "Full Stack Developer Certification",
|
"full-stack-developer-v9-cert": "Full Stack Developer Certification",
|
||||||
"a1-professional-spanish": "A1 Professional Spanish",
|
"a1-professional-spanish": "A1 Professional Spanish",
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ function Map({ forLanding = false }: MapProps) {
|
|||||||
<h2 className={forLanding ? 'big-heading' : ''}>
|
<h2 className={forLanding ? 'big-heading' : ''}>
|
||||||
{t(superBlockHeadings[stage])}
|
{t(superBlockHeadings[stage])}
|
||||||
</h2>
|
</h2>
|
||||||
|
{stage === SuperBlockStage.Core && (
|
||||||
|
<p>{t('landing.fsd-restructure-note')}</p>
|
||||||
|
)}
|
||||||
<ul key={stage}>
|
<ul key={stage}>
|
||||||
{superblocks.map(superblock => (
|
{superblocks.map(superblock => (
|
||||||
<MapLi
|
<MapLi
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { type Module } from '../../../../../shared-dist/config/modules';
|
|||||||
import envData from '../../../../config/env.json';
|
import envData from '../../../../config/env.json';
|
||||||
import Block from './block';
|
import Block from './block';
|
||||||
import CheckMark from './check-mark';
|
import CheckMark from './check-mark';
|
||||||
|
import { default as BlockLabelComponent } from './block-label';
|
||||||
|
|
||||||
import './super-block-accordion.css';
|
import './super-block-accordion.css';
|
||||||
|
|
||||||
@@ -107,9 +108,12 @@ const Chapter = ({
|
|||||||
</span>
|
</span>
|
||||||
<ChapterIcon className='map-icon' chapter={dashedName as FsdChapters} />
|
<ChapterIcon className='map-icon' chapter={dashedName as FsdChapters} />
|
||||||
{chapterLabel}
|
{chapterLabel}
|
||||||
|
{isLinkChapter && examSlug && (
|
||||||
|
<BlockLabelComponent blockLabel={BlockLabel.exam} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='chapter-button-right'>
|
<div className='chapter-button-right'>
|
||||||
{!comingSoon && (
|
{!comingSoon && !isLinkChapter && (
|
||||||
<span className='chapter-steps'>
|
<span className='chapter-steps'>
|
||||||
{t('learn.steps-completed', {
|
{t('learn.steps-completed', {
|
||||||
totalSteps,
|
totalSteps,
|
||||||
|
|||||||
221
client/src/templates/Introduction/components/super-block-map.tsx
Normal file
221
client/src/templates/Introduction/components/super-block-map.tsx
Normal file
@@ -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 (
|
||||||
|
<div className='block-ui'>
|
||||||
|
{visibleBlocks.map(block => {
|
||||||
|
const blockChallenges = superBlockChallenges.filter(
|
||||||
|
challenge => challenge.block === block
|
||||||
|
);
|
||||||
|
const blockLabel = blockChallenges[0]?.blockLabel ?? null;
|
||||||
|
|
||||||
|
if (!blockChallenges.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Block
|
||||||
|
key={block}
|
||||||
|
block={block}
|
||||||
|
blockLabel={blockLabel}
|
||||||
|
challenges={blockChallenges}
|
||||||
|
superBlock={superBlock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{showCertification && !!user && (
|
||||||
|
<CertChallenge
|
||||||
|
certification={certification}
|
||||||
|
superBlock={superBlock}
|
||||||
|
title={title}
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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<string, SuperBlocks[]>
|
||||||
|
>;
|
||||||
|
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 (
|
||||||
|
<li className='chapter requirement' key={requirement}>
|
||||||
|
<Link
|
||||||
|
className='chapter-button'
|
||||||
|
data-playwright-test-label='requirement-button'
|
||||||
|
to={requirementLink}
|
||||||
|
>
|
||||||
|
<div className='chapter-button-left'>
|
||||||
|
<span className='checkmark-wrap chapter-checkmark-wrap'>
|
||||||
|
<CheckMark isCompleted={isRequirementComplete} />
|
||||||
|
</span>
|
||||||
|
<SuperBlockIcon className='map-icon' superBlock={requirement} />
|
||||||
|
{requirementTitle}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return requirementItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{certificationCollectionSuperBlocks.includes(superBlock) && (
|
||||||
|
<>
|
||||||
|
<ul className='super-block-accordion requirement-list'>
|
||||||
|
{getRequirementItems()}
|
||||||
|
</ul>
|
||||||
|
<Spacer size='m' />
|
||||||
|
<h2 className='text-center big-subheading'>
|
||||||
|
{t(`intro:misc-text.courses`)}
|
||||||
|
</h2>
|
||||||
|
<Spacer size='m' />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SuperBlockAccordion
|
||||||
|
challenges={superBlockChallenges}
|
||||||
|
superBlock={superBlock}
|
||||||
|
structure={structure}
|
||||||
|
chosenBlock={initialExpandedBlock}
|
||||||
|
completedChallengeIds={completedChallengeIds}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BlockList
|
||||||
|
certification={certification}
|
||||||
|
disabledBlocks={disabledBlocks}
|
||||||
|
showCertification={showCertification}
|
||||||
|
superBlock={superBlock}
|
||||||
|
superBlockChallenges={superBlockChallenges}
|
||||||
|
title={title}
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SuperBlockMap.displayName = 'SuperBlockMap';
|
||||||
|
|
||||||
|
export default SuperBlockMap;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import { WindowLocation } from '@gatsbyjs/reach-router';
|
import { WindowLocation } from '@gatsbyjs/reach-router';
|
||||||
import { graphql } from 'gatsby';
|
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 React, { useEffect, memo, useMemo } from 'react';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { useTranslation, withTranslation } from 'react-i18next';
|
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 { useFeatureValue } from '@growthbook/growthbook-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
chapterBasedSuperBlocks,
|
SuperBlocks,
|
||||||
SuperBlocks
|
certificationCollectionSuperBlocks
|
||||||
} from '../../../../shared-dist/config/curriculum';
|
} from '../../../../shared-dist/config/curriculum';
|
||||||
import DonateModal from '../../components/Donation/donation-modal';
|
import DonateModal from '../../components/Donation/donation-modal';
|
||||||
import Login from '../../components/Header/components/login';
|
import Login from '../../components/Header/components/login';
|
||||||
@@ -39,12 +39,10 @@ import {
|
|||||||
BlockLayouts,
|
BlockLayouts,
|
||||||
BlockLabel
|
BlockLabel
|
||||||
} from '../../../../shared-dist/config/blocks';
|
} from '../../../../shared-dist/config/blocks';
|
||||||
import Block from './components/block';
|
|
||||||
import CertChallenge from './components/cert-challenge';
|
|
||||||
import LegacyLinks from './components/legacy-links';
|
import LegacyLinks from './components/legacy-links';
|
||||||
import HelpTranslate from './components/help-translate';
|
import HelpTranslate from './components/help-translate';
|
||||||
import SuperBlockIntro from './components/super-block-intro';
|
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 { resetExpansion, toggleBlock } from './redux';
|
||||||
|
|
||||||
import './intro.css';
|
import './intro.css';
|
||||||
@@ -178,8 +176,6 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
|
|||||||
() => allChallenges.filter(c => c.superBlock === superBlock),
|
() => allChallenges.filter(c => c.superBlock === superBlock),
|
||||||
[allChallenges, superBlock]
|
[allChallenges, superBlock]
|
||||||
);
|
);
|
||||||
const blocks = uniq(superBlockChallenges.map(({ block }) => block));
|
|
||||||
|
|
||||||
const completedChallenges = useMemo(
|
const completedChallenges = useMemo(
|
||||||
() =>
|
() =>
|
||||||
(user?.completedChallenges ?? []).filter(completedChallenge =>
|
(user?.completedChallenges ?? []).filter(completedChallenge =>
|
||||||
@@ -239,7 +235,9 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks[0];
|
const fallbackBlock = superBlockChallenges[0]?.block;
|
||||||
|
|
||||||
|
return fallbackBlock ?? '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const initializeExpandedState = () => {
|
const initializeExpandedState = () => {
|
||||||
@@ -279,51 +277,25 @@ const SuperBlockIntroductionPage = (props: SuperBlockProps) => {
|
|||||||
<HelpTranslate superBlock={superBlock} />
|
<HelpTranslate superBlock={superBlock} />
|
||||||
<Spacer size='l' />
|
<Spacer size='l' />
|
||||||
<h2 className='text-center big-subheading'>
|
<h2 className='text-center big-subheading'>
|
||||||
{t(`intro:misc-text.courses`)}
|
{certificationCollectionSuperBlocks.includes(superBlock)
|
||||||
|
? t(`intro:misc-text.requirements`)
|
||||||
|
: t(`intro:misc-text.courses`)}
|
||||||
</h2>
|
</h2>
|
||||||
<Spacer size='m' />
|
<Spacer size='m' />
|
||||||
{chapterBasedSuperBlocks.includes(superBlock) ? (
|
<SuperBlockMap
|
||||||
<SuperBlockAccordion
|
certification={certification}
|
||||||
challenges={superBlockChallenges}
|
completedChallengeIds={completedChallenges.map(c => c.id)}
|
||||||
superBlock={superBlock}
|
disabledBlocks={disabledBlocksFeature}
|
||||||
structure={
|
initialExpandedBlock={initialExpandedBlock}
|
||||||
currentSuperBlockStructure as ChapterBasedSuperBlockStructure
|
showCertification={showCertification}
|
||||||
}
|
structure={
|
||||||
chosenBlock={initialExpandedBlock}
|
currentSuperBlockStructure as ChapterBasedSuperBlockStructure
|
||||||
completedChallengeIds={completedChallenges.map(c => c.id)}
|
}
|
||||||
/>
|
superBlock={superBlock}
|
||||||
) : (
|
superBlockChallenges={superBlockChallenges}
|
||||||
<div className='block-ui'>
|
title={title}
|
||||||
{blocks
|
user={user}
|
||||||
.filter(block => {
|
/>
|
||||||
return !disabledBlocksFeature.includes(block);
|
|
||||||
})
|
|
||||||
.map(block => {
|
|
||||||
const blockChallenges = superBlockChallenges.filter(
|
|
||||||
c => c.block === block
|
|
||||||
);
|
|
||||||
const blockLabel = blockChallenges[0].blockLabel;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Block
|
|
||||||
key={block}
|
|
||||||
block={block}
|
|
||||||
blockLabel={blockLabel}
|
|
||||||
challenges={blockChallenges}
|
|
||||||
superBlock={superBlock}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{showCertification && !!user && (
|
|
||||||
<CertChallenge
|
|
||||||
certification={certification}
|
|
||||||
superBlock={superBlock}
|
|
||||||
title={title}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isSignedIn && !signInLoading && (
|
{!isSignedIn && !signInLoading && (
|
||||||
<>
|
<>
|
||||||
<Spacer size='l' />
|
<Spacer size='l' />
|
||||||
|
|||||||
@@ -327,9 +327,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"chapterType": "exam",
|
"chapterType": "exam",
|
||||||
|
"comingSoon": true,
|
||||||
"dashedName": "javascript-certification-exam",
|
"dashedName": "javascript-certification-exam",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
|
"comingSoon": true,
|
||||||
"dashedName": "javascript-certification-exam",
|
"dashedName": "javascript-certification-exam",
|
||||||
"blocks": ["exam-javascript-certification"]
|
"blocks": ["exam-javascript-certification"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,9 +296,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"chapterType": "exam",
|
"chapterType": "exam",
|
||||||
|
"comingSoon": true,
|
||||||
"dashedName": "responsive-web-design-certification-exam",
|
"dashedName": "responsive-web-design-certification-exam",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
|
"comingSoon": true,
|
||||||
"dashedName": "responsive-web-design-certification-exam",
|
"dashedName": "responsive-web-design-certification-exam",
|
||||||
"blocks": ["exam-responsive-web-design-certification"]
|
"blocks": ["exam-responsive-web-design-certification"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ test.describe('Public profile certifications', () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: /View.+Certification/ })
|
page.getByRole('link', { name: /View.+Certification/ })
|
||||||
).toHaveCount(20);
|
).toHaveCount(22);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Should show claimed certifications if the username includes uppercase characters', async ({
|
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 page.waitForURL('/certifiedboozer');
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('link', { name: /View.+Certification/ })
|
page.getByRole('link', { name: /View.+Certification/ })
|
||||||
).toHaveCount(20);
|
).toHaveCount(22);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.afterAll(() => {
|
test.afterAll(() => {
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ test.describe('Donation modal appearance logic - Certified user claiming a new b
|
|||||||
page
|
page
|
||||||
}) => {
|
}) => {
|
||||||
await page.goto(
|
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();
|
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 ({
|
test('should not appear if FSD review module is completed', async ({
|
||||||
page
|
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('checkbox', { name: /Review/ }).click();
|
||||||
await page.getByRole('button', { name: 'Submit', exact: true }).click();
|
await page.getByRole('button', { name: 'Submit', exact: true }).click();
|
||||||
await page.getByRole('button', { name: /Submit and go/ }).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.
|
// 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.
|
// By completing this lecture, we claim both the block and its module.
|
||||||
await page.goto(
|
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
|
// Wait for the page content to render
|
||||||
|
|||||||
82
e2e/full-stack-page.spec.ts
Normal file
82
e2e/full-stack-page.spec.ts
Normal file
@@ -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.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -25,7 +25,7 @@ const links = {
|
|||||||
multipleChoiceQuestion:
|
multipleChoiceQuestion:
|
||||||
'/learn/a2-english-for-developers/learn-greetings-in-your-first-day-at-the-office/task-7',
|
'/learn/a2-english-for-developers/learn-greetings-in-your-first-day-at-the-office/task-7',
|
||||||
assignment:
|
assignment:
|
||||||
'/learn/full-stack-developer/review-semantic-html/review-semantic-html'
|
'/learn/responsive-web-design-v9/review-semantic-html/review-semantic-html'
|
||||||
};
|
};
|
||||||
|
|
||||||
const titles = {
|
const titles = {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ interface PageData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const challengePath =
|
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';
|
const challengeTitle = 'Test Challenge Title';
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,13 @@ const landingPageElements = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const nonArchivedSuperBlocks = [
|
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.A2English].title,
|
||||||
intro[SuperBlocks.B1English].title,
|
intro[SuperBlocks.B1English].title,
|
||||||
intro[SuperBlocks.TheOdinProject].title,
|
intro[SuperBlocks.TheOdinProject].title,
|
||||||
|
|||||||
@@ -7,7 +7,31 @@ test.beforeEach(async ({ page }) => {
|
|||||||
|
|
||||||
const LANDING_PAGE_LINKS = [
|
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'
|
name: 'Certified Full Stack Developer Curriculum'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -40,7 +64,7 @@ test.describe('Map Component', () => {
|
|||||||
page.getByText(translations.landing['interview-prep-heading'])
|
page.getByText(translations.landing['interview-prep-heading'])
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
const curriculumBtns = page.getByTestId('curriculum-map-button');
|
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) {
|
for (const { name, slug } of LANDING_PAGE_LINKS) {
|
||||||
const superblockLink = page.getByRole('link', {
|
const superblockLink = page.getByRole('link', {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import translations from '../client/i18n/locales/english/translations.json';
|
|||||||
const pageWithSpeaking =
|
const pageWithSpeaking =
|
||||||
'/learn/b1-english-for-developers/learn-about-adverbial-phrases/task-19';
|
'/learn/b1-english-for-developers/learn-about-adverbial-phrases/task-19';
|
||||||
const pageWithoutSpeaking =
|
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.describe('Multiple Choice Question Challenge - With Speaking Modal', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ test.describe('Should be shown automatically', () => {
|
|||||||
test.describe('Should be shown manually', () => {
|
test.describe('Should be shown manually', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
const urlWithProjectPreview =
|
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);
|
await page.goto(urlWithProjectPreview);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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.describe('Quiz challenge', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
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
|
// The navigation should be blocked, the user should stay on the same page
|
||||||
await expect(page).toHaveURL(
|
await expect(page).toHaveURL(
|
||||||
allowTrailingSlash(
|
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' })
|
.getByRole('button', { name: 'Yes, I want to leave the quiz' })
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
await page.waitForURL('/learn/full-stack-developer/#quiz-basic-html');
|
await page.waitForURL('/learn/responsive-web-design-v9/#quiz-basic-html');
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('heading', { level: 3, name: 'Basic HTML Quiz' })
|
page.getByRole('heading', { level: 3, name: 'Basic HTML Quiz' })
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
@@ -242,7 +243,7 @@ test.describe('Quiz challenge', () => {
|
|||||||
.getByRole('button', { name: 'Yes, I want to leave the quiz' })
|
.getByRole('button', { name: 'Yes, I want to leave the quiz' })
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
await page.waitForURL('/learn/full-stack-developer/#quiz-basic-html');
|
await page.waitForURL('/learn/responsive-web-design-v9/#quiz-basic-html');
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('heading', { level: 3, name: 'Basic HTML Quiz' })
|
page.getByRole('heading', { level: 3, name: 'Basic HTML Quiz' })
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|||||||
@@ -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 ({
|
test('should expand the correct block when user goes to the page from breadcrumb click', async ({
|
||||||
page
|
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
|
await page
|
||||||
.getByRole('link', {
|
.getByRole('link', {
|
||||||
@@ -114,7 +116,9 @@ test.describe('Super Block Page - Authenticated User', () => {
|
|||||||
})
|
})
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
await page.waitForURL('/learn/full-stack-developer/#workshop-cafe-menu');
|
await page.waitForURL(
|
||||||
|
'/learn/responsive-web-design-v9/#workshop-cafe-menu'
|
||||||
|
);
|
||||||
|
|
||||||
// Chapter
|
// Chapter
|
||||||
await expect(
|
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
|
// HTML chapter
|
||||||
await expect(
|
await expect(
|
||||||
@@ -173,7 +177,7 @@ test.describe('Super Block Page - Authenticated User', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
test.setTimeout(20000);
|
test.setTimeout(20000);
|
||||||
|
|
||||||
await page.goto('/learn/full-stack-developer');
|
await page.goto('/learn/responsive-web-design-v9');
|
||||||
|
|
||||||
// HTML chapter
|
// HTML chapter
|
||||||
await expect(
|
await expect(
|
||||||
@@ -194,7 +198,9 @@ test.describe('Super Block Page - Authenticated User', () => {
|
|||||||
})
|
})
|
||||||
).toHaveAttribute('aria-expanded', 'true');
|
).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.
|
// Wait for the page to finish loading so that the current challenge ID can be registered.
|
||||||
await expect(
|
await expect(
|
||||||
@@ -202,7 +208,7 @@ test.describe('Super Block Page - Authenticated User', () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
// Go back to the super block page
|
// 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
|
// Semantic HTML module
|
||||||
await expect(
|
await expect(
|
||||||
@@ -248,7 +254,7 @@ test.describe('Super Block Page - Unauthenticated User', () => {
|
|||||||
test('should expand the first block of the super block', async ({
|
test('should expand the first block of the super block', async ({
|
||||||
page
|
page
|
||||||
}) => {
|
}) => {
|
||||||
await page.goto('/learn/full-stack-developer');
|
await page.goto('/learn/responsive-web-design-v9');
|
||||||
|
|
||||||
// First chapter
|
// First chapter
|
||||||
await expect(
|
await expect(
|
||||||
|
|||||||
@@ -339,6 +339,19 @@ export const superBlockToCertMap: {
|
|||||||
[SuperBlocks.FullStackDeveloper]: null
|
[SuperBlocks.FullStackDeveloper]: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const certificationRequirements: Partial<
|
||||||
|
Record<Certification, SuperBlocks[]>
|
||||||
|
> = {
|
||||||
|
[Certification.FullStackDeveloperV9]: [
|
||||||
|
SuperBlocks.RespWebDesignV9,
|
||||||
|
SuperBlocks.JsV9,
|
||||||
|
SuperBlocks.FrontEndDevLibsV9,
|
||||||
|
SuperBlocks.PythonV9,
|
||||||
|
SuperBlocks.RelationalDbV9,
|
||||||
|
SuperBlocks.BackEndDevApisV9
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
export type CertSlug = (typeof Certification)[keyof typeof Certification];
|
export type CertSlug = (typeof Certification)[keyof typeof Certification];
|
||||||
|
|
||||||
export const linkedInCredentialIds = {
|
export const linkedInCredentialIds = {
|
||||||
|
|||||||
@@ -104,7 +104,15 @@ export type StageMap = {
|
|||||||
|
|
||||||
// Groups of superblocks in learn map. This should include all superblocks.
|
// Groups of superblocks in learn map. This should include all superblocks.
|
||||||
export const superBlockStages: StageMap = {
|
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.English]: [SuperBlocks.A2English, SuperBlocks.B1English],
|
||||||
[SuperBlockStage.Professional]: [SuperBlocks.FoundationalCSharp],
|
[SuperBlockStage.Professional]: [SuperBlocks.FoundationalCSharp],
|
||||||
[SuperBlockStage.Extra]: [
|
[SuperBlockStage.Extra]: [
|
||||||
@@ -133,18 +141,12 @@ export const superBlockStages: StageMap = {
|
|||||||
[SuperBlockStage.Next]: [],
|
[SuperBlockStage.Next]: [],
|
||||||
[SuperBlockStage.Upcoming]: [
|
[SuperBlockStage.Upcoming]: [
|
||||||
SuperBlocks.FullStackOpen,
|
SuperBlocks.FullStackOpen,
|
||||||
SuperBlocks.RespWebDesignV9,
|
|
||||||
SuperBlocks.JsV9,
|
|
||||||
SuperBlocks.FrontEndDevLibsV9,
|
|
||||||
SuperBlocks.PythonV9,
|
|
||||||
SuperBlocks.RelationalDbV9,
|
|
||||||
SuperBlocks.BackEndDevApisV9,
|
|
||||||
SuperBlocks.FullStackDeveloperV9,
|
|
||||||
SuperBlocks.A1Spanish,
|
SuperBlocks.A1Spanish,
|
||||||
SuperBlocks.A2Spanish,
|
SuperBlocks.A2Spanish,
|
||||||
SuperBlocks.A2Chinese,
|
SuperBlocks.A2Chinese,
|
||||||
SuperBlocks.A1Chinese,
|
SuperBlocks.A1Chinese,
|
||||||
SuperBlocks.DevPlayground
|
SuperBlocks.DevPlayground,
|
||||||
|
SuperBlocks.FullStackDeveloper
|
||||||
],
|
],
|
||||||
// Catalog is treated like upcoming for now
|
// Catalog is treated like upcoming for now
|
||||||
// Add catalog superBlocks to catalog.ts when adding new superBlocks
|
// Add catalog superBlocks to catalog.ts when adding new superBlocks
|
||||||
@@ -428,6 +430,11 @@ export const chapterBasedSuperBlocks = [
|
|||||||
];
|
];
|
||||||
Object.freeze(chapterBasedSuperBlocks);
|
Object.freeze(chapterBasedSuperBlocks);
|
||||||
|
|
||||||
|
export const certificationCollectionSuperBlocks = [
|
||||||
|
SuperBlocks.FullStackDeveloperV9
|
||||||
|
];
|
||||||
|
Object.freeze(certificationCollectionSuperBlocks);
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
showUpcomingChanges: boolean;
|
showUpcomingChanges: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ interface Block<T> {
|
|||||||
const ver = 'v1';
|
const ver = 'v1';
|
||||||
|
|
||||||
export const orderedSuperBlockInfo = [
|
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.RespWebDesignNew, public: true },
|
||||||
{ dashedName: SuperBlocks.DataAnalysisPy, public: true },
|
{ dashedName: SuperBlocks.DataAnalysisPy, public: true },
|
||||||
{ dashedName: SuperBlocks.MachineLearningPy, public: true },
|
{ dashedName: SuperBlocks.MachineLearningPy, public: true },
|
||||||
@@ -49,7 +56,6 @@ export const orderedSuperBlockInfo = [
|
|||||||
{ dashedName: SuperBlocks.TheOdinProject, public: true },
|
{ dashedName: SuperBlocks.TheOdinProject, public: true },
|
||||||
{ dashedName: SuperBlocks.RespWebDesign, public: true },
|
{ dashedName: SuperBlocks.RespWebDesign, public: true },
|
||||||
{ dashedName: SuperBlocks.PythonForEverybody, public: true },
|
{ dashedName: SuperBlocks.PythonForEverybody, public: true },
|
||||||
{ dashedName: SuperBlocks.FullStackDeveloper, public: false },
|
|
||||||
{ dashedName: SuperBlocks.JsAlgoDataStructNew, public: false },
|
{ dashedName: SuperBlocks.JsAlgoDataStructNew, public: false },
|
||||||
{ dashedName: SuperBlocks.FrontEndDevLibs, public: false },
|
{ dashedName: SuperBlocks.FrontEndDevLibs, public: false },
|
||||||
{ dashedName: SuperBlocks.DataVis, public: false },
|
{ dashedName: SuperBlocks.DataVis, public: false },
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import { resolve, dirname } from 'path';
|
|||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { submitTypes } from '../../../shared-dist/config/challenge-types';
|
import { submitTypes } from '../../../shared-dist/config/challenge-types';
|
||||||
import { type ChallengeNode } from '../../../client/src/redux/prop-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 type { Chapter } from '../../../shared-dist/config/chapters';
|
||||||
import { getSuperblockStructure } from '../../../curriculum/src/file-handler';
|
import { getSuperblockStructure } from '../../../curriculum/src/file-handler';
|
||||||
import { patchBlock } from './patches';
|
import { patchBlock } from './patches';
|
||||||
@@ -114,9 +117,39 @@ const intros = JSON.parse(
|
|||||||
export const orderedSuperBlockInfo: OrderedSuperBlocks = {
|
export const orderedSuperBlockInfo: OrderedSuperBlocks = {
|
||||||
[SuperBlockStage.Core]: [
|
[SuperBlockStage.Core]: [
|
||||||
{
|
{
|
||||||
dashedName: SuperBlocks.FullStackDeveloper,
|
dashedName: SuperBlocks.RespWebDesignV9,
|
||||||
public: false,
|
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) {
|
for (const superBlockKey of superBlockKeys) {
|
||||||
if (superBlockKey === SuperBlocks.FullStackDeveloper) {
|
if (chapterBasedSuperBlocks.includes(superBlockKey)) {
|
||||||
buildChapterBasedCurriculum(superBlockKey);
|
buildChapterBasedCurriculum(superBlockKey);
|
||||||
} else {
|
} else {
|
||||||
buildBlockBasedCurriculum(superBlockKey);
|
buildBlockBasedCurriculum(superBlockKey);
|
||||||
@@ -284,7 +317,7 @@ export function buildExtCurriculumDataV2(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildChapterBasedCurriculum(superBlockKey: SuperBlocks) {
|
function buildChapterBasedCurriculum(superBlockKey: SuperBlocks) {
|
||||||
const { chapters } = getSuperblockStructure('full-stack-developer') as {
|
const { chapters } = getSuperblockStructure(superBlockKey) as {
|
||||||
chapters: Chapter[];
|
chapters: Chapter[];
|
||||||
};
|
};
|
||||||
const blocksWithData = curriculum[superBlockKey].blocks;
|
const blocksWithData = curriculum[superBlockKey].blocks;
|
||||||
|
|||||||
@@ -91,7 +91,11 @@ const chapterBasedCurriculumSchema = Joi.object().pattern(
|
|||||||
modules: Joi.array()
|
modules: Joi.array()
|
||||||
.items(
|
.items(
|
||||||
Joi.object().keys({
|
Joi.object().keys({
|
||||||
moduleType: Joi.valid('review', 'exam').optional(),
|
moduleType: Joi.valid(
|
||||||
|
'review',
|
||||||
|
'exam',
|
||||||
|
'cert-project'
|
||||||
|
).optional(),
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
comingSoon: Joi.boolean().optional(),
|
comingSoon: Joi.boolean().optional(),
|
||||||
dashedName: Joi.string().regex(slugRE).required(),
|
dashedName: Joi.string().regex(slugRE).required(),
|
||||||
|
|||||||
Reference in New Issue
Block a user