From e38f4e18e4005bec64519b292285273cb493f5f2 Mon Sep 17 00:00:00 2001 From: Chris Dryden <117657238+chrisdrydenaltmetric@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:04:08 +0100 Subject: [PATCH] Merge pull request #33868 from overleaf/dk-package-loading-tests [web] Add tests for pyodide worker streams and output pane rendering GitOrigin-RevId: 41ffc25230be23d68d50c61980cfaf1260a0247d --- .../components/python-output-pane.spec.tsx | 56 +++++++++++ .../unit/editor/pyodide-worker-client.spec.ts | 97 +++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx b/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx index 87dbea0fe0..fcf69cbb85 100644 --- a/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx +++ b/services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx @@ -302,6 +302,62 @@ describe('', function () { ) }) + it('renders stdout, stderr, and info lines with visually distinct CSS classes', function () { + const executablePythonFileContents = [ + 'import sys', + "print('hello!')", + "sys.stderr.write('boom\\n')", + 'while True:', + ' pass', + ].join('\n') + const projectFiles = { + [pythonExecutableScript.filename]: executablePythonFileContents, + } + const ProjectProvider = makeProjectProvider(projectFiles) + + cy.mount( + executablePythonFileContents, + }, + currentDocumentId: pythonExecutableScript.file_id, + openDocName: pythonExecutableScript.filename, + }, + }} + providers={{ FileTreePathProvider, ProjectProvider }} + > + + + + + ) + + cy.findByRole('button', { name: 'Run Python code' }) + .should('not.be.disabled') + .click() + + cy.findByText('hello!') + .should('have.class', 'ide-redesign-python-output-pane-line-stdout') + .and('not.have.class', 'ide-redesign-python-output-pane-line-stderr') + .and('not.have.class', 'ide-redesign-python-output-pane-line-info') + cy.findByText('boom') + .should('have.class', 'ide-redesign-python-output-pane-line-stderr') + .and('not.have.class', 'ide-redesign-python-output-pane-line-stdout') + .and('not.have.class', 'ide-redesign-python-output-pane-line-info') + + cy.findByRole('button', { name: 'Stop Python execution' }) + .should('not.be.disabled') + .click() + + cy.findByText('Execution interrupted') + .should('have.class', 'ide-redesign-python-output-pane-line-info') + .and('not.have.class', 'ide-redesign-python-output-pane-line-stdout') + .and('not.have.class', 'ide-redesign-python-output-pane-line-stderr') + }) + it('can load common python data analysis packages on code execution', function () { const executablePythonFileContents = [ 'import tomli', diff --git a/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts b/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts index 3d082d62ab..81169136af 100644 --- a/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts +++ b/services/web/test/frontend/features/ide-react/unit/editor/pyodide-worker-client.spec.ts @@ -587,6 +587,103 @@ describe('PyodideWorkerClient', function () { }) }) + describe('output-line forwarding', function () { + function setupClientWithOutputTracking() { + const outputCalls: Parameters[] = [] + const client = new PyodideWorkerClient({ + baseAssetPath: BASE_ASSET_PATH, + createWorker, + onOutput: (...args) => { + outputCalls.push(args) + }, + fileUploader: fileUploaderStub, + }) + const worker = WorkerMock.instances[0] + worker.emitMessage({ type: 'listening' }) + return { client, worker, outputCalls } + } + + it('forwards stdout output-line events to the output callback', function () { + const { worker, outputCalls } = setupClientWithOutputTracking() + + worker.emitMessage({ + type: 'output-line', + stream: 'stdout', + line: 'hello!', + fileId: 'main.py', + executionId: 'exec-stdout', + }) + + expect(outputCalls).to.deep.equal([ + ['stdout', 'hello!', 'main.py', 'exec-stdout'], + ]) + }) + + it('forwards stderr output-line events to the output callback', function () { + const { worker, outputCalls } = setupClientWithOutputTracking() + + worker.emitMessage({ + type: 'output-line', + stream: 'stderr', + line: 'boom', + fileId: 'main.py', + executionId: 'exec-stderr', + }) + + expect(outputCalls).to.deep.equal([ + ['stderr', 'boom', 'main.py', 'exec-stderr'], + ]) + }) + + it('forwards info output-line events to the output callback', function () { + const { worker, outputCalls } = setupClientWithOutputTracking() + + worker.emitMessage({ + type: 'output-line', + stream: 'info', + line: 'Loading numpy from package index', + fileId: 'main.py', + executionId: 'exec-info', + }) + + expect(outputCalls).to.deep.equal([ + ['info', 'Loading numpy from package index', 'main.py', 'exec-info'], + ]) + }) + + it('preserves stream type when forwarding stdout, stderr, and info in sequence', function () { + const { worker, outputCalls } = setupClientWithOutputTracking() + + worker.emitMessage({ + type: 'output-line', + stream: 'info', + line: 'Loading package', + fileId: 'main.py', + executionId: 'exec-mixed', + }) + worker.emitMessage({ + type: 'output-line', + stream: 'stdout', + line: 'result', + fileId: 'main.py', + executionId: 'exec-mixed', + }) + worker.emitMessage({ + type: 'output-line', + stream: 'stderr', + line: 'warning', + fileId: 'main.py', + executionId: 'exec-mixed', + }) + + expect(outputCalls.map(call => call[0])).to.deep.equal([ + 'info', + 'stdout', + 'stderr', + ]) + }) + }) + describe('reset', function () { it('terminates the current worker and creates a new one', function () { const client = new PyodideWorkerClient({