mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-28 23:01:57 -04:00
344 lines
9.5 KiB
TypeScript
344 lines
9.5 KiB
TypeScript
import { execSync } from 'child_process';
|
|
|
|
import { test, expect, Page } from '@playwright/test';
|
|
|
|
import translations from '../client/i18n/locales/english/translations.json';
|
|
import { clearEditor, focusEditor, getEditors } from './utils/editor';
|
|
import { alertToBeVisible } from './utils/alerts';
|
|
|
|
interface ChallengeTest {
|
|
text: string;
|
|
testString: string;
|
|
}
|
|
|
|
interface PageData {
|
|
result: {
|
|
data: {
|
|
challengeNode: {
|
|
challenge: { tests: ChallengeTest[] };
|
|
};
|
|
};
|
|
};
|
|
}
|
|
|
|
const expectToRenderResetModal = async (page: Page) => {
|
|
await expect(
|
|
page.getByRole('dialog', { name: translations.learn.reset })
|
|
).toBeVisible();
|
|
|
|
await expect(
|
|
page.getByRole('button', {
|
|
name: translations.buttons.close
|
|
})
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole('heading', {
|
|
name: translations.learn.reset
|
|
})
|
|
).toBeVisible();
|
|
|
|
await expect(
|
|
page.getByText(translations.learn['reset-warn-2'])
|
|
).toBeVisible();
|
|
};
|
|
|
|
test('should render the modal content correctly', async ({ page }) => {
|
|
await page.goto(
|
|
'/learn/responsive-web-design-v9/workshop-cat-photo-app/step-3'
|
|
);
|
|
|
|
await page.getByRole('button', { name: translations.buttons.reset }).click();
|
|
|
|
await expectToRenderResetModal(page);
|
|
|
|
await expect(
|
|
page.getByRole('button', {
|
|
name: translations.buttons['reset-lesson']
|
|
})
|
|
).toBeVisible();
|
|
|
|
await expect(
|
|
page.getByText(
|
|
'Are you sure you wish to reset this lesson (Step 3)? The code editors and tests will be reset.'
|
|
)
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('User can reset challenge', async ({ page, isMobile, browserName }) => {
|
|
const initialText = ' <h2>Cat Photos</h2>';
|
|
const initialEditorText = page
|
|
.getByTestId('editor-container-indexhtml')
|
|
.getByText(initialText);
|
|
|
|
const updatedText = 'Only Dogs';
|
|
const updatedEditorText = page
|
|
.getByTestId('editor-container-indexhtml')
|
|
.getByText(updatedText);
|
|
|
|
await page.goto(
|
|
'/learn/responsive-web-design-v9/workshop-cat-photo-app/step-3'
|
|
);
|
|
|
|
// Building the preview can take a while
|
|
await expect(initialEditorText).toBeVisible();
|
|
|
|
// Modify the text in the editor pane, clearing first, otherwise the existing
|
|
// text will be selected before typing
|
|
await focusEditor({ page, isMobile });
|
|
await clearEditor({ page, browserName });
|
|
await getEditors(page).fill(updatedText);
|
|
await expect(updatedEditorText).toBeVisible();
|
|
|
|
// Run the tests so the lower jaw updates (later we confirm that the update
|
|
// are reset)
|
|
await page
|
|
.getByRole('button', {
|
|
name: translations.buttons['check-code']
|
|
})
|
|
.click();
|
|
|
|
// Reset the challenge
|
|
await page.getByRole('button', { name: translations.buttons.reset }).click();
|
|
await page
|
|
.getByRole('button', { name: translations.buttons['reset-lesson'] })
|
|
.click();
|
|
|
|
// Check it's back to the initial state
|
|
await expect(initialEditorText).toBeVisible();
|
|
await expect(
|
|
page.getByText(translations.learn['sorry-keep-trying'])
|
|
).not.toBeVisible();
|
|
});
|
|
|
|
test.describe('When the user is not logged in', () => {
|
|
test.use({ storageState: { cookies: [], origins: [] } });
|
|
|
|
test('User can reset classic challenge', async ({ page, isMobile }) => {
|
|
const challengePath =
|
|
'/learn/rosetta-code/rosetta-code-challenges/100-doors';
|
|
|
|
// Intercept Gatsby page-data and inject a mock test that always passes
|
|
await page.route(
|
|
`**/page-data${challengePath}/page-data.json`,
|
|
async route => {
|
|
const response = await route.fetch();
|
|
const body = await response.text();
|
|
|
|
const pageData = JSON.parse(body) as PageData;
|
|
pageData.result.data.challengeNode.challenge.tests = [
|
|
{
|
|
text: 'Mock test',
|
|
testString: 'assert(true)'
|
|
}
|
|
];
|
|
|
|
await route.fulfill({
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(pageData)
|
|
});
|
|
}
|
|
);
|
|
|
|
await page.goto(challengePath);
|
|
|
|
const challengeSolution = 'test code';
|
|
await focusEditor({ page, isMobile });
|
|
await getEditors(page).fill(challengeSolution);
|
|
|
|
const submitButton = isMobile
|
|
? page.getByRole('button', { name: translations.buttons.run })
|
|
: page.getByRole('button', { name: translations.buttons['check-code'] });
|
|
|
|
await submitButton.click();
|
|
|
|
await expect(
|
|
page.locator('.view-lines').getByText(challengeSolution)
|
|
).toBeVisible();
|
|
|
|
// Completion dialog shows up
|
|
await expect(
|
|
page.getByText(translations.buttons['submit-continue'])
|
|
).toBeVisible();
|
|
|
|
// Close the dialog
|
|
await page
|
|
.getByRole('button', { name: translations.buttons.close })
|
|
.click();
|
|
|
|
await page
|
|
.getByRole('button', { name: translations.buttons.reset })
|
|
.click();
|
|
|
|
await page
|
|
.getByRole('button', { name: translations.buttons['reset-lesson'] })
|
|
.click();
|
|
|
|
await expect(
|
|
page.locator('.view-lines').getByText(challengeSolution)
|
|
).not.toBeVisible();
|
|
await expect(
|
|
page.getByText(translations.buttons['go-to-next'])
|
|
).not.toBeVisible();
|
|
await expect(
|
|
page.getByText(translations.learn['tests-completed'])
|
|
).not.toBeVisible();
|
|
|
|
if (isMobile) {
|
|
await page.getByText(translations.learn['editor-tabs'].console).click();
|
|
}
|
|
|
|
await expect(
|
|
page.getByText(translations.learn['test-output'])
|
|
).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test('should close when the user clicks the close button', async ({ page }) => {
|
|
await page.goto(
|
|
'/learn/responsive-web-design-v9/workshop-cat-photo-app/step-3'
|
|
);
|
|
|
|
await page.getByRole('button', { name: translations.buttons.reset }).click();
|
|
|
|
await expect(
|
|
page.getByRole('dialog', { name: translations.learn.reset })
|
|
).toBeVisible();
|
|
|
|
await page
|
|
.getByRole('button', {
|
|
name: translations.buttons.close
|
|
})
|
|
.click();
|
|
|
|
await expect(
|
|
page.getByRole('dialog', { name: translations.learn.reset })
|
|
).toBeHidden();
|
|
});
|
|
|
|
test('User can reset on a multi-file project', async ({
|
|
page,
|
|
isMobile,
|
|
browserName
|
|
}) => {
|
|
const sampleText = 'function palindrome() { return true; }';
|
|
|
|
await page.goto(
|
|
'/learn/javascript-v9/lab-palindrome-checker/build-a-palindrome-checker'
|
|
);
|
|
|
|
await focusEditor({ page, isMobile });
|
|
await clearEditor({ page, browserName });
|
|
await getEditors(page).fill(sampleText);
|
|
await expect(page.getByText(sampleText)).toBeVisible();
|
|
|
|
await page.getByRole('button', { name: translations.buttons.revert }).click();
|
|
|
|
await expectToRenderResetModal(page);
|
|
await expect(
|
|
page.getByRole('button', {
|
|
name: translations.buttons['revert-to-saved-code']
|
|
})
|
|
).toBeVisible();
|
|
await page
|
|
.getByRole('button', {
|
|
name: translations.buttons['revert-to-saved-code']
|
|
})
|
|
.click();
|
|
|
|
await expect(page.getByText(translations.learn['revert-warn'])).toBeVisible();
|
|
|
|
await expect(page.getByText(sampleText)).not.toBeVisible();
|
|
});
|
|
|
|
test.describe('Signed in user', () => {
|
|
test.use({ storageState: 'playwright/.auth/development-user.json' });
|
|
|
|
test.beforeEach(() => {
|
|
execSync('node ../tools/scripts/seed/seed-demo-user');
|
|
});
|
|
|
|
test.afterEach(() => {
|
|
execSync('node ../tools/scripts/seed/seed-demo-user --certified-user');
|
|
});
|
|
|
|
test('User can reset on a multi-file project after reloading and saving', async ({
|
|
page,
|
|
isMobile,
|
|
browserName
|
|
}) => {
|
|
test.setTimeout(60000);
|
|
const savedText = 'function palindrome() { return true; }';
|
|
const updatedText = 'function palindrome() { return false; }';
|
|
|
|
await page.goto(
|
|
'/learn/javascript-v9/lab-palindrome-checker/build-a-palindrome-checker'
|
|
);
|
|
|
|
// This first edit should reappear after the reset
|
|
await focusEditor({ page, isMobile });
|
|
await clearEditor({ page, browserName });
|
|
await getEditors(page).fill(savedText);
|
|
await page.keyboard.press('Control+S');
|
|
await alertToBeVisible(page, translations.flash['code-saved']);
|
|
|
|
await page.reload();
|
|
|
|
// This second edit should be reset
|
|
await focusEditor({ page, isMobile });
|
|
await clearEditor({ page, browserName });
|
|
await getEditors(page).fill(updatedText);
|
|
|
|
await page
|
|
.getByRole('button', { name: translations.buttons.revert })
|
|
.click();
|
|
|
|
await page
|
|
.getByRole('button', {
|
|
name: translations.buttons['revert-to-saved-code']
|
|
})
|
|
.click();
|
|
|
|
await expect(page.getByText(updatedText)).not.toBeVisible();
|
|
await expect(page.getByText(savedText)).toBeVisible();
|
|
});
|
|
|
|
test('User can reset on a multi-file project without reloading', async ({
|
|
page,
|
|
isMobile,
|
|
browserName
|
|
}) => {
|
|
test.setTimeout(60000);
|
|
const savedText = 'function palindrome() { return true; }';
|
|
const updatedText = 'function palindrome() { return false; }';
|
|
|
|
await page.goto(
|
|
'/learn/javascript-v9/lab-palindrome-checker/build-a-palindrome-checker'
|
|
);
|
|
|
|
// This first edit should reappear after the reset
|
|
await focusEditor({ page, isMobile });
|
|
await clearEditor({ page, browserName });
|
|
await getEditors(page).fill(savedText);
|
|
await page.keyboard.press('Control+S');
|
|
await alertToBeVisible(page, translations.flash['code-saved']);
|
|
|
|
// This second edit should be reset
|
|
await focusEditor({ page, isMobile });
|
|
await clearEditor({ page, browserName });
|
|
await getEditors(page).fill(updatedText);
|
|
|
|
await page
|
|
.getByRole('button', { name: translations.buttons.revert })
|
|
.click();
|
|
|
|
await page
|
|
.getByRole('button', {
|
|
name: translations.buttons['revert-to-saved-code']
|
|
})
|
|
.click();
|
|
|
|
await expect(page.getByText(updatedText)).not.toBeVisible();
|
|
await expect(page.getByText(savedText)).toBeVisible();
|
|
});
|
|
});
|