diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index f6cecb798a..52e277b419 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -62,6 +62,7 @@ jobs: runs-on: ubuntu-20.04 needs: build-client strategy: + fail-fast: false matrix: browsers: [chrome, firefox, electron] node-version: [16.x] diff --git a/.github/workflows/node.js-tests-upcoming.yml b/.github/workflows/node.js-tests-upcoming.yml index 8d45e5ef7d..ca3254ae99 100644 --- a/.github/workflows/node.js-tests-upcoming.yml +++ b/.github/workflows/node.js-tests-upcoming.yml @@ -15,6 +15,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -47,6 +48,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] diff --git a/.github/workflows/node.js-tests.yml b/.github/workflows/node.js-tests.yml index 0e6aae4e76..9d6f8a81b4 100644 --- a/.github/workflows/node.js-tests.yml +++ b/.github/workflows/node.js-tests.yml @@ -10,6 +10,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -39,6 +40,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -70,6 +72,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -103,6 +106,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] locale: [chinese, espanol] diff --git a/cypress/integration/ShowCertification.js b/cypress/integration/ShowCertification.js index eaad82c898..c740d17325 100644 --- a/cypress/integration/ShowCertification.js +++ b/cypress/integration/ShowCertification.js @@ -1,112 +1,44 @@ -import { SuperBlocks } from '../../config/certification-settings'; - -const certificationUrl = '/certification/developmentuser/responsive-web-design'; -const projects = { - superBlock: SuperBlocks.RespWebDesign, - 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' - } - ] -}; +const certifiedUser = '/certification/certifieduser/responsive-web-design'; 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.get('.donation-modal').should('be.visible'); - 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 - }); + cy.exec('npm run seed:certified-user'); }); describe('while viewing your own,', function () { - before(() => { + beforeEach(() => { cy.login(); - cy.visit(certificationUrl); }); - it('should render a LinkedIn button', function () { + cy.visit(certifiedUser); cy.contains('Add this certification to my LinkedIn profile') .should('have.attr', 'href') .and( 'match', // eslint-disable-next-line max-len - /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/ + /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\/certifieduser\/responsive-web-design/ ); }); it('should render a Twitter button', function () { + cy.visit(certifiedUser); cy.contains('Share this certification on Twitter').should( 'have.attr', 'href', - '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' + 'https://twitter.com/intent/tweet?text=I just earned the Responsive Web Design certification @freeCodeCamp! Check it out here: https://freecodecamp.org/certification/certifieduser/responsive-web-design' ); }); - it("should be issued with today's date", () => { - const date = new Date(); - const issued = `Issued\xa0${new Intl.DateTimeFormat('en-US', { - month: 'long' - }).format(date)} ${date.getDate()}, ${date.getFullYear()}`; + it('should be issued with the submission date', () => { + cy.visit(certifiedUser); + const issued = `Issued\xa0August 3, 2018`; cy.get('[data-cy=issue-date]').should('have.text', issued); }); }); describe("while viewing someone else's,", function () { before(() => { - cy.visit(certificationUrl); + cy.visit(certifiedUser); }); it('should display certificate', function () { diff --git a/cypress/integration/learn/challenges/projects.js b/cypress/integration/learn/challenges/projects.js index f2a41d6148..08d736c8e6 100644 --- a/cypress/integration/learn/challenges/projects.js +++ b/cypress/integration/learn/challenges/projects.js @@ -34,6 +34,7 @@ const pythonProjects = { describe('project submission', () => { beforeEach(() => { cy.exec('npm run seed'); + cy.login(); }); // NOTE: this will fail once challenge tests are added. it('Should be possible to submit Python projects', () => { @@ -46,7 +47,7 @@ describe('project submission', () => { .type('https://replit.com/@camperbot/python-project#main.py'); cy.contains("I've completed this challenge").click(); - cy.contains('Go to next challenge'); + cy.contains('go to next challenge'); // clicking on 'Go to next challenge' seems to have caused flakiness, so // it's commented out until we figure out why. // cy.contains('Go to next challenge').click(); @@ -61,7 +62,6 @@ describe('project submission', () => { 'JavaScript projects can be submitted and then viewed in /settings and on the certifications', { browser: 'electron' }, () => { - cy.login(); cy.fixture('../../config/curriculum.json').then(curriculum => { const { challenges, meta } = curriculum[SuperBlocks.JsAlgoDataStruct].blocks[ diff --git a/cypress/integration/learn/common-components/navbar.js b/cypress/integration/learn/common-components/navbar.js index 6f0a6701ca..928cc7ee71 100644 --- a/cypress/integration/learn/common-components/navbar.js +++ b/cypress/integration/learn/common-components/navbar.js @@ -32,7 +32,7 @@ function waitForAppStart() { }); } -describe('Navbar', () => { +describe('Navbar when logged out', () => { beforeEach(() => { appHasStarted = false; cy.visit('/', { @@ -41,6 +41,32 @@ describe('Navbar', () => { cy.viewport(1300, 660); }); + it('Should have a "Sign in" button', () => { + cy.contains("[data-test-label='landing-small-cta']", 'Sign in'); + }); + + it( + 'Should have `Sign in` link on landing and learn pages' + + ' when not signed in', + () => { + cy.contains(selectors.smallCallToAction, 'Sign in'); + cy.get(selectors.menuButton).click(); + cy.get(selectors.navigationLinks).contains('Curriculum').click(); + cy.contains(selectors.smallCallToAction, 'Sign in'); + } + ); +}); + +describe('Navbar Logged in', () => { + beforeEach(() => { + cy.login(); + appHasStarted = false; + cy.visit('/', { + onBeforeLoad: spyOnListener + }).then(waitForAppStart); + cy.viewport(1300, 660); + }); + it('Should render properly', () => { cy.get('#universal-nav').should('be.visible'); cy.get('#universal-nav').should('have.class', 'universal-nav'); @@ -56,10 +82,6 @@ describe('Navbar', () => { } ); - it('Should have a "Sign in" button', () => { - cy.contains("[data-test-label='landing-small-cta']", 'Sign in'); - }); - // have the curriculum and CTA on landing and /learn pages. it( 'Should have `Radio`, `Forum`, and `Curriculum` links on landing and learn pages' + @@ -75,32 +97,18 @@ describe('Navbar', () => { } ); - it( - 'Should have `Sign in` link on landing and learn pages' + - ' when not signed in', - () => { - cy.contains(selectors.smallCallToAction, 'Sign in'); - cy.get(selectors.menuButton).click(); - cy.get(selectors.navigationLinks).contains('Curriculum').click(); - cy.contains(selectors.smallCallToAction, 'Sign in'); - } - ); - it('Should have `Profile` link when user is signed in', () => { - cy.login(); cy.get(selectors.menuButton).click(); cy.get(selectors.navigationLinks).contains('Profile').click(); cy.url().should('include', '/developmentuser'); }); it('Should have a profile image with class `default-border`', () => { - cy.login(); cy.get(selectors.avatarContainer).should('have.class', 'default-border'); cy.get(selectors.defaultAvatar).should('exist'); }); it('Should have a profile image with dimensions that are <= 31px', () => { - cy.login(); cy.get(selectors.avatarImage).invoke('width').should('lte', 31); cy.get(selectors.avatarImage).invoke('height').should('lte', 31); }); diff --git a/cypress/integration/settings/certifications.js b/cypress/integration/settings/certifications.js index f566d0efc8..8d6daa5b90 100644 --- a/cypress/integration/settings/certifications.js +++ b/cypress/integration/settings/certifications.js @@ -1,57 +1,40 @@ import '@testing-library/cypress/add-commands'; describe('Settings certifications area', () => { - before(() => { - cy.exec('npm run seed'); - cy.login(); - cy.visit('/settings'); - }); - describe('initially', () => { - it('Should render 15 "Claim Certification" buttons', () => { + before(() => { + cy.exec('npm run seed'); + cy.login(); + }); + + it('Should render the default settings page', () => { + cy.visit('/settings/'); cy.findAllByText('Claim Certification').should($btns => { expect($btns).to.have.length(15); }); + cy.findByText('Show Certification').should('not.exist'); + cy.contains('Agree'); + cy.contains('Claim Certification').click(); + cy.contains( + 'To claim a certification, you must first accept our academic honesty policy' + ); + }); + }); + + describe('after isHonest', () => { + before(() => { + cy.exec('npm run seed'); + cy.login(); }); - it('Should render zero "Show Certification" buttons', () => { - cy.contains('Show Certification').should('not.exist'); - }); - - it('Should render one "Agree" button', () => { - cy.contains('Agree').should('exist'); - }); - - describe('before isHonest', () => { - it('Should show "must agree" message when trying to claim a cert', () => { - cy.contains('Claim Certification').click(); - cy.contains( - 'To claim a certification, you must first accept our academic honesty policy' - ).should('exist'); - }); - }); - - describe('after isHonest', () => { - beforeEach(() => { - cy.visit('/'); - cy.login(); - cy.visit('/settings'); - }); - - it('Should render "You have accepted our Academic Honesty Policy." button after clicking "Agree"', () => { - cy.contains('Agree').click({ force: true }); - cy.contains('You have accepted our Academic Honesty Policy.').should( - 'exist' - ); - }); - - it('Should show "incompleted projects" message when clicking "Claim Certification"', () => { - cy.contains('Claim Certification').click({ force: true }); - - cy.contains( - '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 update the user as they try to claim their certifications', () => { + cy.visit('/settings'); + cy.contains('Agree').click(); + cy.contains('You have accepted our Academic Honesty Policy.'); + cy.contains('Claim Certification').click(); + cy.contains( + 'It looks like you have not completed the necessary steps. Please complete the required projects to claim the Responsive Web Design Certification' + ); }); }); }); diff --git a/cypress/integration/settings/settings.js b/cypress/integration/settings/settings.js index 5a40f885f8..c88efcf73f 100644 --- a/cypress/integration/settings/settings.js +++ b/cypress/integration/settings/settings.js @@ -1,6 +1,8 @@ describe('Settings', () => { - it('should be possible to reset your progress', () => { + before(() => { cy.login(); + }); + it('should be possible to reset your progress', () => { cy.visit('/settings'); cy.contains('Reset all of my progress').click(); cy.contains('Reset everything. I want to start from the beginning').click(); diff --git a/cypress/integration/settings/username-change.js b/cypress/integration/settings/username-change.js index ca13b3a1f9..d1bb2d75ab 100644 --- a/cypress/integration/settings/username-change.js +++ b/cypress/integration/settings/username-change.js @@ -1,14 +1,18 @@ describe('Username input field', () => { beforeEach(() => { cy.login(); + }); + + function goToSettings() { cy.visit('/settings'); // Setting aliases here cy.get('input[name=username-settings]').as('usernameInput'); cy.get('form#usernameSettings').as('usernameForm'); - }); + } it('Should be possible to type', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('twaha', { force: true }) @@ -16,6 +20,7 @@ describe('Username input field', () => { }); it('Should show message when validating name', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('twaha', { force: true }); @@ -28,6 +33,7 @@ describe('Username input field', () => { }); it('Should show username is available if it is', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('brad', { force: true }); @@ -41,6 +47,7 @@ describe('Username input field', () => { }); it('Should info message if username is available', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('mrugesh', { force: true }); @@ -58,6 +65,7 @@ describe('Username input field', () => { // eslint-disable-next-line it('Should be able to click the `Save` button if username is avalable', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('oliver', { force: true }); @@ -68,6 +76,7 @@ describe('Username input field', () => { }); it('Should show username is unavailable if it is', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('twaha', { force: true }); @@ -82,6 +91,7 @@ describe('Username input field', () => { // eslint-disable-next-line it('Should not be possible to click the `Save` button if username is unavailable', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('twaha', { force: true }); @@ -97,6 +107,7 @@ describe('Username input field', () => { }); it('Should not show anything if user types their current name', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('developmentuser', { force: true }); @@ -106,6 +117,7 @@ describe('Username input field', () => { // eslint-disable-next-line max-len it('Should not be possible to click the `Save` button if user types their current name', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('developmentuser', { force: true }); @@ -114,6 +126,7 @@ describe('Username input field', () => { }); it('Should show warning if username includes invalid character', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('Quincy Larson', { force: true }); @@ -128,6 +141,7 @@ describe('Username input field', () => { // eslint-disable-next-line max-len it('Should not be able to click the `Save` button if username includes invalid character', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('Quincy Larson', { force: true }); @@ -136,6 +150,7 @@ describe('Username input field', () => { }); it('Should change username if `Save` button is clicked', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('quincy', { force: true }); @@ -149,6 +164,7 @@ describe('Username input field', () => { }); it('Should change username with uppercase characters if `Save` button is clicked', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('Quincy', { force: true }); @@ -162,6 +178,7 @@ describe('Username input field', () => { }); it('Should show flash message showing username has been updated', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('nhcarrigan', { force: true }); @@ -181,6 +198,7 @@ describe('Username input field', () => { }); it('Should be able to close the shown flash message', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('bjorno', { force: true }); @@ -197,6 +215,7 @@ describe('Username input field', () => { }); it('Should change username if enter is pressed', () => { + goToSettings(); cy.get('@usernameInput') .clear({ force: true }) .type('symbol', { force: true }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index af020d1e21..c85d078178 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -33,14 +33,7 @@ // Cypress.Commands.overwrite('visit', (originalFn, url, options) => {}); Cypress.Commands.add('login', () => { - cy.visit('/'); - cy.contains("Get started (it's free)").click(); - cy.location({ timeout: 10000 }).should(loc => { - // I'm not 100% sure why logins get redirected to /learn/ via 301 in - // development, but not in production, but they do. Hence to make it easier - // work on tests, we'll just allow for both. - expect(loc.pathname).to.match(/^\/learn\/?$/); - }); + cy.visit('http://localhost:3000/signin'); cy.contains('Welcome back'); }); @@ -60,7 +53,6 @@ Cypress.Commands.add('toggleAll', () => { }); Cypress.Commands.add('resetUsername', () => { - cy.login(); cy.visit('/settings'); cy.get('@usernameInput') diff --git a/tools/scripts/seed/certifiedUserData.js b/tools/scripts/seed/certifiedUserData.js index 7962f1040d..9b3e21ccc9 100644 --- a/tools/scripts/seed/certifiedUserData.js +++ b/tools/scripts/seed/certifiedUserData.js @@ -4635,7 +4635,7 @@ module.exports = { rand: 0.6126749173148205, theme: 'default', profileUI: { - isLocked: true, + isLocked: false, showAbout: true, showCerts: true, showDonation: true,