feat(client): enable daily challenges + add e2e tests (#61549)

Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com>
This commit is contained in:
Tom
2025-08-04 11:37:06 -05:00
committed by GitHub
parent 86f52e82f3
commit ad8b31df2c
4 changed files with 230 additions and 3 deletions

View File

@@ -29,6 +29,7 @@ function DailyCodingChallengeCalendarDay({
<button
disabled
className='calendar-day not-available'
data-playwright-test-label='calendar-day'
aria-label={`${date && formatDisplayDate(date)}, (${t('aria.not-available')})`}
>
<span className='calendar-day-number' aria-hidden='true'>
@@ -42,6 +43,7 @@ function DailyCodingChallengeCalendarDay({
<Link
to={`/learn/daily-coding-challenge?date=${date}`}
className='calendar-day available'
data-playwright-test-label='calendar-day'
aria-label={`${date && formatDisplayDate(date)}`}
>
<span className='calendar-day-number' aria-hidden='true'>
@@ -49,11 +51,17 @@ function DailyCodingChallengeCalendarDay({
</span>
{isCompleted ? (
<span className='completed'>
<span
className='completed'
data-playwright-test-label='calendar-day-completed'
>
<GreenPass />
</span>
) : (
<span className='not-completed'>
<span
className='not-completed'
data-playwright-test-label='calendar-day-not-completed'
>
<GreenNotCompleted />
</span>
)}

View File

@@ -0,0 +1,212 @@
import { test, expect } from '@playwright/test';
import {
getTodayUsCentral,
formatDate
} from '../client/src/components/daily-coding-challenge/helpers';
const dateRouteRe = /.*\/daily-coding-challenge\/date\/.*/;
const allRouteRe = /.*\/daily-coding-challenge\/all/;
const todayUsCentral = getTodayUsCentral();
const [year, month, day] = todayUsCentral.split('-').map(Number);
const todayMidnight = `${todayUsCentral}T00:00:00.000Z`;
const mockApiChallenge = {
id: 'test-challenge-id',
challengeNumber: 1,
title: 'Test title',
date: todayMidnight,
description: 'Test description',
javascript: {
tests: [
{
text: 'Test text',
testString: 'assert.strictEqual(true, true);'
}
],
challengeFiles: [
{
fileKey: 'scriptjs',
contents: '// JavaScript seed code'
}
]
},
python: {
tests: [
{
text: 'Test text',
testString: '({test: () => { runPython(`assert True == True`)}})'
}
],
challengeFiles: [
{
fileKey: 'mainpy',
contents: '# Python seed code'
}
]
}
};
const mockApiAllChallenges = [
// today
{
// real ID from certified user so it shows completed in calendar
id: '6814d8e1516e86b171929de4',
date: todayMidnight
},
// yesterday, or tomorrow if today is the first
{
id: 'other-id',
date: `${formatDate({ year, month, day: day === 1 ? day + 1 : day - 1 })}T00:00:00.000Z`
}
];
const mockDaysInMonth = new Date(year, month, 0).getDate();
test.describe('Daily Coding Challenges', () => {
test('should show not found page for invalid date', async ({ page }) => {
await page.goto('/learn/daily-coding-challenge?date=invalid-date');
await expect(
page.getByText(/daily coding challenge not found\./i)
).toBeVisible();
});
test('should show not found page for date without challenge', async ({
page
}) => {
await page.route(dateRouteRe, async route => {
await route.fulfill({
status: 404,
headers: { 'Content-Type': 'application/json' },
json: { type: 'error', message: 'Challenge not found.' }
});
});
await page.goto('/learn/daily-coding-challenge?date=2025-01-01');
await expect(
page.getByText(/daily coding challenge not found\./i)
).toBeVisible();
});
test('should show not found page for API error', async ({ page }) => {
await page.route(dateRouteRe, async route => {
await route.fulfill({
status: 500,
headers: { 'Content-Type': 'application/json' },
json: { type: 'error', message: 'Internal server error.' }
});
});
await page.goto('/learn/daily-coding-challenge?date=2025-01-01');
await expect(
page.getByText(/daily coding challenge not found\./i)
).toBeVisible();
});
test('should show not found page for invalid challenge data', async ({
page
}) => {
await page.route(dateRouteRe, async route => {
await route.fulfill({
status: 200,
headers: { 'Content-Type': 'application/json' },
json: { invalid: 'data structure' }
});
});
await page.goto('/learn/daily-coding-challenge?date=2025-06-27');
await expect(
page.getByText(/daily coding challenge not found\./i)
).toBeVisible();
});
test('should load and display a daily coding challenge with a valid date, and should be able to switch between JavaScript and Python', async ({
page
}) => {
await page.route(dateRouteRe, async route => {
await route.fulfill({
status: 200,
headers: { 'Content-Type': 'application/json' },
json: mockApiChallenge
});
});
await page.goto(`/learn/daily-coding-challenge?date=${todayUsCentral}`);
await expect(page.getByText('Test title')).toBeVisible();
await expect(page.getByText('Test description')).toBeVisible();
// Language buttons
await expect(
page.getByRole('button', { name: /javascript/i })
).toBeVisible();
await expect(page.getByRole('button', { name: /python/i })).toBeVisible();
// Should show JS UI by default
await expect(
page.getByRole('button', { name: /script.js/i })
).toBeVisible();
await expect(
page.getByRole('button', { name: /main.py/i })
).not.toBeVisible();
await expect(page.getByRole('button', { name: /console/i })).toBeVisible();
await expect(page.getByTestId('preview-pane-button')).not.toBeVisible();
await expect(page.locator("div[role='code'].monaco-editor")).toContainText(
'// JavaScript seed code'
);
// Show show Python UI after changing language
await page.getByRole('button', { name: /python/i }).click();
await expect(page.getByRole('button', { name: /main.py/i })).toBeVisible();
await expect(
page.getByRole('button', { name: /script.js/i })
).not.toBeVisible();
await expect(page.getByRole('button', { name: /console/i })).toBeVisible();
await expect(page.getByTestId('preview-pane-button')).toBeVisible();
await expect(page.locator("div[role='code'].monaco-editor")).toContainText(
'# Python seed code'
);
await page.goto(`/learn/daily-coding-challenge?date=${todayUsCentral}`);
await expect(page.getByRole('button', { name: /main.py/i })).toBeVisible();
});
});
test.describe('Daily Coding Challenge Archive', () => {
test('should load and display the calendar', async ({ page }) => {
await page.route(allRouteRe, async route => {
await route.fulfill({
status: 200,
headers: { 'Content-Type': 'application/json' },
json: mockApiAllChallenges
});
});
await page.goto('/learn/daily-coding-challenge/archive');
await expect(page.getByText('Daily Coding Challenges')).toBeVisible();
await expect(
page.getByRole('button', { name: /previous month/i })
).toBeVisible();
await expect(
page.getByRole('button', { name: /next month/i })
).toBeVisible();
await expect(
page.getByRole('link', { name: /go to today/i })
).toBeVisible();
const totalCalendarDays = await page.getByTestId('calendar-day').count();
expect(totalCalendarDays).toBe(mockDaysInMonth);
await expect(page.getByTestId('calendar-day-completed')).toHaveCount(1);
await expect(page.getByTestId('calendar-day-not-completed')).toHaveCount(1);
});
});

View File

@@ -54,7 +54,7 @@ CURRICULUM_LOCALE=english
# Show or hide WIP in progress challenges
SHOW_UPCOMING_CHANGES=false
SHOW_DAILY_CODING_CHALLENGES=false
SHOW_DAILY_CODING_CHALLENGES=true
# ---------------------
# New API

View File

@@ -12281,6 +12281,13 @@ module.exports.fullyCertifiedUser = {
id: '672d456f4ac35950b300e93f'
}
],
completedDailyCodingChallenges: [
{
id: '6814d8e1516e86b171929de4',
completedDate: 1729240849345,
languages: ['javascript']
}
],
completedExams: [
{
id: '647e22d18acb466c97ccbef8',