Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/JavaScript/_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ def of(cls, *values):

@classmethod
def from_(cls, source, mapFn=undefined):
if isinstance(source, str):
from ._string import String
source = String(source)
transform = callback(mapFn) if mapFn is not undefined else None
result = cls()
for index, value in enumerate(source):
Expand Down
5 changes: 4 additions & 1 deletion src/JavaScript/_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ._values import to_number, undefined


class Math:
class _Math:
PI = math.pi
E = math.e
LN2 = math.log(2)
Expand Down Expand Up @@ -104,3 +104,6 @@ def pow(base=undefined, exponent=undefined):
@staticmethod
def random():
return random.random()


Math = _Math()
4 changes: 3 additions & 1 deletion src/JavaScript/_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ def parseInt(value=undefined, radix=undefined):
digits = "0123456789abcdefghijklmnopqrstuvwxyz"[:base]
result = 0
count = 0
for char in text.lower():
for char in text:
if "A" <= char <= "Z":
char = char.lower()
if char not in digits:
break
result = result * base + digits.index(char)
Expand Down
2 changes: 2 additions & 0 deletions src/JavaScript/_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ def fromEntries(entries):
def hasOwn(value, key):
if value is None or value is undefined:
raise TypeError("Cannot convert undefined or null to object")
if isinstance(value, str) and to_string(key) == "length":
return True
method = getattr(type(value), "_js_has_own", None)
if method is not None:
return method(value, to_string(key))
Expand Down
2 changes: 1 addition & 1 deletion src/JavaScript/_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def to_string(value=undefined) -> str:
if value is False:
return "false"
if isinstance(value, str):
return str(value)
return value.encode("utf-16-le", "surrogatepass").decode("utf-16-le", "surrogatepass")
return number_string(value)


Expand Down
5 changes: 4 additions & 1 deletion tests/fixtures/conformance.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const values = [undefined, null, false, true, 0, -0, NaN, Infinity, -Infinity,
cases.conversion = values.map(value => [Boolean(value), Number(value), String(value), typeof value]);
cases.equality = [1 === true, NaN === NaN, 0 === -0, Object.is(0, -0),
Object.is(NaN, NaN), {} === {}, "😀" === "\ud83d\ude00"];
cases.namespace = [typeof Math, typeof JSON, typeof Number, Object.hasOwn("😀", "length")];
const unicodeObject = {"😀": 1}; unicodeObject["\ud83d\ude00"] = 2;
cases.unicodeKeys = [Object.keys(unicodeObject), unicodeObject["😀"], Array.from("\ud83d\ude00")];
const obj = {b: 1, 10: 2, 2: 3, "01": 4, 0: 5, "4294967295": 6};
cases.objectOrder = Object.entries(obj);
delete obj.b; obj.b = 7;
Expand Down Expand Up @@ -78,7 +81,7 @@ cases.stringBoundary = ["abc".lastIndexOf("", 0), "abc".lastIndexOf("", -1), "ab
const nums = [undefined, null, true, "1", NaN, Infinity, -0, 3.5, 3, 2**53 - 1, 2**53];
cases.numberPredicates = nums.map(value => [Number.isNaN(value), Number.isFinite(value), Number.isInteger(value), Number.isSafeInteger(value)]);
cases.parseInt = ["12px", "0xff", "010", "1e3", "-0", "bad", "0x"].map(value => parseInt(value));
cases.parseRadix = [["ffz", 16], ["11", 2], ["12", 1], ["z", 36], ["0b11", 0]].map(([text, radix]) => parseInt(text, radix));
cases.parseRadix = [["ffz", 16], ["11", 2], ["12", 1], ["z", 36], ["0b11", 0], ["K", 36], ["K", 36]].map(([text, radix]) => parseInt(text, radix));
cases.parseFloat = ["-1.2e2px", "1e+", "0xff", "Infinity!", "bad"].map(value => parseFloat(value));
const rounding = [-Infinity, -2.5, -1.5, -0.5, -0.2, -0, 0, 0.49999999999999994, 0.5, 1.5, Infinity, NaN];
cases.mathRounding = rounding.map(value => [Math.floor(value), Math.ceil(value), Math.trunc(value), Math.round(value), Math.sign(value)]);
Expand Down
6 changes: 5 additions & 1 deletion tests/test_conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ def observations():
"inf", "\x851", "12", Array(), Array.of(1), Array.of(1, 2), Object()]
cases["conversion"] = [[Boolean(value), Number(value), String(value), typeof(value)] for value in values]
cases["equality"] = [strictEqual(1, True), strictEqual(math.nan, math.nan), strictEqual(0, -0.0), Object.is_(0, -0.0), Object.is_(math.nan, math.nan), strictEqual(Object(), Object()), strictEqual("😀", "\ud83d\ude00")]
cases["namespace"] = [typeof(Math), typeof(JSON), typeof(Number), Object.hasOwn("😀", "length")]
unicode_object = Object({"😀": 1})
unicode_object["\ud83d\ude00"] = 2
cases["unicodeKeys"] = [Object.keys(unicode_object), unicode_object["😀"], Array.from_("\ud83d\ude00")]
obj = Object({"b": 1, "10": 2, "2": 3, "01": 4, "0": 5, "4294967295": 6})
cases["objectOrder"] = Object.entries(obj)
del obj.b
Expand Down Expand Up @@ -91,7 +95,7 @@ def visit(value, index, array):
nums = [undefined, None, True, "1", math.nan, math.inf, -0.0, 3.5, 3, 2**53 - 1, 2**53]
cases["numberPredicates"] = [[Number.isNaN(value), Number.isFinite(value), Number.isInteger(value), Number.isSafeInteger(value)] for value in nums]
cases["parseInt"] = [parseInt(value) for value in ["12px", "0xff", "010", "1e3", "-0", "bad", "0x"]]
cases["parseRadix"] = [parseInt(text, radix) for text, radix in [("ffz", 16), ("11", 2), ("12", 1), ("z", 36), ("0b11", 0)]]
cases["parseRadix"] = [parseInt(text, radix) for text, radix in [("ffz", 16), ("11", 2), ("12", 1), ("z", 36), ("0b11", 0), ("K", 36), ("K", 36)]]
cases["parseFloat"] = [parseFloat(value) for value in ["-1.2e2px", "1e+", "0xff", "Infinity!", "bad"]]
rounding = [-math.inf, -2.5, -1.5, -0.5, -0.2, -0.0, 0, 0.49999999999999994, 0.5, 1.5, math.inf, math.nan]
cases["mathRounding"] = [[Math.floor(value), Math.ceil(value), Math.trunc(value), Math.round(value), Math.sign(value)] for value in rounding]
Expand Down
7 changes: 7 additions & 0 deletions tests/test_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ def test_array_and_string_own_properties(self):
self.assertTrue(Object.hasOwn(value, 1))
self.assertEqual(list(Object.keys("😀")), ["0", "1"])
self.assertEqual(list(Object.values("😀")), ["\ud83d", "\ude00"])
self.assertTrue(Object.hasOwn("😀", "length"))

def test_equivalent_utf16_strings_share_one_property_key(self):
value = Object({"😀": 1})
value["\ud83d\ude00"] = 2
self.assertEqual(list(Object.keys(value)), ["😀"])
self.assertEqual(value["😀"], 2)

def test_null_errors_and_same_value(self):
for value in (None, undefined):
Expand Down
10 changes: 9 additions & 1 deletion tests/test_primitives.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import math
import unittest

from JavaScript import Array, Boolean, Math, Number, String, isFinite, parseFloat, parseInt, strictEqual, undefined
from JavaScript import Array, Boolean, Math, Number, String, isFinite, parseFloat, parseInt, strictEqual, typeof, undefined


class StringTests(unittest.TestCase):
Expand All @@ -20,6 +20,7 @@ def test_conversion_and_utf16_indexing(self):
self.assertIs(value.codePointAt(4), undefined)
self.assertEqual(list(value), ["a", "😀", "b"])
self.assertEqual(list(Array.from_(value)), ["a", "😀", "b"])
self.assertEqual(list(Array.from_("\ud83d\ude00")), ["😀"])
self.assertTrue(strictEqual("😀", "\ud83d\ude00"))
self.assertEqual(String(), "")
self.assertEqual(String(undefined), "undefined")
Expand Down Expand Up @@ -87,6 +88,8 @@ def test_prefix_parsers(self):
self.assertEqual(Number.parseInt("11", 2), 3)
self.assertTrue(math.isnan(parseInt("12", 1)))
self.assertTrue(math.isnan(parseInt("0x")))
self.assertTrue(math.isnan(parseInt("K", 36)))
self.assertEqual(parseInt("K", 36), 20)
self.assertEqual(math.copysign(1, parseInt("-0")), -1)
self.assertEqual(parseFloat("-1.2e2px"), -120)
self.assertEqual(parseFloat("1e+"), 1)
Expand All @@ -96,6 +99,11 @@ def test_prefix_parsers(self):


class MathTests(unittest.TestCase):
def test_math_is_a_noncallable_namespace(self):
self.assertEqual(typeof(Math), "object")
with self.assertRaises(TypeError):
Math()

def test_rounding_and_negative_zero(self):
self.assertEqual(Math.round(2.5), 3)
self.assertEqual(Math.round(-1.5), -1)
Expand Down
Loading