mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
251 lines
8.3 KiB
TypeScript
251 lines
8.3 KiB
TypeScript
import { exec } from 'child_process';
|
|
import { promisify } from 'util';
|
|
import { test, expect } from '@playwright/test';
|
|
import translations from '../client/i18n/locales/english/translations.json';
|
|
|
|
const execP = promisify(exec);
|
|
|
|
// Seed certified user before all tests to ensure progress exists
|
|
test.beforeAll(async () => {
|
|
await execP('node ./tools/scripts/seed/seed-demo-user --certified-user', {
|
|
cwd: process.cwd().replace('/e2e', '')
|
|
});
|
|
});
|
|
|
|
// Re-seed after each test to restore progress (in case a test modifies it)
|
|
test.afterEach(async () => {
|
|
await execP('node ./tools/scripts/seed/seed-demo-user --certified-user', {
|
|
cwd: process.cwd().replace('/e2e', '')
|
|
});
|
|
});
|
|
|
|
test.describe('Module reset - Block level (v8 grid layout)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to a superblock where certified user has completed challenges
|
|
// Certified user has isRespWebDesignCert: true with completed challenges
|
|
await page.goto('/learn/2022/responsive-web-design');
|
|
});
|
|
|
|
test('should show reset button on block hover', async ({ page }) => {
|
|
// First block should have progress for certified user
|
|
const blockHeader = page.locator('.block-header-wrapper').first();
|
|
|
|
// Reset button should be hidden initially (opacity: 0)
|
|
const resetButton = blockHeader.locator('.block-reset-button');
|
|
await expect(resetButton).toHaveCSS('opacity', '0');
|
|
|
|
// Hover over the block header
|
|
await blockHeader.hover();
|
|
|
|
// Reset button should become visible on hover (opacity: 1 for enabled, 0.3 for disabled)
|
|
await expect(resetButton).toBeVisible();
|
|
});
|
|
|
|
test('should open reset modal when clicking reset button', async ({
|
|
page
|
|
}) => {
|
|
// Find a block with an enabled reset button (has progress)
|
|
const blockHeader = page
|
|
.locator('.block-header-wrapper')
|
|
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
|
|
.first();
|
|
await blockHeader.hover();
|
|
|
|
// Click the reset button
|
|
const resetButton = blockHeader.locator('.block-reset-button');
|
|
await resetButton.click();
|
|
|
|
// Modal should be visible
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Modal should contain the reset heading
|
|
await expect(
|
|
page.getByText(
|
|
translations.learn['reset-progress-description'].split("'")[0],
|
|
{ exact: false }
|
|
)
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('should close modal when clicking cancel button', async ({ page }) => {
|
|
// Find a block with an enabled reset button
|
|
const blockHeader = page
|
|
.locator('.block-header-wrapper')
|
|
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
|
|
.first();
|
|
await blockHeader.hover();
|
|
await blockHeader.locator('.block-reset-button').click();
|
|
|
|
// Modal should be visible
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Click the cancel button
|
|
await page
|
|
.getByRole('button', {
|
|
name: translations.learn['reset-progress-nevermind']
|
|
})
|
|
.click();
|
|
|
|
// Modal should be hidden
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
});
|
|
|
|
test('should disable reset button until verification phrase is entered', async ({
|
|
page
|
|
}) => {
|
|
// Find a block with an enabled reset button
|
|
const blockHeader = page
|
|
.locator('.block-header-wrapper')
|
|
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
|
|
.first();
|
|
await blockHeader.hover();
|
|
await blockHeader.locator('.block-reset-button').click();
|
|
|
|
// Confirm button should be disabled initially
|
|
const confirmButton = page.getByRole('button', {
|
|
name: translations.learn['reset-progress-confirm']
|
|
});
|
|
await expect(confirmButton).toBeDisabled();
|
|
|
|
// Enter incorrect text
|
|
const verifyInput = page.getByRole('textbox');
|
|
await verifyInput.fill('wrong text');
|
|
await expect(confirmButton).toBeDisabled();
|
|
|
|
// Enter correct verification phrase
|
|
await verifyInput.fill(translations.learn['reset-progress-verify']);
|
|
await expect(confirmButton).toBeEnabled();
|
|
});
|
|
});
|
|
|
|
test.describe('Module reset - Full reset flow', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/learn/2022/responsive-web-design');
|
|
});
|
|
|
|
test('should reset block progress when confirmed', async ({ page }) => {
|
|
// Find a block with an enabled reset button (has progress)
|
|
const blockHeader = page
|
|
.locator('.block-header-wrapper')
|
|
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
|
|
.first();
|
|
|
|
// Open the reset modal
|
|
await blockHeader.hover();
|
|
await blockHeader.locator('.block-reset-button').click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Enter verification phrase
|
|
const verifyInput = page.getByRole('textbox');
|
|
await verifyInput.fill(translations.learn['reset-progress-verify']);
|
|
|
|
const confirmButton = page.getByRole('button', {
|
|
name: translations.learn['reset-progress-confirm']
|
|
});
|
|
await confirmButton.click();
|
|
|
|
await expect(
|
|
page.getByText(
|
|
translations.learn['reset-progress-success'].split("'")[0],
|
|
{ exact: false }
|
|
)
|
|
).toBeVisible();
|
|
|
|
await page
|
|
.getByRole('button', {
|
|
name: translations.learn['reset-progress-dismiss']
|
|
})
|
|
.click();
|
|
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
});
|
|
});
|
|
|
|
// Skip: full-stack-developer page is not built in dev environment
|
|
test.describe.skip('Module reset - Chapter level (v9 accordion layout)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Navigate to a chapter-based superblock
|
|
await page.goto('/learn/full-stack-developer');
|
|
});
|
|
|
|
test('should show reset button on chapter hover', async ({ page }) => {
|
|
// Find a chapter header wrapper
|
|
const chapterHeader = page.locator('.chapter-header-wrapper').first();
|
|
|
|
// Reset button should be hidden initially
|
|
const resetButton = chapterHeader.locator('.block-reset-button');
|
|
await expect(resetButton).toHaveCSS('opacity', '0');
|
|
|
|
// Hover over the chapter header
|
|
await chapterHeader.hover();
|
|
|
|
// Reset button should become visible on hover
|
|
await expect(resetButton).toHaveCSS('opacity', '1');
|
|
});
|
|
|
|
test('should show reset button on module hover', async ({ page }) => {
|
|
// Expand a chapter first
|
|
const chapterButton = page.locator('.chapter-button-main').first();
|
|
await chapterButton.click();
|
|
|
|
// Find a module header wrapper
|
|
const moduleHeader = page.locator('.module-header-wrapper').first();
|
|
|
|
// Hover over the module header
|
|
await moduleHeader.hover();
|
|
|
|
// Reset button should become visible on hover
|
|
const resetButton = moduleHeader.locator('.block-reset-button');
|
|
await expect(resetButton).toHaveCSS('opacity', '1');
|
|
});
|
|
|
|
test('should open reset modal for chapter with correct title', async ({
|
|
page
|
|
}) => {
|
|
// Hover over a chapter to reveal the reset button
|
|
const chapterHeader = page.locator('.chapter-header-wrapper').first();
|
|
await chapterHeader.hover();
|
|
|
|
// Click the reset button
|
|
const resetButton = chapterHeader.locator('.block-reset-button');
|
|
await resetButton.click();
|
|
|
|
// Modal should be visible with the chapter name in the heading
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Module reset - Accessibility', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/learn/2022/responsive-web-design');
|
|
});
|
|
|
|
test('reset button should have proper aria-label', async ({ page }) => {
|
|
// Find a reset button and check its aria-label
|
|
const resetButton = page.locator('.block-reset-button').first();
|
|
const ariaLabel = await resetButton.getAttribute('aria-label');
|
|
|
|
// Should contain "Reset progress for"
|
|
expect(ariaLabel).toContain('Reset progress for');
|
|
});
|
|
|
|
test('modal should be keyboard accessible', async ({ page }) => {
|
|
// Find a block with an enabled reset button
|
|
const blockHeader = page
|
|
.locator('.block-header-wrapper')
|
|
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
|
|
.first();
|
|
await blockHeader.hover();
|
|
await blockHeader.locator('.block-reset-button').click();
|
|
|
|
// Modal should be visible
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Press Escape to close
|
|
await page.keyboard.press('Escape');
|
|
|
|
// Modal should be hidden
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
});
|
|
});
|