From 90d35945c1449f27d84528f46d87f97a07757b26 Mon Sep 17 00:00:00 2001 From: pmelin Date: Fri, 10 Mar 2023 07:41:25 +0000 Subject: [PATCH] chore(cypress): TS Migration: projects.ts, show-cert-from-superblock.ts and challenges.ts (#49635) --- .github/workflows/e2e-web.yml | 2 +- .../e2e/default/learn/challenges/projects.js | 187 --------------- .../e2e/default/learn/challenges/projects.ts | 224 ++++++++++++++++++ ...rblock.js => show-cert-from-superblock.ts} | 6 +- .../{challenges.js => challenges.ts} | 16 +- cypress/tsconfig.json | 6 +- 6 files changed, 237 insertions(+), 204 deletions(-) delete mode 100644 cypress/e2e/default/learn/challenges/projects.js create mode 100644 cypress/e2e/default/learn/challenges/projects.ts rename cypress/e2e/default/learn/responsive-web-design/{show-cert-from-superblock.js => show-cert-from-superblock.ts} (95%) rename cypress/e2e/default/legacy/redirects/{challenges.js => challenges.ts} (77%) diff --git a/.github/workflows/e2e-web.yml b/.github/workflows/e2e-web.yml index 750e439d3bf..7519e424ad5 100644 --- a/.github/workflows/e2e-web.yml +++ b/.github/workflows/e2e-web.yml @@ -78,7 +78,7 @@ jobs: node-version: [18.x] include: - browsers: electron - spec: cypress/e2e/default/learn/challenges/projects.js + spec: cypress/e2e/default/learn/challenges/projects.ts - browsers: chrome spec: cypress/e2e/default/**/*.{js,ts} - browsers: firefox diff --git a/cypress/e2e/default/learn/challenges/projects.js b/cypress/e2e/default/learn/challenges/projects.js deleted file mode 100644 index 19661090056..00000000000 --- a/cypress/e2e/default/learn/challenges/projects.js +++ /dev/null @@ -1,187 +0,0 @@ -import { SuperBlocks } from '../../../../../config/certification-settings'; - -const selectors = { - editor: '.react-monaco-editor-container' -}; - -const pythonProjects = { - superBlock: SuperBlocks.MachineLearningPy, - 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', () => { - 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', () => { - const { superBlock, block, challenges } = pythonProjects; - 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); - }); - }); - it( - 'JavaScript projects can be submitted and then viewed in /settings and on the certifications', - { browser: 'electron' }, - () => { - cy.fixture('../../config/curriculum.json').then(curriculum => { - const targetBlock = - 'javascript-algorithms-and-data-structures-projects'; - const javaScriptSuperBlock = Object.values(curriculum).filter( - ({ blocks }) => blocks[targetBlock] - )[0]; - const { challenges, meta } = javaScriptSuperBlock.blocks[targetBlock]; - - const projectTitles = meta.challengeOrder.map(([, title]) => title); - const projectsInOrder = projectTitles.map(projectTitle => { - return challenges.find(({ title }) => title === projectTitle); - }); - - // We need to wait for everything to finish loading and hydrating, so we - // use this text as a proxy for that. - const textInNextPage = projectTitles.slice(1); - // The following text exists on the donation modal - textInNextPage.push('Nicely done'); - - projectsInOrder.forEach( - ({ block, superBlock, dashedName, solutions }, i) => { - const url = `/learn/${superBlock}/${block}/${dashedName}`; - cy.visit(url); - - solutions.forEach(files => { - files.forEach(({ contents }) => { - cy.get(selectors.editor, { timeout: 16000 }) - .click() - .focused() - .type('{ctrl+a}{del}'); - // NOTE: clipboard operations are flaky in watch mode, because - // the document can lose focus - cy.window() - .its('navigator.clipboard') - .invoke('writeText', contents); - cy.document().invoke('execCommand', 'paste'); - cy.contains('Run the Tests').click(); - cy.contains('Submit and go to next challenge', { - timeout: 16000 - }).click(); - cy.contains(textInNextPage[i]); - }); - }); - } - ); - - cy.visit('/settings'); - - projectTitles.forEach(title => { - cy.get(`[data-cy="${title}"]`).click(); - // TODO: if we write a test to check that the solution is visible - // before reloading, we should include that here. - cy.contains('Solution for'); - cy.contains('Close').click(); - }); - - // Claim and view solutions on certification page - - cy.setPrivacyTogglesToPublic(); - cy.get( - `a[href="/certification/developmentuser/${projectsInOrder[0]?.superBlock}"]` - ).click(); - cy.contains('Show Certification').click(); - - projectTitles.forEach(title => { - cy.get(`[data-cy="${title} solution"]`).click(); - // TODO: if we write a test to check that the solution is visible - // before reloading, we should include that here. - cy.contains('Solution for'); - cy.contains('Close').click(); - }); - }); - } - ); - - it( - 'Ctrl + enter triggers the completion modal on multifile projects', - { browser: 'electron' }, - () => { - cy.fixture('../../config/curriculum.json').then(curriculum => { - const targetBlock = 'build-a-personal-portfolio-webpage-project'; - const portfolioBlock = Object.values(curriculum).filter( - ({ blocks }) => blocks[targetBlock] - )[0]; - const { challenges, meta } = portfolioBlock.blocks[targetBlock]; - - const projectTitle = meta.challengeOrder[0][1]; - const { block, superBlock, dashedName, solutions } = challenges.find( - ({ title }) => title === projectTitle - ); - - const url = `/learn/${superBlock}/${block}/${dashedName}`; - cy.visit(url); - - solutions[0].forEach(({ contents, fileKey }) => { - const tabSelector = `[data-cy=editor-tab-${fileKey}]`; - if (fileKey !== 'indexhtml') { - cy.get(tabSelector).click(); - } - const editorContainerSelector = `[data-cy=editor-container-${fileKey}]`; - cy.get(editorContainerSelector, { timeout: 16000 }) - .find(selectors.editor, { timeout: 16000 }) - .click() - .focused() - .type('{ctrl+a}{del}'); - // NOTE: clipboard operations are flaky in watch mode, because - // the document can lose focus - cy.window().its('navigator.clipboard').invoke('writeText', contents); - cy.document().invoke('execCommand', 'paste'); - }); - - cy.get('[data-cy=editor-container-indexhtml', { timeout: 16000 }) - .find(selectors.editor, { timeout: 16000 }) - .click() - .focused() - .type('{ctrl+enter}'); - // check the modal exists - cy.contains('Submit and go to next challenge'); - cy.contains('Download my solution'); - }); - } - ); -}); diff --git a/cypress/e2e/default/learn/challenges/projects.ts b/cypress/e2e/default/learn/challenges/projects.ts new file mode 100644 index 00000000000..9fd89a7f7c2 --- /dev/null +++ b/cypress/e2e/default/learn/challenges/projects.ts @@ -0,0 +1,224 @@ +import { SuperBlocks } from '../../../../../config/certification-settings'; + +interface Meta { + challengeOrder: string[][]; +} + +interface File { + contents: unknown; + fileKey: string; +} + +type Solution = File[]; + +interface Challenge { + title: string; + block: string; + superBlock: string; + dashedName: string; + solutions: Solution[]; + isPrivate?: boolean; +} + +interface Curriculum { + [key: string]: { + blocks: { + [key: string]: { + meta: Meta; + challenges: Challenge[]; + }; + }; + }; +} + +const selectors = { + editor: '.react-monaco-editor-container' +}; + +const pythonProjects = { + superBlock: SuperBlocks.MachineLearningPy, + 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', () => { + 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', () => { + const { superBlock, block, challenges } = pythonProjects; + 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); + }); + }); + it( + 'JavaScript projects can be submitted and then viewed in /settings and on the certifications', + { browser: 'electron' }, + () => { + cy.fixture('../../config/curriculum.json').then( + (curriculum: Curriculum) => { + const targetBlock = + 'javascript-algorithms-and-data-structures-projects'; + const javaScriptSuperBlock = Object.values(curriculum).filter( + ({ blocks }) => blocks[targetBlock] + )[0]; + const { challenges, meta } = javaScriptSuperBlock.blocks[targetBlock]; + + const projectTitles = meta.challengeOrder.map(([, title]) => title); + const projectsInOrder = projectTitles.map(projectTitle => { + return challenges.find(({ title }) => title === projectTitle); + }) as Challenge[]; + + // We need to wait for everything to finish loading and hydrating, so we + // use this text as a proxy for that. + const textInNextPage = projectTitles.slice(1); + // The following text exists on the donation modal + textInNextPage.push('Nicely done'); + + projectsInOrder.forEach( + ({ block, superBlock, dashedName, solutions }, i) => { + const url = `/learn/${superBlock}/${block}/${dashedName}`; + cy.visit(url); + + solutions.forEach(files => { + files.forEach(({ contents }) => { + cy.get(selectors.editor, { timeout: 16000 }) + .click() + .focused() + .type('{ctrl+a}{del}'); + // NOTE: clipboard operations are flaky in watch mode, because + // the document can lose focus + cy.window() + .its('navigator.clipboard') + .invoke('writeText', contents); + cy.document().invoke('execCommand', 'paste'); + cy.contains('Run the Tests').click(); + cy.contains('Submit and go to next challenge', { + timeout: 16000 + }).click(); + cy.contains(textInNextPage[i]); + }); + }); + } + ); + + cy.visit('/settings'); + + projectTitles.forEach(title => { + cy.get(`[data-cy="${title}"]`).click(); + // TODO: if we write a test to check that the solution is visible + // before reloading, we should include that here. + cy.contains('Solution for'); + cy.contains('Close').click(); + }); + + // Claim and view solutions on certification page + + cy.setPrivacyTogglesToPublic(); + cy.get( + `a[href="/certification/developmentuser/${projectsInOrder[0]?.superBlock}"]` + ).click(); + cy.contains('Show Certification').click(); + + projectTitles.forEach(title => { + cy.get(`[data-cy="${title} solution"]`).click(); + // TODO: if we write a test to check that the solution is visible + // before reloading, we should include that here. + cy.contains('Solution for'); + cy.contains('Close').click(); + }); + } + ); + } + ); + + it( + 'Ctrl + enter triggers the completion modal on multifile projects', + { browser: 'electron' }, + () => { + cy.fixture('../../config/curriculum.json').then( + (curriculum: Curriculum) => { + const targetBlock = 'build-a-personal-portfolio-webpage-project'; + const portfolioBlock = Object.values(curriculum).filter( + ({ blocks }) => blocks[targetBlock] + )[0]; + const { challenges, meta } = portfolioBlock.blocks[targetBlock]; + + const projectTitle = meta.challengeOrder[0][1]; + const { block, superBlock, dashedName, solutions } = challenges.find( + ({ title }) => title === projectTitle + ) as Challenge; + + const url = `/learn/${superBlock}/${block}/${dashedName}`; + cy.visit(url); + + solutions[0].forEach(({ contents, fileKey }) => { + const tabSelector = `[data-cy=editor-tab-${fileKey}]`; + if (fileKey !== 'indexhtml') { + cy.get(tabSelector).click(); + } + const editorContainerSelector = `[data-cy=editor-container-${fileKey}]`; + cy.get(editorContainerSelector, { timeout: 16000 }) + .find(selectors.editor, { timeout: 16000 }) + .click() + .focused() + .type('{ctrl+a}{del}'); + // NOTE: clipboard operations are flaky in watch mode, because + // the document can lose focus + cy.window() + .its('navigator.clipboard') + .invoke('writeText', contents); + cy.document().invoke('execCommand', 'paste'); + }); + + cy.get('[data-cy=editor-container-indexhtml', { timeout: 16000 }) + .find(selectors.editor, { timeout: 16000 }) + .click() + .focused() + .type('{ctrl+enter}'); + // check the modal exists + cy.contains('Submit and go to next challenge'); + cy.contains('Download my solution'); + } + ); + } + ); +}); diff --git a/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.js b/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.ts similarity index 95% rename from cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.js rename to cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.ts index 0f27715640d..c826abf401f 100644 --- a/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.js +++ b/cypress/e2e/default/learn/responsive-web-design/show-cert-from-superblock.ts @@ -74,9 +74,9 @@ describe('Front End Development Libraries Superblock', () => { cy.contains("I've completed this challenge") .should('not.be.disabled') .click(); - cy.intercept(`${Cypress.env('API_LOCATION')}/project-completed`).as( - 'challengeCompleted' - ); + cy.intercept( + `${String(Cypress.env('API_LOCATION'))}/project-completed` + ).as('challengeCompleted'); cy.contains('Submit and go to next challenge').click(); cy.wait('@challengeCompleted') .its('response.statusCode') diff --git a/cypress/e2e/default/legacy/redirects/challenges.js b/cypress/e2e/default/legacy/redirects/challenges.ts similarity index 77% rename from cypress/e2e/default/legacy/redirects/challenges.js rename to cypress/e2e/default/legacy/redirects/challenges.ts index f53a7bbc85f..25e17287699 100644 --- a/cypress/e2e/default/legacy/redirects/challenges.js +++ b/cypress/e2e/default/legacy/redirects/challenges.ts @@ -1,5 +1,5 @@ -const locations = { - chalSuper: '/challenges/responsive-web-design/', +const testLocations = { + chalSuper: '/challenges/responsive-web-design', chalBlock: '/challenges/responsive-web-design/basic-html-and-html5', chalChallenge: // eslint-disable-next-line max-len @@ -13,32 +13,32 @@ const locations = { describe('challenges/superblock redirect', function () { it('redirects to learn/superblock', () => { - cy.visit(locations.chalSuper); + cy.visit(testLocations.chalSuper); cy.title().should( 'eq', 'Legacy Responsive Web Design Certification | freeCodeCamp.org' ); cy.location().should(loc => { - expect(loc.pathname).to.eq(locations.learnSuper); + expect(loc.pathname).to.eq(testLocations.learnSuper); }); }); }); describe('challenges/superblock/block redirect', function () { it('redirects to learn/superblock/block', () => { - cy.visit(locations.chalBlock); + cy.visit(testLocations.chalBlock); cy.title().should('eq', 'Basic HTML and HTML5 | freeCodeCamp.org'); cy.location().should(loc => { - expect(loc.pathname).to.eq(locations.learnBlock); + expect(loc.pathname).to.eq(testLocations.learnBlock); }); }); }); describe('challenges/superblock/block/challenge redirect', function () { it('redirects to learn/superblock/block/challenge', () => { - cy.visit(locations.chalChallenge); + cy.visit(testLocations.chalChallenge); cy.title().should( 'eq', @@ -46,7 +46,7 @@ describe('challenges/superblock/block/challenge redirect', function () { 'Basic HTML and HTML5: Say Hello to HTML Elements | freeCodeCamp.org' ); cy.location().should(loc => { - expect(loc.pathname).to.eq(locations.learnChallenge); + expect(loc.pathname).to.eq(testLocations.learnChallenge); }); }); }); diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index cd326955081..3f175f2aeef 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -4,10 +4,6 @@ "lib": ["es5", "dom", "ES2017"], "types": ["cypress", "@testing-library/cypress"] }, - "include": [ - "e2e/**/*.ts", - "support/**/*.ts", - "e2e/default/learn/challenges/projects.js" - ], + "include": ["e2e/**/*.ts", "support/**/*.ts"], "extends": "../tsconfig-base.json" }