fix(client): duplicate console output (#66350)

This commit is contained in:
Oliver Eyton-Williams
2026-03-11 21:15:50 +01:00
committed by GitHub
parent b72d31c209
commit 9aa6f05fa1
3 changed files with 58 additions and 58 deletions
@@ -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();
});
});
+13 -12
View File
@@ -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');
}
).toContainText('Your test output will go here');
}).toPass();
};
@@ -211,12 +209,13 @@ test.describe('Challenge Output Component Tests', () => {
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 expect(async () => {
await replaceTextInCodeEditor({
browserName,
page,
isMobile,
text: 'var myName;',
containerId: 'editor-container-scriptjs',
updatesConsole: true
containerId: 'editor-container-scriptjs'
});
await runChallengeTest(page, isMobile);
await expect(closeButton).toBeVisible();
}).toPass();
await closeButton.click();
await expect(