javascript 0.3.0

A JavaScript engine implementation in Rust
Documentation
use javascript::*;

// Initialize logger for this integration test binary so `RUST_LOG` is honored.
// Using `ctor` ensures initialization runs before tests start.
#[ctor::ctor(unsafe)]
fn __init_test_logger() {
    let _ = env_logger::Builder::from_env(env_logger::Env::default()).is_test(true).try_init();
}

#[cfg(test)]
mod object_literal_tests {
    use super::*;

    #[test]
    fn test_basic_object_literal() {
        let script = "let obj = {a: 1, b: 2}; obj.a + obj.b";
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "3");
    }

    #[test]
    fn test_object_property_access() {
        let script = "let obj = {name: 'hello', value: 42}; obj.name";
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"hello\"");
    }

    #[test]
    fn test_empty_object() {
        let script = "let empty = {}; empty";
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "{}");
    }

    #[test]
    fn test_nested_object() {
        let script = "let nested = {a: {b: 1}}; nested.a.b";
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "1");
    }

    #[test]
    fn test_object_with_string_keys() {
        let script = "let obj = {'key': 123}; obj.key";
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "123");
    }

    #[test]
    fn test_console_log_with_object() {
        // This test verifies that console.log works with objects
        // We can't easily capture stdout in tests, so we just ensure it doesn't crash
        let script = "let obj = {test: 'value'}; console.log(obj.test); obj.test";
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"value\"");
    }

    #[test]
    fn test_intentionally_failing_object() {
        let script = "let obj = {a: 1, b: 2}; obj.a + obj.b";
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "3");
    }

    #[test]
    fn test_getter_setter_basic() {
        let script = r#"
            let obj = {
                _value: 0,
                get value() { return this._value; },
                set value(v) { this._value = v * 2; }
            };
            obj.value = 5;
            obj.value
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "10");
    }

    #[test]
    fn test_getter_setter_with_computed_property() {
        let script = r#"
            let obj = {
                _data: {},
                get data() { return this._data; },
                set data(value) { this._data = { processed: value * 10 }; }
            };
            obj.data = 3;
            obj.data.processed
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "30");
    }

    #[test]
    fn test_concise_method_parsing() {
        let script = r#"
            let obj = { foo() { return 7; } };
            obj.foo();
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "7");
    }

    #[test]
    fn test_computed_getter_parsing() {
        let script = r#"
            function CustomError() {}
            let obj = { get [Symbol.toPrimitive]() { return 42; } };
            42
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "42");
    }

    #[test]
    fn test_object_to_string() {
        let script = r#"
            let obj = {a: 1, b: 2};
            obj.toString();
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"[object Object]\"");
    }

    #[test]
    fn test_object_to_string_with_super() {
        let script = r#"
            class Base {
                toString() {
                    return "Base toString";
                }
            }
            class Derived extends Base {}
            let obj = new Derived();
            obj.toString();
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"Base toString\"");
    }

    #[test]
    fn test_object_to_string_with_super_2() {
        let script = r#"
            class A {
                toString() {
                    return "A toString";
                }
            }
            class B extends A {
                toString() {
                    return "B " + super.toString();
                }
            }
            let obj = new B();
            obj.toString();
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"B A toString\"");
    }

    #[test]
    fn test_object_to_string_with_super_3() {
        let script = r#"
            var obj = { toString() { return 'obj -> ' + super.toString(); } };
            obj.toString();
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"obj -> [object Object]\"");
    }

    #[test]
    fn test_object_to_string_with_super_4() {
        let script = r#"
            const proto = {
                toString() {
                    return 'proto';
                }
            };

            const obj = {
                toString() {
                    return 'obj -> ' + super.toString();
                }
            };

            Reflect.setPrototypeOf(obj, proto);

            obj.toString();
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"obj -> proto\"");
    }

    #[test]
    fn test_object_to_string_with_super_5() {
        let script = r#"
            const proto = {
                toString() {
                    return 'proto';
                }
            };

            const obj = {
                toString() {
                    return 'obj -> ' + super.toString();
                }
            };

            // Reflect.setPrototypeOf(obj, proto);

            obj.toString();
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "\"obj -> [object Object]\"");
    }

    #[test]
    fn test_object_with_reference_error() {
        let script = r#"
            let obj = {
                get value() { return nonExistentVar; }
            };
            obj.value;
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>);
        match result {
            Err(e) => {
                let err_msg = format!("{:?}", e);
                assert!(err_msg.contains("nonExistentVar"), "Expected nonExistentVar, got {:?}", err_msg);
            }
            _ => panic!("Expected nonExistentVar, got {:?}", result),
        }
    }

    #[test]
    fn test_object_with_reference_error_2() {
        let script = r#"
            const obj = {
                __proto__: theProtoObj,
                handler,
            };
            console.log(obj);
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>);
        match result {
            Err(e) => {
                let err_msg = format!("{:?}", e);
                assert!(err_msg.contains("theProtoObj"), "Expected theProtoObj error, got {:?}", err_msg);
            }
            _ => panic!("Expected theProtoObj error, got {:?}", result),
        }
    }

    #[test]
    fn test_arrow_function_name_on_property() {
        let script = r#"
            let o = { id: () => {} };
            let d = Object.getOwnPropertyDescriptor(o.id, 'name');
            d.value === 'id' && d.enumerable === false && d.writable === false && d.configurable === true
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "true");
    }
    #[test]
    fn test_cover_parenthesized_expression_name_on_property() {
        let script = r#"
            var namedSym = Symbol('test262');
            var anonSym = Symbol();
            var o;

            o = {
              xId: (0, function() {}),
              id: (function() {}),
              [anonSym]: (function() {}),
              [namedSym]: (function() {})
            };

            if (o.xId.name === 'xId') throw new Error('xId should not have inferred name');

            var dId = Object.getOwnPropertyDescriptor(o.id, 'name');
            if (!(dId.value === 'id' && dId.enumerable === false && dId.writable === false && dId.configurable === true)) throw new Error('o.id name descriptor mismatch');

            var dAnon = Object.getOwnPropertyDescriptor(o[anonSym], 'name');
            if (!(dAnon.value === '' && dAnon.enumerable === false && dAnon.writable === false && dAnon.configurable === true)) throw new Error('o[anonSym] name descriptor mismatch');

            var dNamed = Object.getOwnPropertyDescriptor(o[namedSym], 'name');
            if (!(dNamed.value === '[test262]' && dNamed.enumerable === false && dNamed.writable === false && dNamed.configurable === true)) throw new Error('o[namedSym] name descriptor mismatch');

            true
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "true");
    }
    #[test]
    fn test_class_expression_name_on_property() {
        let script = r#"
            let o = { id: class {} };
            let d = Object.getOwnPropertyDescriptor(o.id, 'name');
            d.value === 'id' && d.enumerable === false && d.writable === false && d.configurable === true
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "true");
    }

    #[test]
    fn test_anonymous_class_has_binding_name() {
        let script = r#"
            let c = class {};
            let d = Object.getOwnPropertyDescriptor(c, 'name');
            d.value === 'c' && d.enumerable === false && d.writable === false && d.configurable === true
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "true");
    }

    #[test]
    fn test_test262_class_expression_property_names() {
        let script = r#"
            var namedSym = Symbol('test262');
            var anonSym = Symbol();
            var o;

            o = {
              xId: class x {},
              id: class {},
              [anonSym]: class {},
              [namedSym]: class {}
            };

            // Assert the named-class case does not use inferred property name
            if (o.xId.name === 'xId') throw new Error('xId should not have inferred name');

            // Property-initializer class expressions should get name from property key
            var dId = Object.getOwnPropertyDescriptor(o.id, 'name');
            if (!(dId.value === 'id' && dId.enumerable === false && dId.writable === false && dId.configurable === true)) throw new Error('o.id name descriptor mismatch');

            var dAnon = Object.getOwnPropertyDescriptor(o[anonSym], 'name');
            if (!(dAnon.value === '' && dAnon.enumerable === false && dAnon.writable === false && dAnon.configurable === true)) throw new Error('o[anonSym] name descriptor mismatch');

            var dNamed = Object.getOwnPropertyDescriptor(o[namedSym], 'name');
            if (!(dNamed.value === '[test262]' && dNamed.enumerable === false && dNamed.writable === false && dNamed.configurable === true)) throw new Error('o[namedSym] name descriptor mismatch');

            true
        "#;
        let result = evaluate_script(script, false, None::<&std::path::Path>).unwrap();
        assert_eq!(result, "true");
    }
}