| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- import React, { FC, PropsWithChildren } from 'react'
- import PythonOutputPane from '@/features/ide-react/components/editor/python/python-output-pane'
- import {
- EditorProviders,
- projectDefaults,
- } from '../../../helpers/editor-providers'
- import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
- import { ProjectContext } from '@/shared/context/project-context'
- import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
- import { PythonExecutionProvider } from '@/features/ide-react/context/python-execution-context'
- const pythonExecutableScript: Record<string, string> = {
- file_id: 'test-py-doc-id',
- filename: 'test.py',
- }
- const FileTreePathProvider: FC<PropsWithChildren> = ({ children }) => {
- return (
- <FileTreePathContext.Provider
- value={{
- pathInFolder: () => pythonExecutableScript.filename,
- findEntityByPath: () => null,
- previewByPath: () => null,
- dirname: () => null,
- }}
- >
- {children}
- </FileTreePathContext.Provider>
- )
- }
- function makeProjectProvider(fileContents: Record<string, string>) {
- const ProjectProvider: FC<PropsWithChildren> = ({ children }) => {
- const projectSnapshot = {
- refresh: async () => {},
- getDocPaths: () => Object.keys(fileContents),
- getDocContents: (path: string) => fileContents[path] ?? null,
- } as unknown as ProjectSnapshot
- return (
- <ProjectContext.Provider
- value={{
- projectId: projectDefaults._id,
- project: projectDefaults,
- joinProject: () => {},
- updateProject: () => {},
- joinedOnce: true,
- projectSnapshot,
- tags: [],
- features: projectDefaults.features,
- name: projectDefaults.name,
- }}
- >
- {children}
- </ProjectContext.Provider>
- )
- }
- return ProjectProvider
- }
- describe('<PythonOutputPane />', function () {
- beforeEach(function () {
- window.metaAttributesCache.set('ol-baseAssetPath', '/__cypress/src/')
- })
- it('executes a Python script and displays its output', function () {
- const executablePythonFileContents = "print('hello!')"
- 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('exist')
- })
- it('can import and use values from other project Python files', function () {
- const executablePythonFileContents =
- 'from message import message\nprint(message)'
- const importedPythonFile = {
- filename: 'message.py',
- file_contents: "message = 'hello!'",
- }
- const projectFiles = {
- [pythonExecutableScript.filename]: executablePythonFileContents,
- [importedPythonFile.filename]: importedPythonFile.file_contents,
- }
- 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('exist')
- })
- it('can import files from different directories relative to the executable script', function () {
- const executablePythonFileContents = [
- 'from scripts.data_importers.csv_importer import print_data',
- 'print_data()',
- ].join('\n')
- const csvImporterFile = {
- filename: 'scripts/data_importers/csv_importer.py',
- file_contents: [
- 'import csv',
- '',
- 'def print_data():',
- ' with open("food_items.csv", "r") as f:',
- ' reader = csv.reader(f)',
- ' for row in reader:',
- ' print(",".join(row))',
- ].join('\n'),
- }
- const csvDataFile = {
- filename: 'food_items.csv',
- file_contents: 'name,type\nPizza,Italian\nSushi,Japanese\nTacos,Mexican',
- }
- const projectFiles = {
- 'scripts/test.py': executablePythonFileContents,
- [csvImporterFile.filename]: csvImporterFile.file_contents,
- [csvDataFile.filename]: csvDataFile.file_contents,
- }
- const ProjectProvider = makeProjectProvider(projectFiles)
- const NestedFileTreePathProvider: FC<PropsWithChildren> = ({
- children,
- }) => (
- <FileTreePathContext.Provider
- value={{
- pathInFolder: () => 'scripts/test.py',
- findEntityByPath: () => null,
- previewByPath: () => null,
- dirname: () => null,
- }}
- >
- {children}
- </FileTreePathContext.Provider>
- )
- cy.mount(
- <EditorProviders
- scope={{
- editor: {
- sharejs_doc: {
- doc_id: pythonExecutableScript.file_id,
- getSnapshot: () => executablePythonFileContents,
- },
- currentDocumentId: pythonExecutableScript.file_id,
- openDocName: 'test.py',
- },
- }}
- providers={{
- FileTreePathProvider: NestedFileTreePathProvider,
- ProjectProvider,
- }}
- >
- <PythonExecutionProvider>
- <PythonOutputPane />
- </PythonExecutionProvider>
- </EditorProviders>
- )
- cy.findByRole('button', { name: 'Run Python code' })
- .should('not.be.disabled')
- .click()
- cy.findByText('name,type').should('exist')
- cy.findByText('Pizza,Italian').should('exist')
- cy.findByText('Sushi,Japanese').should('exist')
- cy.findByText('Tacos,Mexican').should('exist')
- })
- it('renders stderr output with the stderr line class', function () {
- const executablePythonFileContents = [
- 'import sys',
- "print('hello!')",
- "sys.stderr.write('boom\\n')",
- ].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')
- cy.findByText('boom').should(
- 'have.class',
- 'ide-redesign-python-output-pane-line-stderr'
- )
- })
- it('renders the interrupt message as an info line', function () {
- const executablePythonFileContents = 'while True:\n pass\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.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'
- )
- })
- it('can load common python data analysis packages on code execution', function () {
- const executablePythonFileContents = [
- 'import tomli',
- '',
- "print(tomli.loads('greeting = \"hello from tomli\"')['greeting'])",
- ].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("ModuleNotFoundError: No module named 'tomli'").should(
- 'not.exist'
- )
- cy.findByText('hello from tomli').should('exist')
- })
- it('auto-installs python packages imported by the executing script', function () {
- const executablePythonFileContents = [
- 'import tomli',
- '',
- "print(tomli.loads('greeting = \"hello from tomli\"')['greeting'])",
- ].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("ModuleNotFoundError: No module named 'tomli'").should(
- 'not.exist'
- )
- cy.findByText('hello from tomli').should('exist')
- })
- })
|