python-output-pane.spec.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import React, { FC, PropsWithChildren } from 'react'
  2. import PythonOutputPane from '@/features/ide-react/components/editor/python/python-output-pane'
  3. import {
  4. EditorProviders,
  5. projectDefaults,
  6. } from '../../../helpers/editor-providers'
  7. import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
  8. import { ProjectContext } from '@/shared/context/project-context'
  9. import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
  10. import { PythonExecutionProvider } from '@/features/ide-react/context/python-execution-context'
  11. const pythonExecutableScript: Record<string, string> = {
  12. file_id: 'test-py-doc-id',
  13. filename: 'test.py',
  14. }
  15. const FileTreePathProvider: FC<PropsWithChildren> = ({ children }) => {
  16. return (
  17. <FileTreePathContext.Provider
  18. value={{
  19. pathInFolder: () => pythonExecutableScript.filename,
  20. findEntityByPath: () => null,
  21. previewByPath: () => null,
  22. dirname: () => null,
  23. }}
  24. >
  25. {children}
  26. </FileTreePathContext.Provider>
  27. )
  28. }
  29. function makeProjectProvider(fileContents: Record<string, string>) {
  30. const ProjectProvider: FC<PropsWithChildren> = ({ children }) => {
  31. const projectSnapshot = {
  32. refresh: async () => {},
  33. getDocPaths: () => Object.keys(fileContents),
  34. getDocContents: (path: string) => fileContents[path] ?? null,
  35. } as unknown as ProjectSnapshot
  36. return (
  37. <ProjectContext.Provider
  38. value={{
  39. projectId: projectDefaults._id,
  40. project: projectDefaults,
  41. joinProject: () => {},
  42. updateProject: () => {},
  43. joinedOnce: true,
  44. projectSnapshot,
  45. tags: [],
  46. features: projectDefaults.features,
  47. name: projectDefaults.name,
  48. }}
  49. >
  50. {children}
  51. </ProjectContext.Provider>
  52. )
  53. }
  54. return ProjectProvider
  55. }
  56. describe('<PythonOutputPane />', function () {
  57. beforeEach(function () {
  58. window.metaAttributesCache.set('ol-baseAssetPath', '/__cypress/src/')
  59. })
  60. it('executes a Python script and displays its output', function () {
  61. const executablePythonFileContents = "print('hello!')"
  62. const projectFiles = {
  63. [pythonExecutableScript.filename]: executablePythonFileContents,
  64. }
  65. const ProjectProvider = makeProjectProvider(projectFiles)
  66. cy.mount(
  67. <EditorProviders
  68. scope={{
  69. editor: {
  70. sharejs_doc: {
  71. doc_id: pythonExecutableScript.file_id,
  72. getSnapshot: () => executablePythonFileContents,
  73. },
  74. currentDocumentId: pythonExecutableScript.file_id,
  75. openDocName: pythonExecutableScript.filename,
  76. },
  77. }}
  78. providers={{ FileTreePathProvider, ProjectProvider }}
  79. >
  80. <PythonExecutionProvider>
  81. <PythonOutputPane />
  82. </PythonExecutionProvider>
  83. </EditorProviders>
  84. )
  85. cy.findByRole('button', { name: 'Run Python code' })
  86. .should('not.be.disabled')
  87. .click()
  88. cy.findByText('hello!').should('exist')
  89. })
  90. it('can import and use values from other project Python files', function () {
  91. const executablePythonFileContents =
  92. 'from message import message\nprint(message)'
  93. const importedPythonFile = {
  94. filename: 'message.py',
  95. file_contents: "message = 'hello!'",
  96. }
  97. const projectFiles = {
  98. [pythonExecutableScript.filename]: executablePythonFileContents,
  99. [importedPythonFile.filename]: importedPythonFile.file_contents,
  100. }
  101. const ProjectProvider = makeProjectProvider(projectFiles)
  102. cy.mount(
  103. <EditorProviders
  104. scope={{
  105. editor: {
  106. sharejs_doc: {
  107. doc_id: pythonExecutableScript.file_id,
  108. getSnapshot: () => executablePythonFileContents,
  109. },
  110. currentDocumentId: pythonExecutableScript.file_id,
  111. openDocName: pythonExecutableScript.filename,
  112. },
  113. }}
  114. providers={{ FileTreePathProvider, ProjectProvider }}
  115. >
  116. <PythonExecutionProvider>
  117. <PythonOutputPane />
  118. </PythonExecutionProvider>
  119. </EditorProviders>
  120. )
  121. cy.findByRole('button', { name: 'Run Python code' })
  122. .should('not.be.disabled')
  123. .click()
  124. cy.findByText('hello!').should('exist')
  125. })
  126. it('can import files from different directories relative to the executable script', function () {
  127. const executablePythonFileContents = [
  128. 'from scripts.data_importers.csv_importer import print_data',
  129. 'print_data()',
  130. ].join('\n')
  131. const csvImporterFile = {
  132. filename: 'scripts/data_importers/csv_importer.py',
  133. file_contents: [
  134. 'import csv',
  135. '',
  136. 'def print_data():',
  137. ' with open("food_items.csv", "r") as f:',
  138. ' reader = csv.reader(f)',
  139. ' for row in reader:',
  140. ' print(",".join(row))',
  141. ].join('\n'),
  142. }
  143. const csvDataFile = {
  144. filename: 'food_items.csv',
  145. file_contents: 'name,type\nPizza,Italian\nSushi,Japanese\nTacos,Mexican',
  146. }
  147. const projectFiles = {
  148. 'scripts/test.py': executablePythonFileContents,
  149. [csvImporterFile.filename]: csvImporterFile.file_contents,
  150. [csvDataFile.filename]: csvDataFile.file_contents,
  151. }
  152. const ProjectProvider = makeProjectProvider(projectFiles)
  153. const NestedFileTreePathProvider: FC<PropsWithChildren> = ({
  154. children,
  155. }) => (
  156. <FileTreePathContext.Provider
  157. value={{
  158. pathInFolder: () => 'scripts/test.py',
  159. findEntityByPath: () => null,
  160. previewByPath: () => null,
  161. dirname: () => null,
  162. }}
  163. >
  164. {children}
  165. </FileTreePathContext.Provider>
  166. )
  167. cy.mount(
  168. <EditorProviders
  169. scope={{
  170. editor: {
  171. sharejs_doc: {
  172. doc_id: pythonExecutableScript.file_id,
  173. getSnapshot: () => executablePythonFileContents,
  174. },
  175. currentDocumentId: pythonExecutableScript.file_id,
  176. openDocName: 'test.py',
  177. },
  178. }}
  179. providers={{
  180. FileTreePathProvider: NestedFileTreePathProvider,
  181. ProjectProvider,
  182. }}
  183. >
  184. <PythonExecutionProvider>
  185. <PythonOutputPane />
  186. </PythonExecutionProvider>
  187. </EditorProviders>
  188. )
  189. cy.findByRole('button', { name: 'Run Python code' })
  190. .should('not.be.disabled')
  191. .click()
  192. cy.findByText('name,type').should('exist')
  193. cy.findByText('Pizza,Italian').should('exist')
  194. cy.findByText('Sushi,Japanese').should('exist')
  195. cy.findByText('Tacos,Mexican').should('exist')
  196. })
  197. })