mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 10:22:16 +00:00
fix(client): duplicate console output (#66350)
This commit is contained in:
committed by
GitHub
parent
b72d31c209
commit
9aa6f05fa1
@@ -246,22 +246,22 @@ export function* executeTests(testRunner, tests, testTimeout = 5000) {
|
||||
return testResults;
|
||||
}
|
||||
|
||||
// updates preview frame and the fcc console.
|
||||
export function* previewChallengeSaga(action) {
|
||||
const flushLogs = action?.type !== actionTypes.previewMounted;
|
||||
function* flush() {
|
||||
yield put(initLogs());
|
||||
yield put(initConsole(''));
|
||||
}
|
||||
|
||||
function* previewChallengeSaga() {
|
||||
const isBuildEnabled = yield select(isBuildEnabledSelector);
|
||||
if (!isBuildEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isExecuting = yield select(isExecutingSelector);
|
||||
// executeChallengeSaga flushes the logs, so there's no need to if that's
|
||||
// just happened.
|
||||
if (flushLogs && !isExecuting) {
|
||||
yield put(initLogs());
|
||||
yield put(initConsole(''));
|
||||
// the challenge execution will update the preview, so this saga doesn't
|
||||
// need to do anything.
|
||||
if (yield select(isExecutingSelector)) {
|
||||
return;
|
||||
}
|
||||
yield delay(700);
|
||||
|
||||
const logProxy = yield channel();
|
||||
const proxyLogger = args => {
|
||||
@@ -317,7 +317,9 @@ export function* previewChallengeSaga(action) {
|
||||
const logs = results[0].logs?.filter(
|
||||
log => !LOGS_TO_IGNORE.some(msg => log.msg === msg)
|
||||
);
|
||||
yield put(updateConsole(logs?.map(log => log.msg).join('\n')));
|
||||
const output = logs?.map(log => log.msg).join('\n');
|
||||
|
||||
yield put(updateConsole(output));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,10 +335,11 @@ export function* previewChallengeSaga(action) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor this so that we can use a single saga for all challenge
|
||||
// updates (then they can all go in the same `takeLatest` call and be cancelled
|
||||
// appropriately)
|
||||
function* updatePreviewSaga(action) {
|
||||
export function* updatePreviewSaga(action) {
|
||||
yield flush();
|
||||
if (action.type === actionTypes.updateFile) {
|
||||
yield delay(700);
|
||||
}
|
||||
const challengeData = yield select(challengeDataSelector);
|
||||
if (
|
||||
challengeData.challengeType === challengeTypes.python ||
|
||||
@@ -390,10 +393,14 @@ function* previewProjectSolutionSaga({ payload }) {
|
||||
export function createExecuteChallengeSaga(types) {
|
||||
return [
|
||||
takeLatest(types.executeChallenge, executeCancellableChallengeSaga),
|
||||
takeLatest(types.updateFile, updatePreviewSaga),
|
||||
takeLatest(
|
||||
[types.challengeMounted, types.resetChallenge, types.previewMounted],
|
||||
previewChallengeSaga
|
||||
[
|
||||
types.updateFile,
|
||||
types.challengeMounted,
|
||||
types.resetChallenge,
|
||||
types.previewMounted
|
||||
],
|
||||
updatePreviewSaga
|
||||
),
|
||||
takeLatest(types.projectPreviewMounted, previewProjectSolutionSaga)
|
||||
];
|
||||
|
||||
@@ -4,15 +4,7 @@
|
||||
import { expectSaga } from 'redux-saga-test-plan';
|
||||
import { describe, it, vi } from 'vitest';
|
||||
|
||||
import { previewChallengeSaga, executeTests } from './execute-challenge-saga';
|
||||
|
||||
vi.mock('redux-saga/effects', async importOriginal => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
delay: vi.fn()
|
||||
};
|
||||
});
|
||||
import { executeTests, updatePreviewSaga } from './execute-challenge-saga';
|
||||
|
||||
vi.mock('i18next', async () => ({
|
||||
default: {
|
||||
@@ -33,9 +25,9 @@ const challengeMounted = { type: 'challenge.challengeMounted' };
|
||||
const previewMounted = { type: 'challenge.previewMounted' };
|
||||
const resetChallenge = { type: 'challenge.resetChallenge' };
|
||||
|
||||
describe('previewChallengeSaga', () => {
|
||||
describe('updatePreviewSaga', () => {
|
||||
it('flushes logs on challengeMounted', () => {
|
||||
return expectSaga(previewChallengeSaga, challengeMounted)
|
||||
return expectSaga(updatePreviewSaga, challengeMounted)
|
||||
.withReducer(reducer)
|
||||
.put({ type: 'challenge.initLogs' })
|
||||
.silentRun();
|
||||
@@ -43,15 +35,15 @@ describe('previewChallengeSaga', () => {
|
||||
// warnings. Increasing the timeout just makes the tests take longer.
|
||||
});
|
||||
it('flushes logs on reset', () => {
|
||||
return expectSaga(previewChallengeSaga, resetChallenge)
|
||||
return expectSaga(updatePreviewSaga, resetChallenge)
|
||||
.withReducer(reducer)
|
||||
.put({ type: 'challenge.initLogs' })
|
||||
.silentRun();
|
||||
});
|
||||
it('does not flush logs on previewMounted', () => {
|
||||
return expectSaga(previewChallengeSaga, previewMounted)
|
||||
it('flushes logs on previewMounted', () => {
|
||||
return expectSaga(updatePreviewSaga, previewMounted)
|
||||
.withReducer(reducer)
|
||||
.not.put({ type: 'challenge.initLogs' })
|
||||
.put({ type: 'challenge.initLogs' })
|
||||
.silentRun();
|
||||
});
|
||||
});
|
||||
|
||||
+26
-25
@@ -13,8 +13,8 @@ const outputTexts = {
|
||||
> 1 | var
|
||||
| ^`,
|
||||
empty: `// running tests
|
||||
1. You should declare myName with the var keyword, ending with a semicolon
|
||||
// tests completed`,
|
||||
1. You should declare myName with the var keyword, ending with a semicolon
|
||||
// tests completed`,
|
||||
passed: `// running tests
|
||||
// tests completed`
|
||||
};
|
||||
@@ -33,20 +33,18 @@ const replaceTextInCodeEditor = async ({
|
||||
browserName,
|
||||
isMobile,
|
||||
text,
|
||||
containerId = 'editor-container-indexhtml',
|
||||
updatesConsole = false
|
||||
containerId = 'editor-container-indexhtml'
|
||||
}: InsertTextParameters) => {
|
||||
await expect(async () => {
|
||||
await clearEditor({ page, browserName, isMobile });
|
||||
await getEditors(page).fill(text);
|
||||
await expect(page.getByTestId(containerId)).toContainText(text);
|
||||
if (updatesConsole) {
|
||||
await expect(
|
||||
page.getByRole('region', {
|
||||
name: translations.learn['editor-tabs'].console
|
||||
})
|
||||
).not.toContainText('Your test output will go here');
|
||||
}
|
||||
|
||||
await expect(
|
||||
page.getByRole('region', {
|
||||
name: translations.learn['editor-tabs'].console
|
||||
})
|
||||
).toContainText('Your test output will go here');
|
||||
}).toPass();
|
||||
};
|
||||
|
||||
@@ -211,12 +209,13 @@ test.describe('Challenge Output Component Tests', () => {
|
||||
page,
|
||||
isMobile
|
||||
}) => {
|
||||
await runChallengeTest(page, isMobile);
|
||||
await expect(async () => {
|
||||
await runChallengeTest(page, isMobile);
|
||||
|
||||
await expect(page.getByTestId('output-text')).toContainText(
|
||||
outputTexts.empty,
|
||||
{ timeout: 10000 }
|
||||
);
|
||||
expect(await page.getByTestId('output-text').textContent()).toEqual(
|
||||
expect.stringContaining(outputTexts.empty)
|
||||
);
|
||||
}).toPass();
|
||||
});
|
||||
|
||||
test('should contain final output after test pass', async ({
|
||||
@@ -226,15 +225,17 @@ test.describe('Challenge Output Component Tests', () => {
|
||||
}) => {
|
||||
const closeButton = page.getByRole('button', { name: 'Close' });
|
||||
await focusEditor({ page, isMobile });
|
||||
await replaceTextInCodeEditor({
|
||||
browserName,
|
||||
page,
|
||||
isMobile,
|
||||
text: 'var myName;',
|
||||
containerId: 'editor-container-scriptjs',
|
||||
updatesConsole: true
|
||||
});
|
||||
await runChallengeTest(page, isMobile);
|
||||
await expect(async () => {
|
||||
await replaceTextInCodeEditor({
|
||||
browserName,
|
||||
page,
|
||||
isMobile,
|
||||
text: 'var myName;',
|
||||
containerId: 'editor-container-scriptjs'
|
||||
});
|
||||
await runChallengeTest(page, isMobile);
|
||||
await expect(closeButton).toBeVisible();
|
||||
}).toPass();
|
||||
await closeButton.click();
|
||||
|
||||
await expect(
|
||||
|
||||
Reference in New Issue
Block a user