feat(test, e2e) test suit for cypress (#42138)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Sem Bauke
2021-06-14 18:37:52 +02:00
committed by GitHub
parent 08fc4014c7
commit 22b45761a7
35 changed files with 367 additions and 55 deletions

View File

@@ -0,0 +1,128 @@
/* 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(certificationUrl);
});
it('should render a LinkedIn button', function () {
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/
);
});
it('should render a Twitter button', function () {
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'
);
});
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()}`;
cy.get('[data-cy=issue-date]').should('have.text', issued);
});
});
describe("while viewing someone else's,", function () {
before(() => {
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 () {
cy.contains('Add this certification to my LinkedIn profile').should(
'not.exist'
);
});
it('should not render a Twitter button', function () {
cy.contains('Share this certification on Twitter').should('not.exist');
});
});
});

View File

@@ -0,0 +1,70 @@
/* global cy */
const selectors = {
heading: "[data-test-label='landing-header']",
callToAction: "[data-test-label='landing-big-cta']",
certifications: "[data-test-label='certifications']",
testimonials: "[data-test-label='testimonial-cards']",
landingPageImage: '.landing-page-image'
};
const certifications = [
'Responsive Web Design',
'JavaScript Algorithms and Data Structures',
'Front End Development Libraries',
'Data Visualization',
'APIs and Microservices',
'Quality Assurance',
'Scientific Computing with Python',
'Data Analysis with Python',
'Information Security',
'Machine Learning with Python'
];
describe('Landing page', () => {
it('Should render', () => {
cy.visit('/');
cy.title().should(
'eq',
'Learn to Code — For Free — Coding Courses for Busy People'
);
cy.contains(selectors.callToAction, "Get started (it's free)");
cy.get(selectors.callToAction).should('have.length', 2);
});
it('Has visible header and sub-header', () => {
cy.contains(selectors.heading, 'Learn to code — for free.');
cy.contains('Build projects.').should('be.visible');
cy.contains('Earn certifications.').should('be.visible');
cy.contains(
'Since 2014, more than 40,000 freeCodeCamp.org ' +
'graduates have gotten jobs at tech companies including:'
).should('be.visible');
});
it('Has 5 brand logos', () => {
cy.get('.logo-row').children().its('length').should('eq', 5);
});
it('Has `as seens as` section', () => {
cy.contains('Build projects.').should('be.visible');
cy.get('.big-heading').siblings().get('svg');
});
it('Has a visible large image on large viewports', function () {
cy.viewport(1200, 660).get(selectors.landingPageImage).should('be.visible');
cy.viewport(1199, 660).get(selectors.landingPageImage).should('not.exist');
});
it('Has links to all the certifications', function () {
cy.get(selectors.certifications).children().its('length').should('eq', 10);
cy.wrap(certifications).each(cert => {
cy.get(selectors.certifications).contains(cert);
});
});
it('Has 3 testimonial cards', function () {
cy.get(selectors.testimonials).children().its('length').should('eq', 3);
});
});

View File

@@ -0,0 +1,41 @@
/* global cy */
const locations = {
index:
'learn/apis-and-microservices/managing-packages-with-npm/' +
'how-to-use-package-json-the-core-of-any-node-js-project-or-npm-package'
};
const selectors = {
defaultOutput: '.output-text',
input: 'input[name="solution"]'
};
const unhandledErrorMessage = 'Something is not quite right';
const runningOutput = '// running tests';
const finishedOutput = '// tests completed';
describe('Backend challenge', function () {
it('renders', () => {
cy.visit(locations.index);
cy.title().should(
'eq',
'Managing Packages with Npm - How to Use package.json, the Core of Any' +
' Node.js Project or npm Package | Learn | freeCodeCamp.org'
);
});
it('does not generate unhandled errors on submission', () => {
cy.visit(locations.index);
cy.get(selectors.input)
.type('https://example.com')
.type('{enter}')
.then(() => {
cy.get(selectors.defaultOutput)
.contains(runningOutput)
.contains(finishedOutput);
cy.contains(unhandledErrorMessage).should('not.exist');
});
});
});

View File

@@ -0,0 +1,64 @@
/* global cy */
const selectors = {
defaultOutput: '.output-text',
editor: '.monaco-editor',
hotkeys: '.default-layout > div',
runTestsButton: 'button:contains("Run the Tests")'
};
const locations = {
index:
'/learn/responsive-web-design/basic-html-and-html5/' +
'say-hello-to-html-elements'
};
const defaultOutput = `
/**
* Your test output will go here
*/`;
const runningOutput = '// running tests';
const finishedOutput = '// tests completed';
describe('Classic challenge', function () {
before(() => {
cy.visit(locations.index);
});
it('renders the default output text', () => {
cy.title().should(
'eq',
'Learn Basic HTML and HTML5: Say Hello to HTML Elements |' +
' freeCodeCamp.org'
);
cy.get(selectors.defaultOutput).contains(defaultOutput);
});
it('shows test output when the tests are run', () => {
// first wait for the editor to load
cy.get(selectors.editor, { timeout: 15000 });
cy.get(selectors.runTestsButton)
.click()
.then(() => {
cy.get(selectors.defaultOutput)
.contains(runningOutput)
.contains(finishedOutput);
});
});
it('shows test output when the tests are triggered by the keyboard', () => {
// first wait for the editor to load
cy.get(selectors.editor, {
timeout: 15000
});
cy.get(selectors.hotkeys)
.focus()
.type('{ctrl}{enter}')
.then(() => {
cy.get(selectors.defaultOutput)
.contains(runningOutput)
.contains(finishedOutput);
});
});
});

View File

@@ -0,0 +1,52 @@
/* global cy */
const projects = {
superBlock: 'machine-learning-with-python',
block: 'machine-learning-with-python-projects',
challenges: [
{
slug: 'book-recommendation-engine-using-knn',
nextChallengeText: 'Linear Regression Health Costs Calculator'
},
{
slug: 'cat-and-dog-image-classifier',
nextChallengeText: 'Book Recommendation Engine using KNN'
},
{
slug: 'linear-regression-health-costs-calculator',
nextChallengeText: 'Neural Network SMS Text Classifier'
},
{
slug: 'neural-network-sms-text-classifier',
nextChallengeText: 'Find the Symmetric Difference'
},
{
slug: 'rock-paper-scissors',
nextChallengeText: 'Cat and Dog Image Classifier'
}
]
};
describe('project submission', () => {
// NOTE: this will fail once challenge tests are added.
it('Should be possible to submit Python projects', () => {
const { superBlock, block, challenges } = projects;
challenges.forEach(({ slug }) => {
const url = `/learn/${superBlock}/${block}/${slug}`;
cy.visit(url);
cy.get('#dynamic-front-end-form')
.get('#solution')
.type('https://replit.com/@camperbot/python-project#main.py');
cy.contains("I've completed this challenge").click();
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();
// The next two commands are to confirm that go to next challenge has
// moved us to the expected challenge before we loop again.
// cy.get('.title-text').should('include.text', nextChallengeText);
// cy.url().should('not.have.string', url);
});
});
});

View File

@@ -0,0 +1,19 @@
/* global cy */
describe('Certification intro page', () => {
before(() => {
cy.clearCookies();
cy.login();
cy.visit('/learn/coding-interview-prep');
});
it('Should render', () => {
cy.contains(
"If you're looking for free coding exercises to prepare for your next job interview, we've got you covered."
).should('be.visible');
});
it('Title should not include the word "Certification"', () => {
cy.title().should('eq', 'Coding Interview Prep | freeCodeCamp.org');
});
});

View File

@@ -0,0 +1,31 @@
/* global cy */
const selectors = {
footer: '.site-footer'
};
describe('Footer', () => {
it('Should render on landing page', () => {
cy.visit('/');
cy.get(selectors.footer).should('be.visible');
});
it('Should render on learn page', () => {
cy.visit('/learn');
cy.get(selectors.footer).should('be.visible');
cy.visit('/learn/');
cy.get(selectors.footer).should('be.visible');
});
it('Should render on superblock page', () => {
cy.visit('/learn/responsive-web-design/');
cy.get(selectors.footer).should('be.visible');
});
it('Should not render on challenge page', () => {
cy.visit(
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements'
);
cy.get(selectors.footer).should('not.exist');
});
});

View File

@@ -0,0 +1,36 @@
/* global cy */
describe('Help Button', () => {
it('should be visible', () => {
cy.visit(
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements'
);
cy.get('#get-help-dropdown').scrollIntoView().should('be.visible');
});
it('should toggle the dropdown menu', () => {
cy.get('#get-help-dropdown').scrollIntoView().click();
cy.get('ul[role="menu"]').should('be.visible');
});
it('should render three links when video is available', () => {
cy.get('ul[role="menu"]').within(() => {
cy.get('a').should('have.length', 3);
cy.get('a').eq(0).contains('Get a Hint');
cy.get('a').eq(1).contains('Watch a Video');
cy.get('a').eq(2).contains('Ask for Help');
});
});
it('should render two links when video is not available', () => {
cy.visit(
'/learn/front-end-libraries/bootstrap/apply-the-default-bootstrap-button-style'
);
cy.get('#get-help-dropdown').scrollIntoView().click();
cy.get('ul[role="menu"]').within(() => {
cy.get('a').should('have.length', 2);
cy.get('a').eq(0).contains('Get a Hint');
cy.get('a').eq(1).contains('Ask for Help');
});
});
});

View File

@@ -0,0 +1,109 @@
/* global cy */
const selectors = {
heading: "[data-test-label='landing-header']",
smallCallToAction: "[data-test-label='landing-small-cta']",
navigationLinks: '.nav-list',
avatarContainer: '.avatar-container',
defaultAvatar: '.avatar-container',
menuButton: '.toggle-button-nav',
avatarImage: '.avatar-container .avatar'
};
let appHasStarted;
function spyOnListener(win) {
const addListener = win.EventTarget.prototype.addEventListener;
win.EventTarget.prototype.addEventListener = function (name) {
if (name === 'click') {
appHasStarted = true;
win.EventTarget.prototype.addEventListener = addListener;
}
return addListener.apply(this, arguments);
};
}
function waitForAppStart() {
return new Promise(resolve => {
const isReady = () => {
if (appHasStarted) {
return resolve();
}
return setTimeout(isReady, 0);
};
isReady();
});
}
describe('Navbar', () => {
beforeEach(() => {
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');
});
it(
'Should take user to learn page when clicked on ' + 'the freeCodeCamp logo',
() => {
cy.get('.universal-nav-middle').within(() => {
cy.get('svg').click();
});
cy.url().should('include', '/learn');
}
);
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' +
'page when not signed in',
() => {
cy.get(selectors.menuButton).click();
cy.get(selectors.navigationLinks).contains('Forum');
cy.get(selectors.navigationLinks).contains('Curriculum').click();
cy.url().should('include', '/learn');
cy.get(selectors.navigationLinks).contains('Curriculum');
cy.get(selectors.navigationLinks).contains('Forum');
cy.get(selectors.navigationLinks).contains('Radio');
}
);
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);
});
});

View File

@@ -0,0 +1,77 @@
/* global cy */
const search = query => {
cy.get('.ais-SearchBox').within(() => {
cy.get('input').type(query);
});
cy.wait(300);
};
const clear = () => {
cy.get('.ais-SearchBox').within(() => {
cy.get('input').clear();
});
};
describe('Search bar', () => {
before(() => {
cy.visit('/');
});
beforeEach(() => {
clear();
});
it('Should render properly', () => {
cy.get('.ais-SearchBox').should('be.visible');
});
it('Should accept input and display hits', () => {
search('freeCodeCamp');
cy.get('.ais-Hits-list').children().should('to.have.length.of.at.least', 1);
});
it('Should clear hits when input is cleared', () => {
search('freeCodeCamp');
cy.get('.ais-Hits-list').children().should('to.have.length.of.at.least', 1);
clear();
cy.get('div.ais-Hits').should('not.exist');
});
it('Should show up to 8 hits when height >= 768px', () => {
cy.viewport(1300, 768);
search('freeCodeCamp');
cy.get('.ais-Hits-list').children().should('to.have.length.of', 8);
});
it('Should show up to 5 hits when height < 768px', () => {
cy.viewport(1300, 767);
search('freeCodeCamp');
cy.get('.ais-Hits-list').children().should('to.have.length.of', 5);
});
it('Should show no hits for queries that do not exist in the Algolia index', () => {
search('testtttt');
cy.get('.ais-Hits-list').children().should('to.have.length.of', 0);
cy.contains('No tutorials found');
});
it('Should not redirect to the News search page if there are no hits', () => {
search('testtttt');
cy.get('.ais-SearchBox-form').submit();
cy.url('/');
});
});

View File

@@ -0,0 +1,65 @@
/* global cy */
const selectors = {
donateSupport: {
firstTitle: '.donate-support h4:first-of-type b',
secondTitle: '.donate-support h4:last-of-type b',
firstText: '.donate-support p:first-of-type',
secondText: '.donate-support p:last-of-type',
link: '.donate-support a'
}
};
describe('Donate page', () => {
before(() => {
cy.clearCookies();
cy.exec('npm run seed');
cy.login();
cy.visit('/donate');
});
it('Should render', () => {
cy.title().should('eq', 'Support our nonprofit | freeCodeCamp.org');
});
it('Should display default amount and duration', () => {
cy.contains('Confirm your donation of $5 / month:').should('be.visible');
});
it('Should have support section', () => {
cy.contains(
'Want to make a bigger one-time donation, mail us a check, or give in other ways?'
).should('be.visible');
});
it('Support section should have support text', () => {
cy.contains(
selectors.donateSupport.firstTitle,
'Want to make a bigger one-time donation, mail us a check, or give in other ways?'
);
cy.contains(
selectors.donateSupport.secondTitle,
'Need help with your current or past donations?'
);
cy.contains(
selectors.donateSupport.firstText,
"Here are many other ways you can support our non-profit's mission."
);
cy.contains(
selectors.donateSupport.secondText,
'Forward a copy of your donation receipt to donors@freecodecamp.org and tell us how we can help.'
);
});
it('Support section should have donation link', () => {
cy.get(selectors.donateSupport.link).should(
'have.attr',
'href',
'https://www.freecodecamp.org/news/how-to-donate-to-free-code-camp'
);
});
it('Donor alert should not be visible for non-donor', () => {
cy.get('.alert-info').should('not.exist');
});
});

View File

@@ -0,0 +1,45 @@
/* global cy */
const selectors = {
donateAlert: {
firstText: '.alert-info p:first-child',
secondText: '.alert-info p:last-child',
link: '.alert-info a'
}
};
describe('Donate page', () => {
before(() => {
cy.clearCookies();
cy.exec('npm run seed -- --donor');
cy.login();
cy.visit('/donate');
});
after(() => {
cy.exec('npm run seed');
});
it('Donor alert should be visible for donor', () => {
cy.get('.alert-info').should('be.visible');
});
it('Donor should see alert message', () => {
cy.contains(
selectors.donateAlert.firstText,
'Thank you for being a supporter of freeCodeCamp. You currently have a recurring donation.'
);
cy.contains(
selectors.donateAlert.lastText,
"Want to make a bigger one-time donation, mail us a check, or give in other ways? Here are many other ways you can support our non-profit's mission."
);
});
it('Donor alert section should have donation link', () => {
cy.get(selectors.donateAlert.link).should(
'have.attr',
'href',
'https://www.freecodecamp.org/news/how-to-donate-to-free-code-camp'
);
});
});

View File

@@ -0,0 +1,45 @@
/* global cy */
describe('Donate page', () => {
before(() => {
cy.clearCookies();
cy.exec('npm run seed');
cy.login();
});
after(() => {
cy.exec('npm run seed');
});
const projects = [
'tribute-page',
'survey-form',
'product-landing-page',
'technical-documentation-page',
'personal-portfolio-webpage'
];
it('Should be able to submit projects', () => {
const submitProject = str => {
cy.visit(
`/learn/responsive-web-design/responsive-web-design-projects/build-a-${str}`
);
cy.get('#dynamic-front-end-form')
.get('#solution')
.type('https://codepen.io/camperbot/full/oNvPqqo', {
force: true
});
cy.contains("I've completed this challenge").click();
cy.contains('Submit and go to next challenge').click();
};
projects.forEach(project => submitProject(project));
});
it('Should have a pop up modal', () => {
cy.contains(
'Nicely done. You just completed Responsive Web Design Projects.'
);
});
});

View File

@@ -0,0 +1,85 @@
/* global cy expect */
const selectors = {
challengeMap: "[data-test-label='learn-curriculum-map']"
};
const locations = {
index: '/learn'
};
const superBlockNames = [
'Responsive Web Design Certification',
'JavaScript Algorithms and Data Structures Certification',
'Front End Development Libraries Certification',
'Data Visualization Certification',
'APIs and Microservices Certification',
'Quality Assurance Certification',
'Scientific Computing with Python Certification',
'Data Analysis with Python Certification',
'Information Security Certification',
'Machine Learning with Python Certification',
'Coding Interview Prep (Thousands of hours of challenges)'
];
describe('Learn Landing page (not logged in)', () => {
it('Should render', () => {
cy.visit(locations.index);
cy.title().should(
'eq',
'Learn to Code — For Free — Coding Courses for Busy People'
);
});
it('Has the correct heading for an unauthenticated User', () => {
cy.visit(locations.index);
cy.contains('h1', "Welcome to freeCodeCamp's curriculum.");
});
it('Should render a curriculum map', () => {
cy.document().then(document => {
const superBlocks = document.querySelectorAll(
`${selectors.challengeMap} > li > a`
);
expect(superBlocks).to.have.length(11);
superBlocks.forEach((superBlock, idx) => {
expect(superBlock.innerText).to.have.string(superBlockNames[idx]);
});
});
});
});
describe('Quotes', () => {
beforeEach(() => {
cy.visit('/');
cy.contains("Get started (it's free)").click();
});
it('Should show a quote', () => {
cy.get('blockquote').within(() => {
cy.get('q').should('be.visible');
});
});
it('Should show quote author', () => {
cy.get('blockquote').within(() => {
cy.get('cite').should('be.visible');
});
});
});
describe('Superblocks and Blocks', () => {
beforeEach(() => {
cy.visit('/');
cy.contains("Get started (it's free)").click();
});
it('Has all superblocks visible', () => {
cy.wrap(superBlockNames.slice(1)).each(name => {
cy.contains(name).should('be.visible');
});
});
});

View File

@@ -0,0 +1,54 @@
/* global cy expect */
const locations = {
chalSuper: '/challenges/responsive-web-design/',
chalBlock: '/challenges/responsive-web-design/basic-html-and-html5',
chalChallenge:
// eslint-disable-next-line max-len
'/challenges/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements',
learnSuper: '/learn/responsive-web-design',
learnBlock: '/learn/responsive-web-design/basic-html-and-html5',
learnChallenge:
// eslint-disable-next-line max-len
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements'
};
describe('challenges/superblock redirect', function () {
it('redirects to learn/superblock', () => {
cy.visit(locations.chalSuper);
cy.title().should(
'eq',
'Responsive Web Design Certification | freeCodeCamp.org'
);
cy.location().should(loc => {
expect(loc.pathname).to.eq(locations.learnSuper);
});
});
});
describe('challenges/superblock/block redirect', function () {
it('redirects to learn/superblock/block', () => {
cy.visit(locations.chalBlock);
cy.title().should('eq', 'Basic HTML and HTML5 | freeCodeCamp.org');
cy.location().should(loc => {
expect(loc.pathname).to.eq(locations.learnBlock);
});
});
});
describe('challenges/superblock/block/challenge redirect', function () {
it('redirects to learn/superblock/block/challenge', () => {
cy.visit(locations.chalChallenge);
cy.title().should(
'eq',
// eslint-disable-next-line max-len
'Learn Basic HTML and HTML5: Say Hello to HTML Elements | freeCodeCamp.org'
);
cy.location().should(loc => {
expect(loc.pathname).to.eq(locations.learnChallenge);
});
});
});

View File

@@ -0,0 +1,81 @@
/* global cy */
const selectors = {
tableOfContents: '.intro-toc',
warningMessage: '.flash-message-enter-active'
};
const locations = {
index: '/learn/responsive-web-design/basic-css/'
};
const lessonNames = [
'Change the Color of Text',
'Use CSS Selectors to Style Elements',
'Use a CSS Class to Style an Element',
'Style Multiple Elements with a CSS Class',
'Change the Font Size of an Element',
'Set the Font Family of an Element',
'Import a Google Font',
'Specify How Fonts Should Degrade',
'Size Your Images',
'Add Borders Around Your Elements',
'Add Rounded Corners with border-radius',
'Make Circular Images with a border-radius',
'Give a Background Color to a div Element',
'Set the id of an Element',
'Use an id Attribute to Style an Element',
'Adjust the Padding of an Element',
'Adjust the Margin of an Element',
'Add a Negative Margin to an Element',
'Add Different Padding to Each Side of an Element',
'Add Different Margins to Each Side of an Element',
'Use Clockwise Notation to Specify the Padding of an Element',
'Use Clockwise Notation to Specify the Margin of an Element',
'Use Attribute Selectors to Style Elements',
'Understand Absolute versus Relative Units',
'Style the HTML Body Element',
'Inherit Styles from the Body Element',
'Prioritize One Style Over Another',
'Override Styles in Subsequent CSS',
'Override Class Declarations by Styling ID Attributes',
'Override Class Declarations with Inline Styles',
'Override All Other Styles by using Important',
'Use Hex Code for Specific Colors',
'Use Hex Code to Mix Colors',
'Use Abbreviated Hex Code',
'Use RGB values to Color Elements',
'Use RGB to Mix Colors',
'Use CSS Variables to change several elements at once',
'Create a custom CSS Variable',
'Use a custom CSS Variable',
'Attach a Fallback value to a CSS Variable',
'Improve Compatibility with Browser Fallbacks',
'Inherit CSS Variables',
'Change a variable for a specific area',
'Use a media query to change a variable'
];
const warningMessage =
'Note: Some browser extensions may interfere with elements on the page. ' +
'If the tests fail, try disabling your extensions for the most reliable ' +
'experience.';
describe('Basic Css Introduction page', function () {
it('renders', () => {
cy.visit(locations.index);
cy.title().should('eq', 'Basic CSS | freeCodeCamp.org');
});
it('renders a warning user about extensions', () => {
cy.visit(locations.index);
cy.get(selectors.warningMessage).contains(warningMessage);
});
it('renders a lesson index', () => {
lessonNames.forEach(name => {
cy.get(selectors.tableOfContents).contains('span', name);
});
});
});

View File

@@ -0,0 +1,41 @@
/* global cy */
const selectors = {
firstBlock: '.block-ui > .block:nth-child(1) > .map-title'
};
describe('Certification intro page', () => {
before(() => {
cy.clearCookies();
cy.login();
cy.visit('/learn/responsive-web-design');
});
it('Should render', () => {
cy.title().should(
'eq',
'Responsive Web Design Certification | freeCodeCamp.org'
);
});
it('Should have certification intro text', () => {
cy.contains(
"In this Responsive Web Design Certification, you'll learn the languages that developers use to build webpages"
).should('be.visible');
});
it('First block should be expanded', () => {
cy.contains('Say Hello to HTML Elements').should('be.visible');
});
it('Second block should be closed', () => {
cy.contains('Change the Color of Text').should('not.exist');
});
it('Block should handle toggle clicks correctly', () => {
cy.get(selectors.firstBlock).click();
cy.contains('Say Hello to HTML Elements').should('not.exist');
cy.get(selectors.firstBlock).click();
cy.contains('Say Hello to HTML Elements').should('be.visible');
});
});

View File

@@ -0,0 +1,59 @@
/* global cy expect */
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', () => {
cy.findAllByText('Claim Certification').should($btns => {
expect($btns).to.have.length(15);
});
});
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');
});
});
});
});

View File

@@ -0,0 +1,44 @@
/* global cy */
describe('Email input field', () => {
beforeEach(() => {
cy.exec('npm run seed');
cy.login();
cy.visit('/settings');
});
it('Should be possible to submit the new email', () => {
cy.get('[id=new-email]')
.type('bar@foo.com')
.should('have.attr', 'value', 'bar@foo.com');
cy.get('[id=confirm-email]')
.type('bar@foo.com')
.should('have.attr', 'value', 'bar@foo.com');
cy.get('[id=form-update-email]').within(() => {
cy.contains('Save').click();
});
cy.contains(
'Check your email and click the link we sent you to confirm your new email address.'
);
});
it('Displays an error message when there are problems with the submitted emails', () => {
cy.get('[id=new-email]').type('bar@foo.com');
cy.get('[id=confirm-email]').type('foo@bar.com');
cy.get('[class=help-block]').contains(
'Both new email addresses must be the same'
);
cy.get('[id=new-email]').clear().type('foo@bar.com');
cy.get('[class=help-block]').contains(
'This email is the same as your current email'
);
});
it('Should be possible to get Quincys weekly email', () => {
cy.contains('Yes please').click();
});
});

View File

@@ -0,0 +1,39 @@
/* global cy */
describe('Picture input field', () => {
beforeEach(() => {
cy.login();
cy.visit('/settings');
// Setting aliases here
cy.get('input#about-picture').as('pictureInput');
});
it('Should be possible to type', () => {
cy.get('@pictureInput')
.clear({ force: true })
.type('twaha', { force: true })
.should('have.attr', 'value', 'twaha');
});
it('Show an error message if an incorrect url was submitted', () => {
cy.get('@pictureInput')
.clear({ force: true })
.type('https://s3.amazonaws.com/freecodecamp/camper-image', {
force: true
})
.then(() => {
cy.contains('URL must link directly to an image file');
});
});
it('Can submit a correct URL', () => {
cy.get('@pictureInput')
.clear({ force: true })
.type(
'https://s3.amazonaws.com/freecodecamp/camper-image-placeholder.png',
{
force: true
}
);
cy.wait(500);
cy.get('#camper-identity > .btn').should('not.be.disabled');
});
});

View File

@@ -0,0 +1,15 @@
/* global cy expect */
describe('Settings', () => {
it('should be possible to reset your progress', () => {
cy.visit('/');
cy.contains("Get started (it's free)").click();
cy.visit('/settings');
cy.contains('Reset all of my progress').click();
cy.contains('Reset everything. I want to start from the beginning').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq('/');
});
cy.contains('Your progress has been reset');
});
});

View File

@@ -0,0 +1,219 @@
/* global cy */
describe('Username input field', () => {
beforeEach(() => {
cy.visit('/');
cy.contains("Get started (it's free)").click({ force: true });
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', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('twaha', { force: true })
.should('have.attr', 'value', 'twaha');
});
it('Should show message when validating name', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('twaha', { force: true });
cy.contains('Validating username...')
.should('have.attr', 'role', 'alert')
// We are checking for classes here to check for proper styling
// This will be replaced with Percy in the future
.should('have.class', 'alert alert-info');
});
it('Should show username is available if it is', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('brad', { force: true });
cy.contains('Username is available')
.should('be.visible')
.should('have.attr', 'role', 'alert')
// We are checking for classes here to check for proper styling
// This will be replaced with Percy in the future
.should('have.class', 'alert alert-success');
});
it('Should info message if username is available', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('mrugesh', { force: true });
cy.contains(
'Please note, changing your username will also change ' +
'the URL to your profile and your certifications.'
)
.should('be.visible')
.should('have.attr', 'role', 'alert')
// We are checking for classes here to check for proper styling
// This will be replaced with Percy in the future
.should('have.class', 'alert alert-info');
});
// eslint-disable-next-line
it('Should be able to click the `Save` button if username is avalable', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('oliver', { force: true });
cy.get('@usernameForm').within(() => {
cy.contains('Save').should('not.be.disabled');
});
});
it('Should show username is unavailable if it is', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('twaha', { force: true });
cy.contains('Username not available')
.should('be.visible')
.should('have.attr', 'role', 'alert')
// We are checking for classes here to check for proper styling
// This will be replaced with Percy in the future
.should('have.class', 'alert alert-warning');
});
// eslint-disable-next-line
it('Should not be possible to click the `Save` button if username is unavailable', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('twaha', { force: true });
cy.contains('Username is available').should('not.exist');
cy.contains('Username not available').should('not.exist');
cy.contains(
'Please note, changing your username will also change ' +
'the URL to your profile and your certifications.'
).should('not.exist');
cy.get('@usernameForm').contains('Save').should('be.disabled');
});
it('Should not show anything if user types their current name', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('developmentuser', { force: true });
cy.get('@usernameForm').contains('Save').should('be.disabled');
});
// eslint-disable-next-line max-len
it('Should not be possible to click the `Save` button if user types their current name', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('developmentuser', { force: true });
cy.get('@usernameForm').contains('Save').should('be.disabled');
});
it('Should show warning if username includes invalid character', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('Quincy Larson', { force: true });
cy.contains('Username "Quincy Larson" contains invalid characters')
.should('be.visible')
.should('have.attr', 'role', 'alert')
// We are checking for classes here to check for proper styling
// This will be replaced with Percy in the future
.should('have.class', 'alert alert-danger');
});
// eslint-disable-next-line max-len
it('Should not be able to click the `Save` button if username includes invalid character', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('Quincy Larson', { force: true });
cy.get('@usernameForm').contains('Save').should('be.disabled');
});
it('Should change username if `Save` button is clicked', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('quincy', { force: true });
cy.contains('Username is available');
cy.get('@usernameForm').contains('Save').click({ force: true });
cy.contains('Account Settings for quincy').should('be.visible');
cy.resetUsername();
});
it('Should show flash message showing username has been updated', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('nhcarrigan', { force: true });
cy.contains('Username is available');
cy.get('@usernameInput').type('{enter}', { force: true, release: false });
cy.contains('We have updated your username to nhcarrigan')
.should('be.visible')
// We are checking for classes here to check for proper styling
// This will be replaced with Percy in the future
.should(
'have.class',
'flash-message alert alert-success alert-dismissable'
);
cy.resetUsername();
});
it('Should be able to close the shown flash message', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('bjorno', { force: true });
cy.contains('Username is available');
cy.get('@usernameInput').type('{enter}', { force: true, release: false });
cy.contains('We have updated your username to bjorno').within(() => {
cy.get('button').click();
});
cy.contains('We have updated your username to bjorno').should('not.exist');
cy.resetUsername();
});
it('Should change username if enter is pressed', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('symbol', { force: true });
cy.contains('Username is available');
cy.get('@usernameInput').type('{enter}', { force: true, release: false });
cy.contains('Account Settings for symbol').should('be.visible');
cy.resetUsername();
});
it('Should show warning if username includes uppercase characters', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('QuincyLarson', { force: true });
cy.contains('Username "QuincyLarson" must be lowercase')
.should('be.visible')
.should('have.attr', 'role', 'alert')
.should('have.class', 'alert alert-danger');
});
it('Should not be able to click the `Save` button if username includes uppercase characters', () => {
cy.get('@usernameInput')
.clear({ force: true })
.type('QuincyLarson', { force: true });
cy.get('@usernameForm').contains('Save').should('be.disabled');
});
});

View File

@@ -0,0 +1,88 @@
/* global cy */
describe('The Document Metadata', () => {
before(() => {
cy.visit('/');
cy.document();
});
const social = {
description: 'Learn to Code — For Free'
};
const challengs = {
responsiveWebDesign:
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements',
rosetaCode: '/learn/coding-interview-prep/rosetta-code/100-doors',
projectEuler:
'/learn/coding-interview-prep/project-euler/problem-1-multiples-of-3-and-5'
};
const scripts = {
mathjax: {
selector: 'body script[id="mathjax"]',
src: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML'
}
};
it('landing page has correct <meta> for description', () => {
cy.get('head meta[name="description"]').should(
'have.attr',
'content',
'Learn to Code — For Free'
);
});
it('landing page has correct <meta> for og title', () => {
cy.get('head meta[name="og:title"]').should(
'have.attr',
'content',
'freeCodeCamp.org'
);
});
it('landing page has correct <meta> for og description', () => {
cy.get('head meta[name="og:description"]').should(
'have.attr',
'content',
social.description
);
});
it('landing page has correct <meta> for twitter title', () => {
cy.get('head meta[name="twitter:title"]').should(
'have.attr',
'content',
'freeCodeCamp.org'
);
});
it('landing page has correct <meta>for twitter description', () => {
cy.get('head meta[name="twitter:description"]').should(
'have.attr',
'content',
social.description
);
});
it('landing page should not have mathjax body script', () => {
cy.reload();
cy.get(scripts.mathjax.selector).should('not.exist');
});
it('responsive webdesign challenges should not have mathjax body script', () => {
cy.visit(challengs.responsiveWebDesign);
cy.reload();
cy.get(scripts.mathjax.selector).should('not.exist');
});
it('project euler challenges should have mathjax body script', () => {
cy.visit(challengs.projectEuler);
cy.reload();
cy.get(scripts.mathjax.selector).should(
'have.attr',
'src',
scripts.mathjax.src
);
});
it('rosetta code challenges should have mathjax body script', () => {
cy.visit(challengs.projectEuler);
cy.reload();
cy.get(scripts.mathjax.selector).should(
'have.attr',
'src',
scripts.mathjax.src
);
});
});

View File

@@ -0,0 +1,42 @@
/* global cy */
describe('Top contributor in user profile', () => {
before(() => {
cy.clearCookies();
cy.exec('npm run seed -- --top-contributor');
});
after(() => {
cy.exec('npm run seed');
});
beforeEach(() => {
cy.login();
cy.contains('Profile').click({ force: true });
// The following line is only required if you want to test it in development
// cy.contains('Preview custom 404 page').click();
});
it('Should show `Top Contributor` text with badge', () => {
cy.contains('Top Contributor')
.parent()
.within(() => {
cy.contains('Top Contributor').should('be.visible');
cy.get('svg').should('be.visible');
});
});
// eslint-disable-next-line max-len
it('Should take user to `Top Contributor` page when `Top Contributor` gets clicked', () => {
cy.contains('Top Contributor').should(
'have.attr',
'href',
'/top-contributors'
);
});
it('Should show years when it was achieved', () => {
cy.contains('2017, 2018 and 2019').should('be.visible');
});
});

View File

@@ -0,0 +1,25 @@
/* global cy expect */
describe('Report User', () => {
beforeEach(() => {
cy.exec('npm run seed');
cy.login();
});
it('should be possible to report a user from their profile page', () => {
// Since going to a user page intially generates a 404, we have to ignore
// status codes on that request
cy.visit('/twaha', { failOnStatusCode: false });
// The following line is only required if you want to test it in development
// cy.contains('Preview custom 404 page').click();
cy.contains("Flag This User's Account for Abuse").click();
cy.contains("Do you want to report twaha's portfolio for abuse?");
cy.get('[id=report-user-textarea]').type('Some details');
cy.contains('Submit the report').click();
cy.location().should(loc => {
expect(loc.pathname).to.eq('/learn');
});
cy.contains('A report was sent to the team with foo@bar.com in copy');
});
});
// A report was sent to the team with foo@bar.com in copy