chore: import upstream snapshot with attribution
i18n - Build Validation / Validate i18n Builds (24) (push) Has been cancelled
CI - Node.js / Lint (24) (push) Has been cancelled
CI - Node.js / Build (24) (push) Has been cancelled
CI - Node.js / Test (24) (push) Has been cancelled
CI - Node.js / Test - Upcoming Changes (24) (push) Has been cancelled
CI - Node.js / Test - i18n (italian, 24) (push) Has been cancelled
CI - Node.js / Test - i18n (portuguese, 24) (push) Has been cancelled
CD - Docker - GHCR Images / Build and Push Images (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 11:55:53 +08:00
commit dde272c4b8
19405 changed files with 2730632 additions and 0 deletions
@@ -0,0 +1,176 @@
const path = require('path');
const { viewTypes } = require('@freecodecamp/shared/config/challenge-types');
const backend = path.resolve(
__dirname,
'../../src/templates/Challenges/projects/backend/show.tsx'
);
const classic = path.resolve(
__dirname,
'../../src/templates/Challenges/classic/show.tsx'
);
const frontend = path.resolve(
__dirname,
'../../src/templates/Challenges/projects/frontend/show.tsx'
);
const codeAlly = path.resolve(
__dirname,
'../../src/templates/Challenges/codeally/show.tsx'
);
const superBlockIntro = path.resolve(
__dirname,
'../../src/templates/Introduction/super-block-intro.tsx'
);
const quiz = path.resolve(
__dirname,
'../../src/templates/Challenges/quiz/show.tsx'
);
const exam = path.resolve(
__dirname,
'../../src/templates/Challenges/exam/show.tsx'
);
const msTrophy = path.resolve(
__dirname,
'../../src/templates/Challenges/ms-trophy/show.tsx'
);
const fillInTheBlank = path.resolve(
__dirname,
'../../src/templates/Challenges/fill-in-the-blank/show.tsx'
);
const generic = path.resolve(
__dirname,
'../../src/templates/Challenges/generic/show.tsx'
);
const examDownload = path.resolve(
__dirname,
'../../src/templates/Challenges/exam-download/show.tsx'
);
const views = {
backend,
classic,
modern: classic,
frontend,
quiz,
codeAlly,
exam,
msTrophy,
fillInTheBlank,
generic,
examDownload
};
function getIsFirstStepInBlock(id, nodes) {
const current = nodes[id];
const previous = nodes[id - 1];
if (!previous) return true;
return previous.challenge.block !== current.challenge.block;
}
function getTemplateComponent(challengeType) {
return views[viewTypes[challengeType]];
}
exports.getTemplateComponent = getTemplateComponent;
exports.createChallengePages = function (
createPage,
{ idToNextPathCurrentCurriculum, idToPrevPathCurrentCurriculum }
) {
return function (node, index, allChallengeNodes) {
const {
dashedName,
disableLoopProtectTests,
disableLoopProtectPreview,
certification,
superBlock,
chapter,
module,
block,
fields: { slug, blockHashSlug },
required = [],
template,
challengeType,
id,
isLastChallengeInBlock,
saveSubmissionToDB
} = node.challenge;
createPage({
path: slug,
component: getTemplateComponent(challengeType),
context: {
challengeMeta: {
blockHashSlug,
dashedName,
certification,
disableLoopProtectTests,
disableLoopProtectPreview,
superBlock,
chapter,
module,
block,
isFirstStep: getIsFirstStepInBlock(index, allChallengeNodes),
template,
required,
isLastChallengeInBlock: isLastChallengeInBlock,
nextChallengePath: idToNextPathCurrentCurriculum[node.id],
prevChallengePath: idToPrevPathCurrentCurriculum[node.id],
id,
saveSubmissionToDB
},
projectPreview: getProjectPreviewConfig(
node.challenge,
allChallengeNodes
),
id: node.id
}
});
};
};
// TODO: figure out a cleaner way to get the last challenge in a block.
function getProjectPreviewConfig(challenge, allChallengeNodes) {
const { block } = challenge;
const challengesInBlock = allChallengeNodes
.filter(({ challenge }) => challenge.block === block)
.map(({ challenge }) => challenge);
const lastChallenge = challengesInBlock[challengesInBlock.length - 1];
const solutionFiles = lastChallenge.solutions[0] ?? [];
const lastChallengeFiles = lastChallenge.challengeFiles ?? [];
const findFileByKey = (key, files) =>
files.find(file => file.fileKey === key);
const projectPreviewChallengeFiles = lastChallengeFiles.map(file => ({
...file,
contents:
findFileByKey(file.fileKey, solutionFiles)?.contents ?? file.contents
}));
return {
challengeData: {
challengeType: lastChallenge.challengeType,
challengeFiles: projectPreviewChallengeFiles
}
};
}
exports.createSuperBlockIntroPages = function (createPage) {
return function ({ superBlock }) {
createPage({
path: `/learn/${superBlock}/`,
component: superBlockIntro,
context: {
superBlock
}
});
};
};
+1
View File
@@ -0,0 +1 @@
module.exports = require('./challenge-page-creator');
@@ -0,0 +1,142 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import React from 'react';
import { Provider } from 'react-redux';
import ShallowRenderer from 'react-test-renderer/shallow';
import { describe, test, expect, vi } from 'vitest';
import FourOhFourPage from '../../src/pages/404';
import Certification from '../../src/pages/certification';
import Learn from '../../src/pages/learn';
import { createStore } from '../../src/redux/create-store';
import layoutSelector from './layout-selector';
vi.mock('../../src/analytics');
vi.mock('../../src/utils/get-words');
const store = createStore();
// TODO: rather than testing which props passed from layoutSelector to the
// component it renders, test that the rendered component has the expected
// features (i.e. has a footer or not, etc.). That should be possible in
// react-testing-library.
interface NameAndProps {
props: Record<string, unknown>;
name: string;
}
function getComponentNameAndProps(
elementType: React.JSXElementConstructor<never>,
pathname: string,
pageContext?: { challengeMeta?: { block?: string; superBlock?: string } }
): NameAndProps {
const utils = ShallowRenderer.createRenderer();
const LayoutReactComponent = layoutSelector({
element: { type: elementType, props: {}, key: '' },
props: {
data: {},
location: {
pathname
},
pageContext,
params: { '*': '' },
path: ''
}
});
utils.render(<Provider store={store}>{LayoutReactComponent}</Provider>);
const view = utils.getRenderOutput();
return {
props: view.props.children.props as Record<string, unknown>,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
name: view.props.children.type.WrappedComponent.displayName
// TODO: Revisit this when react-test-renderer is replaced with
// react-testing-library
};
}
const challengePageContext = {
challengeMeta: {
block: 'Basic HTML and HTML5',
superBlock: 'responsive-web-design'
}
};
describe('Layout selector', () => {
test('Challenges should have DefaultLayout and no footer', () => {
const challengePath =
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements';
const componentObj = getComponentNameAndProps(
Learn,
challengePath,
challengePageContext
);
expect(componentObj.name).toEqual('DefaultLayout');
expect(componentObj.props.showFooter).toEqual(false);
});
test('SuperBlock path should have DefaultLayout and footer', () => {
const superBlockPath = '/learn/responsive-web-design/';
const componentObj = getComponentNameAndProps(Learn, superBlockPath);
expect(componentObj.name).toEqual('DefaultLayout');
expect(componentObj.props.showFooter).toEqual(true);
});
test('i18n challenge path should have DefaultLayout and no footer', () => {
const challengePath =
'espanol/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements/';
const componentObj = getComponentNameAndProps(
Learn,
challengePath,
challengePageContext
);
expect(componentObj.name).toEqual('DefaultLayout');
expect(componentObj.props.showFooter).toEqual(false);
});
test('i18n superBlock path should have DefaultLayout and footer', () => {
const superBlockPath = '/learn/responsive-web-design/';
const componentObj = getComponentNameAndProps(Learn, superBlockPath);
expect(componentObj.name).toEqual('DefaultLayout');
expect(componentObj.props.showFooter).toEqual(true);
});
test('404 page should have DefaultLayout and footer', () => {
const challengePath =
'/espanol/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements/';
const componentObj = getComponentNameAndProps(
FourOhFourPage,
challengePath
);
expect(componentObj.name).toEqual('DefaultLayout');
expect(componentObj.props.showFooter).toEqual(true);
});
test('Certification path should have CertificationLayout', () => {
const challengePath =
'/certification/mot01/javascript-algorithms-and-data-structures/';
const componentObj = getComponentNameAndProps(Certification, challengePath);
expect(componentObj.name).toEqual('CertificationLayout');
});
test('Status paths should return raw element without layout', () => {
const TestComponent = () => <div>Test</div>;
const statusPath = '/status/version';
const result = layoutSelector({
element: { type: TestComponent, props: {}, key: '' },
props: {
data: {},
location: {
pathname: statusPath
},
params: { '*': '' },
path: ''
}
});
// The result should be the element directly, not wrapped in a layout
expect(result.type).toBe(TestComponent);
});
});
+69
View File
@@ -0,0 +1,69 @@
import React from 'react';
import CertificationLayout from '../../src/components/layouts/certification';
import DefaultLayout from '../../src/components/layouts/default';
import FourOhFourPage from '../../src/pages/404';
interface LayoutSelectorProps {
element: JSX.Element;
props: {
data: { challengeNode?: { challenge?: { usesMultifileEditor?: boolean } } };
location: { pathname: string };
pageContext?: { challengeMeta?: { block?: string; superBlock?: string } };
params: { '*'?: string };
path: string;
};
}
export default function layoutSelector({
element,
props
}: LayoutSelectorProps): JSX.Element {
const {
location: { pathname }
} = props;
const isDailyChallenge = props.path === '/learn/daily-coding-challenge/*';
const dailyChallengeParam = props.params['*'];
const isChallenge = !!props.pageContext?.challengeMeta || isDailyChallenge;
// Return raw element for status endpoints without any layout
if (/^\/status\//.test(pathname)) {
return element;
}
if (element.type === FourOhFourPage) {
return (
<DefaultLayout pathname={pathname} showFooter={true}>
{element}
</DefaultLayout>
);
} else if (/\/certification\//.test(pathname)) {
return (
<CertificationLayout pathname={pathname}>{element}</CertificationLayout>
);
} else if (isChallenge) {
return (
<DefaultLayout
pathname={pathname}
showFooter={false}
isChallenge={true}
isDailyChallenge={isDailyChallenge}
dailyChallengeParam={dailyChallengeParam}
usesMultifileEditor={
props.data?.challengeNode?.challenge?.usesMultifileEditor
}
block={props.pageContext?.challengeMeta?.block}
superBlock={props.pageContext?.challengeMeta?.superBlock}
>
{element}
</DefaultLayout>
);
} else {
return (
<DefaultLayout pathname={pathname} showFooter={true}>
{element}
</DefaultLayout>
);
}
}
+10
View File
@@ -0,0 +1,10 @@
/**
* The function is useful in cases where we want to preload a page
* but the link of the page isn't rendered on the screen.
* For more details, see https://github.com/freeCodeCamp/freeCodeCamp/pull/55472.
*/
export const preloadPage = (path?: string) => {
if (!window.___loader || !path) return;
window.___loader.hovering(path);
};
+5
View File
@@ -0,0 +1,5 @@
const envData = require('../../config/env.json');
const { clientLocale } = envData;
exports.pathPrefix = clientLocale === 'english' ? '' : '/' + clientLocale;