mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 10:47:35 -05:00
Major highlights: 1. Merge&simplify base.ts and pyrepl.ts; kill base.ts 2. improve and extente the py-repl integration tests 3. Reorder the code in pyrepl.ts. This part of the PR doesn't change much of the concrete logic: it's just a sequence of renaming variables, moving code around, group code into functions, killing code which is no longer needed. But the end result is much better and nicer to read, IMHO. Minor highlights: 1. py-repl now uses the new logic in pyexec.ts to run the code 2. after PR Add display impl, rm outputManage, print and console.log default to browser console #749 py-repl no longer displayed the result of the last evaluated expression (e.g. if you typed 42 and run it, it displayed nothing). This PR re-introduces this behavior, which is what you would expect by a REPL. 3. improve the pytest --dev option: now it implies --no-fake-server so that sourcemaps works automatically 4. improve the names of the CSS classes to be more consistent 5. kill pyrepl.test.ts: the old tests didn't check anything useful, this style of unit test doesn't really add much value if you have good integration tests (which now we have) and trying to revive them was not worth the hassle
232 lines
7.6 KiB
Python
232 lines
7.6 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.keyboard.press("Shift+Enter")
|
|
|
|
# when we use locator('button').click() the message appears
|
|
# immediately, with keyboard.press we need to wait for it. I don't
|
|
# really know why it has a different behavior, I didn't investigate
|
|
# further.
|
|
self.wait_for_console("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
|