Files
OxideJS-New/crates/oxide_vm/tests/event_target_tests.rs
T

297 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! EventTarget 验收测试:addEventListener / removeEventListener / dispatchEvent
//! 三方法、原型链(Worker / MessagePort / BroadcastChannel 经 EventTarget.prototype)、
//! GC 剪枝。
//!
//! 集成测试走公共 API(`eval` 返回脚本完成值字符串),不访问私有字段。
//! 目标对象一律用 MessagePort(EventTarget 实例)——普通对象原型链上没有
//! EventTarget.prototype,DOM 语义下 addEventListener 也只存在于 EventTarget 实例。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_compiler::DefaultCompilerService;
use oxide_parser::Allocator;
use oxide_vm::vm::Vm;
/// 编译并运行脚本,返回完成值字符串(解析 / 编译 / 运行失败时返回错误描述)。
fn eval(source: &str) -> String {
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, source) {
Ok(p) => p,
Err(e) => return format!("parse error: {}", e[0].message),
};
let module = match Compiler::new().compile(&program) {
Ok(m) => m,
Err(e) => return format!("compile error: {e}"),
};
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
match vm.run(&Arc::new(module)) {
// 字符串结果经 lookup_str 提取内容(JsValue Display 只打印 {string})。
Ok(result) => vm.lookup_str(result).unwrap_or_else(|| format!("{result}")),
Err(e) => format!("vm error: {e}"),
}
}
/// 建一个注入编译服务的 VM 并运行首段脚本(供 GC 测试中途收集)。
fn vm_with_script(source: &str) -> Vm {
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse 应成功");
let module = Compiler::new().compile(&program).expect("compile 应成功");
let mut vm = Vm::new();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
vm.run(&Arc::new(module)).expect("run 应成功");
vm
}
/// 在既有 VM 上运行一段脚本,返回完成值字符串。
fn vm_eval(vm: &mut Vm, source: &str) -> String {
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse 应成功");
let module = Compiler::new().compile(&program).expect("compile 应成功");
match vm.run(&Arc::new(module)) {
// 字符串结果经 lookup_str 提取内容(JsValue Display 只打印 {string})。
Ok(result) => vm.lookup_str(result).unwrap_or_else(|| format!("{result}")),
Err(e) => format!("vm error: {e}"),
}
}
/// 建一个 MessagePort 目标的前缀(每个测试目标独立,避免跨测试串扰)。
const TARGET: &str = "var mc = new MessageChannel(); var t = mc.port1;";
/// dispatchEvent(new Event('x')) 返回 true(无 preventDefault)。
#[test]
fn dispatch_event_returns_true() {
assert_eq!(eval(&format!("{TARGET} t.dispatchEvent(new Event('x'));")), "true");
}
/// preventDefault() 后 dispatchEvent 返回 false(cancelable 事件)。
#[test]
fn dispatch_event_returns_false_after_prevent_default() {
assert_eq!(
eval(&format!(
"{TARGET} t.addEventListener('x', function(e) {{ e.preventDefault(); }}); \
t.dispatchEvent(new Event('x', {{ cancelable: true }}));"
)),
"false"
);
}
/// 非 cancelable 事件 preventDefault 无效,dispatchEvent 仍返回 true。
#[test]
fn dispatch_event_non_cancelable_stays_true() {
assert_eq!(
eval(&format!(
"{TARGET} t.addEventListener('x', function(e) {{ e.preventDefault(); }}); \
t.dispatchEvent(new Event('x'));"
)),
"true"
);
}
/// removeEventListener 生效:移除后派发不再调用监听器。
#[test]
fn remove_event_listener_works() {
assert_eq!(
eval(&format!(
"{TARGET} var calls = 0; \
var fn = function(e) {{ calls++; }}; \
t.addEventListener('x', fn); \
t.removeEventListener('x', fn); \
t.dispatchEvent(new Event('x')); \
calls;"
)),
"0"
);
}
/// 重复登记(同 type + callback + capture)为 no-op:派发只调用一次。
#[test]
fn duplicate_add_event_listener_is_noop() {
assert_eq!(
eval(&format!(
"{TARGET} var calls = 0; \
var fn = function(e) {{ calls++; }}; \
t.addEventListener('x', fn); \
t.addEventListener('x', fn); \
t.dispatchEvent(new Event('x')); \
calls;"
)),
"1"
);
}
/// once 选项:监听器调用一次后自动移除。
#[test]
fn once_listener_removed_after_call() {
assert_eq!(
eval(&format!(
"{TARGET} var calls = 0; \
var fn = function(e) {{ calls++; }}; \
t.addEventListener('x', fn, {{ once: true }}); \
t.dispatchEvent(new Event('x')); \
t.dispatchEvent(new Event('x')); \
calls;"
)),
"1"
);
}
/// stopImmediatePropagation 截断后续监听器。
#[test]
fn stop_immediate_propagation_truncates() {
assert_eq!(
eval(&format!(
"{TARGET} var calls = 0; \
t.addEventListener('x', function(e) {{ calls++; e.stopImmediatePropagation(); }}); \
t.addEventListener('x', function(e) {{ calls++; }}); \
t.dispatchEvent(new Event('x')); \
calls;"
)),
"1"
);
}
/// 监听器回调的 this 是目标对象、实参是事件(e.type 为类型串)。
#[test]
fn listener_receives_target_this_and_event() {
assert_eq!(
eval(&format!(
"{TARGET} var gotThis = null; var gotType = null; \
t.addEventListener('x', function(e) {{ gotThis = (this === t); gotType = e.type; }}); \
t.dispatchEvent(new Event('x')); \
gotThis + ':' + gotType;"
)),
"true:x"
);
}
/// MessagePort 原型链经 EventTarget.prototype:port.addEventListener 生效。
#[test]
fn port_add_event_listener_works() {
assert_eq!(
eval(
"var mc = new MessageChannel(); var calls = 0; \
mc.port1.addEventListener('message', function(e) { calls++; }); \
mc.port1.dispatchEvent(new Event('message')); \
calls;"
),
"1"
);
}
/// BroadcastChannel 原型链经 EventTarget.prototype:channel.addEventListener 生效。
#[test]
fn channel_add_event_listener_works() {
assert_eq!(
eval(
"var ch = new BroadcastChannel('t'); var calls = 0; \
ch.addEventListener('message', function(e) { calls++; }); \
ch.dispatchEvent(new Event('message')); \
calls;"
),
"1"
);
}
/// Worker.prototype 原型链经 EventTarget.prototype:worker.addEventListener 生效。
#[test]
fn worker_add_event_listener_works() {
let path = std::env::temp_dir().join(format!("oxide_et_{}.js", std::process::id()));
let _ = std::fs::write(&path, "1");
let script = format!(
"var w = new Worker('{}'); var calls = 0; \
w.addEventListener('message', function(e) {{ calls++; }}); \
w.dispatchEvent(new Event('message')); \
calls;",
path.display()
);
assert_eq!(eval(&script), "1");
let _ = std::fs::remove_file(&path);
}
/// 监听器对象经 GC 后仍有效(目标存活时回调随目标存活,无悬垂)。
#[test]
fn listener_survives_gc_while_target_live() {
let mut vm = vm_with_script(
"var mc = new MessageChannel(); globalThis.t = mc.port1; \
globalThis.t.addEventListener('x', function(e) { globalThis.calls = (globalThis.calls || 0) + 1; });",
);
// 强制完整收集:目标存活(globalThis.t),监听器回调应保活。
vm.collect_session_gc();
// 收集后派发:监听器仍应被调用(无悬垂)。
assert_eq!(vm_eval(&mut vm, "globalThis.t.dispatchEvent(new Event('x')); globalThis.calls;"), "1");
}
/// 目标对象被回收后注册表剪枝(GC 后无悬垂指针)。
#[test]
fn registry_pruned_after_target_collected() {
// t 是局部变量,脚本结束后无引用 → 目标死亡。
let mut vm =
vm_with_script("var mc = new MessageChannel(); var t = mc.port1; t.addEventListener('x', function() {});");
// 强制完整收集:注册表应按 mark 位剪枝死目标条目。
vm.collect_session_gc();
// 剪枝后无悬垂:新建同型目标派发不触发旧监听器(旧条目已剪),无崩溃。
assert_eq!(
vm_eval(&mut vm, "var mc2 = new MessageChannel(); mc2.port1.dispatchEvent(new Event('x')); 'ok';"),
"ok"
);
}
/// ErrorEvent 构造器:init 的 message 经原型 getter 可读。
#[test]
fn error_event_constructor_message() {
assert_eq!(
eval("new ErrorEvent('error', { message: 'boom' }).message;"),
"boom"
);
}
/// ErrorEvent 缺省字段:message 空串、filename 空串、lineno 与 colno 为 0、
/// error 为 null。
#[test]
fn error_event_defaults() {
assert_eq!(
eval(
"var e = new ErrorEvent('error'); \
e.message === '' && e.filename === '' && e.lineno === 0 && e.colno === 0 && e.error === null;"
),
"true"
);
}
/// ErrorEvent 原型链:instanceof ErrorEvent 与 instanceof Event 均真,
/// 原型双亲为 Event.prototype。
#[test]
fn error_event_proto_chain() {
assert_eq!(
eval(
"var e = new ErrorEvent('error'); \
e instanceof ErrorEvent && e instanceof Event && \
Object.getPrototypeOf(e) === ErrorEvent.prototype && \
Object.getPrototypeOf(ErrorEvent.prototype) === Event.prototype;"
),
"true"
);
}
/// ErrorEvent 非构造形态抛 TypeError。
#[test]
fn error_event_requires_new() {
assert_eq!(
eval("try { ErrorEvent('error'); 'no-throw'; } catch (e) { e instanceof TypeError; }"),
"true"
);
}
/// ErrorEvent 盒边 GC 保活:error 字段引用对象经完整收集后仍存活。
#[test]
fn error_event_box_edge_survives_gc() {
let mut vm = vm_with_script(
"globalThis.e = new ErrorEvent('error', { error: { tag: 1 } });",
);
// 强制完整收集:error 字段是盒内 GC 边,对象应保活。
vm.collect_session_gc();
assert_eq!(vm_eval(&mut vm, "globalThis.e.error.tag;"), "1");
}