fix(client): address nav UX issues (#40823)

Co-authored-by: nhcarrigan <nhcarrigan@gmail.com>
This commit is contained in:
Tom
2021-01-30 00:43:06 -06:00
committed by Mrugesh Mohapatra
parent 1875984423
commit c56a9c966f
15 changed files with 485 additions and 278 deletions

View File

@ -396,7 +396,8 @@
"update-email-1": "更新你的邮件地址",
"update-email-2": "在这里更新你的邮件地址:",
"email": "邮箱",
"and": "和"
"and": "和",
"change-theme": "Sign in to change theme."
},
"icons": {
"gold-cup": "金奖杯",

View File

@ -398,7 +398,8 @@
"update-email-1": "Update your email address",
"update-email-2": "Update your email address here:",
"email": "Email",
"and": "and"
"and": "and",
"change-theme": "Sign in to change theme."
},
"icons": {
"gold-cup": "Gold Cup",

View File

@ -398,7 +398,8 @@
"update-email-1": "Actualiza tu correo electrónico",
"update-email-2": "Actualiza tu correo electrónico aquí:",
"email": "Correo electrónico",
"and": "y"
"and": "y",
"change-theme": "Sign in to change theme."
},
"icons": {
"gold-cup": "Copa de Oro",

View File

@ -470,7 +470,8 @@ const translationsSchema = {
'update-email-1': 'Update your email address',
'update-email-2': 'Update your email address here:',
email: 'Email',
and: 'and'
and: 'and',
'change-theme': 'Sign in to change theme.'
},
icons: {
'gold-cup': 'Gold Cup',

View File

@ -1,50 +0,0 @@
/* eslint-disable jsx-a11y/no-onchange */
import React from 'react';
import { useTranslation } from 'react-i18next';
const {
availableLangs,
i18nextCodes,
langDisplayNames
} = require('../../../i18n/allLangs');
const { homeLocation } = require('../../../config/env');
const locales = availableLangs.client;
const LanguageMenu = () => {
const { i18n, t } = useTranslation();
const i18nLanguage = i18n.language;
const currentLanguage = Object.keys(i18nextCodes).find(
key => i18nextCodes[key] === i18nLanguage
);
const changeLanguage = e => {
const path = window.location.pathname;
if (e.target.value === 'espanol') {
window.location.replace(`${homeLocation}/espanol${path}`);
} else {
window.location.replace(`${homeLocation}${path}`);
}
};
return (
<div className='language-menu'>
<label>
{t('footer.language')}
<select onChange={e => changeLanguage(e)} value={currentLanguage}>
{locales.map((lang, i) => {
return (
<option key={i} value={lang}>
{langDisplayNames[lang]}
</option>
);
})}
</select>
</label>
</div>
);
};
export default LanguageMenu;

View File

@ -13,32 +13,6 @@ exports[`<Footer /> matches snapshot 1`] = `
<div
className="footer-desc-col"
>
<div
className="language-menu"
>
<label>
footer.language
<select
onChange={[Function]}
>
<option
value="english"
>
English
</option>
<option
value="espanol"
>
Español
</option>
<option
value="chinese"
>
中文
</option>
</select>
</label>
</div>
<p>
footer.tax-exempt-status
</p>

View File

@ -20,21 +20,6 @@
overflow-x: hidden;
}
.footer-container .language-menu {
display: flex;
margin-bottom: 10px;
}
.footer-container .language-menu label {
font-weight: normal;
}
.footer-container .language-menu select {
padding: 0 5px;
margin-left: 10px;
font-weight: normal;
}
.footer-container p {
margin: 0 0 1.45rem;
line-height: 30px;

View File

@ -2,11 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import Link from '../helpers/Link';
import LanguageMenu from './LanguageMenu';
import './footer.css';
const { showLocaleDropdownMenu = false } = require('../../../config/env');
const propTypes = {
children: PropTypes.any
};
@ -26,7 +23,6 @@ function Footer() {
<div className='footer-container'>
<div className='footer-top'>
<div className='footer-desc-col'>
{showLocaleDropdownMenu ? <LanguageMenu /> : null}
<p>{t('footer.tax-exempt-status')}</p>
<p>{t('footer.mission-statement')}</p>
<p>{t('footer.donation-initiatives')}</p>

View File

@ -1,21 +1,23 @@
/* global expect */
import React from 'react';
import ShallowRenderer from 'react-test-renderer/shallow';
/* import { useTranslation } from 'react-i18next';
import { I18nextProvider } from 'react-i18next';
import i18n from '../../../i18n/configForTests';*/
import { UniversalNav } from './components/UniversalNav';
import { NavLinks } from './components/NavLinks';
import AuthOrProfile from './components/AuthOrProfile';
import { apiLocation } from '../../../../config/env.json';
describe('<UniversalNav />', () => {
const UniversalNavProps = {
displayMenu: false,
menuButtonRef: {},
searchBarRef: {},
toggleDisplayMenu: function() {},
pathName: '/'
pathName: '/',
fetchState: {
pending: false
}
};
it('renders to the DOM', () => {
const shallow = new ShallowRenderer();
@ -26,14 +28,14 @@ describe('<UniversalNav />', () => {
});
describe('<NavLinks />', () => {
it('has expected navigation links', () => {
it('has expected navigation links when not signed in', () => {
const landingPageProps = {
fetchState: {
pending: false
},
user: {
isUserDonating: false,
username: '',
isDonating: false,
username: null,
theme: 'default'
},
i18n: {
@ -45,11 +47,70 @@ describe('<NavLinks />', () => {
shallow.render(<NavLinks {...landingPageProps} />);
const result = shallow.getRenderOutput();
expect(
hasRadioNavItem(result) &&
hasForumNavItem(result) &&
hasDonateNavItem(result) &&
hasSignInNavItem(result) &&
hasCurriculumNavItem(result) &&
hasForumNavItem(result) &&
hasNewsNavItem(result) &&
hasDonateNavItem(result)
hasRadioNavItem(result)
).toBeTruthy();
});
it('has expected navigation links when signed in', () => {
const landingPageProps = {
fetchState: {
pending: false
},
user: {
isDonating: false,
username: 'nhcarrigan',
theme: 'default'
},
i18n: {
language: 'en'
},
toggleNightMode: theme => theme
};
const shallow = new ShallowRenderer();
shallow.render(<NavLinks {...landingPageProps} />);
const result = shallow.getRenderOutput();
expect(
hasDonateNavItem(result) &&
hasCurriculumNavItem(result) &&
hasProfileAndSettingsNavItems(result, landingPageProps.user.username) &&
hasForumNavItem(result) &&
hasNewsNavItem(result) &&
hasRadioNavItem(result) &&
hasSignOutNavItem(result)
).toBeTruthy();
});
it('has expected navigation links when signed in and donating', () => {
const landingPageProps = {
fetchState: {
pending: false
},
user: {
isDonating: true,
username: 'moT01',
theme: 'default'
},
i18n: {
language: 'en'
},
toggleNightMode: theme => theme
};
const shallow = new ShallowRenderer();
shallow.render(<NavLinks {...landingPageProps} />);
const result = shallow.getRenderOutput();
expect(
hasThanksForDonating(result) &&
hasCurriculumNavItem(result) &&
hasProfileAndSettingsNavItems(result, landingPageProps.user.username) &&
hasForumNavItem(result) &&
hasNewsNavItem(result) &&
hasRadioNavItem(result) &&
hasSignOutNavItem(result)
).toBeTruthy();
});
});
@ -68,7 +129,6 @@ describe('<AuthOrProfile />', () => {
const shallow = new ShallowRenderer();
shallow.render(<AuthOrProfile {...defaultUserProps} />);
const componentTree = shallow.getRenderOutput();
expect(avatarHasClass(componentTree, 'default-border')).toBeTruthy();
});
@ -125,7 +185,7 @@ describe('<AuthOrProfile />', () => {
});
const navigationLinks = (component, navItem) => {
return component.props.children.props.children[navItem].props.children.props;
return component.props.children[navItem].props;
};
const profileNavItem = component => component.props.children;
@ -135,29 +195,66 @@ const hasDonateNavItem = component => {
return children === 'buttons.donate' && to === '/donate';
};
const hasThanksForDonating = component => {
const { children } = navigationLinks(component, 0);
return children[0].props.children === 'donate.thanks';
};
const hasSignInNavItem = component => {
const { children } = navigationLinks(component, 1);
return children === 'buttons.sign-in';
};
const hasCurriculumNavItem = component => {
const { children, to } = navigationLinks(component, 2);
return children === 'buttons.curriculum' && to === '/learn';
};
const hasProfileAndSettingsNavItems = (component, username) => {
const fragment = navigationLinks(component, 3);
const profile = fragment.children[0].props;
const settings = fragment.children[1].props;
const hasProfile =
profile.children === 'buttons.profile' && profile.to === `/${username}`;
const hasSettings =
settings.children === 'buttons.settings' && settings.to === '/settings';
return hasProfile && hasSettings;
};
const hasForumNavItem = component => {
const { children, to } = navigationLinks(component, 1);
const { children, to } = navigationLinks(component, 5);
return (
children === 'buttons.forum' && to === 'https://forum.freecodecamp.org'
children[0].props.children === 'buttons.forum' &&
to === 'https://forum.freecodecamp.org/'
);
};
const hasNewsNavItem = component => {
const { children, to } = navigationLinks(component, 2);
const { children, to } = navigationLinks(component, 6);
return (
children === 'buttons.news' && to === 'https://www.freecodecamp.org/news'
children[0].props.children === 'buttons.news' &&
to === 'https://www.freecodecamp.org/news'
);
};
const hasCurriculumNavItem = component => {
const { children, to } = navigationLinks(component, 3);
return children === 'buttons.curriculum' && to === '/learn';
const hasRadioNavItem = component => {
const { children, to } = navigationLinks(component, 7);
return (
children[0].props.children === 'buttons.radio' &&
to === 'https://coderadio.freecodecamp.org'
);
};
const hasRadioNavItem = component => {
const { children, to } = navigationLinks(component, 5);
const hasSignOutNavItem = component => {
const { children } = navigationLinks(component, 10);
const signOutProps = children[1].props;
return (
children === 'buttons.radio' && to === 'https://coderadio.freecodecamp.org'
signOutProps.children === 'buttons.sign-out' &&
signOutProps.href === `${apiLocation}/signout`
);
};

View File

@ -2,30 +2,71 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import { Link, SkeletonSprite } from '../../helpers';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
// faCheck,
faCheckSquare,
faHeart,
faSquare,
faExternalLinkAlt
} from '@fortawesome/free-solid-svg-icons';
import { Link } from '../../helpers';
import { updateUserFlag } from '../../../redux/settings';
import {
clientLocale,
forumLocation,
radioLocation,
newsLocation
apiLocation
} from '../../../../../config/env.json';
import createLanguageRedirect from '../../createLanguageRedirect';
// import createLanguageRedirect from '../../createLanguageRedirect';
import createExternalRedirect from '../../createExternalRedirects';
const {
/* const {
availableLangs,
i18nextCodes,
langDisplayNames
} = require('../../../../i18n/allLangs');
} = require('../../../../i18n/allLangs'); */
const locales = availableLangs.client;
// const locales = availableLangs.client;
// The linter was complaining about inline comments. Add the code below above
// the sign out button when the language menu is ready to be added
/*
<div className='nav-link nav-link-header' key='lang-header'>
{t('footer.language')}
</div>
{locales.map(lang =>
// current lang is a button that closes the menu
i18n.language === i18nextCodes[lang] ? (
<button
className='nav-link nav-link-lang nav-link-flex'
onClick={() => toggleDisplayMenu()}
>
<span>{langDisplayNames[lang]}</span>
<FontAwesomeIcon icon={faCheck} />
</button>
) : (
<Link
className='nav-link nav-link-lang nav-link-flex'
external={true}
// Todo: should treat other lang client application links as external??
key={'lang-' + lang}
to={createLanguageRedirect({
clientLocale,
lang
})}
>
{langDisplayNames[lang]}
</Link>
)
)
*/
const propTypes = {
displayMenu: PropTypes.bool,
fetchState: PropTypes.shape({ pending: PropTypes.bool }),
i18n: PropTypes.object,
t: PropTypes.func,
toggleDisplayMenu: PropTypes.func,
toggleNightMode: PropTypes.func.isRequired,
user: PropTypes.object
};
@ -36,117 +77,134 @@ const mapDispatchToProps = {
export class NavLinks extends Component {
toggleTheme(currentTheme = 'default', toggleNightMode) {
console.log('attempting to toggle night mode');
toggleNightMode(currentTheme === 'night' ? 'default' : 'night');
}
render() {
const {
displayMenu,
// i18n,
fetchState,
i18n,
t,
// toggleDisplayMenu,
toggleNightMode,
user: { isUserDonating = false, username, theme }
user: { isDonating = false, username, theme }
} = this.props;
const { pending } = fetchState;
return pending ? (
<div className='nav-skeleton'>
<SkeletonSprite />
</div>
<div className='nav-skeleton' />
) : (
<div className='main-nav-group'>
<ul className={'nav-list' + (displayMenu ? ' display-menu' : '')}>
<li key='donate'>
{isUserDonating ? (
<span className='nav-link'>{t('donate.thanks')}</span>
) : (
<Link
className='nav-link'
external={true}
sameTab={false}
to='/donate'
>
{t('buttons.donate')}
</Link>
)}
</li>
<li key='forum'>
<div className={'nav-list' + (displayMenu ? ' display-menu' : '')}>
{isDonating ? (
<div className='nav-link nav-link-flex nav-link-header' key='donate'>
<span>{t('donate.thanks')}</span>
<FontAwesomeIcon icon={faHeart} />
</div>
) : (
<Link
className='nav-link'
external={true}
key='donate'
sameTab={false}
to='/donate'
>
{t('buttons.donate')}
</Link>
)}
{!username && (
<a
className='nav-link nav-link-sign-in'
href={`${apiLocation}/signin`}
key='signin'
>
{t('buttons.sign-in')}
</a>
)}
<Link className='nav-link' key='learn' to='/learn'>
{t('buttons.curriculum')}
</Link>
{username && (
<>
<Link
className='nav-link'
external={true}
key='profile'
sameTab={false}
to={forumLocation}
to={`/${username}`}
>
{t('buttons.forum')}
{t('buttons.profile')}
</Link>
</li>
<li key='news'>
<Link
className='nav-link'
external={true}
key='settings'
sameTab={false}
to={newsLocation}
to={`/settings`}
>
{t('buttons.news')}
{t('buttons.settings')}
</Link>
</li>
<li key='learn'>
<Link className='nav-link' to='/learn'>
{t('buttons.curriculum')}
</Link>
</li>
{username && (
<li key='profile'>
<Link className='nav-link' to={`/${username}`}>
{t('buttons.profile')}
</Link>
</li>
</>
)}
<hr className='nav-line' />
<Link
className='nav-link nav-link-flex'
external={true}
key='forum'
sameTab={false}
to={createExternalRedirect('forum', { clientLocale })}
>
<span>{t('buttons.forum')}</span>
<FontAwesomeIcon icon={faExternalLinkAlt} />
</Link>
<Link
className='nav-link nav-link-flex'
external={true}
key='news'
sameTab={false}
to={createExternalRedirect('news', { clientLocale })}
>
<span>{t('buttons.news')}</span>
<FontAwesomeIcon icon={faExternalLinkAlt} />
</Link>
<Link
className='nav-link nav-link-flex'
external={true}
key='radio'
sameTab={false}
to={radioLocation}
>
<span>{t('buttons.radio')}</span>
<FontAwesomeIcon icon={faExternalLinkAlt} />
</Link>
<hr className='nav-line' />
<button
className={
'nav-link nav-link-flex' + (!username ? ' nav-link-header' : '')
}
disabled={!username}
key='theme'
onClick={() => this.toggleTheme(theme, toggleNightMode)}
>
{username ? (
<>
<span>{t('settings.labels.night-mode')}</span>
{theme === 'night' ? (
<FontAwesomeIcon icon={faCheckSquare} />
) : (
<FontAwesomeIcon icon={faSquare} />
)}
</>
) : (
<span className='nav-link-dull'>{t('misc.change-theme')}</span>
)}
<li key='radio'>
<Link
className='nav-link'
external={true}
sameTab={false}
to={radioLocation}
>
{t('buttons.radio')}
</Link>
</li>
<li key='theme'>
<button
className='nav-link'
disabled={!username}
onClick={() => this.toggleTheme(theme, toggleNightMode)}
>
{username
? t('settings.labels.night-mode') +
(theme === 'night' ? ' ✓' : '')
: 'Sign in to change theme'}
</button>
</li>
<li key='lang-header'>
<span className='nav-link'>{t('footer.language')}</span>
</li>
{locales.map(lang => (
<li key={'lang-' + lang}>
<Link
className='nav-link sub-link'
// Todo: should treat other lang client application links as external??
external={true}
to={createLanguageRedirect({
clientLocale,
lang
})}
>
{langDisplayNames[lang]}
{i18n.language === i18nextCodes[lang] ? ' ✓' : ''}
</Link>
</li>
))}
</ul>
</button>
{username && (
<>
<hr className='nav-line-2' />
<a className='nav-link' href={`${apiLocation}/signout`}>
{t('buttons.sign-out')}
</a>
</>
)}
</div>
);
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from '../../helpers';
import { Link, SkeletonSprite } from '../../helpers';
import NavLogo from './NavLogo';
import SearchBar from '../../search/searchBar/SearchBar';
import MenuButton from './MenuButton';
@ -15,33 +15,50 @@ export const UniversalNav = ({
searchBarRef,
user,
fetchState
}) => (
<nav
className={'universal-nav' + (displayMenu ? ' expand-nav' : '')}
id='universal-nav'
>
<div
className={'universal-nav-left' + (displayMenu ? ' display-search' : '')}
}) => {
const { pending } = fetchState;
return (
<nav
className={'universal-nav' + (displayMenu ? ' expand-nav' : '')}
id='universal-nav'
>
<SearchBar innerRef={searchBarRef} />
</div>
<div className='universal-nav-middle'>
<Link id='universal-nav-logo' to='/learn'>
<NavLogo />
<span className='sr-only'>freeCodeCamp.org</span>
</Link>
</div>
<div className='universal-nav-right main-nav'>
<MenuButton
<div
className={
'universal-nav-left' + (displayMenu ? ' display-search' : '')
}
>
<SearchBar innerRef={searchBarRef} />
</div>
<div className='universal-nav-middle'>
<Link id='universal-nav-logo' to='/learn'>
<NavLogo />
<span className='sr-only'>freeCodeCamp.org</span>
</Link>
</div>
<div className='universal-nav-right main-nav'>
{pending ? (
<div className='nav-skeleton'>
<SkeletonSprite />
</div>
) : (
<MenuButton
displayMenu={displayMenu}
innerRef={menuButtonRef}
onClick={toggleDisplayMenu}
user={user}
/>
)}
</div>
<NavLinks
displayMenu={displayMenu}
innerRef={menuButtonRef}
onClick={toggleDisplayMenu}
fetchState={fetchState}
toggleDisplayMenu={toggleDisplayMenu}
user={user}
/>
</div>
<NavLinks displayMenu={displayMenu} fetchState={fetchState} user={user} />
</nav>
);
</nav>
);
};
UniversalNav.displayName = 'UniversalNav';
export default UniversalNav;

View File

@ -66,6 +66,7 @@
text-decoration: none;
background-color: var(--theme-color);
}
#universal-nav-logo:focus {
background-color: inherit;
}
@ -90,19 +91,14 @@
margin: 0 0 0 -12px;
padding: 0;
list-style: none;
max-width: 300px;
}
.nav-list li {
display: block;
margin: 0;
padding: 0;
max-width: 250px;
}
.nav-link {
margin: 0;
padding: 2px 15px 0 15px;
display: flex;
align-items: center;
padding: 2px 15px 0 25px;
color: var(--gray-00);
background-color: var(--gray-90);
opacity: 1;
@ -113,42 +109,47 @@
border: none;
}
.nav-list span.nav-link {
padding: 2px 15px 0 15px;
background-color: var(--gray-75);
}
.nav-list a.nav-link:hover,
.nav-list button.nav-link:hover {
.nav-link:hover,
.nav-link:active {
color: var(--theme-color);
text-decoration: none;
background: white;
cursor: pointer;
}
.nav-list button:disabled {
background-color: var(--gray-75);
.nav-link-header,
.nav-link-header:hover,
.nav-link-header:active {
color: var(--gray-00);
background-color: var(--gray-90);
cursor: default;
}
.nav-list button:disabled:hover {
color: white;
background-color: var(--gray-75);
.nav-link .fa-external-link-alt {
color: var(--gray-45);
}
.nav-list li a.nav-link:focus {
background: var(--theme-color);
color: white;
.nav-link .fa-check,
.nav-link .fa-check-square {
width: 18px !important;
height: auto !important;
}
.nav-list li a.nav-link:focus:hover {
color: var(--theme-color);
background: white;
.nav-link-lang {
padding-left: 30px;
}
.nav-list a.btn-cta {
padding: 0 20px;
height: 30px;
margin-left: 19px;
margin-right: 15px;
.nav-link-flex {
display: flex;
justify-content: space-between;
}
.nav-link-sign-in {
display: none;
}
.nav-link-dull {
color: var(--gray-45);
}
.nav-skeleton {
@ -247,6 +248,23 @@
color: var(--theme-color);
}
.nav-line,
.nav-line-2 {
border-color: var(--gray-45);
width: 100%;
margin: 0;
}
.nav-line-2 {
border-top-width: 2px;
}
.signup-btn {
max-height: calc(var(--header-height) - 6px);
padding: 0 8px;
margin-left: 2px;
}
@media (max-width: 980px) {
.universal-nav-left {
display: none;
@ -306,7 +324,7 @@
}
}
@media (max-width: 400px) {
@media (max-width: 455px) {
.universal-nav {
padding: 0 5px;
}
@ -315,14 +333,26 @@
padding: 0 5px;
}
.nav-link-sign-in {
display: flex;
}
.navatar .signup-btn {
display: none;
}
.navatar {
display: none;
}
#universal-nav-logo {
left: 5px;
max-width: 45%;
max-width: 60%;
}
}
.signup-btn {
max-height: calc(var(--header-height) - 6px);
padding: 0 4px;
margin-left: 2px;
@media (max-width: 300px) {
#universal-nav-logo {
max-width: none;
left: -170px;
}
}

View File

@ -0,0 +1,16 @@
import { forumLocation } from '../../config/env.json';
const createExternalRedirect = (page, { clientLocale }) => {
const isNotEnglish = clientLocale !== 'english';
if (clientLocale === 'chinese') {
return `https://chinese.freecodecamp.org/${page}`;
}
if (page === 'forum') {
return `${forumLocation}/${isNotEnglish ? 'c/' + clientLocale + '/' : ''}`;
}
return `https://www.freecodecamp.org/${
isNotEnglish ? clientLocale + '/news' : 'news'
}`;
};
export default createExternalRedirect;

View File

@ -0,0 +1,81 @@
/* global expect */
import createExternalRedirect from './createExternalRedirects';
describe('createExternalRedirects', () => {
describe('english redirects', () => {
const envVars = {
clientLocale: 'english'
};
const englishForumUrl = 'https://forum.freecodecamp.org/';
const englishNewsUrl = 'https://www.freecodecamp.org/news';
it('should generate correct forum link', () => {
const receivedUrl = createExternalRedirect('forum', { ...envVars });
expect(receivedUrl).toBe(englishForumUrl);
});
it('should generate correct news link', () => {
const receivedUrl = createExternalRedirect('news', { ...envVars });
expect(receivedUrl).toBe(englishNewsUrl);
});
});
describe('chinese redirects', () => {
const envVars = {
clientLocale: 'chinese'
};
const englishForumUrl = 'https://chinese.freecodecamp.org/forum';
const englishNewsUrl = 'https://chinese.freecodecamp.org/news';
it('should generate correct forum link', () => {
const receivedUrl = createExternalRedirect('forum', { ...envVars });
expect(receivedUrl).toBe(englishForumUrl);
});
it('should generate correct news link', () => {
const receivedUrl = createExternalRedirect('news', { ...envVars });
expect(receivedUrl).toBe(englishNewsUrl);
});
});
describe('spanish redirects', () => {
const envVars = {
clientLocale: 'espanol'
};
const englishForumUrl = 'https://forum.freecodecamp.org/c/espanol/';
const englishNewsUrl = 'https://www.freecodecamp.org/espanol/news';
it('should generate correct forum link', () => {
const receivedUrl = createExternalRedirect('forum', { ...envVars });
expect(receivedUrl).toBe(englishForumUrl);
});
it('should generate correct news link', () => {
const receivedUrl = createExternalRedirect('news', { ...envVars });
expect(receivedUrl).toBe(englishNewsUrl);
});
});
describe('french redirects', () => {
const envVars = {
clientLocale: 'francais'
};
const englishForumUrl = 'https://forum.freecodecamp.org/c/francais/';
const englishNewsUrl = 'https://www.freecodecamp.org/francais/news';
it('should generate correct forum link', () => {
const receivedUrl = createExternalRedirect('forum', { ...envVars });
expect(receivedUrl).toBe(englishForumUrl);
});
it('should generate correct news link', () => {
const receivedUrl = createExternalRedirect('news', { ...envVars });
expect(receivedUrl).toBe(englishNewsUrl);
});
});
});

View File

@ -54,7 +54,6 @@ FREECODECAMP_NODE_ENV='development'
# Languages to build
CLIENT_LOCALE=english
CURRICULUM_LOCALE=english
SHOW_LOCALE_DROPDOWN_MENU=true
# Show or hide WIP in progress challenges
SHOW_UPCOMING_CHANGES=false