mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat: add catalog (#66680)
Co-authored-by: jdwilkin4 <jwilkin4@hotmail.com>
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"sign-in": "Sign in",
|
||||
"sign-up-email-list": "Sign up for Quincy's weekly emails",
|
||||
"sign-out": "Sign out",
|
||||
"catalog": "Catalog",
|
||||
"curriculum": "Curriculum",
|
||||
"contribute": "Contribute",
|
||||
"podcast": "Podcast",
|
||||
@@ -1500,6 +1501,7 @@
|
||||
"intermediate": "Intermediate",
|
||||
"advanced": "Advanced"
|
||||
},
|
||||
"duration-singular": "{{duration}} hour",
|
||||
"duration": "{{duration}} hours",
|
||||
"no-results": "No courses found. Try adjusting your filters to see more results.",
|
||||
"topic": {
|
||||
|
||||
@@ -152,6 +152,11 @@ function NavLinks({
|
||||
{t('buttons.curriculum')}
|
||||
</Link>
|
||||
</li>
|
||||
<li key='catalog'>
|
||||
<Link className='nav-link' onKeyDown={handleMenuKeyDown} to='/catalog'>
|
||||
{t('buttons.catalog')}
|
||||
</Link>
|
||||
</li>
|
||||
{currentUserName && (
|
||||
<>
|
||||
<li key='profile'>
|
||||
|
||||
@@ -28,6 +28,11 @@ const CatalogItem: React.FC<CatalogItemProps> = ({
|
||||
summary: string[];
|
||||
};
|
||||
|
||||
const duration =
|
||||
hours === 1
|
||||
? t('curriculum.catalog.duration-singular', { duration: hours })
|
||||
: t('curriculum.catalog.duration', { duration: hours });
|
||||
|
||||
return (
|
||||
<Link to={`/learn/${superBlock}`} key={superBlock} className='catalog-item'>
|
||||
<div className='catalog-item-top'>
|
||||
@@ -48,10 +53,7 @@ const CatalogItem: React.FC<CatalogItemProps> = ({
|
||||
</div>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faClock} />
|
||||
{' '}
|
||||
{showAllSummaries
|
||||
? t('curriculum.catalog.duration', { duration: hours })
|
||||
: `${hours} hours`}
|
||||
{duration}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { SuperBlocks } from '@freecodecamp/shared/config/curriculum';
|
||||
import { catalog } from '@freecodecamp/shared/config/catalog';
|
||||
import LandingCatalog from './landing-catalog';
|
||||
|
||||
const featuredSuperBlocks = [
|
||||
SuperBlocks.LearnPythonForBeginners,
|
||||
SuperBlocks.ComputerBasics,
|
||||
SuperBlocks.BasicHtml
|
||||
];
|
||||
|
||||
describe('LandingCatalog', () => {
|
||||
test('renders the catalog heading', () => {
|
||||
render(<LandingCatalog />);
|
||||
expect(screen.getByText('landing.catalog.heading')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders three featured course items and a see all link', () => {
|
||||
render(<LandingCatalog />);
|
||||
// 3 featured courses + 1 "See All" link
|
||||
expect(screen.getAllByRole('link')).toHaveLength(4);
|
||||
});
|
||||
|
||||
test('featured courses link to their superblock learn pages', () => {
|
||||
render(<LandingCatalog />);
|
||||
for (const superBlock of featuredSuperBlocks) {
|
||||
const course = catalog.find(c => c.superBlock === superBlock);
|
||||
const link = screen.getByRole('link', {
|
||||
name: new RegExp(`topic\\.${course!.topic}`)
|
||||
});
|
||||
expect(link).toHaveAttribute('href', `/learn/${superBlock}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('has a "See All Courses" link to /catalog', () => {
|
||||
render(<LandingCatalog />);
|
||||
const seeAllLink = screen.getByRole('link', {
|
||||
name: 'landing.catalog.seeAll'
|
||||
});
|
||||
expect(seeAllLink).toHaveAttribute('href', '/catalog');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, test, vi } from 'vitest';
|
||||
import { catalog } from '@freecodecamp/shared/config/catalog';
|
||||
import CatalogPage from './catalog';
|
||||
|
||||
vi.mock('../components/catalog-item', () => ({
|
||||
default: ({ superBlock }: { superBlock: string }) => (
|
||||
<a data-testid='catalog-item' href={`/learn/${superBlock}`}>
|
||||
{superBlock}
|
||||
</a>
|
||||
)
|
||||
}));
|
||||
|
||||
describe('CatalogPage', () => {
|
||||
test('renders the catalog page title', () => {
|
||||
render(<CatalogPage />);
|
||||
expect(screen.getByText('curriculum.catalog.title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders a catalog item for each entry', () => {
|
||||
render(<CatalogPage />);
|
||||
const items = screen.getAllByTestId('catalog-item');
|
||||
expect(items).toHaveLength(catalog.length);
|
||||
});
|
||||
|
||||
test('catalog items link to their superblock learn pages', () => {
|
||||
render(<CatalogPage />);
|
||||
for (const course of catalog) {
|
||||
const item = screen.getByRole('link', { name: course.superBlock });
|
||||
expect(item).toHaveAttribute('href', `/learn/${course.superBlock}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('renders level and topic filter dropdowns', () => {
|
||||
render(<CatalogPage />);
|
||||
expect(screen.getByText(/Level:/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Topic:/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -2,8 +2,6 @@ import React, { useState, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Col, Spacer, Dropdown, MenuItem, Alert } from '@freecodecamp/ui';
|
||||
import { catalog } from '@freecodecamp/shared/config/catalog';
|
||||
import { showUpcomingChanges } from '../../config/env.json';
|
||||
import FourOhFour from '../components/FourOhFour';
|
||||
import CatalogItem from '../components/catalog-item';
|
||||
|
||||
import './catalog.css';
|
||||
@@ -69,7 +67,7 @@ const CatalogPage = () => {
|
||||
});
|
||||
}, [selectedLevels, selectedTopics]);
|
||||
|
||||
return showUpcomingChanges ? (
|
||||
return (
|
||||
<main>
|
||||
<Spacer size='l' />
|
||||
<h1 className='text-center'>{t('curriculum.catalog.title')}</h1>
|
||||
@@ -164,8 +162,6 @@ const CatalogPage = () => {
|
||||
</Col>
|
||||
<Spacer size='l' />
|
||||
</main>
|
||||
) : (
|
||||
<FourOhFour />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import LandingCatalog from '../components/landing/components/landing-catalog';
|
||||
import Faq from '../components/landing/components/faq';
|
||||
import Benefits from '../components/landing/components/benefits';
|
||||
import { useClaimableCertsNotification } from '../components/helpers/use-claimable-certs-notification';
|
||||
import { showUpcomingChanges } from '../../config/env.json';
|
||||
|
||||
import '../components/landing/landing.css';
|
||||
|
||||
@@ -24,7 +23,7 @@ const Landing = () => (
|
||||
<Benefits />
|
||||
<Testimonials />
|
||||
<Certifications />
|
||||
{showUpcomingChanges && <LandingCatalog />}
|
||||
<LandingCatalog />
|
||||
<Faq />
|
||||
</main>
|
||||
);
|
||||
|
||||
+5
-1
@@ -138,7 +138,7 @@ test.describe('Header', () => {
|
||||
await expect(menuButton).toBeFocused();
|
||||
});
|
||||
|
||||
test('The menu should contain links to: donate, curriculum, forum, news, radio, contribute, and podcast', async ({
|
||||
test('The menu should contain links to: donate, curriculum, catalog, forum, news, radio, contribute, and podcast', async ({
|
||||
page
|
||||
}) => {
|
||||
const menuButton = page.getByTestId(headerComponentElements.menuButton);
|
||||
@@ -157,6 +157,10 @@ test.describe('Header', () => {
|
||||
name: translations.buttons.curriculum,
|
||||
href: '/learn'
|
||||
},
|
||||
{
|
||||
name: translations.buttons.catalog,
|
||||
href: '/catalog'
|
||||
},
|
||||
{
|
||||
name: translations.buttons.forum,
|
||||
href: links.nav.forum
|
||||
|
||||
Reference in New Issue
Block a user