fix(client): display legacy certs like current ones (#42038)

* fix: display legacy certs like the current ones

* fix: link projects in legacy certs to project pages

* fix: update tests to changed legacy cert display

* fix: update tests for removed legacy certs forms

* fix: display legacy certs like the current ones

* fix: submit projects for cert on projects pages

* fix: remove legacy certs form submitting handling

* fix: move claiming cert setup before both tests

* fix: remove legacy cert update props and actions

* fix: remove legacy cert updates from api

* fix: correct merge conflict
This commit is contained in:
gikf
2021-06-11 18:06:46 +02:00
committed by GitHub
parent c6aa6ddbcd
commit bc9e8a69de
10 changed files with 155 additions and 385 deletions

View File

@ -28,7 +28,6 @@ export default function settingsController(app) {
updateMyCurrentChallenge updateMyCurrentChallenge
); );
api.post('/update-my-portfolio', ifNoUser401, updateMyPortfolio); api.post('/update-my-portfolio', ifNoUser401, updateMyPortfolio);
api.post('/update-my-projects', ifNoUser401, updateMyProjects);
api.post( api.post(
'/update-my-theme', '/update-my-theme',
ifNoUser401, ifNoUser401,
@ -159,16 +158,6 @@ function updateMyProfileUI(req, res, next) {
); );
} }
function updateMyProjects(req, res, next) {
const {
user,
body: { projects: project }
} = req;
return user
.updateMyProjects(project)
.subscribe(message => res.json({ message }), next);
}
function updateMyAbout(req, res, next) { function updateMyAbout(req, res, next) {
const { const {
user, user,

View File

@ -80,7 +80,6 @@ let blocklist = [
'unsubscribed', 'unsubscribed',
'update-my-portfolio', 'update-my-portfolio',
'update-my-profile-ui', 'update-my-profile-ui',
'update-my-projects',
'update-my-theme', 'update-my-theme',
'update-my-username', 'update-my-username',
'user', 'user',

View File

@ -1,8 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux'; import { find, first } from 'lodash-es';
import { connect } from 'react-redux';
import { find, first, values, isString } from 'lodash-es';
import { import {
Table, Table,
Button, Button,
@ -21,16 +19,10 @@ import {
import SectionHeader from './SectionHeader'; import SectionHeader from './SectionHeader';
import ProjectModal from '../SolutionViewer/ProjectModal'; import ProjectModal from '../SolutionViewer/ProjectModal';
import { FullWidthRow, Spacer } from '../helpers'; import { FullWidthRow, Spacer } from '../helpers';
import { Form } from '../formHelpers';
import { maybeUrlRE } from '../../utils'; import { maybeUrlRE } from '../../utils';
import reallyWeirdErrorMessage from '../../utils/reallyWeirdErrorMessage';
import './certification.css'; import './certification.css';
import { updateLegacyCert } from '../../redux/settings';
const mapDispatchToProps = dispatch =>
bindActionCreators({ updateLegacyCert }, dispatch);
const propTypes = { const propTypes = {
completedChallenges: PropTypes.arrayOf( completedChallenges: PropTypes.arrayOf(
@ -61,7 +53,6 @@ const propTypes = {
isRespWebDesignCert: PropTypes.bool, isRespWebDesignCert: PropTypes.bool,
isSciCompPyCertV7: PropTypes.bool, isSciCompPyCertV7: PropTypes.bool,
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
updateLegacyCert: PropTypes.func.isRequired,
username: PropTypes.string, username: PropTypes.string,
verifyCert: PropTypes.func.isRequired verifyCert: PropTypes.func.isRequired
}; };
@ -156,7 +147,6 @@ export class CertificationSettings extends Component {
super(props); super(props);
this.state = { ...initialState }; this.state = { ...initialState };
this.handleSubmitLegacy = this.handleSubmitLegacy.bind(this);
} }
createHandleLinkButtonClick = to => e => { createHandleLinkButtonClick = to => e => {
@ -258,9 +248,9 @@ export class CertificationSettings extends Component {
); );
}; };
renderCertifications = certName => { renderCertifications = (certName, projectsMap) => {
const { t } = this.props; const { t } = this.props;
const { certSlug } = first(projectMap[certName]); const { certSlug } = first(projectsMap[certName]);
return ( return (
<FullWidthRow key={certName}> <FullWidthRow key={certName}>
<Spacer /> <Spacer />
@ -277,17 +267,18 @@ export class CertificationSettings extends Component {
<tbody> <tbody>
{this.renderProjectsFor( {this.renderProjectsFor(
certName, certName,
this.getUserIsCertMap()[certName] this.getUserIsCertMap()[certName],
projectsMap
)} )}
</tbody> </tbody>
</Table> </Table>
</FullWidthRow> </FullWidthRow>
); );
}; };
renderProjectsFor = (certName, isCert) => { renderProjectsFor = (certName, isCert, projectsMap) => {
const { username, isHonest, createFlashMessage, t, verifyCert } = const { username, isHonest, createFlashMessage, t, verifyCert } =
this.props; this.props;
const { certSlug } = first(projectMap[certName]); const { certSlug } = first(projectsMap[certName]);
const certLocation = `/certification/${username}/${certSlug}`; const certLocation = `/certification/${username}/${certSlug}`;
const createClickHandler = certSlug => e => { const createClickHandler = certSlug => e => {
e.preventDefault(); e.preventDefault();
@ -298,7 +289,7 @@ export class CertificationSettings extends Component {
? verifyCert(certSlug) ? verifyCert(certSlug)
: createFlashMessage(honestyInfoMessage); : createFlashMessage(honestyInfoMessage);
}; };
return projectMap[certName] return projectsMap[certName]
.map(({ link, title, id }) => ( .map(({ link, title, id }) => (
<tr className='project-row' key={id}> <tr className='project-row' key={id}>
<td className='project-title col-sm-8'> <td className='project-title col-sm-8'>
@ -325,160 +316,6 @@ export class CertificationSettings extends Component {
]); ]);
}; };
// legacy projects rendering
handleSubmitLegacy({ values: formChalObj }) {
const { isHonest, createFlashMessage, verifyCert, updateLegacyCert } =
this.props;
let legacyTitle;
let certSlug;
let certs = Object.keys(legacyProjectMap);
let loopBreak = false;
for (let certTitle of certs) {
for (let chalTitle of legacyProjectMap[certTitle]) {
if (chalTitle.title === Object.keys(formChalObj)[0]) {
certSlug = chalTitle.certSlug;
loopBreak = true;
legacyTitle = certTitle;
break;
}
}
if (loopBreak) {
break;
}
}
// make an object with keys as challenge ids and values as solutions
let idsToSolutions = {};
for (let i of Object.keys(formChalObj)) {
for (let j of legacyProjectMap[legacyTitle]) {
if (i === j.title) {
idsToSolutions[j.id] = formChalObj[i];
break;
}
}
}
// filter the new solutions that need to be updated
const completedChallenges = this.props.completedChallenges;
let challengesToUpdate = {};
let newChallengeFound = true;
let oldSubmissions = 0;
for (let submittedChal of Object.keys(idsToSolutions)) {
for (let i of completedChallenges) {
if (i.id === submittedChal) {
if (idsToSolutions[submittedChal] !== i.solution) {
challengesToUpdate[submittedChal] = idsToSolutions[submittedChal];
}
oldSubmissions++;
newChallengeFound = false;
break;
}
}
if (newChallengeFound && idsToSolutions[submittedChal] !== '') {
challengesToUpdate[submittedChal] = idsToSolutions[submittedChal];
}
newChallengeFound = true;
}
const valuesSaved = values(formChalObj).filter(Boolean).filter(isString);
const isProjectSectionComplete = valuesSaved.length === oldSubmissions;
if (isProjectSectionComplete) {
return isHonest
? verifyCert(certSlug)
: createFlashMessage(honestyInfoMessage);
}
return updateLegacyCert({ challengesToUpdate, certSlug });
}
renderLegacyCertifications = certName => {
const { username, createFlashMessage, completedChallenges, t } = this.props;
const { certSlug } = first(legacyProjectMap[certName]);
const certLocation = `/certification/${username}/${certSlug}`;
const challengeTitles = legacyProjectMap[certName].map(item => item.title);
const isCertClaimed = this.getUserIsCertMap()[certName];
const initialObject = {};
let filledforms = 0;
legacyProjectMap[certName].forEach(project => {
let completedProject = find(completedChallenges, function (challenge) {
return challenge['id'] === project['id'];
});
if (!completedProject) {
initialObject[project.title] = '';
} else {
initialObject[project.title] = completedProject.solution;
filledforms++;
}
});
const options = challengeTitles.reduce(
(options, current) => {
options.types[current] = 'url';
return options;
},
{ types: {} }
);
const formFields = challengeTitles.map(title => ({
name: title,
label: title
}));
const fullForm = filledforms === challengeTitles.length;
const createClickHandler = certLocation => e => {
e.preventDefault();
if (isCertClaimed) {
return navigate(certLocation);
}
return createFlashMessage(reallyWeirdErrorMessage);
};
const buttonStyle = {
marginBottom: '1.45rem'
};
return (
<FullWidthRow key={certSlug}>
<Spacer />
<h3 className='text-center' id={`cert-${certSlug}`}>
{certName}
</h3>
<Form
buttonText={
fullForm ? t('buttons.claim-cert') : t('buttons.save-progress')
}
enableSubmit={fullForm}
formFields={formFields}
hideButton={isCertClaimed}
id={certSlug}
initialValues={{
...initialObject
}}
options={options}
submit={this.handleSubmitLegacy}
/>
{isCertClaimed ? (
<div className={'col-xs-12'}>
<Button
bsSize='sm'
bsStyle='primary'
className={'col-xs-12'}
href={certLocation}
id={'button-' + certSlug}
onClick={createClickHandler(certLocation)}
style={buttonStyle}
target='_blank'
>
{t('buttons.show-cert')}
</Button>
</div>
) : null}
</FullWidthRow>
);
};
renderLegacyFullStack = () => { renderLegacyFullStack = () => {
const { const {
isFullStackCert, isFullStackCert,
@ -587,10 +424,14 @@ export class CertificationSettings extends Component {
return ( return (
<section id='certification-settings'> <section id='certification-settings'>
<SectionHeader>{t('settings.headings.certs')}</SectionHeader> <SectionHeader>{t('settings.headings.certs')}</SectionHeader>
{certifications.map(this.renderCertifications)} {certifications.map(certName =>
this.renderCertifications(certName, projectMap)
)}
<SectionHeader>{t('settings.headings.legacy-certs')}</SectionHeader> <SectionHeader>{t('settings.headings.legacy-certs')}</SectionHeader>
{this.renderLegacyFullStack()} {this.renderLegacyFullStack()}
{legacyCertifications.map(this.renderLegacyCertifications)} {legacyCertifications.map(certName =>
this.renderCertifications(certName, legacyProjectMap)
)}
{isOpen ? ( {isOpen ? (
<ProjectModal <ProjectModal
files={files} files={files}
@ -609,7 +450,4 @@ export class CertificationSettings extends Component {
CertificationSettings.displayName = 'CertificationSettings'; CertificationSettings.displayName = 'CertificationSettings';
CertificationSettings.propTypes = propTypes; CertificationSettings.propTypes = propTypes;
export default connect( export default withTranslation()(CertificationSettings);
null,
mapDispatchToProps
)(withTranslation()(CertificationSettings));

View File

@ -22,7 +22,9 @@ describe('<certification />', () => {
); );
expect( expect(
container.querySelector('#button-legacy-data-visualization') container.querySelector(
'a[href="/certification/developmentuser/legacy-data-visualization"]'
)
).toHaveTextContent('buttons.show-cert'); ).toHaveTextContent('buttons.show-cert');
}); });
@ -32,33 +34,36 @@ describe('<certification />', () => {
); );
expect( expect(
container.querySelector('#button-legacy-data-visualization') container.querySelector(
).toHaveAttribute( 'a[href="/certification/developmentuser/legacy-data-visualization"]'
'href', )
'/certification/developmentuser/legacy-data-visualization' ).toBeInTheDocument();
);
}); });
// full forms with unclaimed certs should should not shallow render button // full forms with unclaimed certs should not shallow render show cert button
it('Should not render show cert button for unclaimed full form', () => { it('Should not render show cert button for unclaimed cert with completed projects', () => {
const { container } = renderWithRedux( const { container } = renderWithRedux(
<CertificationSettings {...defaultTestProps} /> <CertificationSettings {...defaultTestProps} />
); );
expect( expect(
container.querySelector('#button-legacy-back-end') container.querySelector(
).not.toBeInTheDocument(); 'a[href="/certification/developmentuser/legacy-back-end"]'
)
).not.toHaveTextContent('buttons.show-cert');
}); });
// empty forms with unclaimed certs should should not shallow render button // empty forms with unclaimed certs should not shallow render show cert button
it('Should not render show cert button for empty form', () => { it('Should not render show cert button for cert with no completed projects', () => {
const { container } = renderWithRedux( const { container } = renderWithRedux(
<CertificationSettings {...defaultTestProps} /> <CertificationSettings {...defaultTestProps} />
); );
expect( expect(
container.querySelector('#button-legacy-front-end') container.querySelector(
).not.toBeInTheDocument(); 'a[href="/certification/developmentuser/legacy-front-end"]'
)
).not.toHaveTextContent('buttons.show-cert');
}); });
it('Render button when only solution is present', () => { it('Render button when only solution is present', () => {
@ -243,7 +248,6 @@ const defaultTestProps = {
isSciCompPyCertV7: false, isSciCompPyCertV7: false,
isDataAnalysisPyCertV7: false, isDataAnalysisPyCertV7: false,
isMachineLearningPyCertV7: false, isMachineLearningPyCertV7: false,
updateLegacyCert: () => {},
username: 'developmentuser', username: 'developmentuser',
verifyCert: () => {}, verifyCert: () => {},
errors: {}, errors: {},

View File

@ -5,10 +5,6 @@ import { createDangerZoneSaga } from './danger-zone-saga';
import { createSettingsSagas } from './settings-sagas'; import { createSettingsSagas } from './settings-sagas';
import { createUpdateMyEmailSaga } from './update-email-saga'; import { createUpdateMyEmailSaga } from './update-email-saga';
// prettier-ignore
import { createUpdateLegacyCertSaga } from
'./update-legacy-certificate-saga';
export const ns = 'settings'; export const ns = 'settings';
const defaultFetchState = { const defaultFetchState = {
@ -31,7 +27,6 @@ export const types = createTypes(
...createAsyncTypes('submitNewAbout'), ...createAsyncTypes('submitNewAbout'),
...createAsyncTypes('submitNewUsername'), ...createAsyncTypes('submitNewUsername'),
...createAsyncTypes('updateMyEmail'), ...createAsyncTypes('updateMyEmail'),
...createAsyncTypes('updateLegacyCert'),
...createAsyncTypes('updateUserFlag'), ...createAsyncTypes('updateUserFlag'),
...createAsyncTypes('submitProfileUI'), ...createAsyncTypes('submitProfileUI'),
...createAsyncTypes('verifyCert'), ...createAsyncTypes('verifyCert'),
@ -44,8 +39,7 @@ export const types = createTypes(
export const sagas = [ export const sagas = [
...createSettingsSagas(types), ...createSettingsSagas(types),
...createUpdateMyEmailSaga(types), ...createUpdateMyEmailSaga(types),
...createDangerZoneSaga(types), ...createDangerZoneSaga(types)
...createUpdateLegacyCertSaga(types)
]; ];
const checkForSuccessPayload = ({ type, payload }) => const checkForSuccessPayload = ({ type, payload }) =>
@ -78,12 +72,6 @@ export const updateMyEmail = createAction(types.updateMyEmail);
export const updateMyEmailComplete = createAction(types.updateMyEmailComplete); export const updateMyEmailComplete = createAction(types.updateMyEmailComplete);
export const updateMyEmailError = createAction(types.updateMyEmailError); export const updateMyEmailError = createAction(types.updateMyEmailError);
export const updateLegacyCert = createAction(types.updateLegacyCert);
export const updateLegacyCertComplete = createAction(
types.updateLegacyCertComplete
);
export const updateLegacyCertError = createAction(types.updateLegacyCertError);
export const updateUserFlag = createAction(types.updateUserFlag); export const updateUserFlag = createAction(types.updateUserFlag);
export const updateUserFlagComplete = createAction( export const updateUserFlagComplete = createAction(
types.updateUserFlagComplete, types.updateUserFlagComplete,

View File

@ -1,38 +0,0 @@
import { takeEvery, call, put } from 'redux-saga/effects';
import { putUpdateLegacyCert } from '../../utils/ajax';
import { submitComplete } from '../';
import { createFlashMessage } from '../../components/Flash/redux';
import reallyWeirdErrorMessage from '../../utils/reallyWeirdErrorMessage';
import { updateLegacyCertError } from './';
function* updateLegacyCertSaga({
payload: { superBlock, challengesToUpdate }
}) {
// shape the body of the http call so it is consumable by api
const body = {
projects: {
[superBlock]: challengesToUpdate
}
};
// shape to update completed challenges in redux store
let reduxShape = [];
for (let obj in challengesToUpdate) {
if (challengesToUpdate.hasOwnProperty(obj)) {
reduxShape.push({ id: obj, solution: challengesToUpdate[obj] });
}
}
try {
const { data: response } = yield call(putUpdateLegacyCert, body);
yield put(submitComplete({ challArray: reduxShape }));
yield put(createFlashMessage(response));
} catch (e) {
yield put(updateLegacyCertError(e));
yield put(createFlashMessage(reallyWeirdErrorMessage));
}
}
export function createUpdateLegacyCertSaga(types) {
return [takeEvery(types.updateLegacyCert, updateLegacyCertSaga)];
}

View File

@ -16,10 +16,17 @@ const dataAnalysisPyBase =
'/learn/data-analysis-with-python/data-analysis-with-python-projects'; '/learn/data-analysis-with-python/data-analysis-with-python-projects';
const machineLearningPyBase = const machineLearningPyBase =
'/learn/machine-learning-with-python/machine-learning-with-python-projects'; '/learn/machine-learning-with-python/machine-learning-with-python-projects';
const legacyFrontEndBase = ''; const takeHomeBase = '/learn/coding-interview-prep/take-home-projects';
const legacyBackEndBase = ''; const legacyFrontEndBase = feLibsBase;
const legacyDataVisBase = ''; const legacyFrontEndResponsiveBase = responsiveWebBase;
const legacyInfosecQaBase = ''; const legacyFrontEndTakeHomeBase = takeHomeBase;
const legacyBackEndBase = apiMicroBase;
const legacyBackEndTakeHomeBase = takeHomeBase;
const legacyDataVisBase = dataVisBase;
const legacyDataVisFrontEndBase = feLibsBase;
const legacyDataVisTakeHomeBase = takeHomeBase;
const legacyInfosecQaQaBase = qaBase;
const legacyInfosecQaInfosecBase = infoSecBase;
// TODO: generate this automatically in a separate file // TODO: generate this automatically in a separate file
// from the md/meta.json files for each cert and projects // from the md/meta.json files for each cert and projects
@ -33,7 +40,7 @@ const certMap = [
{ {
id: 'bd7158d8c242eddfaeb5bd13', id: 'bd7158d8c242eddfaeb5bd13',
title: 'Build a Personal Portfolio Webpage', title: 'Build a Personal Portfolio Webpage',
link: `${legacyFrontEndBase}/build-a-personal-portfolio-webpage`, link: `${legacyFrontEndResponsiveBase}/build-a-personal-portfolio-webpage`,
certSlug: 'legacy-front-end' certSlug: 'legacy-front-end'
}, },
{ {
@ -57,37 +64,37 @@ const certMap = [
{ {
id: 'bd7158d8c442eddfaeb5bd10', id: 'bd7158d8c442eddfaeb5bd10',
title: 'Show the Local Weather', title: 'Show the Local Weather',
link: `${legacyFrontEndBase}/show-the-local-weather`, link: `${legacyFrontEndTakeHomeBase}/show-the-local-weather`,
certSlug: 'legacy-front-end' certSlug: 'legacy-front-end'
}, },
{ {
id: 'bd7158d8c442eddfaeb5bd1f', id: 'bd7158d8c442eddfaeb5bd1f',
title: 'Use the TwitchTV JSON API', title: 'Use the TwitchTV JSON API',
link: `${legacyFrontEndBase}/use-the-twitchtv-json-api`, link: `${legacyFrontEndTakeHomeBase}/use-the-twitch-json-api`,
certSlug: 'legacy-front-end' certSlug: 'legacy-front-end'
}, },
{ {
id: 'bd7158d8c442eddfaeb5bd18', id: 'bd7158d8c442eddfaeb5bd18',
title: 'Stylize Stories on Camper News', title: 'Build a Tribute Page',
link: `${legacyFrontEndBase}/stylize-stories-on-camper-news`, link: `${legacyFrontEndResponsiveBase}/build-a-tribute-page`,
certSlug: 'legacy-front-end' certSlug: 'legacy-front-end'
}, },
{ {
id: 'bd7158d8c442eddfaeb5bd19', id: 'bd7158d8c442eddfaeb5bd19',
title: 'Build a Wikipedia Viewer', title: 'Build a Wikipedia Viewer',
link: `${legacyFrontEndBase}/build-a-wikipedia-viewer`, link: `${legacyFrontEndTakeHomeBase}/build-a-wikipedia-viewer`,
certSlug: 'legacy-front-end' certSlug: 'legacy-front-end'
}, },
{ {
id: 'bd7158d8c442eedfaeb5bd1c', id: 'bd7158d8c442eedfaeb5bd1c',
title: 'Build a Tic Tac Toe Game', title: 'Build a Tic Tac Toe Game',
link: `${legacyFrontEndBase}/build-a-tic-tac-toe-game`, link: `${legacyFrontEndTakeHomeBase}/build-a-tic-tac-toe-game`,
certSlug: 'legacy-front-end' certSlug: 'legacy-front-end'
}, },
{ {
id: 'bd7158d8c442eddfaeb5bd1c', id: 'bd7158d8c442eddfaeb5bd1c',
title: 'Build a Simon Game', title: 'Build a Simon Game',
link: `${legacyFrontEndBase}/build-a-simon-game`, link: `${legacyFrontEndTakeHomeBase}/build-a-simon-game`,
certSlug: 'legacy-front-end' certSlug: 'legacy-front-end'
} }
] ]
@ -119,7 +126,7 @@ const certMap = [
{ {
id: 'bd7158d8c443edefaeb5bdee', id: 'bd7158d8c443edefaeb5bdee',
title: 'Image Search Abstraction Layer', title: 'Image Search Abstraction Layer',
link: `${legacyBackEndBase}/image-search-abstraction-layer`, link: `${legacyBackEndTakeHomeBase}/build-an-image-search-abstraction-layer`,
certSlug: 'legacy-back-end' certSlug: 'legacy-back-end'
}, },
{ {
@ -131,31 +138,31 @@ const certMap = [
{ {
id: 'bd7158d8c443eddfaeb5bdef', id: 'bd7158d8c443eddfaeb5bdef',
title: 'Build a Voting App', title: 'Build a Voting App',
link: `${legacyBackEndBase}/build-a-voting-app`, link: `${legacyBackEndTakeHomeBase}/build-a-voting-app`,
certSlug: 'legacy-back-end' certSlug: 'legacy-back-end'
}, },
{ {
id: 'bd7158d8c443eddfaeb5bdff', id: 'bd7158d8c443eddfaeb5bdff',
title: 'Build a Nightlife Coordination App', title: 'Build a Nightlife Coordination App',
link: `${legacyBackEndBase}/build-a-nightlife-coordination-app`, link: `${legacyBackEndTakeHomeBase}/build-a-nightlife-coordination-app`,
certSlug: 'legacy-back-end' certSlug: 'legacy-back-end'
}, },
{ {
id: 'bd7158d8c443eddfaeb5bd0e', id: 'bd7158d8c443eddfaeb5bd0e',
title: 'Chart the Stock Market', title: 'Chart the Stock Market',
link: `${legacyBackEndBase}/chart-the-stock-market`, link: `${legacyBackEndTakeHomeBase}/chart-the-stock-market`,
certSlug: 'legacy-back-end' certSlug: 'legacy-back-end'
}, },
{ {
id: 'bd7158d8c443eddfaeb5bd0f', id: 'bd7158d8c443eddfaeb5bd0f',
title: 'Manage a Book Trading Club', title: 'Manage a Book Trading Club',
link: `${legacyBackEndBase}/manage-a-book-trading-club`, link: `${legacyBackEndTakeHomeBase}/manage-a-book-trading-club`,
certSlug: 'legacy-back-end' certSlug: 'legacy-back-end'
}, },
{ {
id: 'bd7158d8c443eddfaeb5bdee', id: 'bd7158d8c443eddfaeb5bdee',
title: 'Build a Pinterest Clone', title: 'Build a Pinterest Clone',
link: `${legacyBackEndBase}/build-a-pinterest-clone`, link: `${legacyBackEndTakeHomeBase}/build-a-pinterest-clone`,
certSlug: 'legacy-back-end' certSlug: 'legacy-back-end'
} }
] ]
@ -177,31 +184,31 @@ const certMap = [
{ {
id: 'bd7157d8c242eddfaeb5bd13', id: 'bd7157d8c242eddfaeb5bd13',
title: 'Build a Markdown Previewer', title: 'Build a Markdown Previewer',
link: `${legacyDataVisBase}/build-a-markdown-previewer`, link: `${legacyDataVisFrontEndBase}/build-a-markdown-previewer`,
certSlug: 'legacy-data-visualization' certSlug: 'legacy-data-visualization'
}, },
{ {
id: 'bd7156d8c242eddfaeb5bd13', id: 'bd7156d8c242eddfaeb5bd13',
title: 'Build a Camper Leaderboard', title: 'Build a freeCodeCamp Forum Homepage',
link: `${legacyDataVisBase}/build-a-camper-leaderboard`, link: `${legacyDataVisTakeHomeBase}/build-a-freecodecamp-forum-homepage`,
certSlug: 'legacy-data-visualization' certSlug: 'legacy-data-visualization'
}, },
{ {
id: 'bd7155d8c242eddfaeb5bd13', id: 'bd7155d8c242eddfaeb5bd13',
title: 'Build a Recipe Box', title: 'Build a Recipe Box',
link: `${legacyDataVisBase}/build-a-recipe-box`, link: `${legacyDataVisTakeHomeBase}/build-a-recipe-box`,
certSlug: 'legacy-data-visualization' certSlug: 'legacy-data-visualization'
}, },
{ {
id: 'bd7154d8c242eddfaeb5bd13', id: 'bd7154d8c242eddfaeb5bd13',
title: 'Build the Game of Life', title: 'Build the Game of Life',
link: `${legacyDataVisBase}/build-the-game-of-life`, link: `${legacyDataVisTakeHomeBase}/build-the-game-of-life`,
certSlug: 'legacy-data-visualization' certSlug: 'legacy-data-visualization'
}, },
{ {
id: 'bd7153d8c242eddfaeb5bd13', id: 'bd7153d8c242eddfaeb5bd13',
title: 'Build a Roguelike Dungeon Crawler Game', title: 'Build a Roguelike Dungeon Crawler Game',
link: `${legacyDataVisBase}/build-a-roguelike-dungeon-crawler-game`, link: `${legacyDataVisTakeHomeBase}/build-a-roguelike-dungeon-crawler-game`,
certSlug: 'legacy-data-visualization' certSlug: 'legacy-data-visualization'
}, },
{ {
@ -225,13 +232,13 @@ const certMap = [
{ {
id: 'bd7198d8c242eddfaeb5bd13', id: 'bd7198d8c242eddfaeb5bd13',
title: 'Show National Contiguity with a Force Directed Graph', title: 'Show National Contiguity with a Force Directed Graph',
link: `${legacyDataVisBase}/show-national-contiguity-with-a-force-directed-graph`, link: `${legacyDataVisTakeHomeBase}/show-national-contiguity-with-a-force-directed-graph`,
certSlug: 'legacy-data-visualization' certSlug: 'legacy-data-visualization'
}, },
{ {
id: 'bd7108d8c242eddfaeb5bd13', id: 'bd7108d8c242eddfaeb5bd13',
title: 'Map Data Across the Globe', title: 'Map Data Across the Globe',
link: `${legacyDataVisBase}/map-data-across-the-globe`, link: `${legacyDataVisTakeHomeBase}/map-data-across-the-globe`,
certSlug: 'legacy-data-visualization' certSlug: 'legacy-data-visualization'
} }
] ]
@ -247,31 +254,31 @@ const certMap = [
{ {
id: '587d8249367417b2b2512c41', id: '587d8249367417b2b2512c41',
title: 'Metric-Imperial Converter', title: 'Metric-Imperial Converter',
link: `${legacyInfosecQaBase}/metric-imperial-converter`, link: `${legacyInfosecQaQaBase}/metric-imperial-converter`,
certSlug: 'information-security-and-quality-assurance' certSlug: 'information-security-and-quality-assurance'
}, },
{ {
id: '587d8249367417b2b2512c42', id: '587d8249367417b2b2512c42',
title: 'Issue Tracker', title: 'Issue Tracker',
link: `${legacyInfosecQaBase}/issue-tracker`, link: `${legacyInfosecQaQaBase}/issue-tracker`,
certSlug: 'information-security-and-quality-assurance' certSlug: 'information-security-and-quality-assurance'
}, },
{ {
id: '587d824a367417b2b2512c43', id: '587d824a367417b2b2512c43',
title: 'Personal Library', title: 'Personal Library',
link: `${legacyInfosecQaBase}/personal-library`, link: `${legacyInfosecQaQaBase}/personal-library`,
certSlug: 'information-security-and-quality-assurance' certSlug: 'information-security-and-quality-assurance'
}, },
{ {
id: '587d824a367417b2b2512c44', id: '587d824a367417b2b2512c44',
title: 'Stock Price Checker', title: 'Stock Price Checker',
link: `${legacyInfosecQaBase}/stock-price-checker`, link: `${legacyInfosecQaInfosecBase}/stock-price-checker`,
certSlug: 'information-security-and-quality-assurance' certSlug: 'information-security-and-quality-assurance'
}, },
{ {
id: '587d824a367417b2b2512c45', id: '587d824a367417b2b2512c45',
title: 'Anonymous Message Board', title: 'Anonymous Message Board',
link: `${legacyInfosecQaBase}/anonymous-message-board`, link: `${legacyInfosecQaInfosecBase}/anonymous-message-board`,
certSlug: 'information-security-and-quality-assurance' certSlug: 'information-security-and-quality-assurance'
} }
] ]

View File

@ -65,10 +65,6 @@ export function addDonation(body) {
return post('/donate/add-donation', body); return post('/donate/add-donation', body);
} }
export function putUpdateLegacyCert(body) {
return post('/update-my-projects', body);
}
export function postReportUser(body) { export function postReportUser(body) {
return post('/user/report-user', body); return post('/user/report-user', body);
} }

View File

@ -1,9 +1,51 @@
/* global cy */ /* global cy */
const certificationUrl = '/certification/developmentuser/responsive-web-design';
const projects = {
superBlock: 'responsive-web-design',
block: 'responsive-web-design-projects',
challenges: [
{
slug: 'build-a-tribute-page',
solution: 'https://codepen.io/moT01/pen/ZpJpKp'
},
{
slug: 'build-a-survey-form',
solution: 'https://codepen.io/moT01/pen/LrrjGz?editors=1010'
},
{
slug: 'build-a-product-landing-page',
solution: 'https://codepen.io/moT01/full/qKyKYL/'
},
{
slug: 'build-a-technical-documentation-page',
solution: 'https://codepen.io/moT01/full/JBvzNL/'
},
{
slug: 'build-a-personal-portfolio-webpage',
solution: 'https://codepen.io/moT01/pen/vgOaoJ'
}
]
};
describe('A certification,', function () { describe('A certification,', function () {
describe('while viewing your own,', function () {
before(() => { before(() => {
cy.exec('npm run seed');
cy.login(); cy.login();
// submit projects for certificate
const { superBlock, block, challenges } = projects;
challenges.forEach(({ slug, solution }) => {
const url = `/learn/${superBlock}/${block}/${slug}`;
cy.visit(url);
cy.get('#dynamic-front-end-form')
.get('#solution')
.type(solution, { force: true, delay: 0 });
cy.contains("I've completed this challenge")
.should('not.be.disabled')
.click();
cy.contains('Submit and go to next challenge').click().wait(1000);
});
cy.visit('/settings'); cy.visit('/settings');
// set user settings to public to claim a cert // set user settings to public to claim a cert
@ -22,32 +64,16 @@ describe('A certification,', function () {
} }
}); });
// fill in legacy front end form // claim certificate
cy.get('#dynamic-legacy-front-end input').each(el => { cy.get('a[href*="developmentuser/responsive-web-design"]').click({
cy.wrap(el) force: true
.clear({ force: true }) });
.type('https://nhl.com', { force: true, delay: 0 });
}); });
// if "Save Progress" button exists describe('while viewing your own,', function () {
cy.get('#dynamic-legacy-front-end').then(form => { before(() => {
if (form[0][10] && form[0][10].innerHTML === 'Save Progress') { cy.login();
form[0][10].click({ force: true }); cy.visit(certificationUrl);
cy.wait(1000);
}
});
// if "Claim Certification" button exists
cy.get('#dynamic-legacy-front-end').then(form => {
if (form[0][10] && form[0][10].innerHTML === 'Claim Certification') {
form[0][10].click({ force: true });
cy.wait(1000);
}
});
cy.get('#button-legacy-front-end')
.contains('Show Certification')
.click({ force: true });
}); });
it('should render a LinkedIn button', function () { it('should render a LinkedIn button', function () {
@ -56,7 +82,7 @@ describe('A certification,', function () {
.and( .and(
'match', 'match',
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/https:\/\/www\.linkedin\.com\/profile\/add\?startTask=CERTIFICATION_NAME&name=Legacy Front End&organizationId=4831032&issueYear=\d\d\d\d&issueMonth=\d\d?&certUrl=https:\/\/freecodecamp\.org\/certification\/developmentuser\/legacy-front-end/ /https:\/\/www\.linkedin\.com\/profile\/add\?startTask=CERTIFICATION_NAME&name=Responsive Web Design&organizationId=4831032&issueYear=\d\d\d\d&issueMonth=\d\d?&certUrl=https:\/\/freecodecamp\.org\/certification\/developmentuser\/responsive-web-design/
); );
}); });
@ -64,7 +90,7 @@ describe('A certification,', function () {
cy.contains('Share this certification on Twitter').should( cy.contains('Share this certification on Twitter').should(
'have.attr', 'have.attr',
'href', 'href',
'https://twitter.com/intent/tweet?text=I just earned the Legacy Front End certification @freeCodeCamp! Check it out here: https://freecodecamp.org/certification/developmentuser/legacy-front-end' 'https://twitter.com/intent/tweet?text=I just earned the Responsive Web Design certification @freeCodeCamp! Check it out here: https://freecodecamp.org/certification/developmentuser/responsive-web-design'
); );
}); });
@ -79,10 +105,14 @@ describe('A certification,', function () {
describe("while viewing someone else's,", function () { describe("while viewing someone else's,", function () {
before(() => { before(() => {
cy.go('back'); cy.visit(certificationUrl);
cy.get('.toggle-button-nav').click(); });
cy.get('.nav-list').contains('Sign out').click();
cy.visit('/certification/developmentuser/legacy-front-end'); it('should display certificate', function () {
cy.contains('has successfully completed the freeCodeCamp.org').should(
'exist'
);
cy.contains('Responsive Web Design').should('exist');
}); });
it('should not render a LinkedIn button', function () { it('should not render a LinkedIn button', function () {

View File

@ -10,9 +10,9 @@ describe('Settings certifications area', () => {
}); });
describe('initially', () => { describe('initially', () => {
it('Should render 11 "Claim Certification" buttons', () => { it('Should render 15 "Claim Certification" buttons', () => {
cy.findAllByText('Claim Certification').should($btns => { cy.findAllByText('Claim Certification').should($btns => {
expect($btns).to.have.length(11); expect($btns).to.have.length(15);
}); });
}); });
@ -54,49 +54,6 @@ describe('Settings certifications area', () => {
'It looks like you have not completed the necessary steps. Please complete the required projects to claim the Responsive Web Design Certification' 'It looks like you have not completed the necessary steps. Please complete the required projects to claim the Responsive Web Design Certification'
).should('exist'); ).should('exist');
}); });
it('Should show "Your projects have been updated." message after submitting projects', () => {
cy.get(
'#dynamic-information-security-and-quality-assurance input'
).each(el => {
cy.wrap(el)
.clear({ force: true })
.type('https://nhl.com', { force: true, delay: 0 });
});
cy.get('#dynamic-information-security-and-quality-assurance').then(
form => {
if (form[0][5] && form[0][5].innerHTML === 'Save Progress') {
form[0][5].click({ force: true });
cy.wait(1000);
}
}
);
cy.contains('Your projects have been updated.').should('exist');
});
it('Should render 12 "Claim Certification" buttons after submitting legacy projects', () => {
cy.findAllByText('Claim Certification').should($btns => {
expect($btns).to.have.length(12);
});
});
it('Should show "congrats" message after claiming a cert', () => {
cy.get(
'#dynamic-information-security-and-quality-assurance button'
).click();
cy.contains(
'@developmentuser, you have successfully claimed the Legacy Information Security and Quality Assurance Certification! Congratulations on behalf of the freeCodeCamp.org team!'
).should('exist');
});
it('Should render a "Show Certification" button after submitting enough projects', () => {
cy.findAllByText('Show Certification').should($btns => {
expect($btns).to.have.length(1);
});
});
}); });
}); });
}); });