diff --git a/docs/project/changelog.md b/docs/project/changelog.md index a779aba39..f3dbb3241 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -102,6 +102,11 @@ substitutions: `ERRNO_CODES` APIs. {pr}`2582` +- {{ Fix }} The `bool` operator on a `JsProxy` now behaves more consistently: it + returns `False` if JavaScript would say that `!!x` is `false`, or if `x` is an + empty container. Otherwise it returns `True`. + {pr}`2803` + ### REPL - {{ Enhancement }} Add a spinner while the REPL is loading diff --git a/src/core/hiwire.c b/src/core/hiwire.c index 70da45cc0..84dbd7f99 100644 --- a/src/core/hiwire.c +++ b/src/core/hiwire.c @@ -724,11 +724,20 @@ EM_JS_BOOL(bool, hiwire_get_bool, (JsRef idobj), { if (!val) { return false; } + // We want to return false on container types with size 0. if (val.size === 0) { - // I think things with a size are all container types. + if(/HTML[A-Za-z]*Element/.test(Object.prototype.toString.call(val))){ + // HTMLSelectElement and HTMLInputElement can have size 0 but we still + // want to return true. + return true; + } + // I think other things with a size are container types. return false; } - if (Array.isArray(val) && val.length === 0) { + if (val.length === 0 && JsArray_Check(idobj)) { + return false; + } + if (val.byteLength === 0) { return false; } return true; diff --git a/src/tests/test_jsproxy.py b/src/tests/test_jsproxy.py index 3e7075e7c..a318fca6b 100644 --- a/src/tests/test_jsproxy.py +++ b/src/tests/test_jsproxy.py @@ -87,15 +87,66 @@ def test_jsproxy_document(selenium): from js import document el = document.createElement("div") + assert el.tagName == "DIV" + assert bool(el) + assert not document.body.children document.body.appendChild(el) + assert document.body.children assert document.body.children.length == 1 - assert document.body.children[0].tagName == "DIV" + assert document.body.children[0] == el assert repr(document) == "[object HTMLDocument]" - el = document.createElement("div") assert len(dir(el)) >= 200 assert "appendChild" in dir(el) +@pytest.mark.parametrize( + "js,result", + [ + ("{}", False), + ("{a:1}", True), + ("[]", False), + ("[1]", True), + ("new Map()", False), + ("new Map([[0, 0]])", True), + ("new Set()", False), + ("new Set([0])", True), + ("class T {}; T", True), + ("class T {}; new T()", True), + ("new Uint8Array(0)", False), + ("new Uint8Array(1)", True), + ("new ArrayBuffer(0)", False), + ("new ArrayBuffer(1)", True), + ], +) +@run_in_pyodide +def test_jsproxy_bool(selenium, js, result): + from pyodide.code import run_js + + assert bool(run_js(js)) == result + + +@pytest.mark.xfail_browsers(node="No document in node") +@pytest.mark.parametrize( + "js,result", + [ + ("document.createElement('div')", True), + ("document.createElement('select')", True), + ("document.createElement('p')", True), + ("document.createElement('style')", True), + ("document.createElement('ul')", True), + ("document.createElement('ul').style", True), + ("document.querySelectorAll('x')", False), + ("document.querySelectorAll('body')", True), + ("document.all", False), + ], +) +@run_in_pyodide +def test_jsproxy_bool_html(selenium, js, result): + from pyodide.code import run_js + + assert bool(run_js(js)) == result + + @pytest.mark.xfail_browsers(node="No ImageData in node") @run_in_pyodide def test_jsproxy_imagedata(selenium):