mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Add display impl, rm outputManage, print and console.log default to browser console (#749)
* Add display impl, remove outputManage, print and console.log defaults to terminal * Fixing tests * Lint * Erase unecessary code, add cuter CSS formating for errors, fix problems around REPL output * Add fix to repl2 and lint * lint * Allow for list of display, fix elif to else * Add better global option * test work * xfails * (antocuni, mariana): let's try to start again with TDD methodology: write the minimum test and code for a simple display() * (antocuni, mariana): this test works out of the box * WIP: this test is broken, mariana is going to fix it * add a failing test * Add ability to deal with targets * Add append arg and append tests * Add multiple values to display * Small adjustments to tests. I noticed I wasn;t running all at some point * add display test * Add console tests * Add async tests * Fix repl tests * Fixing merging issues * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address antocuni's review * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixing more tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * linting * Improve repl tests * Change my test so codespell is hapy with it * Test: change test_runtime_config to use json instead of toml to see if stops failing on CI * kill this file: it is a merge artifact since it was renamed into test_py_config.py on the main branch * Change test execution order and add async tests to async test file Co-authored-by: Antonio Cuni <anto.cuni@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -50,13 +50,13 @@ bar = alt.Chart(source).mark_bar().encode(
|
||||
height=200
|
||||
).add_selection(pts)
|
||||
|
||||
alt.vconcat(
|
||||
display(alt.vconcat(
|
||||
rect + circ,
|
||||
bar
|
||||
).resolve_legend(
|
||||
color="independent",
|
||||
size="independent"
|
||||
)
|
||||
))
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -47,7 +47,7 @@ folium.Choropleth(
|
||||
|
||||
folium.LayerControl().add_to(m)
|
||||
|
||||
m
|
||||
display(m)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<py-script>
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
now.strftime("%m/%d/%Y, %H:%M:%S")
|
||||
display(now.strftime("%m/%d/%Y, %H:%M:%S"))
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -48,7 +48,7 @@ tpc = ax1.tripcolor(triang, z, shading='flat')
|
||||
fig1.colorbar(tpc)
|
||||
ax1.set_title('tripcolor of Delaunay triangulation, flat shading')
|
||||
|
||||
fig1
|
||||
display(fig1)
|
||||
</py-script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -26,10 +26,8 @@
|
||||
]
|
||||
</py-config>
|
||||
<py-box widths="2/3;1/3">
|
||||
<py-repl id="my-repl" auto-generate="true" std-out="output" std-err="err-div"> </py-repl>
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
<div id="output" class="p-4"></div>
|
||||
</py-box>
|
||||
<footer id="err-div" class="bg-red-700 text-white text-center border-t-4 border-green-500 fixed inset-x-0 bottom-0 p-4 hidden">
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
# demonstrates how use the global PyScript pyscript_loader
|
||||
# to send operation log messages to it
|
||||
import utils
|
||||
utils.now()
|
||||
display(utils.now())
|
||||
</py-script>
|
||||
|
||||
<py-script>
|
||||
|
||||
@@ -111,45 +111,19 @@ export class BaseEvalElement extends HTMLElement {
|
||||
this.preEvaluate();
|
||||
|
||||
let source: string;
|
||||
let output: string;
|
||||
try {
|
||||
source = this.source ? await this.getSourceFromFile(this.source)
|
||||
: this.getSourceFromElement();
|
||||
|
||||
this._register_esm(runtime);
|
||||
<string>await runtime.run(
|
||||
`output_manager.change(out="${this.outputElement.id}", err="${this.errorElement.id}", append=${this.appendOutput ? 'True' : 'False'})`,
|
||||
);
|
||||
output = <string>await runtime.run(source);
|
||||
|
||||
if (output !== undefined) {
|
||||
if (Element === undefined) {
|
||||
Element = <Element>runtime.globals.get('Element');
|
||||
}
|
||||
const out = Element(this.outputElement.id);
|
||||
out.write.callKwargs(output, { append: this.appendOutput });
|
||||
|
||||
this.outputElement.hidden = false;
|
||||
this.outputElement.style.display = 'block';
|
||||
try {
|
||||
<string>await runtime.run(`set_current_display_target(target_id="${this.id}")`);
|
||||
<string>await runtime.run(source);
|
||||
} finally {
|
||||
<string>await runtime.run(`set_current_display_target(target_id=None)`);
|
||||
}
|
||||
|
||||
await runtime.run(`output_manager.revert()`);
|
||||
|
||||
// check if this REPL contains errors, delete them and remove error classes
|
||||
const errorElements = document.querySelectorAll(`div[id^='${this.errorElement.id}'][error]`);
|
||||
if (errorElements.length > 0) {
|
||||
errorElements.forEach( errorElement =>
|
||||
{
|
||||
errorElement.classList.add('hidden');
|
||||
if (this.hasAttribute('std-err')) {
|
||||
this.errorElement.hidden = true;
|
||||
this.errorElement.style.removeProperty('display');
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
removeClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
||||
|
||||
removeClasses(this.errorElement, ['py-error']);
|
||||
this.postEvaluate();
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
@@ -159,7 +133,7 @@ export class BaseEvalElement extends HTMLElement {
|
||||
}
|
||||
const out = Element(this.errorElement.id);
|
||||
|
||||
addClasses(this.errorElement, ['bg-red-200', 'p-2']);
|
||||
addClasses(this.errorElement, ['py-error']);
|
||||
out.write.callKwargs(err.toString(), { append: this.appendOutput });
|
||||
if (this.errorElement.children.length === 0){
|
||||
this.errorElement.setAttribute('error', '');
|
||||
|
||||
@@ -89,34 +89,6 @@ export function make_PyWidget(runtime: Runtime) {
|
||||
createWidget(runtime, this.name, this.code, this.klass);
|
||||
}
|
||||
|
||||
initOutErr(): void {
|
||||
if (this.hasAttribute('output')) {
|
||||
this.errorElement = this.outputElement = document.getElementById(this.getAttribute('output'));
|
||||
|
||||
// in this case, the default output-mode is append, if hasn't been specified
|
||||
if (!this.hasAttribute('output-mode')) {
|
||||
this.setAttribute('output-mode', 'append');
|
||||
}
|
||||
} else {
|
||||
if (this.hasAttribute('std-out')) {
|
||||
this.outputElement = document.getElementById(this.getAttribute('std-out'));
|
||||
} else {
|
||||
// In this case neither output or std-out have been provided so we need
|
||||
// to create a new output div to output to
|
||||
this.outputElement = document.createElement('div');
|
||||
this.outputElement.classList.add('output');
|
||||
this.outputElement.hidden = true;
|
||||
this.outputElement.id = this.id + '-' + this.getAttribute('exec-id');
|
||||
}
|
||||
|
||||
if (this.hasAttribute('std-err')) {
|
||||
this.errorElement = document.getElementById(this.getAttribute('std-err'));
|
||||
} else {
|
||||
this.errorElement = this.outputElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSourceFromFile(s: string): Promise<string> {
|
||||
const response = await fetch(s);
|
||||
return await response.text();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import io
|
||||
import sys
|
||||
import time
|
||||
from textwrap import dedent
|
||||
|
||||
@@ -124,6 +123,33 @@ class PyScript:
|
||||
)
|
||||
|
||||
|
||||
def set_current_display_target(target_id):
|
||||
get_current_display_target._id = target_id
|
||||
|
||||
|
||||
def get_current_display_target():
|
||||
return get_current_display_target._id
|
||||
|
||||
|
||||
get_current_display_target._id = None
|
||||
|
||||
|
||||
def display(*values, target=None, append=True):
|
||||
default_target = get_current_display_target()
|
||||
|
||||
if default_target is None and target is None:
|
||||
raise Exception(
|
||||
"Implicit target not allowed here. Please use display(..., target=...)"
|
||||
)
|
||||
|
||||
if target is not None:
|
||||
for v in values:
|
||||
Element(target).write(v, append=append)
|
||||
else:
|
||||
for v in values:
|
||||
Element(default_target).write(v, append=append)
|
||||
|
||||
|
||||
class Element:
|
||||
def __init__(self, element_id, element=None):
|
||||
self._id = element_id
|
||||
@@ -365,59 +391,4 @@ class PyListTemplate:
|
||||
pass
|
||||
|
||||
|
||||
class OutputCtxManager:
|
||||
def __init__(self, out=None, output_to_console=True, append=True):
|
||||
self._out = out
|
||||
self._prev = out
|
||||
self.output_to_console = output_to_console
|
||||
self._append = append
|
||||
|
||||
def change(self, out=None, output_to_console=True, append=True):
|
||||
self._prev = self._out
|
||||
self._out = out
|
||||
self.output_to_console = output_to_console
|
||||
self._append = append
|
||||
|
||||
def revert(self):
|
||||
self._out = self._prev
|
||||
|
||||
def write(self, value):
|
||||
if self._out:
|
||||
Element(self._out).write(value, self._append)
|
||||
|
||||
if self.output_to_console:
|
||||
console.info(value)
|
||||
|
||||
|
||||
class OutputManager:
|
||||
def __init__(self, out=None, err=None, output_to_console=True, append=True):
|
||||
sys.stdout = self._out_manager = OutputCtxManager(
|
||||
out=out, output_to_console=output_to_console, append=append
|
||||
)
|
||||
sys.stderr = self._err_manager = OutputCtxManager(
|
||||
out=err, output_to_console=output_to_console, append=append
|
||||
)
|
||||
self.output_to_console = output_to_console
|
||||
self._append = append
|
||||
|
||||
def change(self, out=None, err=None, output_to_console=True, append=True):
|
||||
self._out_manager.change(
|
||||
out=out, output_to_console=output_to_console, append=append
|
||||
)
|
||||
sys.stdout = self._out_manager
|
||||
self._err_manager.change(
|
||||
out=err, output_to_console=output_to_console, append=append
|
||||
)
|
||||
sys.stderr = self._err_manager
|
||||
self.output_to_console = output_to_console
|
||||
self._append = append
|
||||
|
||||
def revert(self):
|
||||
self._out_manager.revert()
|
||||
self._err_manager.revert()
|
||||
sys.stdout = self._out_manager
|
||||
sys.stderr = self._err_manager
|
||||
|
||||
|
||||
pyscript = PyScript()
|
||||
output_manager = OutputManager()
|
||||
|
||||
@@ -89,6 +89,7 @@ html {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
color: #0f172a;
|
||||
.py-pop-up {
|
||||
text-align: center;
|
||||
width: 600px;
|
||||
@@ -107,7 +108,16 @@ html {
|
||||
right: 5%;
|
||||
}
|
||||
|
||||
.py-box {
|
||||
/* Pop-up second layer end */
|
||||
|
||||
.py-error{
|
||||
background-color: rgb(254 226 226);
|
||||
border: solid;
|
||||
border-color: #fca5a5;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.py-box{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
@@ -8,7 +8,7 @@ class TestBasic(PyScriptTest):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
print('hello pyscript')
|
||||
display('hello pyscript')
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from .support import PyScriptTest
|
||||
|
||||
|
||||
class TestAsync(PyScriptTest):
|
||||
def test_multiple_async(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
import js
|
||||
import asyncio
|
||||
for i in range(3):
|
||||
js.console.log('A', i)
|
||||
await asyncio.sleep(0.1)
|
||||
</py-script>
|
||||
|
||||
<py-script>
|
||||
import js
|
||||
import asyncio
|
||||
for i in range(3):
|
||||
js.console.log('B', i)
|
||||
await asyncio.sleep(0.1)
|
||||
js.console.log("async tadone")
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
self.wait_for_console("async tadone")
|
||||
assert self.console.log.lines == [
|
||||
"Python initialization complete",
|
||||
"A 0",
|
||||
"B 0",
|
||||
"A 1",
|
||||
"B 1",
|
||||
"A 2",
|
||||
"B 2",
|
||||
"async tadone",
|
||||
]
|
||||
247
pyscriptjs/tests/integration/test_02_output.py
Normal file
247
pyscriptjs/tests/integration/test_02_output.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import re
|
||||
|
||||
from .support import PyScriptTest
|
||||
|
||||
|
||||
class TestOutuput(PyScriptTest):
|
||||
def test_simple_display(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
display('hello world')
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_html = self.page.content()
|
||||
pattern = r'<div id="py-.*">hello world</div>'
|
||||
assert re.search(pattern, inner_html)
|
||||
|
||||
def test_consecutive_display(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
display('hello 1')
|
||||
</py-script>
|
||||
<py-script>
|
||||
display('hello 2')
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
# need to improve this to get the first/second input
|
||||
# instead of just searching for it in the page
|
||||
inner_html = self.page.content()
|
||||
first_pattern = r'<div id="py-.*?-2">hello 1</div>'
|
||||
assert re.search(first_pattern, inner_html)
|
||||
second_pattern = r'<div id="py-.*?-3">hello 2</div>'
|
||||
assert re.search(second_pattern, inner_html)
|
||||
|
||||
assert first_pattern is not second_pattern
|
||||
|
||||
def test_multiple_display_calls_same_tag(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
display('hello')
|
||||
display('world')
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_html = self.page.content()
|
||||
pattern = r'<div id="py-.*?-2">hello</div>'
|
||||
assert re.search(pattern, inner_html)
|
||||
pattern = r'<div id="py-.*?-3">world</div>'
|
||||
assert re.search(pattern, inner_html)
|
||||
|
||||
def test_no_implicit_target(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
def display_hello():
|
||||
# this fails because we don't have any implicit target
|
||||
# from event handlers
|
||||
display('hello')
|
||||
</py-script>
|
||||
<button id="my-button" py-onClick="display_hello()">Click me</button>
|
||||
"""
|
||||
)
|
||||
self.page.locator("text=Click me").click()
|
||||
text = self.page.text_content("body")
|
||||
assert "hello" not in text
|
||||
|
||||
# currently the test infrastructure doesn't allow to easily assert that
|
||||
# js exceptions were raised this is a workaround but we need a better fix.
|
||||
# Antonio promised to write it
|
||||
assert len(self._page_errors) == 1
|
||||
console_text = self._page_errors
|
||||
assert (
|
||||
"Implicit target not allowed here. Please use display(..., target=...)"
|
||||
in console_text[0].message
|
||||
)
|
||||
self._page_errors = []
|
||||
|
||||
def test_explicit_target_pyscript_tag(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
def display_hello():
|
||||
display('hello', target='second-pyscript-tag')
|
||||
</py-script>
|
||||
<py-script id="second-pyscript-tag">
|
||||
display_hello()
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
text = self.page.locator("id=second-pyscript-tag-2").inner_text()
|
||||
assert "hello" in text
|
||||
|
||||
def test_explicit_target_on_button_tag(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
def display_hello():
|
||||
display('hello', target='my-button')
|
||||
</py-script>
|
||||
<button id="my-button" py-onClick="display_hello()">Click me</button>
|
||||
"""
|
||||
)
|
||||
self.page.locator("text=Click me").click()
|
||||
text = self.page.locator("id=my-button").inner_text()
|
||||
assert "hello" in text
|
||||
|
||||
def test_explicit_different_target_from_call(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script id="first-pyscript-tag">
|
||||
def display_hello():
|
||||
display('hello', target='second-pyscript-tag')
|
||||
</py-script>
|
||||
<py-script id="second-pyscript-tag">
|
||||
print('nothing to see here')
|
||||
</py-script>
|
||||
<py-script>
|
||||
display_hello()
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
text = self.page.locator("id=second-pyscript-tag").all_inner_texts()
|
||||
assert "hello" in text
|
||||
|
||||
def test_append_true(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
display('hello world', append=True)
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_html = self.page.content()
|
||||
pattern = r'<div id="py-.*">hello world</div>'
|
||||
assert re.search(pattern, inner_html)
|
||||
|
||||
def test_append_false(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
display('hello world', append=False)
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_html = self.page.content()
|
||||
pattern = r'<py-script id="py-.*">hello world</py-script>'
|
||||
assert re.search(pattern, inner_html)
|
||||
|
||||
def test_display_multiple_values(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
hello = 'hello'
|
||||
world = 'world'
|
||||
display(hello, world)
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_text = self.page.inner_text("html")
|
||||
assert inner_text == "hello\nworld"
|
||||
|
||||
def test_display_list_dict_tuple(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
l = ['A', 1, '!']
|
||||
d = {'B': 2, 'List': l}
|
||||
t = ('C', 3, '!')
|
||||
display(l, d, t)
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_text = self.page.inner_text("html")
|
||||
print(inner_text)
|
||||
assert (
|
||||
inner_text
|
||||
== "['A', 1, '!']\n{'B': 2, 'List': ['A', 1, '!']}\n('C', 3, '!')"
|
||||
)
|
||||
|
||||
def test_image_display(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-config> packages = [ "matplotlib"] </py-config>
|
||||
<py-script>
|
||||
import matplotlib.pyplot as plt
|
||||
xpoints = [3, 6, 9]
|
||||
ypoints = [1, 2, 3]
|
||||
plt.plot(xpoints, ypoints)
|
||||
plt.show()
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_html = self.page.content()
|
||||
pattern = r'<style id="matplotlib-figure-styles">'
|
||||
assert re.search(pattern, inner_html)
|
||||
|
||||
def test_empty_HTML_and_console_output(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
print('print from python')
|
||||
console.log('print from js')
|
||||
console.error('error from js');
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_html = self.page.content()
|
||||
assert re.search("", inner_html)
|
||||
console_text = self.console.all.lines
|
||||
assert "print from python" in console_text
|
||||
assert "print from js" in console_text
|
||||
assert "error from js" in console_text
|
||||
|
||||
def test_text_HTML_and_console_output(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
display('0')
|
||||
print('print from python')
|
||||
console.log('print from js')
|
||||
console.error('error from js');
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_text = self.page.inner_text("html")
|
||||
assert "0" == inner_text
|
||||
console_text = self.console.all.lines
|
||||
assert "print from python" in console_text
|
||||
assert "print from js" in console_text
|
||||
assert "error from js" in console_text
|
||||
|
||||
def test_console_line_break(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
print('1print\\n2print')
|
||||
print('1console\\n2console')
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
console_text = self.console.all.lines
|
||||
assert console_text.index("1print") == (console_text.index("2print") - 1)
|
||||
assert console_text.index("1console") == (console_text.index("2console") - 1)
|
||||
75
pyscriptjs/tests/integration/test_03_async.py
Normal file
75
pyscriptjs/tests/integration/test_03_async.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import re
|
||||
|
||||
from .support import PyScriptTest
|
||||
|
||||
|
||||
class TestAsync(PyScriptTest):
|
||||
def test_multiple_async(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script>
|
||||
import js
|
||||
import asyncio
|
||||
for i in range(3):
|
||||
js.console.log('A', i)
|
||||
await asyncio.sleep(0.1)
|
||||
</py-script>
|
||||
|
||||
<py-script>
|
||||
import js
|
||||
import asyncio
|
||||
for i in range(3):
|
||||
js.console.log('B', i)
|
||||
await asyncio.sleep(0.1)
|
||||
js.console.log("async tadone")
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
self.wait_for_console("async tadone")
|
||||
assert self.console.log.lines == [
|
||||
"Python initialization complete",
|
||||
"A 0",
|
||||
"B 0",
|
||||
"A 1",
|
||||
"B 1",
|
||||
"A 2",
|
||||
"B 2",
|
||||
"async tadone",
|
||||
]
|
||||
|
||||
def test_multiple_async_display(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script id="py1">
|
||||
def say_hello():
|
||||
display('hello')
|
||||
</py-script>
|
||||
<py-script id="py2">
|
||||
say_hello()
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_html = self.page.content()
|
||||
pattern = r'<div id="py2-2">hello</div>'
|
||||
assert re.search(pattern, inner_html)
|
||||
|
||||
def test_multiple_async_multiple_display(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-script id='pyA'>
|
||||
import asyncio
|
||||
for i in range(2):
|
||||
display('A')
|
||||
await asyncio.sleep(0)
|
||||
</py-script>
|
||||
|
||||
<py-script id='pyB'>
|
||||
import asyncio
|
||||
for i in range(2):
|
||||
display('B')
|
||||
await asyncio.sleep(0)
|
||||
</py-script>
|
||||
"""
|
||||
)
|
||||
inner_text = self.page.inner_text("html")
|
||||
assert "A\nB\nA\nB" in inner_text
|
||||
@@ -39,8 +39,8 @@ class TestStyle(PyScriptTest):
|
||||
<py-config>
|
||||
name = "foo"
|
||||
</py-config>
|
||||
<py-script>print("hello")</py-script>
|
||||
<py-repl>print("hello")</py-repl>
|
||||
<py-script>display("hello")</py-script>
|
||||
<py-repl>display("hello")</py-repl>
|
||||
<py-title>hello</py-title>
|
||||
<py-inputbox label="my input">
|
||||
import js
|
||||
|
||||
@@ -95,7 +95,7 @@ class TestConfig(PyScriptTest):
|
||||
import sys, js
|
||||
pyodide_version = sys.modules["pyodide"].__version__
|
||||
js.console.log("version", pyodide_version)
|
||||
pyodide_version
|
||||
display(pyodide_version)
|
||||
</py-script>
|
||||
""",
|
||||
)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from .support import PyScriptTest
|
||||
|
||||
|
||||
@@ -20,15 +23,15 @@ class TestPyRepl(PyScriptTest):
|
||||
"""
|
||||
)
|
||||
|
||||
self.page.locator("py-repl").type("print(2+2)")
|
||||
self.page.locator("py-repl").type('display("hello")')
|
||||
|
||||
# We only have one button in the page
|
||||
self.page.locator("button").click()
|
||||
|
||||
# The result gets the id of the repl + n
|
||||
repl_result = self.page.wait_for_selector("#my-repl-1", state="attached")
|
||||
repl_result = self.page.wait_for_selector("#my-repl-2", state="attached")
|
||||
|
||||
assert repl_result.inner_text() == "4"
|
||||
assert repl_result.inner_text() == "hello"
|
||||
|
||||
def test_repl_runs_with_shift_enter(self):
|
||||
self.pyscript_run(
|
||||
@@ -36,10 +39,77 @@ class TestPyRepl(PyScriptTest):
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
"""
|
||||
)
|
||||
self.page.locator("py-repl").type("2+2")
|
||||
self.page.locator("py-repl").type('display("hello")')
|
||||
|
||||
# Confirm that we get a result by using the keys shortcut
|
||||
self.page.keyboard.press("Shift+Enter")
|
||||
py_repl = self.page.query_selector("#my-repl-2")
|
||||
assert "hello" in py_repl.inner_text()
|
||||
|
||||
def test_repl_console_ouput(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
"""
|
||||
)
|
||||
self.page.locator("py-repl").type("print('apple')")
|
||||
self.page.keyboard.press("Enter")
|
||||
self.page.locator("py-repl").type("console.log('banana')")
|
||||
self.page.locator("button").click()
|
||||
|
||||
# The result gets the id of the repl + n
|
||||
repl_result = self.page.wait_for_selector("#my-repl-1", state="attached")
|
||||
|
||||
assert repl_result.text_content() == "4"
|
||||
assert repl_result.inner_text() == ""
|
||||
|
||||
def test_repl_error_ouput(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
"""
|
||||
)
|
||||
self.page.locator("py-repl").type("this is an error")
|
||||
self.page.locator("button").click()
|
||||
expect(self.page.locator(".py-error")).to_be_visible()
|
||||
|
||||
# console errors are observable on the headed instance
|
||||
# but is just not possible to access them using the self object
|
||||
@pytest.mark.xfail(reason="Cannot access console errors")
|
||||
def test_repl_error_ouput_console(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
"""
|
||||
)
|
||||
self.page.locator("py-repl").type("this is an error")
|
||||
self.page.locator("button").click()
|
||||
|
||||
def test_repl_error_and_fail_moving_forward_ouput(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
"""
|
||||
)
|
||||
self.page.locator("py-repl").type("this is an error")
|
||||
self.page.locator("button").click()
|
||||
expect(self.page.locator(".py-error")).to_be_visible()
|
||||
self.page.keyboard.press("Shift+Enter")
|
||||
expect(self.page.locator(".py-error")).to_be_visible()
|
||||
|
||||
# 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
|
||||
def test_repl_show_error_fix_error_check_for_ouput(self):
|
||||
self.pyscript_run(
|
||||
"""
|
||||
<py-repl id="my-repl" auto-generate="true"> </py-repl>
|
||||
"""
|
||||
)
|
||||
self.page.locator("py-repl").type("d")
|
||||
self.page.keyboard.press("Shift+Enter")
|
||||
expect(self.page.locator(".py-error")).to_be_visible()
|
||||
self.page.keyboard.press("Backspace")
|
||||
self.page.locator("py-repl").type("display('ok')")
|
||||
self.page.keyboard.press("Shift+Enter")
|
||||
repl_result = self.page.wait_for_selector("#my-repl-2", state="attached")
|
||||
assert repl_result.inner_text() == "ok"
|
||||
|
||||
@@ -258,20 +258,18 @@ class TestExamples(PyScriptTest):
|
||||
assert self.page.title() == "REPL"
|
||||
wait_for_render(self.page, "*", "<py-repl.*?>")
|
||||
|
||||
self.page.locator("py-repl").type("print('Hello, World!')")
|
||||
self.page.locator("py-repl").type("display('Hello, World!')")
|
||||
self.page.locator("button").click()
|
||||
|
||||
assert self.page.locator("#my-repl-1").text_content() == "Hello, World!"
|
||||
assert self.page.locator("#my-repl-2").text_content() == "Hello, World!"
|
||||
|
||||
# Confirm that using the second repl still works properly
|
||||
self.page.locator("#my-repl-2").type("2*2")
|
||||
self.page.locator("#my-repl-2").type("display(2*2)")
|
||||
self.page.keyboard.press("Shift+Enter")
|
||||
# Make sure that the child of the second repl is attached properly
|
||||
# before looking into the text_content
|
||||
second_repl_result = self.page.wait_for_selector(
|
||||
"#my-repl-2-2", state="attached"
|
||||
)
|
||||
assert second_repl_result.text_content() == "4"
|
||||
assert self.page.wait_for_selector("#my-repl-2-1", state="attached")
|
||||
assert self.page.locator("#my-repl-2-1").text_content() == "4"
|
||||
|
||||
def test_repl2(self):
|
||||
self.goto("examples/repl2.html")
|
||||
@@ -279,7 +277,7 @@ class TestExamples(PyScriptTest):
|
||||
assert self.page.title() == "Custom REPL Example"
|
||||
wait_for_render(self.page, "*", "<py-repl.*?>")
|
||||
# confirm we can import utils and run one command
|
||||
self.page.locator("py-repl").type("import utils\nutils.now()")
|
||||
self.page.locator("py-repl").type("import utils\ndisplay(utils.now())")
|
||||
self.page.locator("button").click()
|
||||
# Make sure the output is in the page
|
||||
self.page.wait_for_selector("#output")
|
||||
|
||||
Reference in New Issue
Block a user