fix: 修复 String 原型方法边界条件

This commit is contained in:
爱特每羊队
2026-06-27 19:45:18 +08:00
parent a1993e617d
commit f9297097bf
4 changed files with 571 additions and 28 deletions
@@ -267,3 +267,259 @@ fn string_call_conversion_stays_primitive() {
assert!(v.is_string());
assert_eq!(to_str(&vm, v), "123");
}
// ── Plan 01: replace tests ──
#[test]
fn string_replace_non_global_first_only() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'aaa'.replace(/a/, 'b')").unwrap();
assert_eq!(to_str(&vm, s), "baa");
}
#[test]
fn string_replace_global_all() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'aaa'.replace(/a/g, 'b')").unwrap();
assert_eq!(to_str(&vm, s), "bbb");
}
#[test]
fn string_replace_function_replacer_match() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.replace(/l/, function(m){return m.toUpperCase()})").unwrap();
assert_eq!(to_str(&vm, s), "heLlo");
}
#[test]
fn string_replace_function_replacer_offset() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'abc'.replace(/b/, function(m,o){return o})").unwrap();
assert_eq!(to_str(&vm, s), "a1c");
}
#[test]
fn string_replace_string_pattern() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.replace('l', 'L')").unwrap();
assert_eq!(to_str(&vm, s), "heLlo");
}
// ── Plan 02: split tests ──
#[test]
fn string_split_regex_capture_groups() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'a1b2c'.split(/(\\d)/)").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 5);
assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a");
assert_eq!(to_str(&vm, obj.get_prop_at(1)), "1");
assert_eq!(to_str(&vm, obj.get_prop_at(2)), "b");
assert_eq!(to_str(&vm, obj.get_prop_at(3)), "2");
assert_eq!(to_str(&vm, obj.get_prop_at(4)), "c");
}
#[test]
fn string_split_regex_capture_with_limit() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'a1b2c'.split(/(\\d)/, 3)").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3);
}
#[test]
fn string_split_regex_no_capture() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'a,b,c'.split(/,/)").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3);
assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a");
assert_eq!(to_str(&vm, obj.get_prop_at(1)), "b");
assert_eq!(to_str(&vm, obj.get_prop_at(2)), "c");
}
#[test]
fn string_split_string_separator() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'a,b,c'.split(',')").unwrap();
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3);
}
// ── Plan 03: match tests ──
#[test]
fn string_match_non_global_captures() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'12-34'.match(/(\\d+)-(\\d+)/)").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3);
assert_eq!(to_str(&vm, obj.get_prop_at(0)), "12-34");
assert_eq!(to_str(&vm, obj.get_prop_at(1)), "12");
assert_eq!(to_str(&vm, obj.get_prop_at(2)), "34");
}
#[test]
fn string_match_no_match_null() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'abc'.match(/x/)").unwrap();
assert!(result.is_null());
}
#[test]
fn string_match_global_flat() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'aba'.match(/a/g)").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 2);
assert_eq!(to_str(&vm, obj.get_prop_at(0)), "a");
assert_eq!(to_str(&vm, obj.get_prop_at(1)), "a");
}
#[test]
fn string_match_string_pattern() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.match('ll')").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 1);
assert_eq!(to_str(&vm, obj.get_prop_at(0)), "ll");
}
// ── Plan 04: matchAll tests ──
#[test]
fn string_match_all_returns_iterator() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'aba'.matchAll(/a/g)").unwrap();
assert!(result.is_object());
}
#[test]
fn string_match_all_next_returns_match() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var it = 'ab'.matchAll(/a/g); it.next()").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
let done = obj.get_prop_at(1).as_bool();
assert!(!done);
assert!(obj.get_prop_at(0).is_object());
}
#[test]
fn string_match_all_next_exhausted() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var it = 'a'.matchAll(/x/g); it.next(); it.next()").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
let done = obj.get_prop_at(1).as_bool();
assert!(done);
}
// ── Plan 05: substring tests ──
#[test]
fn string_substring_nan_index() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.substring(NaN, 2)").unwrap();
assert_eq!(to_str(&vm, s), "he");
}
#[test]
fn string_substring_negative_index() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.substring(-3, 2)").unwrap();
assert_eq!(to_str(&vm, s), "he");
}
#[test]
fn string_substring_swap() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.substring(3, 1)").unwrap();
assert_eq!(to_str(&vm, s), "el");
}
// ── Plan 06: substr + at tests ──
#[test]
fn string_substr_positive() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.substr(1, 3)").unwrap();
assert_eq!(to_str(&vm, s), "ell");
}
#[test]
fn string_substr_negative_start() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.substr(-3, 2)").unwrap();
assert_eq!(to_str(&vm, s), "ll");
}
#[test]
fn string_substr_negative_length() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.substr(1, -1)").unwrap();
assert_eq!(to_str(&vm, s), "");
}
#[test]
fn string_at_positive() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.at(1)").unwrap();
assert_eq!(to_str(&vm, s), "e");
}
#[test]
fn string_at_negative() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.at(-1)").unwrap();
assert_eq!(to_str(&vm, s), "o");
}
#[test]
fn string_at_out_of_range() {
let mut vm = Vm::new();
let s = eval(&mut vm, "'hello'.at(10)").unwrap();
assert!(s.is_undefined());
}
// ── Plan 07: lastIndexOf tests ──
#[test]
fn string_last_index_of_basic() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.lastIndexOf('l')").unwrap();
assert_eq!(result.as_int(), 3);
}
#[test]
fn string_last_index_of_not_found() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.lastIndexOf('x')").unwrap();
assert_eq!(result.as_int(), -1);
}
#[test]
fn string_last_index_of_empty_string() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.lastIndexOf('')").unwrap();
assert_eq!(result.as_int(), 5);
}
#[test]
fn string_last_index_of_with_position() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.lastIndexOf('l', 2)").unwrap();
assert_eq!(result.as_int(), 2);
}
#[test]
fn string_last_index_of_empty_with_position() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.lastIndexOf('', 3)").unwrap();
assert_eq!(result.as_int(), 3);
}