From bc9e8a69ded189762a5b785e26c44c29e44f7e24 Mon Sep 17 00:00:00 2001 From: gikf <60067306+gikf@users.noreply.github.com> Date: Fri, 11 Jun 2021 18:06:46 +0200 Subject: [PATCH] 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 --- api-server/src/server/boot/settings.js | 11 - api-server/src/server/utils/constants.js | 1 - .../src/components/settings/Certification.js | 192 ++---------------- .../components/settings/Certification.test.js | 34 ++-- client/src/redux/settings/index.js | 14 +- .../update-legacy-certificate-saga.js | 38 ---- client/src/resources/certAndProjectMap.js | 69 ++++--- client/src/utils/ajax.js | 4 - cypress/integration/ShowCertification.js | 130 +++++++----- .../integration/settings/certifications.js | 47 +---- 10 files changed, 155 insertions(+), 385 deletions(-) delete mode 100644 client/src/redux/settings/update-legacy-certificate-saga.js diff --git a/api-server/src/server/boot/settings.js b/api-server/src/server/boot/settings.js index a46b298ff4..d3d02d6c59 100644 --- a/api-server/src/server/boot/settings.js +++ b/api-server/src/server/boot/settings.js @@ -28,7 +28,6 @@ export default function settingsController(app) { updateMyCurrentChallenge ); api.post('/update-my-portfolio', ifNoUser401, updateMyPortfolio); - api.post('/update-my-projects', ifNoUser401, updateMyProjects); api.post( '/update-my-theme', 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) { const { user, diff --git a/api-server/src/server/utils/constants.js b/api-server/src/server/utils/constants.js index db84d7d404..772b1c6ee3 100644 --- a/api-server/src/server/utils/constants.js +++ b/api-server/src/server/utils/constants.js @@ -80,7 +80,6 @@ let blocklist = [ 'unsubscribed', 'update-my-portfolio', 'update-my-profile-ui', - 'update-my-projects', 'update-my-theme', 'update-my-username', 'user', diff --git a/client/src/components/settings/Certification.js b/client/src/components/settings/Certification.js index ddfd707d9e..5c6d2cd3b4 100644 --- a/client/src/components/settings/Certification.js +++ b/client/src/components/settings/Certification.js @@ -1,8 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; -import { find, first, values, isString } from 'lodash-es'; +import { find, first } from 'lodash-es'; import { Table, Button, @@ -21,16 +19,10 @@ import { import SectionHeader from './SectionHeader'; import ProjectModal from '../SolutionViewer/ProjectModal'; import { FullWidthRow, Spacer } from '../helpers'; -import { Form } from '../formHelpers'; import { maybeUrlRE } from '../../utils'; -import reallyWeirdErrorMessage from '../../utils/reallyWeirdErrorMessage'; import './certification.css'; -import { updateLegacyCert } from '../../redux/settings'; - -const mapDispatchToProps = dispatch => - bindActionCreators({ updateLegacyCert }, dispatch); const propTypes = { completedChallenges: PropTypes.arrayOf( @@ -61,7 +53,6 @@ const propTypes = { isRespWebDesignCert: PropTypes.bool, isSciCompPyCertV7: PropTypes.bool, t: PropTypes.func.isRequired, - updateLegacyCert: PropTypes.func.isRequired, username: PropTypes.string, verifyCert: PropTypes.func.isRequired }; @@ -156,7 +147,6 @@ export class CertificationSettings extends Component { super(props); this.state = { ...initialState }; - this.handleSubmitLegacy = this.handleSubmitLegacy.bind(this); } createHandleLinkButtonClick = to => e => { @@ -258,9 +248,9 @@ export class CertificationSettings extends Component { ); }; - renderCertifications = certName => { + renderCertifications = (certName, projectsMap) => { const { t } = this.props; - const { certSlug } = first(projectMap[certName]); + const { certSlug } = first(projectsMap[certName]); return ( @@ -277,17 +267,18 @@ export class CertificationSettings extends Component { {this.renderProjectsFor( certName, - this.getUserIsCertMap()[certName] + this.getUserIsCertMap()[certName], + projectsMap )} ); }; - renderProjectsFor = (certName, isCert) => { + renderProjectsFor = (certName, isCert, projectsMap) => { const { username, isHonest, createFlashMessage, t, verifyCert } = this.props; - const { certSlug } = first(projectMap[certName]); + const { certSlug } = first(projectsMap[certName]); const certLocation = `/certification/${username}/${certSlug}`; const createClickHandler = certSlug => e => { e.preventDefault(); @@ -298,7 +289,7 @@ export class CertificationSettings extends Component { ? verifyCert(certSlug) : createFlashMessage(honestyInfoMessage); }; - return projectMap[certName] + return projectsMap[certName] .map(({ link, title, id }) => ( @@ -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 ( - - -

- {certName} -

-
- {isCertClaimed ? ( -
- -
- ) : null} - - ); - }; - renderLegacyFullStack = () => { const { isFullStackCert, @@ -587,10 +424,14 @@ export class CertificationSettings extends Component { return (
{t('settings.headings.certs')} - {certifications.map(this.renderCertifications)} + {certifications.map(certName => + this.renderCertifications(certName, projectMap) + )} {t('settings.headings.legacy-certs')} {this.renderLegacyFullStack()} - {legacyCertifications.map(this.renderLegacyCertifications)} + {legacyCertifications.map(certName => + this.renderCertifications(certName, legacyProjectMap) + )} {isOpen ? ( ', () => { ); expect( - container.querySelector('#button-legacy-data-visualization') + container.querySelector( + 'a[href="/certification/developmentuser/legacy-data-visualization"]' + ) ).toHaveTextContent('buttons.show-cert'); }); @@ -32,33 +34,36 @@ describe('', () => { ); expect( - container.querySelector('#button-legacy-data-visualization') - ).toHaveAttribute( - 'href', - '/certification/developmentuser/legacy-data-visualization' - ); + container.querySelector( + 'a[href="/certification/developmentuser/legacy-data-visualization"]' + ) + ).toBeInTheDocument(); }); - // full forms with unclaimed certs should should not shallow render button - it('Should not render show cert button for unclaimed full form', () => { + // full forms with unclaimed certs should not shallow render show cert button + it('Should not render show cert button for unclaimed cert with completed projects', () => { const { container } = renderWithRedux( ); expect( - container.querySelector('#button-legacy-back-end') - ).not.toBeInTheDocument(); + container.querySelector( + 'a[href="/certification/developmentuser/legacy-back-end"]' + ) + ).not.toHaveTextContent('buttons.show-cert'); }); - // empty forms with unclaimed certs should should not shallow render button - it('Should not render show cert button for empty form', () => { + // empty forms with unclaimed certs should not shallow render show cert button + it('Should not render show cert button for cert with no completed projects', () => { const { container } = renderWithRedux( ); expect( - container.querySelector('#button-legacy-front-end') - ).not.toBeInTheDocument(); + container.querySelector( + 'a[href="/certification/developmentuser/legacy-front-end"]' + ) + ).not.toHaveTextContent('buttons.show-cert'); }); it('Render button when only solution is present', () => { @@ -243,7 +248,6 @@ const defaultTestProps = { isSciCompPyCertV7: false, isDataAnalysisPyCertV7: false, isMachineLearningPyCertV7: false, - updateLegacyCert: () => {}, username: 'developmentuser', verifyCert: () => {}, errors: {}, diff --git a/client/src/redux/settings/index.js b/client/src/redux/settings/index.js index 277f7e508d..9609c90c8a 100644 --- a/client/src/redux/settings/index.js +++ b/client/src/redux/settings/index.js @@ -5,10 +5,6 @@ import { createDangerZoneSaga } from './danger-zone-saga'; import { createSettingsSagas } from './settings-sagas'; import { createUpdateMyEmailSaga } from './update-email-saga'; -// prettier-ignore -import { createUpdateLegacyCertSaga } from -'./update-legacy-certificate-saga'; - export const ns = 'settings'; const defaultFetchState = { @@ -31,7 +27,6 @@ export const types = createTypes( ...createAsyncTypes('submitNewAbout'), ...createAsyncTypes('submitNewUsername'), ...createAsyncTypes('updateMyEmail'), - ...createAsyncTypes('updateLegacyCert'), ...createAsyncTypes('updateUserFlag'), ...createAsyncTypes('submitProfileUI'), ...createAsyncTypes('verifyCert'), @@ -44,8 +39,7 @@ export const types = createTypes( export const sagas = [ ...createSettingsSagas(types), ...createUpdateMyEmailSaga(types), - ...createDangerZoneSaga(types), - ...createUpdateLegacyCertSaga(types) + ...createDangerZoneSaga(types) ]; const checkForSuccessPayload = ({ type, payload }) => @@ -78,12 +72,6 @@ export const updateMyEmail = createAction(types.updateMyEmail); export const updateMyEmailComplete = createAction(types.updateMyEmailComplete); 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 updateUserFlagComplete = createAction( types.updateUserFlagComplete, diff --git a/client/src/redux/settings/update-legacy-certificate-saga.js b/client/src/redux/settings/update-legacy-certificate-saga.js deleted file mode 100644 index 8a1b9c8e0a..0000000000 --- a/client/src/redux/settings/update-legacy-certificate-saga.js +++ /dev/null @@ -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)]; -} diff --git a/client/src/resources/certAndProjectMap.js b/client/src/resources/certAndProjectMap.js index b22cb380c4..ebc7f6ae0a 100644 --- a/client/src/resources/certAndProjectMap.js +++ b/client/src/resources/certAndProjectMap.js @@ -16,10 +16,17 @@ const dataAnalysisPyBase = '/learn/data-analysis-with-python/data-analysis-with-python-projects'; const machineLearningPyBase = '/learn/machine-learning-with-python/machine-learning-with-python-projects'; -const legacyFrontEndBase = ''; -const legacyBackEndBase = ''; -const legacyDataVisBase = ''; -const legacyInfosecQaBase = ''; +const takeHomeBase = '/learn/coding-interview-prep/take-home-projects'; +const legacyFrontEndBase = feLibsBase; +const legacyFrontEndResponsiveBase = responsiveWebBase; +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 // from the md/meta.json files for each cert and projects @@ -33,7 +40,7 @@ const certMap = [ { id: 'bd7158d8c242eddfaeb5bd13', title: 'Build a Personal Portfolio Webpage', - link: `${legacyFrontEndBase}/build-a-personal-portfolio-webpage`, + link: `${legacyFrontEndResponsiveBase}/build-a-personal-portfolio-webpage`, certSlug: 'legacy-front-end' }, { @@ -57,37 +64,37 @@ const certMap = [ { id: 'bd7158d8c442eddfaeb5bd10', title: 'Show the Local Weather', - link: `${legacyFrontEndBase}/show-the-local-weather`, + link: `${legacyFrontEndTakeHomeBase}/show-the-local-weather`, certSlug: 'legacy-front-end' }, { id: 'bd7158d8c442eddfaeb5bd1f', title: 'Use the TwitchTV JSON API', - link: `${legacyFrontEndBase}/use-the-twitchtv-json-api`, + link: `${legacyFrontEndTakeHomeBase}/use-the-twitch-json-api`, certSlug: 'legacy-front-end' }, { id: 'bd7158d8c442eddfaeb5bd18', - title: 'Stylize Stories on Camper News', - link: `${legacyFrontEndBase}/stylize-stories-on-camper-news`, + title: 'Build a Tribute Page', + link: `${legacyFrontEndResponsiveBase}/build-a-tribute-page`, certSlug: 'legacy-front-end' }, { id: 'bd7158d8c442eddfaeb5bd19', title: 'Build a Wikipedia Viewer', - link: `${legacyFrontEndBase}/build-a-wikipedia-viewer`, + link: `${legacyFrontEndTakeHomeBase}/build-a-wikipedia-viewer`, certSlug: 'legacy-front-end' }, { id: 'bd7158d8c442eedfaeb5bd1c', 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' }, { id: 'bd7158d8c442eddfaeb5bd1c', title: 'Build a Simon Game', - link: `${legacyFrontEndBase}/build-a-simon-game`, + link: `${legacyFrontEndTakeHomeBase}/build-a-simon-game`, certSlug: 'legacy-front-end' } ] @@ -119,7 +126,7 @@ const certMap = [ { id: 'bd7158d8c443edefaeb5bdee', title: 'Image Search Abstraction Layer', - link: `${legacyBackEndBase}/image-search-abstraction-layer`, + link: `${legacyBackEndTakeHomeBase}/build-an-image-search-abstraction-layer`, certSlug: 'legacy-back-end' }, { @@ -131,31 +138,31 @@ const certMap = [ { id: 'bd7158d8c443eddfaeb5bdef', title: 'Build a Voting App', - link: `${legacyBackEndBase}/build-a-voting-app`, + link: `${legacyBackEndTakeHomeBase}/build-a-voting-app`, certSlug: 'legacy-back-end' }, { id: 'bd7158d8c443eddfaeb5bdff', title: 'Build a Nightlife Coordination App', - link: `${legacyBackEndBase}/build-a-nightlife-coordination-app`, + link: `${legacyBackEndTakeHomeBase}/build-a-nightlife-coordination-app`, certSlug: 'legacy-back-end' }, { id: 'bd7158d8c443eddfaeb5bd0e', title: 'Chart the Stock Market', - link: `${legacyBackEndBase}/chart-the-stock-market`, + link: `${legacyBackEndTakeHomeBase}/chart-the-stock-market`, certSlug: 'legacy-back-end' }, { id: 'bd7158d8c443eddfaeb5bd0f', title: 'Manage a Book Trading Club', - link: `${legacyBackEndBase}/manage-a-book-trading-club`, + link: `${legacyBackEndTakeHomeBase}/manage-a-book-trading-club`, certSlug: 'legacy-back-end' }, { id: 'bd7158d8c443eddfaeb5bdee', title: 'Build a Pinterest Clone', - link: `${legacyBackEndBase}/build-a-pinterest-clone`, + link: `${legacyBackEndTakeHomeBase}/build-a-pinterest-clone`, certSlug: 'legacy-back-end' } ] @@ -177,31 +184,31 @@ const certMap = [ { id: 'bd7157d8c242eddfaeb5bd13', title: 'Build a Markdown Previewer', - link: `${legacyDataVisBase}/build-a-markdown-previewer`, + link: `${legacyDataVisFrontEndBase}/build-a-markdown-previewer`, certSlug: 'legacy-data-visualization' }, { id: 'bd7156d8c242eddfaeb5bd13', - title: 'Build a Camper Leaderboard', - link: `${legacyDataVisBase}/build-a-camper-leaderboard`, + title: 'Build a freeCodeCamp Forum Homepage', + link: `${legacyDataVisTakeHomeBase}/build-a-freecodecamp-forum-homepage`, certSlug: 'legacy-data-visualization' }, { id: 'bd7155d8c242eddfaeb5bd13', title: 'Build a Recipe Box', - link: `${legacyDataVisBase}/build-a-recipe-box`, + link: `${legacyDataVisTakeHomeBase}/build-a-recipe-box`, certSlug: 'legacy-data-visualization' }, { id: 'bd7154d8c242eddfaeb5bd13', title: 'Build the Game of Life', - link: `${legacyDataVisBase}/build-the-game-of-life`, + link: `${legacyDataVisTakeHomeBase}/build-the-game-of-life`, certSlug: 'legacy-data-visualization' }, { id: 'bd7153d8c242eddfaeb5bd13', 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' }, { @@ -225,13 +232,13 @@ const certMap = [ { id: 'bd7198d8c242eddfaeb5bd13', 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' }, { id: 'bd7108d8c242eddfaeb5bd13', title: 'Map Data Across the Globe', - link: `${legacyDataVisBase}/map-data-across-the-globe`, + link: `${legacyDataVisTakeHomeBase}/map-data-across-the-globe`, certSlug: 'legacy-data-visualization' } ] @@ -247,31 +254,31 @@ const certMap = [ { id: '587d8249367417b2b2512c41', title: 'Metric-Imperial Converter', - link: `${legacyInfosecQaBase}/metric-imperial-converter`, + link: `${legacyInfosecQaQaBase}/metric-imperial-converter`, certSlug: 'information-security-and-quality-assurance' }, { id: '587d8249367417b2b2512c42', title: 'Issue Tracker', - link: `${legacyInfosecQaBase}/issue-tracker`, + link: `${legacyInfosecQaQaBase}/issue-tracker`, certSlug: 'information-security-and-quality-assurance' }, { id: '587d824a367417b2b2512c43', title: 'Personal Library', - link: `${legacyInfosecQaBase}/personal-library`, + link: `${legacyInfosecQaQaBase}/personal-library`, certSlug: 'information-security-and-quality-assurance' }, { id: '587d824a367417b2b2512c44', title: 'Stock Price Checker', - link: `${legacyInfosecQaBase}/stock-price-checker`, + link: `${legacyInfosecQaInfosecBase}/stock-price-checker`, certSlug: 'information-security-and-quality-assurance' }, { id: '587d824a367417b2b2512c45', title: 'Anonymous Message Board', - link: `${legacyInfosecQaBase}/anonymous-message-board`, + link: `${legacyInfosecQaInfosecBase}/anonymous-message-board`, certSlug: 'information-security-and-quality-assurance' } ] diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js index d9fb422c02..79c87517bd 100644 --- a/client/src/utils/ajax.js +++ b/client/src/utils/ajax.js @@ -65,10 +65,6 @@ export function addDonation(body) { return post('/donate/add-donation', body); } -export function putUpdateLegacyCert(body) { - return post('/update-my-projects', body); -} - export function postReportUser(body) { return post('/user/report-user', body); } diff --git a/cypress/integration/ShowCertification.js b/cypress/integration/ShowCertification.js index a1a1f54f85..02084649ff 100644 --- a/cypress/integration/ShowCertification.js +++ b/cypress/integration/ShowCertification.js @@ -1,53 +1,79 @@ /* 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 () { + before(() => { + cy.exec('npm run seed'); + 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'); + + // set user settings to public to claim a cert + cy.get('label:contains(Public)>input').each(el => { + if (!/toggle-active/.test(el[0].parentElement.className)) { + cy.wrap(el).click({ force: true }); + cy.wait(1000); + } + }); + + // if honest policy not accepted + cy.get('.honesty-policy button').then(btn => { + if (btn[0].innerText === 'Agree') { + btn[0].click({ force: true }); + cy.wait(1000); + } + }); + + // claim certificate + cy.get('a[href*="developmentuser/responsive-web-design"]').click({ + force: true + }); + }); + describe('while viewing your own,', function () { before(() => { cy.login(); - cy.visit('/settings'); - - // set user settings to public to claim a cert - cy.get('label:contains(Public)>input').each(el => { - if (!/toggle-active/.test(el[0].parentElement.className)) { - cy.wrap(el).click({ force: true }); - cy.wait(1000); - } - }); - - // if honest policy not accepted - cy.get('.honesty-policy button').then(btn => { - if (btn[0].innerText === 'Agree') { - btn[0].click({ force: true }); - cy.wait(1000); - } - }); - - // fill in legacy front end form - cy.get('#dynamic-legacy-front-end input').each(el => { - cy.wrap(el) - .clear({ force: true }) - .type('https://nhl.com', { force: true, delay: 0 }); - }); - - // if "Save Progress" button exists - cy.get('#dynamic-legacy-front-end').then(form => { - if (form[0][10] && form[0][10].innerHTML === 'Save Progress') { - form[0][10].click({ force: true }); - 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 }); + cy.visit(certificationUrl); }); it('should render a LinkedIn button', function () { @@ -56,7 +82,7 @@ describe('A certification,', function () { .and( 'match', // 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( 'have.attr', '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 () { before(() => { - cy.go('back'); - cy.get('.toggle-button-nav').click(); - cy.get('.nav-list').contains('Sign out').click(); - cy.visit('/certification/developmentuser/legacy-front-end'); + cy.visit(certificationUrl); + }); + + 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 () { diff --git a/cypress/integration/settings/certifications.js b/cypress/integration/settings/certifications.js index 1facb1d2d3..88edaebe4b 100644 --- a/cypress/integration/settings/certifications.js +++ b/cypress/integration/settings/certifications.js @@ -10,9 +10,9 @@ describe('Settings certifications area', () => { }); describe('initially', () => { - it('Should render 11 "Claim Certification" buttons', () => { + it('Should render 15 "Claim Certification" buttons', () => { 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' ).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); - }); - }); }); }); });