use super::{run, run_to_completion};
use tsrun::{
Guarded, InternalModule, Interpreter, InterpreterConfig, JsError, JsValue, ModulePath,
RuntimeValue, StepResult, value::PropertyKey,
};
#[test]
fn test_runtime_result_complete() {
let mut interp = Interpreter::new();
let result = run(&mut interp, "1 + 2", None).unwrap();
match result {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(3.0));
}
_ => panic!("Expected Complete result"),
}
}
#[test]
fn test_runtime_result_need_imports() {
let mut interp = Interpreter::new();
let result = run(&mut interp, r#"import { foo } from "./utils";"#, None).unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].specifier, "./utils");
assert_eq!(imports[0].resolved_path.as_str(), "utils");
}
_ => panic!("Expected NeedImports result"),
}
}
#[test]
fn test_provide_module() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { add } from "./math";
add(2, 3);
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports[0].specifier, "./math");
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export function add(a: number, b: number): number {
return a + b;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(_) | StepResult::Done => {
}
StepResult::NeedImports(_) => {
panic!("Should not need more imports after providing module");
}
StepResult::Suspended { .. } => {
panic!("Unexpected suspended state");
}
StepResult::Continue => {
panic!("Unexpected Continue state");
}
}
}
StepResult::Complete(_) | StepResult::Done => {
panic!("Expected NeedImports, got Complete/Done");
}
StepResult::Suspended { .. } => {
panic!("Expected NeedImports, got Suspended");
}
StepResult::Continue => {
panic!("Expected NeedImports, got Continue");
}
}
}
#[test]
fn test_internal_module_registered() {
let eval_internal = InternalModule::native("tsrun:host").build();
let config = InterpreterConfig {
internal_modules: vec![eval_internal],
..Default::default()
};
let _interp = Interpreter::with_config(config);
}
#[test]
fn test_internal_module_not_in_need_imports() {
let eval_internal = InternalModule::native("tsrun:host").build();
let config = InterpreterConfig {
internal_modules: vec![eval_internal],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { order } from "tsrun:host";
import { foo } from "./external";
42
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].specifier, "./external");
assert!(!imports.iter().any(|i| i.specifier == "tsrun:host"));
}
_ => panic!("Expected NeedImports"),
}
}
fn test_add(
_interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let a = args.first().cloned().unwrap_or(JsValue::Undefined);
let b = args.get(1).cloned().unwrap_or(JsValue::Undefined);
let result = match (a, b) {
(JsValue::Number(x), JsValue::Number(y)) => JsValue::Number(x + y),
_ => JsValue::Number(f64::NAN),
};
Ok(Guarded::unguarded(result))
}
fn test_get_value(
_interp: &mut Interpreter,
_this: JsValue,
_args: &[JsValue],
) -> Result<Guarded, JsError> {
Ok(Guarded::unguarded(JsValue::Number(42.0)))
}
#[test]
fn test_native_internal_module() {
let test_module = InternalModule::native("eval:test")
.with_function("add", test_add, 2)
.with_function("getValue", test_get_value, 0)
.build();
let config = InterpreterConfig {
internal_modules: vec![test_module],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { add, getValue } from "eval:test";
add(getValue(), 8);
"#,
None,
)
.unwrap();
match result {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(50.0)); }
_ => panic!("Expected Complete"),
}
}
#[test]
fn test_source_internal_module() {
let math_module = InternalModule::source(
"eval:math",
r#"
export function double(x: number): number {
return x * 2;
}
export function square(x: number): number {
return x * x;
}
export const PI = 3.14159;
"#,
);
let config = InterpreterConfig {
internal_modules: vec![math_module],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { double, square, PI } from "eval:math";
double(5) + square(3) + Math.floor(PI);
"#,
None,
)
.unwrap();
match result {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(22.0));
}
_ => panic!("Expected Complete"),
}
}
#[test]
fn test_import_namespace() {
let test_module = InternalModule::native("eval:test")
.with_function("getValue", test_get_value, 0)
.build();
let config = InterpreterConfig {
internal_modules: vec![test_module],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import * as testMod from "eval:test";
testMod.getValue();
"#,
None,
)
.unwrap();
match result {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
_ => panic!("Expected Complete"),
}
}
#[test]
fn test_order_syscall() {
use tsrun::create_eval_internal_module;
let eval_internal = create_eval_internal_module();
let config = InterpreterConfig {
internal_modules: vec![eval_internal],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { order } from "tsrun:host";
const orderId = order({ type: "test", data: 42 });
orderId;
"#,
None,
)
.unwrap();
match result {
StepResult::Suspended { pending, cancelled } => {
assert_eq!(pending.len(), 1);
assert_eq!(cancelled.len(), 0);
let order = &pending[0];
assert_eq!(order.id.0, 1); }
StepResult::Complete(_) => {
}
_ => panic!("Unexpected result"),
}
}
#[test]
fn test_order_syscall_returns_promise() {
use tsrun::create_eval_internal_module;
let eval_internal = create_eval_internal_module();
let config = InterpreterConfig {
internal_modules: vec![eval_internal],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { order } from "tsrun:host";
const p = order({ type: "test" });
typeof p === "object" && p !== null
"#,
None,
)
.unwrap();
match result {
StepResult::Suspended { pending, .. } => {
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].id.0, 1);
}
_ => panic!("Expected Suspended"),
}
}
#[test]
fn test_await_pending_promise_suspends_and_resumes() {
use tsrun::create_eval_internal_module;
let eval_internal = create_eval_internal_module();
let config = InterpreterConfig {
internal_modules: vec![eval_internal],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { order } from "tsrun:host";
// This will suspend when we await the pending promise
const result = await order({ type: "getData" });
result * 2 // This should run after resume with the resolved value
"#,
None,
)
.unwrap();
match result {
StepResult::Suspended { pending, .. } => {
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].id.0, 1);
let response = tsrun::OrderResponse {
id: pending[0].id,
result: Ok(RuntimeValue::unguarded(JsValue::Number(21.0))),
};
interp.fulfill_orders(vec![response]);
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
_ => panic!("Expected Complete after fulfillment, got {:?}", result2),
}
}
StepResult::Complete(v) => {
panic!("Expected Suspended, got Complete with {:?}", v);
}
_ => panic!("Expected Suspended"),
}
}
#[test]
fn test_await_suspension_with_multiple_awaits() {
use tsrun::create_eval_internal_module;
let eval_internal = create_eval_internal_module();
let config = InterpreterConfig {
internal_modules: vec![eval_internal],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { order } from "tsrun:host";
const a = await order({ type: "first" });
const b = await order({ type: "second" });
a + b
"#,
None,
)
.unwrap();
match result {
StepResult::Suspended { pending, .. } => {
assert_eq!(pending.len(), 1);
let response = tsrun::OrderResponse {
id: pending[0].id,
result: Ok(RuntimeValue::unguarded(JsValue::Number(10.0))),
};
interp.fulfill_orders(vec![response]);
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Suspended {
pending: pending2, ..
} => {
assert_eq!(pending2.len(), 1);
let response2 = tsrun::OrderResponse {
id: pending2[0].id,
result: Ok(RuntimeValue::unguarded(JsValue::Number(32.0))),
};
interp.fulfill_orders(vec![response2]);
let result3 = run_to_completion(&mut interp).unwrap();
match result3 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
_ => panic!("Expected Complete after second fulfillment"),
}
}
_ => panic!("Expected Suspended for second await"),
}
}
_ => panic!("Expected Suspended for first await"),
}
}
#[test]
fn test_external_module_named_exports() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { add, multiply } from "./math";
add(2, 3) + multiply(4, 5);
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].specifier, "./math");
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export function add(a: number, b: number): number {
return a + b;
}
export function multiply(a: number, b: number): number {
return a * b;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(25.0));
}
_ => panic!("Expected Complete after providing module"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_external_module_default_export() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import greet from "./greeting";
greet("World");
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports[0].specifier, "./greeting");
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export default function greet(name: string): string {
return "Hello, " + name + "!";
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::String("Hello, World!".into()));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_external_module_mixed_exports() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import Calculator, { PI, E } from "./constants";
const calc = new Calculator();
calc.add(PI, E);
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports[0].specifier, "./constants");
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export const PI = 3.14159;
export const E = 2.71828;
export default class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(rv) => {
if let JsValue::Number(n) = *rv {
assert!((n - 5.85987).abs() < 0.0001);
} else {
panic!("Expected Number");
}
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_external_module_aliased_imports() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { value as myValue, compute as calculate } from "./utils";
calculate(myValue);
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export const value = 10;
export function compute(x: number): number {
return x * 2;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(20.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_multiple_external_modules() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { a } from "./moduleA";
import { b } from "./moduleB";
a + b;
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 2);
assert!(imports.iter().any(|i| i.specifier == "./moduleA"));
assert!(imports.iter().any(|i| i.specifier == "./moduleB"));
for req in &imports {
let source = if req.specifier == "./moduleA" {
"export const a = 10;"
} else {
"export const b = 20;"
};
interp
.provide_module(req.resolved_path.clone(), source)
.unwrap();
}
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(30.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_module_namespace_import() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import * as utils from "./utils";
utils.double(utils.BASE);
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export const BASE = 21;
export function double(x: number): number {
return x * 2;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_module_with_internal_imports() {
use tsrun::create_eval_internal_module;
let eval_internal = create_eval_internal_module();
let config = InterpreterConfig {
internal_modules: vec![eval_internal],
..Default::default()
};
let mut interp = Interpreter::with_config(config);
let result = run(
&mut interp,
r#"
import { helper } from "./myModule";
helper(5);
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].specifier, "./myModule");
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
// Module can use internal modules
export function helper(x: number): number {
return x * 10;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(50.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_export_const_variable() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { CONFIG } from "./config";
CONFIG.name + " v" + CONFIG.version;
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export const CONFIG = {
name: "MyApp",
version: "1.0"
};
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::String("MyApp v1.0".into()));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_export_class() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { Point } from "./geometry";
const p = new Point(3, 4);
p.distance();
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
distance(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(5.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_module_path_normalize() {
assert_eq!(ModulePath::resolve("./utils", None).as_str(), "utils");
assert_eq!(ModulePath::resolve("./foo/bar", None).as_str(), "foo/bar");
assert_eq!(ModulePath::resolve("./a/b/../c", None).as_str(), "a/c");
assert_eq!(ModulePath::resolve("./a/./b/./c", None).as_str(), "a/b/c");
}
#[test]
fn test_module_path_resolve_with_base() {
let base = ModulePath::new("/project/src/main.ts");
assert_eq!(
ModulePath::resolve("./utils", Some(&base)).as_str(),
"/project/src/utils"
);
assert_eq!(
ModulePath::resolve("../shared/lib", Some(&base)).as_str(),
"/project/shared/lib"
);
assert_eq!(
ModulePath::resolve("../../config", Some(&base)).as_str(),
"/config"
);
}
#[test]
fn test_module_path_bare_specifier() {
assert_eq!(ModulePath::resolve("lodash", None).as_str(), "lodash");
assert_eq!(
ModulePath::resolve("@scope/package", None).as_str(),
"@scope/package"
);
let base = ModulePath::new("/project/src/main.ts");
assert_eq!(
ModulePath::resolve("lodash", Some(&base)).as_str(),
"lodash"
);
}
#[test]
fn test_module_path_absolute() {
assert_eq!(ModulePath::resolve("/foo/bar", None).as_str(), "/foo/bar");
assert_eq!(ModulePath::resolve("/foo/../bar", None).as_str(), "/bar");
}
#[test]
fn test_eval_with_path_resolves_imports() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"import { foo } from "./utils";"#,
Some("/project/src/main.ts"),
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].specifier, "./utils");
assert_eq!(imports[0].resolved_path.as_str(), "/project/src/utils");
assert!(imports[0].importer.is_none());
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_nested_module_imports_resolve_correctly() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"import { helper } from "./lib/helpers";"#,
Some("/project/src/main.ts"),
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(
imports[0].resolved_path.as_str(),
"/project/src/lib/helpers"
);
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
import { util } from "../shared";
export function helper(): number { return util(); }
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::NeedImports(imports2) => {
assert_eq!(imports2[0].specifier, "../shared");
assert_eq!(imports2[0].resolved_path.as_str(), "/project/src/shared");
assert_eq!(
imports2[0].importer.as_ref().unwrap().as_str(),
"/project/src/lib/helpers"
);
}
_ => panic!("Expected NeedImports for nested import"),
}
}
_ => panic!("Expected NeedImports for initial import"),
}
}
#[test]
fn test_same_module_different_paths_deduplicated() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { a } from "./utils";
import { b } from "./lib/../utils";
a + b;
"#,
Some("/project/main.ts"),
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].resolved_path.as_str(), "/project/utils");
}
_ => panic!("Expected NeedImports for deduplication test"),
}
}
#[test]
fn test_export_star_as_namespace_basic() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { utils } from "./reexport";
utils.add(2, 3) + utils.BASE;
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].specifier, "./reexport");
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"export * as utils from "./utils";"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::NeedImports(imports2) => {
assert_eq!(imports2.len(), 1);
assert_eq!(imports2[0].specifier, "./utils");
interp
.provide_module(
imports2[0].resolved_path.clone(),
r#"
export const BASE: number = 10;
export function add(a: number, b: number): number {
return a + b;
}
"#,
)
.unwrap();
let result3 = run_to_completion(&mut interp).unwrap();
match result3 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(15.0));
}
_ => panic!("Expected Complete, got {:?}", result3),
}
}
_ => panic!("Expected NeedImports for utils, got {:?}", result2),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_export_star_as_namespace_multiple() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { math, str } from "./combined";
math.double(3) + str.len("hello");
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export * as math from "./mathUtils";
export * as str from "./strUtils";
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::NeedImports(imports2) => {
assert_eq!(imports2.len(), 2);
for req in &imports2 {
if req.specifier == "./mathUtils" {
interp
.provide_module(
req.resolved_path.clone(),
r#"export function double(x: number): number { return x * 2; }"#,
)
.unwrap();
} else if req.specifier == "./strUtils" {
interp
.provide_module(
req.resolved_path.clone(),
r#"export function len(s: string): number { return s.length; }"#,
)
.unwrap();
}
}
let result3 = run_to_completion(&mut interp).unwrap();
match result3 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(11.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports for utility modules"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_export_star_as_namespace_with_other_exports() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { helpers, VERSION } from "./api";
helpers.greet() + " v" + VERSION;
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export * as helpers from "./helpers";
export const VERSION: string = "1.0";
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::NeedImports(imports2) => {
interp
.provide_module(
imports2[0].resolved_path.clone(),
r#"export function greet(): string { return "Hello"; }"#,
)
.unwrap();
let result3 = run_to_completion(&mut interp).unwrap();
match result3 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::String("Hello v1.0".into()));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports for helpers"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_export_star_as_namespace_nested() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { nested } from "./level1";
nested.inner.value;
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"export * as nested from "./level2";"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::NeedImports(imports2) => {
interp
.provide_module(
imports2[0].resolved_path.clone(),
r#"export * as inner from "./level3";"#,
)
.unwrap();
let result3 = run_to_completion(&mut interp).unwrap();
match result3 {
StepResult::NeedImports(imports3) => {
interp
.provide_module(
imports3[0].resolved_path.clone(),
r#"export const value: number = 42;"#,
)
.unwrap();
let result4 = run_to_completion(&mut interp).unwrap();
match result4 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports for level3"),
}
}
_ => panic!("Expected NeedImports for level2"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_let_variable() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { count, increment } from "./counter";
const before = count;
increment();
const after = count;
[before, after];
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export let count: number = 0;
export function increment(): void {
count++;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
if let JsValue::Object(arr) = &*value {
let arr_ref = arr.borrow();
let before = arr_ref
.get_property(&PropertyKey::Index(0))
.unwrap_or(JsValue::Undefined);
let after = arr_ref
.get_property(&PropertyKey::Index(1))
.unwrap_or(JsValue::Undefined);
assert_eq!(before, JsValue::Number(0.0), "before should be 0");
assert_eq!(
after,
JsValue::Number(1.0),
"after should be 1 (live binding)"
);
} else {
panic!("Expected array result");
}
}
_ => panic!("Expected Complete, got {:?}", result2),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_multiple_increments() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { value, add } from "./accumulator";
add(10);
add(20);
add(12);
value;
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export let value: number = 0;
export function add(n: number): void {
value += n;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_object_mutation() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { state, setState } from "./state";
const before = state.value;
setState(42);
const after = state.value;
[before, after];
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export const state = { value: 0 };
export function setState(v: number): void {
state.value = v;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
if let JsValue::Object(arr) = &*value {
let arr_ref = arr.borrow();
let before = arr_ref
.get_property(&PropertyKey::Index(0))
.unwrap_or(JsValue::Undefined);
let after = arr_ref
.get_property(&PropertyKey::Index(1))
.unwrap_or(JsValue::Undefined);
assert_eq!(before, JsValue::Number(0.0));
assert_eq!(after, JsValue::Number(42.0));
} else {
panic!("Expected array result");
}
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_reassignment() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { config, updateConfig } from "./config";
const before = config;
updateConfig({ name: "updated", value: 42 });
const after = config;
[before.name, after.name, after.value];
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export let config = { name: "initial", value: 0 };
export function updateConfig(newConfig: { name: string, value: number }): void {
config = newConfig;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
if let JsValue::Object(arr) = &*value {
let arr_ref = arr.borrow();
let before_name = arr_ref
.get_property(&PropertyKey::Index(0))
.unwrap_or(JsValue::Undefined);
let after_name = arr_ref
.get_property(&PropertyKey::Index(1))
.unwrap_or(JsValue::Undefined);
let after_value = arr_ref
.get_property(&PropertyKey::Index(2))
.unwrap_or(JsValue::Undefined);
assert_eq!(before_name, JsValue::String("initial".into()));
assert_eq!(after_name, JsValue::String("updated".into()));
assert_eq!(after_value, JsValue::Number(42.0));
} else {
panic!("Expected array result");
}
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_namespace_import() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import * as counter from "./counter";
const before = counter.count;
counter.increment();
counter.increment();
const after = counter.count;
[before, after];
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export let count: number = 0;
export function increment(): void {
count++;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
if let JsValue::Object(arr) = &*value {
let arr_ref = arr.borrow();
let before = arr_ref
.get_property(&PropertyKey::Index(0))
.unwrap_or(JsValue::Undefined);
let after = arr_ref
.get_property(&PropertyKey::Index(1))
.unwrap_or(JsValue::Undefined);
assert_eq!(before, JsValue::Number(0.0));
assert_eq!(after, JsValue::Number(2.0));
} else {
panic!("Expected array result");
}
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_through_reexport() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { count, increment } from "./reexport";
const before = count;
increment();
const after = count;
[before, after];
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export { count, increment } from "./counter";
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::NeedImports(imports2) => {
interp
.provide_module(
imports2[0].resolved_path.clone(),
r#"
export let count: number = 0;
export function increment(): void {
count++;
}
"#,
)
.unwrap();
let result3 = run_to_completion(&mut interp).unwrap();
match result3 {
StepResult::Complete(value) => {
if let JsValue::Object(arr) = &*value {
let arr_ref = arr.borrow();
let before = arr_ref
.get_property(&PropertyKey::Index(0))
.unwrap_or(JsValue::Undefined);
let after = arr_ref
.get_property(&PropertyKey::Index(1))
.unwrap_or(JsValue::Undefined);
assert_eq!(before, JsValue::Number(0.0));
assert_eq!(after, JsValue::Number(1.0));
} else {
panic!("Expected array result");
}
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports for counter"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_aliased_import() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { count as myCount, increment as inc } from "./counter";
const before = myCount;
inc();
inc();
inc();
const after = myCount;
[before, after];
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export let count: number = 0;
export function increment(): void {
count++;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
if let JsValue::Object(arr) = &*value {
let arr_ref = arr.borrow();
let before = arr_ref
.get_property(&PropertyKey::Index(0))
.unwrap_or(JsValue::Undefined);
let after = arr_ref
.get_property(&PropertyKey::Index(1))
.unwrap_or(JsValue::Undefined);
assert_eq!(before, JsValue::Number(0.0));
assert_eq!(after, JsValue::Number(3.0));
} else {
panic!("Expected array result");
}
}
_ => panic!("Expected Complete"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_namespace_simple() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import * as counter from "./counter";
counter.count;
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"export let count: number = 42;"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
other => panic!("Expected Complete, got {:?}", other),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_namespace_call() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import * as counter from "./counter";
counter.getValue();
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export function getValue(): number {
return 42;
}
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
other => panic!("Expected Complete, got {:?}", other),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_reexport_debug() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { getValue } from "./reexport";
getValue();
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"export { getValue } from "./source";"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp).unwrap();
match result2 {
StepResult::NeedImports(imports2) => {
interp
.provide_module(
imports2[0].resolved_path.clone(),
r#"
export function getValue(): number {
return 42;
}
"#,
)
.unwrap();
let result3 = run_to_completion(&mut interp).unwrap();
match result3 {
StepResult::Complete(value) => {
assert_eq!(value, JsValue::Number(42.0));
}
_ => panic!("Expected Complete, got {:?}", result3),
}
}
_ => panic!("Expected NeedImports for source"),
}
}
_ => panic!("Expected NeedImports"),
}
}
#[test]
fn test_live_binding_imported_value_is_readonly() {
let mut interp = Interpreter::new();
let result = run(
&mut interp,
r#"
import { count } from "./counter";
count = 100; // Should error - imports are read-only
"#,
None,
)
.unwrap();
match result {
StepResult::NeedImports(imports) => {
interp
.provide_module(
imports[0].resolved_path.clone(),
r#"
export let count: number = 0;
"#,
)
.unwrap();
let result2 = run_to_completion(&mut interp);
assert!(result2.is_err(), "Assigning to import should error");
let err = result2.unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("const")
|| msg.contains("assign")
|| msg.contains("immutable")
|| msg.contains("read"),
"Error should mention const/assign/immutable/read, got: {}",
msg
);
}
_ => panic!("Expected NeedImports"),
}
}