use super::eval;
use tsrun::JsValue;
use tsrun::value::JsString;
#[test]
fn test_object_literal_quoted_keys() {
assert_eq!(
eval(r#"var obj = {"a": 1}; obj.a"#),
JsValue::Number(1.0),
"Quoted string key should be accessible via dot notation"
);
assert_eq!(
eval(r#"var obj = {"a": 1}; obj["a"]"#),
JsValue::Number(1.0),
"Quoted string key should be accessible via bracket notation"
);
}
#[test]
fn test_object_string_numeric_keys() {
assert_eq!(
eval(r#"var obj = {"1": "x"}; obj["1"]"#),
JsValue::String(JsString::from("x"))
);
assert_eq!(
eval(r#"var obj = {0: 1, "1": "x", o: {}}; obj["1"]"#),
JsValue::String(JsString::from("x"))
);
assert_eq!(
eval(r#"var obj = {0: 1, "1": "x", o: {}}; obj[0]"#),
JsValue::Number(1.0)
);
assert_eq!(
eval(r#"var obj = {0: 1, "1": "x", o: {}}; typeof obj.o"#),
JsValue::String(JsString::from("object"))
);
}
#[test]
fn test_object() {
assert_eq!(
eval("const obj: { a: number } = { a: 1 }; obj.a"),
JsValue::Number(1.0)
);
}
#[test]
fn test_object_hasownproperty() {
assert_eq!(
eval("({a: 1} as { a: number }).hasOwnProperty('a')"),
JsValue::Boolean(true)
);
assert_eq!(
eval("({a: 1} as { a: number }).hasOwnProperty('b')"),
JsValue::Boolean(false)
);
assert_eq!(
eval("let o: { x: number } = {x: 1}; o.hasOwnProperty('x')"),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_tostring() {
assert_eq!(
eval("({} as object).toString()"),
JsValue::String(JsString::from("[object Object]"))
);
assert_eq!(
eval("([1,2,3] as number[]).toString()"),
JsValue::String(JsString::from("1,2,3"))
);
}
#[test]
fn test_object_tolocalestring() {
assert_eq!(
eval("({} as object).toLocaleString()"),
JsValue::String(JsString::from("[object Object]"))
);
assert_eq!(
eval("Object(null).toLocaleString()"),
JsValue::String(JsString::from("[object Object]"))
);
}
#[test]
fn test_object_prototype_tostring_call() {
assert_eq!(
eval("Object.prototype.toString.call([])"),
JsValue::String(JsString::from("[object Array]"))
);
assert_eq!(
eval("Object.prototype.toString.call({})"),
JsValue::String(JsString::from("[object Object]"))
);
assert_eq!(
eval("Object.prototype.toString.call(function() {})"),
JsValue::String(JsString::from("[object Function]"))
);
assert_eq!(
eval("Object.prototype.toString.call(null)"),
JsValue::String(JsString::from("[object Null]"))
);
assert_eq!(
eval("Object.prototype.toString.call(undefined)"),
JsValue::String(JsString::from("[object Undefined]"))
);
assert_eq!(
eval("Object.prototype.toString.call(42)"),
JsValue::String(JsString::from("[object Number]"))
);
assert_eq!(
eval("Object.prototype.toString.call('hello')"),
JsValue::String(JsString::from("[object String]"))
);
assert_eq!(
eval("Object.prototype.toString.call(true)"),
JsValue::String(JsString::from("[object Boolean]"))
);
assert_eq!(
eval("Object.prototype.toString.call(new Date())"),
JsValue::String(JsString::from("[object Date]"))
);
assert_eq!(
eval("Object.prototype.toString.call(/test/)"),
JsValue::String(JsString::from("[object RegExp]"))
);
assert_eq!(
eval("Object.prototype.toString.call(new Map())"),
JsValue::String(JsString::from("[object Map]"))
);
assert_eq!(
eval("Object.prototype.toString.call(new Set())"),
JsValue::String(JsString::from("[object Set]"))
);
assert_eq!(
eval("Object.prototype.toString.call(Object(true))"),
JsValue::String(JsString::from("[object Boolean]"))
);
assert_eq!(
eval("Object.prototype.toString.call(Object(42))"),
JsValue::String(JsString::from("[object Number]"))
);
assert_eq!(
eval("Object.prototype.toString.call(Object('hello'))"),
JsValue::String(JsString::from("[object String]"))
);
}
#[test]
fn test_object_fromentries() {
assert_eq!(
eval(
"const entries: Array<[string, number]> = [['a', 1], ['b', 2]]; Object.fromEntries(entries).a"
),
JsValue::Number(1.0)
);
assert_eq!(
eval(
"const entries: Array<[string, number]> = [['a', 1], ['b', 2]]; Object.fromEntries(entries).b"
),
JsValue::Number(2.0)
);
}
#[test]
fn test_object_hasown() {
assert_eq!(
eval("Object.hasOwn({a: 1} as { a: number }, 'a')"),
JsValue::Boolean(true)
);
assert_eq!(
eval("Object.hasOwn({a: 1} as { a: number }, 'b')"),
JsValue::Boolean(false)
);
}
#[test]
fn test_object_create_null_prototype() {
assert_eq!(
eval("Object.create(null).hasOwnProperty"),
JsValue::Undefined
);
assert_eq!(eval("Object.create(null).toString"), JsValue::Undefined);
}
#[test]
fn test_object_create_with_prototype() {
assert_eq!(
eval("let proto: { x: number } = {x: 1}; let o = Object.create(proto); o.x"),
JsValue::Number(1.0)
);
assert_eq!(
eval(
r#"
const proto = { a: 1, b: 2 };
const obj = Object.create(proto);
obj.a + obj.b
"#
),
JsValue::Number(3.0)
);
}
#[test]
fn test_object_create_with_properties_basic() {
assert_eq!(
eval(
r#"
const obj = Object.create(null, {
x: { value: 42 }
});
obj.x
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_create_with_properties_multiple() {
assert_eq!(
eval(
r#"
const obj = Object.create(null, {
x: { value: 10 },
y: { value: 20 },
z: { value: 30 }
});
obj.x + obj.y + obj.z
"#
),
JsValue::Number(60.0)
);
}
#[test]
fn test_object_create_with_properties_writable() {
assert_eq!(
eval(
r#"
const obj: any = Object.create(null, {
x: { value: 1, writable: true }
});
obj.x = 42;
obj.x
"#
),
JsValue::Number(42.0)
);
assert_eq!(
eval(
r#"
const obj: any = Object.create(null, {
x: { value: 1, writable: false }
});
try {
obj.x = 42;
"no error"
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error"
}
"#
),
JsValue::String(JsString::from("TypeError"))
);
}
#[test]
fn test_object_create_with_properties_enumerable() {
assert_eq!(
eval(
r#"
const obj = Object.create(null, {
a: { value: 1, enumerable: true },
b: { value: 2, enumerable: false }
});
Object.keys(obj).length
"#
),
JsValue::Number(1.0)
);
assert_eq!(
eval(
r#"
const obj = Object.create(null, {
a: { value: 1, enumerable: true },
b: { value: 2, enumerable: false }
});
Object.keys(obj)[0]
"#
),
JsValue::String("a".into())
);
}
#[test]
fn test_object_create_with_properties_configurable() {
assert_eq!(
eval(
r#"
const obj: any = Object.create(null, {
x: { value: 42, configurable: true }
});
delete obj.x;
obj.x
"#
),
JsValue::Undefined
);
assert_eq!(
eval(
r#"
const obj: any = Object.create(null, {
x: { value: 42, configurable: false }
});
try {
delete obj.x;
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other";
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_object_create_with_properties_all_attributes() {
assert_eq!(
eval(
r#"
const obj: any = Object.create(null, {
x: { value: 10, writable: true, enumerable: true, configurable: true }
});
obj.x = 20;
[obj.x, Object.keys(obj).length].join(',')
"#
),
JsValue::String("20,1".into())
);
}
#[test]
fn test_object_create_with_prototype_and_properties() {
assert_eq!(
eval(
r#"
const proto = { inherited: 100 };
const obj: any = Object.create(proto, {
own: { value: 42 }
});
obj.inherited + obj.own
"#
),
JsValue::Number(142.0)
);
}
#[test]
fn test_object_create_own_vs_inherited() {
assert_eq!(
eval(
r#"
const proto = { inherited: 1 };
const obj: any = Object.create(proto, {
own: { value: 2 }
});
[obj.hasOwnProperty('own'), obj.hasOwnProperty('inherited')].join(',')
"#
),
JsValue::String("true,false".into())
);
}
#[test]
fn test_object_create_property_descriptor_accessor() {
assert_eq!(
eval(
r#"
let counter = 0;
const obj: any = Object.create(null, {
x: {
get: function() { return ++counter; }
}
});
[obj.x, obj.x, obj.x].join(',')
"#
),
JsValue::String("1,2,3".into())
);
}
#[test]
fn test_object_create_property_descriptor_setter() {
assert_eq!(
eval(
r#"
let stored = 0;
const obj: any = Object.create(null, {
x: {
get: function() { return stored; },
set: function(v: number) { stored = v * 2; }
}
});
obj.x = 21;
obj.x
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_create_object_prototype() {
assert_eq!(
eval(
r#"
const obj = Object.create(Object.prototype, {
x: { value: 42 }
});
typeof obj.hasOwnProperty
"#
),
JsValue::String("function".into())
);
}
#[test]
fn test_object_create_undefined_properties_ignored() {
assert_eq!(
eval(
r#"
const obj = Object.create({ x: 1 }, undefined);
obj.x
"#
),
JsValue::Number(1.0)
);
}
#[test]
fn test_object_create_returns_new_object() {
assert_eq!(
eval(
r#"
const proto = { x: 1 };
const obj = Object.create(proto, {
y: { value: 2 }
});
[proto.hasOwnProperty('y'), obj.hasOwnProperty('y')].join(',')
"#
),
JsValue::String("false,true".into())
);
}
#[test]
fn test_object_create_throws_on_invalid_prototype() {
assert_eq!(
eval(
r#"
try {
Object.create(42 as any);
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other";
}
"#
),
JsValue::String("TypeError".into())
);
assert_eq!(
eval(
r#"
try {
Object.create("string" as any);
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other";
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_object_create_with_symbol_properties() {
assert_eq!(
eval(
r#"
const sym = Symbol('test');
const descriptors: any = {};
descriptors[sym] = { value: 42 };
const obj: any = Object.create(null, descriptors);
obj[sym]
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_create_getownpropertydescriptor() {
assert_eq!(
eval(
r#"
const obj = Object.create(null, {
x: { value: 42, writable: true, enumerable: true, configurable: true }
});
const desc = Object.getOwnPropertyDescriptor(obj, 'x');
[desc.value, desc.writable, desc.enumerable, desc.configurable].join(',')
"#
),
JsValue::String("42,true,true,true".into())
);
}
#[test]
fn test_object_create_default_attributes() {
assert_eq!(
eval(
r#"
const obj = Object.create(null, {
x: { value: 42 }
});
const desc = Object.getOwnPropertyDescriptor(obj, 'x');
[desc.writable, desc.enumerable, desc.configurable].join(',')
"#
),
JsValue::String("false,false,false".into())
);
}
#[test]
fn test_object_create_with_constructor_prototype() {
assert_eq!(
eval(
r#"
function base() {}
var b = new base();
var d = Object.create(b, {
"x": {
value: true,
writable: false
},
"y": {
value: "str",
writable: false
}
});
[String(d.x), d.y].join(',')
"#
),
JsValue::String("true,str".into())
);
}
#[test]
fn test_object_create_with_object_prototype() {
assert_eq!(
eval(
r#"
var newObj = Object.create({}, {
prop: {
value: "ownDataProperty"
}
});
[newObj.hasOwnProperty('prop'), newObj.prop].join(',')
"#
),
JsValue::String("true,ownDataProperty".into())
);
}
#[test]
fn test_object_freeze() {
assert_eq!(
eval(
r#"
let o: { a: number } = {a: 1};
Object.freeze(o);
try {
o.a = 2;
"no error"
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error"
}
"#
),
JsValue::String(JsString::from("TypeError"))
);
assert_eq!(
eval("Object.isFrozen(Object.freeze({a: 1} as { a: number }))"),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_seal() {
assert_eq!(
eval("Object.isSealed(Object.seal({a: 1} as { a: number }))"),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_get_own_property_descriptor() {
assert_eq!(
eval(
r#"
const obj = { x: 42 };
const desc = Object.getOwnPropertyDescriptor(obj, 'x');
desc.value
"#
),
JsValue::Number(42.0)
);
assert_eq!(
eval(
r#"
const obj = { x: 42 };
const desc = Object.getOwnPropertyDescriptor(obj, 'x');
desc.writable
"#
),
JsValue::Boolean(true)
);
assert_eq!(
eval(
r#"
const obj = { x: 42 };
const desc = Object.getOwnPropertyDescriptor(obj, 'x');
desc.enumerable
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_get_own_property_descriptor_primitives() {
assert_eq!(
eval(r#"Object.getOwnPropertyDescriptor(42, "foo")"#),
JsValue::Undefined
);
assert_eq!(
eval(r#"Object.getOwnPropertyDescriptor(true, "bar")"#),
JsValue::Undefined
);
}
#[test]
fn test_object_get_own_property_descriptor_null_undefined_throws() {
assert_eq!(
eval(
r#"
try {
Object.getOwnPropertyDescriptor(null, "x");
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
assert_eq!(
eval(
r#"
try {
Object.getOwnPropertyDescriptor(undefined, "x");
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_object_define_property() {
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 10, writable: true });
obj.x
"#
),
JsValue::Number(10.0)
);
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 10, writable: false });
try {
obj.x = 20;
"no error"
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error"
}
"#
),
JsValue::String(JsString::from("TypeError"))
);
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 10, writable: false });
obj.x
"#
),
JsValue::Number(10.0)
);
}
#[test]
fn test_object_get_prototype_of() {
assert_eq!(
eval(
r#"
const arr: number[] = [1, 2, 3];
Object.getPrototypeOf(arr) === Array.prototype
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_spread_symbol_properties() {
assert_eq!(
eval(
r#"
const sym = Symbol('test');
const o: any = {};
o[sym] = 42;
const copy = {...o};
copy[sym]
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_spread_symbol_has_own_property() {
assert_eq!(
eval(
r#"
const sym = Symbol('test');
const o: any = {};
o[sym] = 1;
const copy = {...o};
Object.prototype.hasOwnProperty.call(copy, sym)
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_spread_mixed_properties() {
assert_eq!(
eval(
r#"
const sym = Symbol('test');
const o: any = { a: 1 };
o[sym] = 2;
const copy = {...o, b: 3};
[copy.a, copy[sym], copy.b].join(',')
"#
),
JsValue::String("1,2,3".into())
);
}
#[test]
fn test_object_prototype_is_prototype_of_basic() {
assert_eq!(
eval("Object.prototype.isPrototypeOf({})"),
JsValue::Boolean(true)
);
assert_eq!(
eval("Object.prototype.isPrototypeOf([])"),
JsValue::Boolean(true)
);
assert_eq!(
eval("Object.prototype.isPrototypeOf(function() {})"),
JsValue::Boolean(true)
);
}
#[test]
fn test_array_prototype_is_prototype_of() {
assert_eq!(
eval("Array.prototype.isPrototypeOf([1, 2, 3])"),
JsValue::Boolean(true)
);
assert_eq!(
eval("Array.prototype.isPrototypeOf({})"),
JsValue::Boolean(false)
);
}
#[test]
fn test_is_prototype_of_custom_chain() {
assert_eq!(
eval(
r#"
const parent: { x: number } = { x: 1 };
const child = Object.create(parent);
parent.isPrototypeOf(child)
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_is_prototype_of_primitives() {
assert_eq!(
eval("Object.prototype.isPrototypeOf(42)"),
JsValue::Boolean(false)
);
assert_eq!(
eval("Object.prototype.isPrototypeOf('hello')"),
JsValue::Boolean(false)
);
assert_eq!(
eval("Object.prototype.isPrototypeOf(null)"),
JsValue::Boolean(false)
);
assert_eq!(
eval("Object.prototype.isPrototypeOf(undefined)"),
JsValue::Boolean(false)
);
}
#[test]
fn test_is_prototype_of_class_inheritance() {
assert_eq!(
eval(
r#"
class Parent {}
class Child extends Parent {}
const c = new Child();
Parent.prototype.isPrototypeOf(c)
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_get_own_property_names() {
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2 };
Object.getOwnPropertyNames(obj).length
"#
),
JsValue::Number(2.0)
);
}
#[test]
fn test_object_define_properties_basic() {
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperties(obj, {
x: { value: 10, writable: true },
y: { value: 20, writable: true }
});
obj.x + obj.y
"#
),
JsValue::Number(30.0)
);
}
#[test]
fn test_object_define_properties_returns_object() {
assert_eq!(
eval(
r#"
const obj: any = {};
const result = Object.defineProperties(obj, {
x: { value: 10 }
});
result === obj
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_define_properties_attributes() {
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperties(obj, {
x: { value: 10, writable: false }
});
try {
obj.x = 20;
"no error"
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error"
}
"#
),
JsValue::String(JsString::from("TypeError"))
);
}
#[test]
fn test_object_define_properties_enumerable() {
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperties(obj, {
a: { value: 1, enumerable: true },
b: { value: 2, enumerable: false }
});
Object.keys(obj).length
"#
),
JsValue::Number(1.0)
);
}
#[test]
fn test_proto_get() {
assert_eq!(
eval(
r#"
const parent: { x: number } = { x: 42 };
const child: any = Object.create(parent);
child.__proto__.x
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_proto_set() {
assert_eq!(
eval(
r#"
const parent: { x: number } = { x: 42 };
const child: { x?: number } = {};
child.__proto__ = parent;
child.x
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_proto_null() {
assert_eq!(
eval(
r#"
const obj: any = {};
obj.__proto__ = null;
obj.__proto__
"#
),
JsValue::Null
);
}
#[test]
fn test_proto_in_literal() {
assert_eq!(
eval(
r#"
const parent: { x: number } = { x: 42 };
const child: any = { __proto__: parent, y: 1 };
child.x
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_keys_has_array_methods() {
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2, c: 3 };
Object.keys(obj).map(k => k.toUpperCase()).length
"#
),
JsValue::Number(3.0)
);
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2 };
Object.keys(obj).filter(k => k === "a").length
"#
),
JsValue::Number(1.0)
);
let result = eval(
r#"
const obj = { a: 1, b: 2 };
Object.keys(obj).sort().join("-")
"#,
);
assert_eq!(result, JsValue::String(JsString::from("a-b")));
}
#[test]
fn test_object_values_has_array_methods() {
assert_eq!(
eval(
r#"
const obj = { a: 10, b: 20, c: 30 };
Object.values(obj).reduce((sum, v) => sum + v, 0)
"#
),
JsValue::Number(60.0)
);
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2, c: 3 };
Object.values(obj).map(v => v * 2).length
"#
),
JsValue::Number(3.0)
);
}
#[test]
fn test_object_entries_has_array_methods() {
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2 };
Object.entries(obj).map(([k, v]) => k + ":" + v).length
"#
),
JsValue::Number(2.0)
);
assert_eq!(
eval(
r#"
const obj = { x: 10, y: 20 };
Object.entries(obj).filter(([k, v]) => v > 15).length
"#
),
JsValue::Number(1.0)
);
}
#[test]
fn test_object_entries_inner_arrays_have_methods() {
assert_eq!(
eval(
r#"
const obj = { a: 1 };
const entries = Object.entries(obj);
entries[0].join("=")
"#
),
JsValue::String(JsString::from("a=1"))
);
}
#[test]
fn test_object_is_basic() {
assert_eq!(eval("Object.is(1, 1)"), JsValue::Boolean(true));
assert_eq!(eval("Object.is('foo', 'foo')"), JsValue::Boolean(true));
assert_eq!(eval("Object.is(true, true)"), JsValue::Boolean(true));
assert_eq!(eval("Object.is(null, null)"), JsValue::Boolean(true));
assert_eq!(
eval("Object.is(undefined, undefined)"),
JsValue::Boolean(true)
);
assert_eq!(eval("Object.is(1, 2)"), JsValue::Boolean(false));
assert_eq!(eval("Object.is('foo', 'bar')"), JsValue::Boolean(false));
assert_eq!(eval("Object.is(true, false)"), JsValue::Boolean(false));
}
#[test]
fn test_object_is_nan() {
assert_eq!(eval("Object.is(NaN, NaN)"), JsValue::Boolean(true));
assert_eq!(eval("NaN === NaN"), JsValue::Boolean(false));
}
#[test]
fn test_object_is_zero() {
assert_eq!(eval("Object.is(0, 0)"), JsValue::Boolean(true));
assert_eq!(eval("Object.is(-0, -0)"), JsValue::Boolean(true));
assert_eq!(eval("Object.is(0, -0)"), JsValue::Boolean(false));
assert_eq!(eval("Object.is(-0, 0)"), JsValue::Boolean(false));
assert_eq!(eval("0 === -0"), JsValue::Boolean(true));
}
#[test]
fn test_object_is_objects() {
assert_eq!(
eval("const obj = {}; Object.is(obj, obj)"),
JsValue::Boolean(true)
);
assert_eq!(eval("Object.is({}, {})"), JsValue::Boolean(false));
}
#[test]
fn test_object_is_symbols() {
assert_eq!(
eval("const s = Symbol('test'); Object.is(s, s)"),
JsValue::Boolean(true)
);
assert_eq!(
eval("Object.is(Symbol('test'), Symbol('test'))"),
JsValue::Boolean(false)
);
}
#[test]
fn test_object_prevent_extensions() {
assert_eq!(
eval(
r#"
const obj: any = { a: 1 };
Object.preventExtensions(obj);
try {
obj.b = 2;
"no error"
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error"
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_object_is_extensible() {
assert_eq!(eval("Object.isExtensible({})"), JsValue::Boolean(true));
assert_eq!(
eval(
r#"
const obj = {};
Object.preventExtensions(obj);
Object.isExtensible(obj)
"#
),
JsValue::Boolean(false)
);
assert_eq!(eval("Object.isExtensible(1)"), JsValue::Boolean(false));
assert_eq!(eval("Object.isExtensible('str')"), JsValue::Boolean(false));
}
#[test]
fn test_object_prevent_extensions_existing_props() {
assert_eq!(
eval(
r#"
const obj: any = { a: 1 };
Object.preventExtensions(obj);
obj.a = 42;
obj.a
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_freeze_prevents_extension() {
assert_eq!(
eval(
r#"
const obj = { a: 1 };
Object.freeze(obj);
Object.isExtensible(obj)
"#
),
JsValue::Boolean(false)
);
}
#[test]
fn test_object_get_own_property_descriptors_basic() {
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2 };
const descs = Object.getOwnPropertyDescriptors(obj);
descs.a.value
"#
),
JsValue::Number(1.0)
);
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2 };
const descs = Object.getOwnPropertyDescriptors(obj);
descs.b.value
"#
),
JsValue::Number(2.0)
);
}
#[test]
fn test_object_get_own_property_descriptors_attributes() {
assert_eq!(
eval(
r#"
const obj = { x: 42 };
const descs = Object.getOwnPropertyDescriptors(obj);
descs.x.writable
"#
),
JsValue::Boolean(true)
);
assert_eq!(
eval(
r#"
const obj = { x: 42 };
const descs = Object.getOwnPropertyDescriptors(obj);
descs.x.enumerable
"#
),
JsValue::Boolean(true)
);
assert_eq!(
eval(
r#"
const obj = { x: 42 };
const descs = Object.getOwnPropertyDescriptors(obj);
descs.x.configurable
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_get_own_property_descriptors_with_define() {
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 10, writable: false, enumerable: false, configurable: false });
const descs = Object.getOwnPropertyDescriptors(obj);
descs.x.writable
"#
),
JsValue::Boolean(false)
);
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 10, writable: false, enumerable: false, configurable: false });
const descs = Object.getOwnPropertyDescriptors(obj);
descs.x.enumerable
"#
),
JsValue::Boolean(false)
);
}
#[test]
fn test_object_get_own_property_descriptors_count() {
assert_eq!(
eval(
r#"
const obj = { a: 1, b: 2, c: 3 };
Object.keys(Object.getOwnPropertyDescriptors(obj)).length
"#
),
JsValue::Number(3.0)
);
}
#[test]
fn test_object_literal_getter_basic() {
assert_eq!(
eval(
r#"
let obj = {
get value() { return 42; }
};
obj.value
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_literal_getter_with_this() {
assert_eq!(
eval(
r#"
let obj = {
x: 10,
get double() { return this.x * 2; }
};
obj.double
"#
),
JsValue::Number(20.0)
);
}
#[test]
fn test_object_literal_setter_basic() {
assert_eq!(
eval(
r#"
let obj = {
_value: 0,
set value(v: number) { this._value = v; }
};
obj.value = 42;
obj._value
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_literal_getter_setter_pair() {
assert_eq!(
eval(
r#"
let obj = {
_count: 0,
get count() { return this._count; },
set count(v: number) { this._count = v; }
};
obj.count = 5;
obj.count
"#
),
JsValue::Number(5.0)
);
}
#[test]
fn test_object_constructor_property() {
assert_eq!(
eval("(new Object()).constructor === Object"),
JsValue::Boolean(true)
);
assert_eq!(eval("({}).constructor === Object"), JsValue::Boolean(true));
}
#[test]
fn test_array_constructor_property() {
assert_eq!(
eval("(new Array()).constructor === Array"),
JsValue::Boolean(true)
);
assert_eq!(eval("([]).constructor === Array"), JsValue::Boolean(true));
}
#[test]
fn test_function_constructor_property() {
assert_eq!(
eval("(function() {}).constructor === Function"),
JsValue::Boolean(true)
);
assert_eq!(
eval("(() => {}).constructor === Function"),
JsValue::Boolean(true)
);
}
#[test]
fn test_string_constructor_property() {
assert_eq!(
eval("(new String('test')).constructor === String"),
JsValue::Boolean(true)
);
}
#[test]
fn test_number_constructor_property() {
assert_eq!(
eval("(new Number(42)).constructor === Number"),
JsValue::Boolean(true)
);
}
#[test]
fn test_boolean_constructor_property() {
assert_eq!(
eval("(new Boolean(true)).constructor === Boolean"),
JsValue::Boolean(true)
);
}
#[test]
fn test_regexp_constructor_property() {
assert_eq!(
eval("(/test/).constructor === RegExp"),
JsValue::Boolean(true)
);
assert_eq!(
eval("(new RegExp('test')).constructor === RegExp"),
JsValue::Boolean(true)
);
}
#[test]
fn test_map_constructor_property() {
assert_eq!(
eval("(new Map()).constructor === Map"),
JsValue::Boolean(true)
);
}
#[test]
fn test_set_constructor_property() {
assert_eq!(
eval("(new Set()).constructor === Set"),
JsValue::Boolean(true)
);
}
#[test]
fn test_date_constructor_property() {
assert_eq!(
eval("(new Date()).constructor === Date"),
JsValue::Boolean(true)
);
}
#[test]
fn test_promise_constructor_property() {
assert_eq!(
eval("(new Promise(() => {})).constructor === Promise"),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_wrapper_number_equality() {
assert_eq!(eval("Object(42) == 42"), JsValue::Boolean(true));
assert_eq!(eval("Object(1.5) == 1.5"), JsValue::Boolean(true));
assert_eq!(eval("Object(0) == 0"), JsValue::Boolean(true));
assert_eq!(eval("Object(-1) == -1"), JsValue::Boolean(true));
assert_eq!(eval("Object(42) === 42"), JsValue::Boolean(false));
}
#[test]
fn test_object_wrapper_string_equality() {
assert_eq!(eval("Object('hello') == 'hello'"), JsValue::Boolean(true));
assert_eq!(eval("Object('') == ''"), JsValue::Boolean(true));
assert_eq!(eval("Object('hello') === 'hello'"), JsValue::Boolean(false));
}
#[test]
fn test_object_wrapper_boolean_equality() {
assert_eq!(eval("Object(true) == true"), JsValue::Boolean(true));
assert_eq!(eval("Object(false) == false"), JsValue::Boolean(true));
assert_eq!(eval("Object(true) === true"), JsValue::Boolean(false));
}
#[test]
fn test_object_wrapper_typeof() {
assert_eq!(
eval("typeof Object(42)"),
JsValue::String(JsString::from("object"))
);
assert_eq!(
eval("typeof Object('hello')"),
JsValue::String(JsString::from("object"))
);
assert_eq!(
eval("typeof Object(true)"),
JsValue::String(JsString::from("object"))
);
}
#[test]
fn test_object_wrapper_valueof() {
assert_eq!(eval("Object(42).valueOf()"), JsValue::Number(42.0));
assert_eq!(
eval("Object('hello').valueOf()"),
JsValue::String(JsString::from("hello"))
);
assert_eq!(eval("Object(true).valueOf()"), JsValue::Boolean(true));
}
#[test]
fn test_object_wrapper_constructor() {
assert_eq!(
eval("Object(42).constructor === Number"),
JsValue::Boolean(true)
);
assert_eq!(
eval("Object('hello').constructor === String"),
JsValue::Boolean(true)
);
assert_eq!(
eval("Object(true).constructor === Boolean"),
JsValue::Boolean(true)
);
}
#[test]
fn test_object_wrapper_not_same_value() {
assert_eq!(eval("Object(42) !== 42"), JsValue::Boolean(true));
assert_eq!(eval("Object('hello') !== 'hello'"), JsValue::Boolean(true));
}
#[test]
fn test_abstract_equality_object_to_number() {
assert_eq!(
eval("let obj = { valueOf: function() { return 42; } }; obj == 42"),
JsValue::Boolean(true)
);
}
#[test]
fn test_abstract_equality_object_to_string() {
assert_eq!(
eval("let obj = { toString: function() { return 'hello'; } }; obj == 'hello'"),
JsValue::Boolean(true)
);
}
#[test]
fn test_abstract_equality_boolean_coercion() {
assert_eq!(eval("1 == true"), JsValue::Boolean(true));
assert_eq!(eval("0 == false"), JsValue::Boolean(true));
assert_eq!(eval("2 == true"), JsValue::Boolean(false)); assert_eq!(eval("'1' == true"), JsValue::Boolean(true)); }
#[test]
fn test_object_literal_true_property_name() {
assert_eq!(
eval("var obj = { true: 1 }; obj.true"),
JsValue::Number(1.0)
);
assert_eq!(
eval("var obj = { true: 1 }; obj['true']"),
JsValue::Number(1.0)
);
}
#[test]
fn test_object_literal_false_property_name() {
assert_eq!(
eval("var obj = { false: 2 }; obj.false"),
JsValue::Number(2.0)
);
assert_eq!(
eval("var obj = { false: 2 }; obj['false']"),
JsValue::Number(2.0)
);
}
#[test]
fn test_object_literal_null_property_name() {
assert_eq!(
eval("var obj = { null: 3 }; obj.null"),
JsValue::Number(3.0)
);
assert_eq!(
eval("var obj = { null: 3 }; obj['null']"),
JsValue::Number(3.0)
);
}
#[test]
fn test_object_literal_reserved_words_as_properties() {
assert_eq!(
eval(
"var obj = { if: 1, else: 2, for: 3, while: 4 }; obj.if + obj.else + obj.for + obj.while"
),
JsValue::Number(10.0)
);
}
#[test]
fn test_object_literal_mixed_properties() {
assert_eq!(
eval(
"var obj = { true: 1, false: 0, null: -1, x: 10 }; obj.true + obj.false + obj.null + obj.x"
),
JsValue::Number(10.0)
);
}
#[test]
fn test_delete_configurable_property() {
assert_eq!(
eval(
r#"
const obj: any = { x: 42 };
const result = delete obj.x;
[result, obj.x === undefined].join(',')
"#
),
JsValue::String("true,true".into())
);
}
#[test]
fn test_delete_non_configurable_property() {
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 42, configurable: false });
Object.getOwnPropertyDescriptor(obj, 'x').configurable
"#
),
JsValue::Boolean(false)
);
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 42, configurable: false });
obj.hasOwnProperty('x')
"#
),
JsValue::Boolean(true)
);
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 42, configurable: false });
Object.getOwnPropertyNames(obj).join(',')
"#
),
JsValue::String("x".into())
);
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', { value: 42, configurable: false });
try {
delete obj.x;
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_delete_math_constant() {
assert_eq!(
eval(
r#"
try {
delete Math.E;
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
assert_eq!(
eval(
r#"
try { delete Math.E; } catch(e) {}
Math.E === Math.E // Should still be defined
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_delete_null_undefined_throws() {
assert_eq!(
eval(
r#"
try {
const n: any = null;
delete n.prop;
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
assert_eq!(
eval(
r#"
try {
const u: any = undefined;
delete u.prop;
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
assert_eq!(
eval(
r#"
try {
const n: any = null;
delete n["prop"];
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_delete_primitives_returns_true() {
assert_eq!(
eval(
r#"
const results: boolean[] = [];
const n: any = 42;
results.push(delete n.foo);
const s: any = "hello";
results.push(delete s.foo);
const b: any = true;
results.push(delete b.foo);
results.join(',')
"#
),
JsValue::String("true,true,true".into())
);
}
#[test]
fn test_object_keys_with_primitives() {
assert_eq!(eval("Object.keys(42).length"), JsValue::Number(0.0));
assert_eq!(eval("Object.keys(true).length"), JsValue::Number(0.0));
assert_eq!(
eval("Object.keys(Symbol('test')).length"),
JsValue::Number(0.0)
);
}
#[test]
fn test_object_keys_null_undefined_throws() {
assert_eq!(
eval(
r#"
try {
Object.keys(null);
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
assert_eq!(
eval(
r#"
try {
Object.keys(undefined);
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : "other error";
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_toprimitive_valueof_returns_primitive() {
assert_eq!(
eval(r#"1 + { valueOf: function() { return 41; } }"#),
JsValue::Number(42.0)
);
assert_eq!(
eval(r#"({ valueOf: function() { return 10; } }) + 5"#),
JsValue::Number(15.0)
);
}
#[test]
fn test_toprimitive_tostring_returns_primitive() {
assert_eq!(
eval(r#"1 + { toString: function() { return "41"; } }"#),
JsValue::String("141".into())
);
}
#[test]
fn test_toprimitive_valueof_priority_over_tostring() {
assert_eq!(
eval(
r#"1 + { valueOf: function() { return 10; }, toString: function() { return "20"; } }"#
),
JsValue::Number(11.0)
);
}
#[test]
fn test_toprimitive_fallback_to_tostring() {
assert_eq!(
eval(r#"1 + { valueOf: function() { return {}; }, toString: function() { return 10; } }"#),
JsValue::Number(11.0)
);
}
#[test]
fn test_toprimitive_both_return_objects_throws_typeerror() {
assert_eq!(
eval(
r#"
try {
1 + { valueOf: function() { return {}; }, toString: function() { return {}; } };
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : e.toString();
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_toprimitive_no_methods_throws_typeerror() {
assert_eq!(
eval(
r#"
try {
1 + Object.create(null);
"no error";
} catch (e) {
e instanceof TypeError ? "TypeError" : e.toString();
}
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_toprimitive_valueof_throws() {
assert_eq!(
eval(
r#"
try {
1 + { valueOf: function() { throw new Error("valueOf error"); } };
"no error";
} catch (e) {
e.message;
}
"#
),
JsValue::String("valueOf error".into())
);
}
#[test]
fn test_toprimitive_string_hint_tostring_first() {
assert_eq!(
eval(
r#"
const obj = {
valueOf: function() { return 10; },
toString: function() { return "hello"; }
};
String(obj)
"#
),
JsValue::String("hello".into())
);
}
#[test]
fn test_toprimitive_comparison_operators() {
assert_eq!(
eval(r#"(({ valueOf: function() { return 5; } }) - 0) < 10"#),
JsValue::Boolean(true)
);
assert_eq!(
eval(r#"(({ valueOf: function() { return 15; } }) - 0) > 10"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_toprimitive_equality_operators() {
assert_eq!(
eval(r#"({ valueOf: function() { return 42; } }) == 42"#),
JsValue::Boolean(true)
);
assert_eq!(
eval(r#"42 == { valueOf: function() { return 42; } }"#),
JsValue::Boolean(true)
);
}
#[test]
fn test_toprimitive_subtraction() {
assert_eq!(
eval(r#"({ valueOf: function() { return 50; } }) - 8"#),
JsValue::Number(42.0)
);
}
#[test]
fn test_toprimitive_multiplication() {
assert_eq!(
eval(r#"({ valueOf: function() { return 6; } }) * 7"#),
JsValue::Number(42.0)
);
}
#[test]
fn test_toprimitive_division() {
assert_eq!(
eval(r#"({ valueOf: function() { return 84; } }) / 2"#),
JsValue::Number(42.0)
);
}
#[test]
fn test_toprimitive_unary_plus() {
assert_eq!(
eval(r#"0 + (+({ valueOf: function() { return 42; } } - 0))"#),
JsValue::Number(42.0)
);
}
#[test]
fn test_toprimitive_unary_minus() {
assert_eq!(
eval(r#"0 - ({ valueOf: function() { return 42; } } - 0)"#),
JsValue::Number(-42.0)
);
}
#[test]
fn test_toprimitive_template_literal() {
assert_eq!(
eval(
r#"
const obj = { toString: function() { return "world"; } };
`hello ${obj}`
"#
),
JsValue::String("hello world".into())
);
}
#[test]
fn test_toprimitive_string_concatenation() {
assert_eq!(
eval(r#""value: " + { toString: function() { return "42"; } }"#),
JsValue::String("value: 42".into())
);
}
#[test]
fn test_toprimitive_array_join_uses_tostring() {
assert_eq!(
eval(
r#"
const obj = { toString: function() { return "X"; } };
String(obj)
"#
),
JsValue::String("X".into())
);
}
#[test]
fn test_getter_only_assignment_throws_typeerror() {
assert_eq!(
eval(
r#"
const obj = {
get value() { return 42; }
};
let result = "no error";
try {
obj.value = 100;
} catch (e) {
result = e instanceof TypeError ? "TypeError" : "other error: " + e;
}
result
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_getter_only_via_define_property_throws_typeerror() {
assert_eq!(
eval(
r#"
const obj: any = {};
Object.defineProperty(obj, 'x', {
get: function() { return 42; }
});
let result = "no error";
try {
obj.x = 100;
} catch (e) {
result = e instanceof TypeError ? "TypeError" : "other error";
}
result
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_getter_setter_pair_does_not_throw() {
assert_eq!(
eval(
r#"
let stored = 0;
const obj = {
get value() { return stored; },
set value(v: number) { stored = v; }
};
obj.value = 100;
stored
"#
),
JsValue::Number(100.0)
);
}
#[test]
fn test_class_getter_only_throws_typeerror() {
assert_eq!(
eval(
r#"
class Foo {
get value(): number { return 42; }
}
const obj = new Foo();
let result = "no error";
try {
(obj as any).value = 100;
} catch (e) {
result = e instanceof TypeError ? "TypeError" : "other error";
}
result
"#
),
JsValue::String("TypeError".into())
);
}
#[test]
fn test_object_literal_computed_getter() {
assert_eq!(
eval(
r#"
let key: string = "foo";
let obj = {
get [key](): number { return 42; }
};
obj.foo
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_object_literal_computed_setter() {
assert_eq!(
eval(
r#"
let key: string = "bar";
let obj = {
_val: 0,
set [key](v: number) { this._val = v; }
};
obj.bar = 100;
obj._val
"#
),
JsValue::Number(100.0)
);
}
#[test]
fn test_object_literal_computed_getter_setter_combined() {
assert_eq!(
eval(
r#"
let key: string = "prop";
let obj = {
_value: 0,
get [key](): number { return this._value; },
set [key](v: number) { this._value = v * 2; }
};
obj.prop = 21;
obj.prop
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_array_hasownproperty_numeric_index() {
assert_eq!(
eval(
r#"
const arr: number[] = [1, 2, 3];
arr.hasOwnProperty(0)
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_array_hasownproperty_string_index() {
assert_eq!(
eval(
r#"
const arr: number[] = [1, 2, 3];
arr.hasOwnProperty("1")
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_array_hasownproperty_out_of_bounds() {
assert_eq!(
eval(
r#"
const arr: number[] = [1, 2, 3];
arr.hasOwnProperty(5)
"#
),
JsValue::Boolean(false)
);
}
#[test]
fn test_array_keys_includes_indices() {
assert_eq!(
eval(
r#"
const arr: number[] = [1, 2, 3];
Object.keys(arr).length
"#
),
JsValue::Number(3.0)
);
}
#[test]
fn test_object_define_property_on_array() {
assert_eq!(
eval(
r#"
const arr: number[] = [];
Object.defineProperty(arr, '0', { value: 42, writable: true, enumerable: true, configurable: true });
arr.hasOwnProperty('0')
"#
),
JsValue::Boolean(true)
);
}
#[test]
fn test_debug_define_property_array_length() {
assert_eq!(
eval(
r#"
const arr: number[] = [];
Object.defineProperty(arr, '0', { value: 42, writable: true, enumerable: true, configurable: true });
arr.length
"#
),
JsValue::Number(1.0)
);
}
#[test]
fn test_define_property_array_access() {
assert_eq!(
eval(
r#"
const arr: number[] = [];
Object.defineProperty(arr, '0', { value: 42, writable: true, enumerable: true, configurable: true });
arr[0]
"#
),
JsValue::Number(42.0)
);
}
#[test]
fn test_define_property_array_object_keys() {
assert_eq!(
eval(
r#"
const arr: number[] = [];
Object.defineProperty(arr, '0', { value: 42, writable: true, enumerable: true, configurable: true });
Object.keys(arr).join(',')
"#
),
JsValue::from("0")
);
}
#[test]
fn test_define_property_array_multiple_indices() {
assert_eq!(
eval(
r#"
const arr: number[] = [];
Object.defineProperty(arr, '0', { value: 1, writable: true, enumerable: true, configurable: true });
Object.defineProperty(arr, '2', { value: 3, writable: true, enumerable: true, configurable: true });
arr.length
"#
),
JsValue::Number(3.0)
);
assert_eq!(
eval(
r#"
const arr: number[] = [];
Object.defineProperty(arr, '0', { value: 1, writable: true, enumerable: true, configurable: true });
Object.defineProperty(arr, '2', { value: 3, writable: true, enumerable: true, configurable: true });
[arr[0], arr[1], arr[2]].join(',')
"#
),
JsValue::from("1,,3")
);
}
#[test]
fn test_unicode_escape_in_property_name() {
assert_eq!(
eval(r#"const obj = { \u0063ase: 1 }; obj.case"#),
JsValue::Number(1.0)
);
}
#[test]
fn test_unicode_escape_identifier_basic() {
assert_eq!(eval(r#"let \u0078 = 42; x"#), JsValue::Number(42.0));
}
#[test]
fn test_unicode_escape_identifier_mixed() {
assert_eq!(eval(r#"let f\u006fo = 123; foo"#), JsValue::Number(123.0));
}
#[test]
fn test_unicode_escape_in_member_access() {
assert_eq!(
eval(r#"const obj = { bar: 42 }; obj.b\u0061r"#),
JsValue::Number(42.0)
);
}
#[test]
#[ignore = "requires full Unicode XID support"]
fn test_unicode_escape_id_continue_middle_dot() {
assert_eq!(eval(r#"var a\u00B7 = 42; a\u00B7"#), JsValue::Number(42.0));
}
#[test]
fn test_unicode_escape_braced_form_in_identifier() {
assert_eq!(eval(r#"var \u{61}bc = 99; abc"#), JsValue::Number(99.0));
}
#[test]
fn test_object_groupby_basic() {
assert_eq!(
eval(
r#"
const items = [
{ type: 'fruit', name: 'apple' },
{ type: 'vegetable', name: 'carrot' },
{ type: 'fruit', name: 'banana' }
];
const grouped = Object.groupBy(items, (item: any) => item.type);
grouped.fruit.length
"#
),
JsValue::Number(2.0)
);
}
#[test]
fn test_object_groupby_empty_array() {
assert_eq!(
eval(
r#"
const grouped = Object.groupBy([], (x: any) => x);
Object.keys(grouped).length
"#
),
JsValue::Number(0.0)
);
}
#[test]
fn test_object_groupby_all_same_key() {
assert_eq!(
eval(
r#"
const nums: number[] = [1, 2, 3, 4, 5];
const grouped = Object.groupBy(nums, () => "all");
grouped.all.length
"#
),
JsValue::Number(5.0)
);
}
#[test]
fn test_object_groupby_unique_keys() {
assert_eq!(
eval(
r#"
const nums: number[] = [1, 2, 3];
const grouped = Object.groupBy(nums, (n: number) => n === 1 ? "a" : n === 2 ? "b" : "c");
Object.keys(grouped).sort().join(',')
"#
),
JsValue::String("a,b,c".into())
);
assert_eq!(
eval(
r#"
const nums: number[] = [1, 2, 3];
const grouped = Object.groupBy(nums, (n: number) => n === 1 ? "a" : n === 2 ? "b" : "c");
[grouped.a.length, grouped.b.length, grouped.c.length].join(',')
"#
),
JsValue::String("1,1,1".into())
);
}
#[test]
fn test_object_groupby_with_tostring_in_callback() {
assert_eq!(
eval(
r#"
const nums: number[] = [1, 2, 3];
const grouped = Object.groupBy(nums, (n: number) => n.toString());
Object.keys(grouped).sort().join(',')
"#
),
JsValue::String("1,2,3".into())
);
}
#[test]
fn test_object_groupby_with_string_constructor_in_callback() {
assert_eq!(
eval(
r#"
const nums: number[] = [1, 2, 3];
const grouped = Object.groupBy(nums, (n: number) => String(n));
Object.keys(grouped).sort().join(',')
"#
),
JsValue::String("1,2,3".into())
);
}
#[test]
fn test_object_groupby_returns_null_prototype_object() {
assert_eq!(
eval(
r#"
const grouped = Object.groupBy([1, 2], (x: number) => "key");
Object.getPrototypeOf(grouped)
"#
),
JsValue::Null
);
}
#[test]
fn test_object_groupby_preserves_order() {
assert_eq!(
eval(
r#"
const nums: number[] = [3, 1, 4, 1, 5, 9, 2, 6];
const grouped = Object.groupBy(nums, (n: number) => n % 2 === 0 ? "even" : "odd");
grouped.odd.join(',')
"#
),
JsValue::String("3,1,1,5,9".into())
);
}
#[test]
fn test_object_groupby_with_index() {
assert_eq!(
eval(
r#"
const letters: string[] = ['a', 'b', 'c', 'd'];
const grouped = Object.groupBy(letters, (_: string, i: number) => i < 2 ? "first" : "second");
[grouped.first.join(''), grouped.second.join('')].join('|')
"#
),
JsValue::String("ab|cd".into())
);
}
#[test]
fn test_object_groupby_coerces_key_to_string() {
assert_eq!(
eval(
r#"
const nums: number[] = [1, 2, 3];
const grouped = Object.groupBy(nums, (n: number) => n);
typeof Object.keys(grouped)[0]
"#
),
JsValue::String("string".into())
);
}