mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 18:55:29 -05:00
* add test and example files * update config to include python plugins in build * add markdown plugin * remove full pyscript execution from pyodide * move loading of pyscript.py from pyodide loagInterpreter to main setupVirtualEnv and add function to create python CE plugins * add plugin class to pyscript.py * add missing import * fix plugin path * add fetchPythonPlugins to PyScriptApp * remove old comments * fix test * add support for python plugins beyond custom elements and add app to python namespace in main * inject reference to PyScript app onto python plugins * add example hook onto markdown plugin * change plugin events logs * remove unused PyPlugin * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix type import * add docstring to fetchPythonPlugins * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * rename addPythonPlugin method * address PR comment * call python plugins on hooks after the interpreted is ready * add test for event hooks and split the test in 2 separate plugins to isolte type of plugins tests * change python plugins initialization and registration, to inject the app from app itself instead of on the plugins themselves * handle case when plugin cannot load due to missing plugin attribute * add test for fail scenario when a plugin module does not have a plugin attribute * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add deprecation warning for pyscript objects loaded in global namespace * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove all from global scope * remove create_custom_element from global scope * rename create_custom_element to define_custom_element * rename attributes in define_custom_element and add docstrings * better handle connect event output * add warning to py_markdown plugin * remove debugging logs * improve tests * remove debugging log * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove unused import * add executable shebang * add pyodide mock module * fmt and lint * Update to pyodide.ffi.create_proxy per pyodide v21 api change * Mock pyodide as package instead of mdoule * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add __init__ to pyodide package * Update pyscriptjs/src/plugin.ts fix logger name Co-authored-by: Antonio Cuni <anto.cuni@gmail.com> * fix pyodide import but handling the diff in their API change * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * oops, conflict resolution blooper * Fix failing integration tests Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jeff Glass <glass.jeffrey@gmail.com> Co-authored-by: Antonio Cuni <anto.cuni@gmail.com> Co-authored-by: FabioRosado <fabiorosado@outlook.com>
260 lines
8.7 KiB
Python
260 lines
8.7 KiB
Python
from .support import PyScriptTest
|
|
|
|
|
|
class TestPyRepl(PyScriptTest):
|
|
def _replace(self, py_repl, newcode):
|
|
"""
|
|
Clear the editor and write new code in it.
|
|
WARNING: this assumes that the textbox has already the focus
|
|
"""
|
|
# clear the editor, write new code
|
|
self.page.keyboard.press("Control+A")
|
|
self.page.keyboard.press("Backspace")
|
|
self.page.keyboard.type(newcode)
|
|
|
|
def test_repl_loads(self):
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl></py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.query_selector("py-repl")
|
|
assert py_repl
|
|
assert "Python" in py_repl.inner_text()
|
|
|
|
def test_execute_preloaded_source(self):
|
|
"""
|
|
Unfortunately it tests two things at once, but it's impossible to write a
|
|
smaller test. I think this is the most basic test that we can write.
|
|
|
|
We test that:
|
|
1. the source code that we put in the tag is loaded inside the editor
|
|
2. clicking the button executes it
|
|
"""
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
print('hello from py-repl')
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
src = py_repl.inner_text()
|
|
assert "print('hello from py-repl')" in src
|
|
py_repl.locator("button").click()
|
|
assert self.console.log.lines[-1] == "hello from py-repl"
|
|
|
|
def test_execute_code_typed_by_the_user(self):
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl></py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
py_repl.type('print("hello")')
|
|
py_repl.locator("button").click()
|
|
assert self.console.log.lines[-1] == "hello"
|
|
|
|
def test_execute_on_shift_enter(self):
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
print("hello world")
|
|
</py-repl>
|
|
"""
|
|
)
|
|
self.page.wait_for_selector("#runButton")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
|
|
assert self.console.log.lines[0] == self.PY_COMPLETE
|
|
assert self.console.log.lines[-1] == "hello world"
|
|
|
|
def test_display(self):
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
display('hello world')
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
py_repl.locator("button").click()
|
|
out_div = py_repl.locator("div.py-repl-output")
|
|
assert out_div.inner_text() == "hello world"
|
|
|
|
def test_show_last_expression(self):
|
|
"""
|
|
Test that we display() the value of the last expression, as you would
|
|
expect by a REPL
|
|
"""
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
42
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
py_repl.locator("button").click()
|
|
out_div = py_repl.locator("div.py-repl-output")
|
|
assert out_div.inner_text() == "42"
|
|
|
|
def test_run_clears_previous_output(self):
|
|
"""
|
|
Check that we clear the previous output of the cell before executing it
|
|
again
|
|
"""
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
display('hello world')
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
out_div = py_repl.locator("div.py-repl-output")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
assert out_div.inner_text() == "hello world"
|
|
#
|
|
# clear the editor, write new code, execute
|
|
self._replace(py_repl, "display('another output')")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
assert out_div.inner_text() == "another output"
|
|
|
|
def test_python_exception(self):
|
|
"""
|
|
See also test01_basic::test_python_exception, since it's very similar
|
|
"""
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
raise Exception('this is an error')
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
py_repl.locator("button").click()
|
|
#
|
|
# check that we sent the traceback to the console
|
|
tb_lines = self.console.error.lines[-1].splitlines()
|
|
assert tb_lines[0] == "[pyexec] Python exception:"
|
|
assert tb_lines[1] == "Traceback (most recent call last):"
|
|
assert tb_lines[-1] == "Exception: this is an error"
|
|
#
|
|
# check that we show the traceback in the page
|
|
err_pre = py_repl.locator("div.py-repl-output > pre.py-error")
|
|
tb_lines = err_pre.inner_text().splitlines()
|
|
assert tb_lines[0] == "Traceback (most recent call last):"
|
|
assert tb_lines[-1] == "Exception: this is an error"
|
|
|
|
def test_python_exception_after_previous_output(self):
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
display('hello world')
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
out_div = py_repl.locator("div.py-repl-output")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
assert out_div.inner_text() == "hello world"
|
|
#
|
|
# clear the editor, write new code, execute
|
|
self._replace(py_repl, "0/0")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
assert "hello world" not in out_div.inner_text()
|
|
assert "ZeroDivisionError" in out_div.inner_text()
|
|
|
|
def test_hide_previous_error_after_successful_run(self):
|
|
"""
|
|
this tests the fact that a new error div should be created once there's an
|
|
error but also that it should disappear automatically once the error
|
|
is fixed
|
|
"""
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl>
|
|
raise Exception('this is an error')
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
out_div = py_repl.locator("div.py-repl-output")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
assert "this is an error" in out_div.inner_text()
|
|
#
|
|
self._replace(py_repl, "display('hello')")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
assert out_div.inner_text() == "hello"
|
|
|
|
def test_output_attribute(self):
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl output="mydiv">
|
|
display('hello world')
|
|
</py-repl>
|
|
<hr>
|
|
<div id="mydiv"></div>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
py_repl.locator("button").click()
|
|
#
|
|
# check that we did NOT write to py-repl-output
|
|
out_div = py_repl.locator("div.py-repl-output")
|
|
assert out_div.inner_text() == ""
|
|
# check that we are using mydiv instead
|
|
mydiv = self.page.locator("#mydiv")
|
|
assert mydiv.inner_text() == "hello world"
|
|
|
|
def test_output_attribute_does_not_exist(self):
|
|
"""
|
|
If we try to use an attribute which doesn't exist, we display an error
|
|
instead
|
|
"""
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl output="I-dont-exist">
|
|
print('I will not be executed')
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repl = self.page.locator("py-repl")
|
|
py_repl.locator("button").click()
|
|
#
|
|
out_div = py_repl.locator("div.py-repl-output")
|
|
msg = "py-repl ERROR: cannot find the output element #I-dont-exist in the DOM"
|
|
assert out_div.inner_text() == msg
|
|
assert "I will not be executed" not in self.console.log.text
|
|
|
|
def test_auto_generate(self):
|
|
self.pyscript_run(
|
|
"""
|
|
<py-repl auto-generate="true">
|
|
</py-repl>
|
|
"""
|
|
)
|
|
py_repls = self.page.locator("py-repl")
|
|
outputs = py_repls.locator("div.py-repl-output")
|
|
assert py_repls.count() == 1
|
|
assert outputs.count() == 1
|
|
#
|
|
# evaluate the py-repl, and wait for the newly generated one
|
|
self.page.keyboard.type("'hello'")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
self.page.locator('py-repl[exec-id="2"]').wait_for()
|
|
assert py_repls.count() == 2
|
|
assert outputs.count() == 2
|
|
#
|
|
# now we type something else: the new py-repl should have the focus
|
|
self.page.keyboard.type("'world'")
|
|
self.page.keyboard.press("Shift+Enter")
|
|
self.page.locator('py-repl[exec-id="3"]').wait_for()
|
|
assert py_repls.count() == 3
|
|
assert outputs.count() == 3
|
|
#
|
|
# check that the code and the outputs are in order
|
|
out_texts = [el.inner_text() for el in self.iter_locator(outputs)]
|
|
assert out_texts == ["hello", "world", ""]
|