javascript 0.3.0

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

const WINDOWS_LIKE_STACK_SIZE: usize = 1024 * 1024;
const TEST_STACK_SIZE: usize = if cfg!(debug_assertions) {
    8 * 1024 * 1024
} else {
    WINDOWS_LIKE_STACK_SIZE
};

fn eval_vm(script: &str) -> String {
    evaluate_script(script, false, None::<&std::path::Path>).unwrap()
}

fn eval_vm_result(script: &str) -> Result<String, JSError> {
    evaluate_script(script, false, None::<&std::path::Path>)
}

fn eval_vm_async_iife(body: &str) -> String {
    let wrapped = format!("(async function() {{\n{}\n}})()", body);
    eval_vm(&wrapped)
}

#[test]
fn spec_promise_constructor_returns_promise_object() {
    let script = r#"
        let p = new Promise(function(resolve, reject) { resolve(1); });
        typeof p.then
    "#;
    assert_eq!(eval_vm(script), "\"function\"");
}

#[test]
fn spec_async_function_call_returns_promise() {
    let script = r#"
        async function f() { return 42; }
        let p = f();
        typeof p.then
    "#;
    assert_eq!(eval_vm(script), "\"function\"");
}

#[test]
fn spec_allsettled_mixed_outcomes() {
    let body = r#"
        return await Promise.allSettled([
            Promise.resolve(1),
            Promise.reject(2),
            Promise.resolve(3)
        ]);
    "#;

    std::thread::Builder::new()
        .stack_size(TEST_STACK_SIZE)
        .spawn(move || {
            let result = eval_vm_async_iife(body);
            assert_eq!(
                result,
                r#"[{"status":"fulfilled","value":1},{"status":"rejected","reason":2},{"status":"fulfilled","value":3}]"#
            );
        })
        .expect("failed to spawn thread")
        .join()
        .expect("thread panicked");
}

#[test]
fn spec_executor_throw_non_error_does_not_throw_sync() {
    let script = r#"
        let threw = false;
        try {
            new Promise(function(resolve, reject) { throw 2; });
        } catch (e) {
            threw = true;
        }
        threw
    "#;
    assert_eq!(eval_vm(script), "false");
}

#[test]
fn spec_executor_throw_error_does_not_throw_sync() {
    let script = r#"
        let threw = false;
        try {
            new Promise(function(resolve, reject) { throw new Error('boom'); });
        } catch (e) {
            threw = true;
        }
        threw
    "#;
    assert_eq!(eval_vm(script), "false");
}

#[test]
fn spec_await_rejected_promise_enters_catch() {
    let body = r#"
        try {
            await Promise.reject(2);
            return 'unexpected';
        } catch (e) {
            return e;
        }
    "#;
    assert_eq!(eval_vm_async_iife(body), "2");
}

#[test]
fn spec_rejected_promise_is_value_not_host_err() {
    let script = r#"new Promise(function(resolve, reject) { throw new Error('boom'); })"#;
    let result = eval_vm_result(script);
    assert!(result.is_ok(), "expected Promise completion, got Err: {:?}", result.err());
}

#[test]
fn spec_await_thenable_fulfill_assimilates_value() {
    let body = r#"
        let thenable = {
            then(resolve, reject) {
                resolve(99);
            }
        };
        return await thenable;
    "#;
    assert_eq!(eval_vm_async_iife(body), "99");
}

#[test]
fn spec_await_thenable_reject_enters_catch() {
    let body = r#"
        let thenable = {
            then(resolve, reject) {
                reject('bad');
            }
        };
        try {
            await thenable;
            return 'unexpected';
        } catch (e) {
            return e;
        }
    "#;
    assert_eq!(eval_vm_async_iife(body), "\"bad\"");
}

#[test]
fn spec_await_non_callable_then_returns_object() {
    let body = r#"
        let obj = { then: 123, value: 7 };
        let out = await obj;
        return out.value;
    "#;
    assert_eq!(eval_vm_async_iife(body), "7");
}

#[test]
fn spec_async_finally_rejection_overrides_prior_rejection() {
    let body = r#"
        let f = async function() {
            try {
                await Promise.reject('early-reject');
            } finally {
                await Promise.reject('override');
            }
        };

        return await f().then(
            function() { return 'fulfilled'; },
            function(reason) { return reason; }
        );
    "#;
    let res = eval_vm_async_iife(body);
    assert!(res.contains("override"));
}

#[test]
fn spec_async_finally_override_matches_cli_script_pattern() {
    let script = r#"
        "use strict";

        function assert(b, msg) {
            if (!b) {
                throw new Error("Assertion failed" + (msg ? ": " + msg : ""));
            }
        }

        function $DONE(err) {
            if (err) {
                console.error("Test failed:", err);
            }
        }

        {
            var f = async() => {
                try {
                    await new Promise(function(resolve, reject) {
                        reject("early-reject");
                    });
                } finally {
                    await new Promise(function(resolve, reject) {
                        reject("override");
                    });
                }
            };

            f().then($DONE, function(value) {
                assert(value === "override", "Awaited rejection in finally block, got: " + value);
            }).then($DONE, $DONE);
        }
    "#;

    let result = eval_vm_result(script);
    assert!(result.is_ok(), "expected no uncaught error, got: {:?}", result.err());
}