diff --git a/.github/workflows/e2e-playwright.yml b/.github/workflows/e2e-playwright.yml index 6d43c4f41fb..a3361c41e43 100644 --- a/.github/workflows/e2e-playwright.yml +++ b/.github/workflows/e2e-playwright.yml @@ -1,8 +1,12 @@ name: CI - E2E - Playwright on: workflow_dispatch: + workflow_run: + workflows: ['CI - Node.js'] + types: + - completed # TODO: refactor with a workflow_call - push: + pull_request: paths-ignore: - 'docs/**' branches: diff --git a/.github/workflows/e2e-web.yml b/.github/workflows/e2e-web.yml index 84ae43f6008..e0b18ed0787 100644 --- a/.github/workflows/e2e-web.yml +++ b/.github/workflows/e2e-web.yml @@ -1,8 +1,12 @@ name: CI - E2E - Web browser on: workflow_dispatch: + workflow_run: + workflows: ['CI - Node.js'] + types: + - completed # TODO: refactor with a workflow_call - push: + pull_request: paths-ignore: - 'docs/**' branches: diff --git a/.github/workflows/temporary-container-checks.yml b/.github/workflows/temporary-container-checks.yml index dc7658dbaa3..13d51cc434a 100644 --- a/.github/workflows/temporary-container-checks.yml +++ b/.github/workflows/temporary-container-checks.yml @@ -3,6 +3,18 @@ name: CI - E2E - Containers on: workflow_dispatch: + workflow_run: + workflows: ['CI - Node.js'] + types: + - completed + # TODO: refactor with a workflow_call + pull_request: + paths-ignore: + - 'docs/**' + branches: + - 'main' + - 'next-**' + - 'e2e-**' jobs: build-client: diff --git a/curriculum/challenges/_meta/build-a-pokemon-search-app-project/meta.json b/curriculum/challenges/_meta/build-a-pokemon-search-app-project/meta.json index d975c075715..0b1acb0d6ea 100644 --- a/curriculum/challenges/_meta/build-a-pokemon-search-app-project/meta.json +++ b/curriculum/challenges/_meta/build-a-pokemon-search-app-project/meta.json @@ -4,7 +4,7 @@ "dashedName": "build-a-pokemon-search-app-project", "usesMultifileEditor": true, "helpCategory": "JavaScript", - "order": 99, + "order": 20, "time": "30 hours", "template": "", "required": [], diff --git a/curriculum/challenges/_meta/rosetta-code-challenges/meta.json b/curriculum/challenges/_meta/rosetta-code-challenges/meta.json index 898d3a8c4be..dbb963eb0fc 100644 --- a/curriculum/challenges/_meta/rosetta-code-challenges/meta.json +++ b/curriculum/challenges/_meta/rosetta-code-challenges/meta.json @@ -3,7 +3,7 @@ "isUpcomingChange": false, "dashedName": "rosetta-code-challenges", "helpCategory": "Rosetta", - "order": 3, + "order": 0, "time": "", "template": "", "required": [], diff --git a/curriculum/test/test-challenges.js b/curriculum/test/test-challenges.js index 29e65636f69..6db9494d2f8 100644 --- a/curriculum/test/test-challenges.js +++ b/curriculum/test/test-challenges.js @@ -42,8 +42,7 @@ const { getLines } = require('../../shared/utils/get-lines'); const { getChallengesForLang, getMetaForBlock } = require('../get-challenges'); const { challengeSchemaValidator } = require('../schema/challenge-schema'); -// const { testedLang, getSuperOrder } = require('../utils'); -const { testedLang } = require('../utils'); +const { testedLang, getSuperOrder } = require('../utils'); const ChallengeTitles = require('./utils/challenge-titles'); const MongoIds = require('./utils/mongo-ids'); const createPseudoWorker = require('./utils/pseudo-worker'); @@ -276,50 +275,49 @@ function populateTestsForLang({ lang, challenges, meta }) { const challengeTitles = new ChallengeTitles(); const validateChallenge = challengeSchemaValidator(); - // if (!process.env.FCC_BLOCK && !process.env.FCC_CHALLENGE_ID) { - // describe('Assert meta order', function () { - // /** This array can be used to skip a superblock - we'll use this - // * when we are working on the new project-based curriculum for - // * a superblock (because keeping those challenges in order is - // * tricky and needs cleaning up before deploying). - // */ - // const superBlocksUnderDevelopment = [ - // ]; - // const superBlocks = new Set([ - // ...Object.values(meta) - // .map(el => el.superBlock) - // .filter(el => !superBlocksUnderDevelopment.includes(el)) - // ]); - // superBlocks.forEach(superBlock => { - // const filteredMeta = Object.values(meta) - // .filter(el => el.superBlock === superBlock) - // .sort((a, b) => a.order - b.order); - // if (!filteredMeta.length) { - // return; - // } - // it(`${superBlock} should have the same order in every meta`, function () { - // const firstOrder = getSuperOrder(filteredMeta[0].superBlock, { - // showNewCurriculum: process.env.SHOW_NEW_CURRICULUM - // }); - // assert.isNumber(firstOrder); - // assert.isTrue( - // filteredMeta.every( - // el => - // getSuperOrder(el.superBlock, { - // showNewCurriculum: process.env.SHOW_NEW_CURRICULUM - // }) === firstOrder - // ), - // 'The superOrder properties are mismatched.' - // ); - // }); - // filteredMeta.forEach((meta, index) => { - // it(`${meta.superBlock} ${meta.name} must be in order`, function () { - // assert.equal(meta.order, index); - // }); - // }); - // }); - // }); - // } + if (!process.env.FCC_BLOCK && !process.env.FCC_CHALLENGE_ID) { + describe('Assert meta order', function () { + /** This array can be used to skip a superblock - we'll use this + * when we are working on the new project-based curriculum for + * a superblock (because keeping those challenges in order is + * tricky and needs cleaning up before deploying). + */ + const superBlocksUnderDevelopment = ['scientific-computing-with-python']; + const superBlocks = new Set([ + ...Object.values(meta) + .map(el => el.superBlock) + .filter(el => !superBlocksUnderDevelopment.includes(el)) + ]); + superBlocks.forEach(superBlock => { + const filteredMeta = Object.values(meta) + .filter(el => el.superBlock === superBlock) + .sort((a, b) => a.order - b.order); + if (!filteredMeta.length) { + return; + } + it(`${superBlock} should have the same order in every meta`, function () { + const firstOrder = getSuperOrder(filteredMeta[0].superBlock, { + showNewCurriculum: process.env.SHOW_NEW_CURRICULUM + }); + assert.isNumber(firstOrder); + assert.isTrue( + filteredMeta.every( + el => + getSuperOrder(el.superBlock, { + showNewCurriculum: process.env.SHOW_NEW_CURRICULUM + }) === firstOrder + ), + 'The superOrder properties are mismatched.' + ); + }); + filteredMeta.forEach((meta, index) => { + it(`${meta.superBlock} ${meta.name} must be in order`, function () { + assert.equal(meta.order, index); + }); + }); + }); + }); + } describe(`Check challenges (${lang})`, function () { this.timeout(5000); diff --git a/cypress/e2e/default/settings/certifications.ts b/cypress/e2e/default/settings/certifications.ts index bad1eb0745d..6380937f5f9 100644 --- a/cypress/e2e/default/settings/certifications.ts +++ b/cypress/e2e/default/settings/certifications.ts @@ -8,9 +8,9 @@ describe('Settings certifications area', () => { }); it('Should render the default settings page', () => { - cy.visit('/settings/'); + cy.visit('/settings'); cy.findAllByText('Claim Certification').should($btns => { - expect($btns).to.have.length(18); + expect($btns).to.have.length(19); }); cy.findByText('Show Certification').should('not.exist'); cy.contains(`I agree to freeCodeCamp's Academic Honesty Policy.`); diff --git a/cypress/e2e/default/tags.js b/cypress/e2e/default/tags.js index 2bce004c504..4907e9c79f5 100644 --- a/cypress/e2e/default/tags.js +++ b/cypress/e2e/default/tags.js @@ -1,7 +1,7 @@ const challenges = { responsiveWebDesign: '/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements', - rosettaCode: '/learn/rosetta-code/100-doors', + rosettaCode: '/learn/rosetta-code/rosetta-code-challenges/100-doors', projectEuler: '/learn/project-euler/project-euler-problems-1-to-100/problem-1-multiples-of-3-and-5' }; diff --git a/e2e/learn.spec.ts b/e2e/learn.spec.ts index fc0057f65a0..66ed7429309 100644 --- a/e2e/learn.spec.ts +++ b/e2e/learn.spec.ts @@ -4,23 +4,29 @@ import words from '../client/i18n/locales/english/motivation.json'; let page: Page; -// const superBlocks = [ -// 'Responsive Web Design', -// 'JavaScript Algorithms and Data Structures', -// 'Front End Development Libraries', -// 'Data Visualization', -// 'Relational Database', -// 'Back End Development and APIs', -// 'Quality Assurance', -// 'Scientific Computing with Python', -// 'Data Analysis with Python', -// 'Information Security', -// 'Machine Learning with Python', -// 'College Algebra with Python', -// 'Foundational C# with Microsoft', -// 'Coding Interview Prep', -// 'Project Euler' -// ]; +const superBlocks = [ + 'Responsive Web Design', + 'JavaScript Algorithms and Data Structures (Beta)', + 'Front End Development Libraries', + 'Data Visualization', + 'Relational Database', + 'Back End Development and APIs', + 'Quality Assurance', + 'Scientific Computing with Python (Beta)', + 'Data Analysis with Python', + 'Information Security', + 'Machine Learning with Python', + 'College Algebra with Python', + 'A2 English for Developers (Beta)', + 'Foundational C# with Microsoft', + 'The Odin Project (Beta)', + 'Coding Interview Prep', + 'Project Euler', + 'Rosetta Code', + 'Legacy Responsive Web Design Challenges', + 'JavaScript Algorithms and Data Structures', + 'Legacy Python for Everybody' +]; test.beforeAll(async ({ browser }) => { page = await browser.newPage(); @@ -56,20 +62,20 @@ test('the page should have all static data correctly placed', async () => { } }); -// Enable this again after we release the 4 new superblocks -// test('the page renders all curriculum certifications', async () => { -// const curriculumBtns = page.getByTestId('curriculum-map-button'); -// await expect(curriculumBtns).toHaveCount(15); -// for (let i = 0; i < superBlocks.length; i++) { -// const btn = curriculumBtns.nth(i); -// await expect(btn).toContainText(superBlocks[i]); -// } -// }); +test('the page renders all curriculum certifications', async () => { + const curriculumBtns = page.getByTestId('curriculum-map-button'); + await expect(curriculumBtns).toHaveCount(superBlocks.length); + for (let i = 0; i < superBlocks.length; i++) { + const btn = curriculumBtns.nth(i); + await expect(btn).toContainText(superBlocks[i]); + } +}); test.describe('Learn (authenticated user)', () => { test.use({ storageState: 'playwright/.auth/certified-user.json' }); test('the page shows a random quote for an authenticated user', async () => { + await page.goto('/learn'); const shownQuote = await page.getByTestId('random-quote').textContent(); const shownAuthorText = await page diff --git a/e2e/map.spec.ts b/e2e/map.spec.ts index 83c7c996361..2bd483fa121 100644 --- a/e2e/map.spec.ts +++ b/e2e/map.spec.ts @@ -15,10 +15,22 @@ const superBlocksWithLinks = [ ...superBlockOrder[SuperBlockStages.FrontEnd], ...superBlockOrder[SuperBlockStages.Backend], ...superBlockOrder[SuperBlockStages.Python], + ...superBlockOrder[SuperBlockStages.English], ...superBlockOrder[SuperBlockStages.Professional], - ...superBlockOrder[SuperBlockStages.Extra] + ...superBlockOrder[SuperBlockStages.Extra], + ...superBlockOrder[SuperBlockStages.Legacy] ]; +const superBlockTitleOverride: Record = { + 'Responsive Web Design': 'Legacy Responsive Web Design Challenges', + 'JavaScript Algorithms and Data Structures': + 'JavaScript Algorithms and Data Structures Certification' +}; + +const superBlockSlugOverride: Record = { + '2022/responsive-web-design': 'responsive-web-design' +}; + test.describe('Map Component', () => { test('should render correctly', async ({ page }) => { await expect( @@ -30,14 +42,20 @@ test.describe('Map Component', () => { await expect( page.getByText(translations.landing['interview-prep-heading']) ).toBeVisible(); - // const curriculumBtns = page.getByTestId('curriculum-map-button'); - // await expect(curriculumBtns).toHaveCount(15); + const curriculumBtns = page.getByTestId('curriculum-map-button'); + await expect(curriculumBtns).toHaveCount(superBlocksWithLinks.length); for (let i = 0; i < superBlocksWithLinks.length; i++) { const superblockLink = page.getByRole('link', { - name: intro[superBlocksWithLinks[i]].title + // This is a hacky bypass because `Responsive Web Design` hits both links. + name: + superBlockTitleOverride[intro[superBlocksWithLinks[i]].title] ?? + intro[superBlocksWithLinks[i]].title }); expect(await superblockLink.getAttribute('href')).toBe( - `/learn/${superBlocksWithLinks[i]}/` + `/learn/${ + superBlockSlugOverride[superBlocksWithLinks[i]] ?? + superBlocksWithLinks[i] + }/` ); await superblockLink.click(); } diff --git a/e2e/video-player.spec.ts b/e2e/video-player.spec.ts index 66258ff0181..e59d751e6ec 100644 --- a/e2e/video-player.spec.ts +++ b/e2e/video-player.spec.ts @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test'; test.beforeEach(async ({ page }) => { await page.goto( - '/learn/scientific-computing-with-python/python-for-everybody/introduction-why-program' + '/learn/python-for-everybody/python-for-everybody/introduction-why-program' ); }); diff --git a/renovate.json b/renovate.json index 08cc9af4d30..ae33a1de912 100644 --- a/renovate.json +++ b/renovate.json @@ -36,6 +36,5 @@ "automerge": false } ], - "ignorePaths": ["api-server"], - "schedule": ["every weekend"] + "ignorePaths": ["api-server"] }