Преглед изворни кода

Merge pull request #33868 from overleaf/dk-package-loading-tests

[web] Add tests for pyodide worker streams and output pane rendering

GitOrigin-RevId: 41ffc25230be23d68d50c61980cfaf1260a0247d
Chris Dryden пре 4 месеци
родитељ
комит
e38f4e18e4

+ 56 - 0
services/web/test/frontend/features/ide-react/components/python-output-pane.spec.tsx

@@ -302,6 +302,62 @@ describe('<PythonOutputPane />', 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(
+      <EditorProviders
+        scope={{
+          editor: {
+            sharejs_doc: {
+              doc_id: pythonExecutableScript.file_id,
+              getSnapshot: () => executablePythonFileContents,
+            },
+            currentDocumentId: pythonExecutableScript.file_id,
+            openDocName: pythonExecutableScript.filename,
+          },
+        }}
+        providers={{ FileTreePathProvider, ProjectProvider }}
+      >
+        <PythonExecutionProvider>
+          <PythonOutputPane />
+        </PythonExecutionProvider>
+      </EditorProviders>
+    )
+
+    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',

+ 97 - 0
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<OutputCallback>[] = []
+      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({