diff --git a/Cargo.lock b/Cargo.lock index 86a2329fdc2..84e4b1db34c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,6 +335,7 @@ dependencies = [ "icu_plurals", "icu_provider", "indexmap", + "indoc", "jemallocator", "num-bigint", "num-integer", @@ -352,6 +353,7 @@ dependencies = [ "static_assertions", "sys-locale", "tap", + "textwrap 0.16.0", "thiserror", "unicode-normalization", "writeable", @@ -2225,6 +2227,12 @@ dependencies = [ "regex", ] +[[package]] +name = "indoc" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2b9d82064e8a0226fddb3547f37f28eaa46d0fc210e275d835f08cf3b76a7" + [[package]] name = "instant" version = "0.1.12" @@ -3635,6 +3643,12 @@ dependencies = [ "serde", ] +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "smol" version = "1.3.0" @@ -3871,6 +3885,11 @@ name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] [[package]] name = "thiserror" @@ -4215,6 +4234,16 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unicode-linebreak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" +dependencies = [ + "hashbrown 0.12.3", + "regex", +] + [[package]] name = "unicode-normalization" version = "0.1.22" diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index b965879d4f2..b1d6ee3f35f 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -82,6 +82,8 @@ sys-locale = { version = "0.2.3", optional = true } [dev-dependencies] criterion = "0.4.0" float-cmp = "0.9.0" +indoc = "2.0.0" +textwrap = "0.16.0" [target.x86_64-unknown-linux-gnu.dev-dependencies] jemallocator = "0.5.0" diff --git a/boa_engine/src/builtins/array/tests.rs b/boa_engine/src/builtins/array/tests.rs index 5501a1b30d9..fed6ed2d9cc 100644 --- a/boa_engine/src/builtins/array/tests.rs +++ b/boa_engine/src/builtins/array/tests.rs @@ -1,1631 +1,950 @@ -use boa_parser::Source; - use super::Array; -use crate::builtins::Number; -use crate::{forward, Context, JsValue}; +use crate::{ + builtins::{error::ErrorKind, Number}, + run_test, Context, JsValue, TestAction, +}; +use indoc::indoc; #[test] fn is_array() { - let mut context = Context::default(); - let init = r#" - var empty = []; - var new_arr = new Array(); - var many = ["a", "b", "c"]; - "#; - context.eval_script(Source::from_bytes(init)).unwrap(); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray(empty)")) - .unwrap(), - JsValue::new(true) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray(new_arr)")) - .unwrap(), - JsValue::new(true) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray(many)")) - .unwrap(), - JsValue::new(true) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray([1, 2, 3])")) - .unwrap(), - JsValue::new(true) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray([])")) - .unwrap(), - JsValue::new(true) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray({})")) - .unwrap(), - JsValue::new(false) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray(new Array)")) - .unwrap(), - JsValue::new(true) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray()")) - .unwrap(), - JsValue::new(false) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray({ constructor: Array })")) - .unwrap(), - JsValue::new(false) - ); - assert_eq!( - context - .eval_script(Source::from_bytes( - "Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })" - )) - .unwrap(), - JsValue::new(false) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray(17)")) - .unwrap(), - JsValue::new(false) - ); - assert_eq!( - context - .eval_script(Source::from_bytes( - "Array.isArray({ __proto__: Array.prototype })" - )) - .unwrap(), - JsValue::new(false) - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.isArray({ length: 0 })")) - .unwrap(), - JsValue::new(false) - ); + run_test([ + TestAction::assert("Array.isArray([])"), + TestAction::assert("Array.isArray(new Array())"), + TestAction::assert("Array.isArray(['a', 'b', 'c'])"), + TestAction::assert("Array.isArray([1, 2, 3])"), + TestAction::assert("!Array.isArray({})"), + TestAction::assert("Array.isArray(new Array)"), + TestAction::assert("!Array.isArray()"), + TestAction::assert("!Array.isArray({ constructor: Array })"), + TestAction::assert( + "!Array.isArray({ push: Array.prototype.push, concat: Array.prototype.concat })", + ), + TestAction::assert("!Array.isArray(17)"), + TestAction::assert("!Array.isArray({ __proto__: Array.prototype })"), + TestAction::assert("!Array.isArray({ length: 0 })"), + ]); } #[test] fn of() { - let mut context = Context::default(); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.of(1, 2, 3)")) - .unwrap() - .to_string(&mut context) - .unwrap(), - context - .eval_script(Source::from_bytes("[1, 2, 3]")) - .unwrap() - .to_string(&mut context) - .unwrap() - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.of(1, 'a', [], undefined, null)")) - .unwrap() - .to_string(&mut context) - .unwrap(), - context - .eval_script(Source::from_bytes("[1, 'a', [], undefined, null]")) - .unwrap() - .to_string(&mut context) - .unwrap() - ); - assert_eq!( - context - .eval_script(Source::from_bytes("Array.of()")) - .unwrap() - .to_string(&mut context) - .unwrap(), - context - .eval_script(Source::from_bytes("[]")) - .unwrap() - .to_string(&mut context) - .unwrap() - ); - - context - .eval_script(Source::from_bytes( - r#"let a = Array.of.call(Date, "a", undefined, 3);"#, - )) - .unwrap(); - assert_eq!( - context - .eval_script(Source::from_bytes("a instanceof Date")) - .unwrap(), - JsValue::new(true) - ); - assert_eq!( - context.eval_script(Source::from_bytes("a[0]")).unwrap(), - JsValue::new("a") - ); - assert_eq!( - context.eval_script(Source::from_bytes("a[1]")).unwrap(), - JsValue::undefined() - ); - assert_eq!( - context.eval_script(Source::from_bytes("a[2]")).unwrap(), - JsValue::new(3) - ); - assert_eq!( - context.eval_script(Source::from_bytes("a.length")).unwrap(), - JsValue::new(3) - ); + run_test([ + TestAction::run_harness(), + TestAction::assert("arrayEquals(Array.of(1, 2, 3), [1, 2, 3])"), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.of(1, 'a', [], undefined, null), + [1, 'a', [], undefined, null] + ) + "#}), + TestAction::assert("arrayEquals(Array.of(), [])"), + TestAction::run("let a = Array.of.call(Date, 'a', undefined, 3);"), + TestAction::assert("a instanceof Date"), + TestAction::assert_eq("a[0]", "a"), + TestAction::assert_eq("a[1]", JsValue::undefined()), + TestAction::assert_eq("a[2]", 3), + TestAction::assert_eq("a.length", 3), + ]); } #[test] fn concat() { - let mut context = Context::default(); - let init = r#" - var empty = []; - var one = [1]; - "#; - context.eval_script(Source::from_bytes(init)).unwrap(); - // Empty ++ Empty - let ee = context - .eval_script(Source::from_bytes("empty.concat(empty)")) - .unwrap() - .display() - .to_string(); - assert_eq!(ee, "[]"); - // Empty ++ NonEmpty - let en = context - .eval_script(Source::from_bytes("empty.concat(one)")) - .unwrap() - .display() - .to_string(); - assert_eq!(en, "[ 1 ]"); - // NonEmpty ++ Empty - let ne = context - .eval_script(Source::from_bytes("one.concat(empty)")) - .unwrap() - .display() - .to_string(); - assert_eq!(ne, "[ 1 ]"); - // NonEmpty ++ NonEmpty - let nn = context - .eval_script(Source::from_bytes("one.concat(one)")) - .unwrap() - .display() - .to_string(); - assert_eq!(nn, "[ 1, 1 ]"); + run_test([ + TestAction::run_harness(), + // Empty ++ Empty + TestAction::assert("arrayEquals([].concat([]), [])"), + // Empty ++ NonEmpty + TestAction::assert("arrayEquals([].concat([1]), [1])"), + // NonEmpty ++ Empty + TestAction::assert("arrayEquals([1].concat([]), [1])"), + // NonEmpty ++ NonEmpty + TestAction::assert("arrayEquals([1].concat([1]), [1, 1])"), + ]); } #[test] fn copy_within() { - let mut context = Context::default(); - - let target = forward(&mut context, "[1,2,3,4,5].copyWithin(-2).join('.')"); - assert_eq!(target, String::from("\"1.2.3.1.2\"")); - - let start = forward(&mut context, "[1,2,3,4,5].copyWithin(0, 3).join('.')"); - assert_eq!(start, String::from("\"4.5.3.4.5\"")); - - let end = forward(&mut context, "[1,2,3,4,5].copyWithin(0, 3, 4).join('.')"); - assert_eq!(end, String::from("\"4.2.3.4.5\"")); - - let negatives = forward(&mut context, "[1,2,3,4,5].copyWithin(-2, -3, -1).join('.')"); - assert_eq!(negatives, String::from("\"1.2.3.3.4\"")); + run_test([ + TestAction::run_harness(), + TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(-2), [1,2,3,1,2])"), + TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(0, 3), [4,5,3,4,5])"), + TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(0, 3, 4), [4,2,3,4,5])"), + TestAction::assert("arrayEquals([1,2,3,4,5].copyWithin(-2, -3, -1), [1,2,3,3,4])"), + ]); } #[test] fn join() { - let mut context = Context::default(); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - "#; - eprintln!("{}", forward(&mut context, init)); - // Empty - let empty = forward(&mut context, "empty.join('.')"); - assert_eq!(empty, String::from("\"\"")); - // One - let one = forward(&mut context, "one.join('.')"); - assert_eq!(one, String::from("\"a\"")); - // Many - let many = forward(&mut context, "many.join('.')"); - assert_eq!(many, String::from("\"a.b.c\"")); + run_test([ + TestAction::assert_eq("[].join('.')", ""), + TestAction::assert_eq("['a'].join('.')", "a"), + TestAction::assert_eq("['a', 'b', 'c'].join('.')", "a.b.c"), + ]); } #[test] fn to_string() { - let mut context = Context::default(); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - "#; - eprintln!("{}", forward(&mut context, init)); - // Empty - let empty = forward(&mut context, "empty.toString()"); - assert_eq!(empty, String::from("\"\"")); - // One - let one = forward(&mut context, "one.toString()"); - assert_eq!(one, String::from("\"a\"")); - // Many - let many = forward(&mut context, "many.toString()"); - assert_eq!(many, String::from("\"a,b,c\"")); + run_test([ + TestAction::assert_eq("[].toString()", ""), + TestAction::assert_eq("['a'].toString()", "a"), + TestAction::assert_eq("['a', 'b', 'c'].toString()", "a,b,c"), + ]); } #[test] fn every() { - let mut context = Context::default(); // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every - let init = r#" - var empty = []; - - var array = [11, 23, 45]; - function callback(element) { - return element > 10; - } - function callback2(element) { - return element < 10; - } - - var appendArray = [1,2,3,4]; - function appendingCallback(elem,index,arr) { - arr.push('new'); - return elem !== "new"; - } - - var delArray = [1,2,3,4]; - function deletingCallback(elem,index,arr) { - arr.pop() - return elem < 3; - } - "#; - eprintln!("{}", forward(&mut context, init)); - let result = forward(&mut context, "array.every(callback);"); - assert_eq!(result, "true"); - - let result = forward(&mut context, "empty.every(callback);"); - assert_eq!(result, "true"); - - let result = forward(&mut context, "array.every(callback2);"); - assert_eq!(result, "false"); - - let result = forward(&mut context, "appendArray.every(appendingCallback);"); - assert_eq!(result, "true"); - - let result = forward(&mut context, "delArray.every(deletingCallback);"); - assert_eq!(result, "true"); + run_test([ + TestAction::run(indoc! {r#" + function appendingCallback(elem,index,arr) { + arr.push('new'); + return elem !== "new"; + } + function deletingCallback(elem,index,arr) { + arr.pop() + return elem < 3; + } + "#}), + TestAction::assert("[11, 23, 45].every(e => e > 10)"), + TestAction::assert("[].every(e => e < 10)"), + TestAction::assert("![11, 23, 45].every(e => e < 10)"), + TestAction::assert("[1,2,3,4].every(appendingCallback)"), + TestAction::assert("[1,2,3,4].every(deletingCallback)"), + ]); } #[test] fn find() { - let mut context = Context::default(); - let init = r#" - function comp(a) { - return a == "a"; - } - var many = ["a", "b", "c"]; - "#; - eprintln!("{}", forward(&mut context, init)); - let found = forward(&mut context, "many.find(comp)"); - assert_eq!(found, String::from("\"a\"")); + run_test([TestAction::assert_eq( + "['a', 'b', 'c'].find(e => e == 'a')", + "a", + )]); } #[test] fn find_index() { - let mut context = Context::default(); - - let code = r#" - function comp(item) { - return item == 2; - } - var many = [1, 2, 3]; - var empty = []; - var missing = [4, 5, 6]; - "#; - - forward(&mut context, code); - - let many = forward(&mut context, "many.findIndex(comp)"); - assert_eq!(many, String::from("1")); - - let empty = forward(&mut context, "empty.findIndex(comp)"); - assert_eq!(empty, String::from("-1")); - - let missing = forward(&mut context, "missing.findIndex(comp)"); - assert_eq!(missing, String::from("-1")); + run_test([ + TestAction::assert_eq("[1, 2, 3].findIndex(e => e == 2)", 1), + TestAction::assert_eq("[].findIndex(e => e == 2)", -1), + TestAction::assert_eq("[4, 5, 6].findIndex(e => e == 2)", -1), + ]); } #[test] fn flat() { - let mut context = Context::default(); - - let code = r#" - var depth1 = ['a', ['b', 'c']]; - var flat_depth1 = depth1.flat(); - - var depth2 = ['a', ['b', ['c'], 'd']]; - var flat_depth2 = depth2.flat(2); - "#; - forward(&mut context, code); - - assert_eq!(forward(&mut context, "flat_depth1[0]"), "\"a\""); - assert_eq!(forward(&mut context, "flat_depth1[1]"), "\"b\""); - assert_eq!(forward(&mut context, "flat_depth1[2]"), "\"c\""); - assert_eq!(forward(&mut context, "flat_depth1.length"), "3"); - - assert_eq!(forward(&mut context, "flat_depth2[0]"), "\"a\""); - assert_eq!(forward(&mut context, "flat_depth2[1]"), "\"b\""); - assert_eq!(forward(&mut context, "flat_depth2[2]"), "\"c\""); - assert_eq!(forward(&mut context, "flat_depth2[3]"), "\"d\""); - assert_eq!(forward(&mut context, "flat_depth2.length"), "4"); -} - -#[test] -fn flat_empty() { - let mut context = Context::default(); - - let code = r#" - var empty = [[]]; - var flat_empty = empty.flat(); - "#; - forward(&mut context, code); - - assert_eq!(forward(&mut context, "flat_empty.length"), "0"); -} - -#[test] -fn flat_infinity() { - let mut context = Context::default(); - - let code = r#" - var arr = [[[[[['a']]]]]]; - var flat_arr = arr.flat(Infinity) - "#; - forward(&mut context, code); - - assert_eq!(forward(&mut context, "flat_arr[0]"), "\"a\""); - assert_eq!(forward(&mut context, "flat_arr.length"), "1"); + run_test([ + TestAction::run_harness(), + TestAction::assert("arrayEquals( [[]].flat(), [] )"), + TestAction::assert(indoc! {r#" + arrayEquals( + ['a', ['b', 'c']].flat(), + ['a', 'b', 'c'] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ['a', ['b', ['c'], 'd']].flat(2), + ['a', 'b', 'c', 'd'] + ) + "#}), + TestAction::assert("arrayEquals( [[[[[['a']]]]]].flat(Infinity), ['a'] )"), + ]); } #[test] fn flat_map() { - let mut context = Context::default(); - - let code = r#" - var double = [1, 2, 3]; - var double_flatmap = double.flatMap(i => [i * 2]); - - var sentence = ["it's Sunny", "in Cali"]; - var flat_split_sentence = sentence.flatMap(x => x.split(" ")); - "#; - forward(&mut context, code); - - assert_eq!(forward(&mut context, "double_flatmap[0]"), "2"); - assert_eq!(forward(&mut context, "double_flatmap[1]"), "4"); - assert_eq!(forward(&mut context, "double_flatmap[2]"), "6"); - assert_eq!(forward(&mut context, "double_flatmap.length"), "3"); - - assert_eq!(forward(&mut context, "flat_split_sentence[0]"), "\"it's\""); - assert_eq!(forward(&mut context, "flat_split_sentence[1]"), "\"Sunny\""); - assert_eq!(forward(&mut context, "flat_split_sentence[2]"), "\"in\""); - assert_eq!(forward(&mut context, "flat_split_sentence[3]"), "\"Cali\""); - assert_eq!(forward(&mut context, "flat_split_sentence.length"), "4"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + [1, 2, 3].flatMap(i => [i * 2]), + [2, 4, 6] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ["it's Sunny", "in Cali"].flatMap(x => x.split(" ")), + ["it's", "Sunny", "in", "Cali"] + ) + "#}), + ]); } #[test] fn flat_map_with_hole() { - let mut context = Context::default(); - - let code = r#" - var arr = [0, 1, 2]; - delete arr[1]; - var arr_flattened = arr.flatMap(i => [i * 2]); - "#; - forward(&mut context, code); - - assert_eq!(forward(&mut context, "arr_flattened[0]"), "0"); - assert_eq!(forward(&mut context, "arr_flattened[1]"), "4"); - assert_eq!(forward(&mut context, "arr_flattened.length"), "2"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + var arr = [0, 1, 2]; + delete arr[1]; + arrayEquals( + arr.flatMap(i => [i * 2]), + [0, 4] + ) + "#}), + ]); } #[test] fn flat_map_not_callable() { - let mut context = Context::default(); - - let code = r#" - try { + run_test([TestAction::assert_native_error( + indoc! {r#" var array = [1,2,3]; array.flatMap("not a function"); - } catch (err) { - err.name === "TypeError" - } - "#; - - assert_eq!(forward(&mut context, code), "true"); + "#}, + ErrorKind::Type, + "flatMap mapper function is not callable", + )]); } #[test] fn push() { - let mut context = Context::default(); - let init = r#" - var arr = [1, 2]; - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!(forward(&mut context, "arr.push()"), "2"); - assert_eq!(forward(&mut context, "arr.push(3, 4)"), "4"); - assert_eq!(forward(&mut context, "arr[2]"), "3"); - assert_eq!(forward(&mut context, "arr[3]"), "4"); + run_test([ + TestAction::run("var arr = [1, 2];"), + TestAction::assert_eq("arr.push()", 2), + TestAction::assert_eq("arr.push(3, 4)", 4), + TestAction::assert_eq("arr[2]", 3), + TestAction::assert_eq("arr[3]", 4), + ]); } #[test] fn pop() { - let mut context = Context::default(); - let init = r#" - var empty = [ ]; - var one = [1]; - var many = [1, 2, 3, 4]; - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!( - forward(&mut context, "empty.pop()"), - String::from("undefined") - ); - assert_eq!(forward(&mut context, "one.pop()"), "1"); - assert_eq!(forward(&mut context, "one.length"), "0"); - assert_eq!(forward(&mut context, "many.pop()"), "4"); - assert_eq!(forward(&mut context, "many[0]"), "1"); - assert_eq!(forward(&mut context, "many.length"), "3"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var one = [1]; + var many = [1, 2, 3, 4]; + "#}), + TestAction::assert_eq("[].pop()", JsValue::undefined()), + TestAction::assert_eq("one.pop()", 1), + TestAction::assert("arrayEquals(one, [])"), + TestAction::assert_eq("many.pop()", 4), + TestAction::assert("arrayEquals(many, [1, 2, 3])"), + ]); } #[test] fn shift() { - let mut context = Context::default(); - let init = r#" - var empty = [ ]; - var one = [1]; - var many = [1, 2, 3, 4]; - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!( - forward(&mut context, "empty.shift()"), - String::from("undefined") - ); - assert_eq!(forward(&mut context, "one.shift()"), "1"); - assert_eq!(forward(&mut context, "one.length"), "0"); - assert_eq!(forward(&mut context, "many.shift()"), "1"); - assert_eq!(forward(&mut context, "many[0]"), "2"); - assert_eq!(forward(&mut context, "many.length"), "3"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var one = [1]; + var many = [1, 2, 3, 4]; + "#}), + TestAction::assert_eq("[].shift()", JsValue::undefined()), + TestAction::assert_eq("one.shift()", 1), + TestAction::assert("arrayEquals(one, [])"), + TestAction::assert_eq("many.shift()", 1), + TestAction::assert("arrayEquals(many, [2, 3, 4])"), + ]); } #[test] fn unshift() { - let mut context = Context::default(); - let init = r#" - var arr = [3, 4]; - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!(forward(&mut context, "arr.unshift()"), "2"); - assert_eq!(forward(&mut context, "arr.unshift(1, 2)"), "4"); - assert_eq!(forward(&mut context, "arr[0]"), "1"); - assert_eq!(forward(&mut context, "arr[1]"), "2"); + run_test([ + TestAction::run_harness(), + TestAction::run("var arr = [3, 4];"), + TestAction::assert_eq("arr.unshift()", 2), + TestAction::assert_eq("arr.unshift(1, 2)", 4), + TestAction::assert("arrayEquals(arr, [1, 2, 3, 4])"), + ]); } #[test] fn reverse() { - let mut context = Context::default(); - let init = r#" - var arr = [1, 2]; - var reversed = arr.reverse(); - "#; - eprintln!("{}", forward(&mut context, init)); - assert_eq!(forward(&mut context, "reversed[0]"), "2"); - assert_eq!(forward(&mut context, "reversed[1]"), "1"); - assert_eq!(forward(&mut context, "arr[0]"), "2"); - assert_eq!(forward(&mut context, "arr[1]"), "1"); + run_test([ + TestAction::run_harness(), + TestAction::run("var arr = [1, 2];"), + TestAction::assert("arrayEquals(arr.reverse(), [2, 1])"), + TestAction::assert("arrayEquals(arr, [2, 1])"), + ]); } #[test] fn index_of() { - let mut context = Context::default(); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - var duplicates = ["a", "b", "c", "a", "b"]; - "#; - eprintln!("{}", forward(&mut context, init)); - - // Empty - let empty = forward(&mut context, "empty.indexOf('a')"); - assert_eq!(empty, String::from("-1")); - - // One - let one = forward(&mut context, "one.indexOf('a')"); - assert_eq!(one, String::from("0")); - // Missing from one - let missing_from_one = forward(&mut context, "one.indexOf('b')"); - assert_eq!(missing_from_one, String::from("-1")); - - // First in many - let first_in_many = forward(&mut context, "many.indexOf('a')"); - assert_eq!(first_in_many, String::from("0")); - // Second in many - let second_in_many = forward(&mut context, "many.indexOf('b')"); - assert_eq!(second_in_many, String::from("1")); - - // First in duplicates - let first_in_many = forward(&mut context, "duplicates.indexOf('a')"); - assert_eq!(first_in_many, String::from("0")); - // Second in duplicates - let second_in_many = forward(&mut context, "duplicates.indexOf('b')"); - assert_eq!(second_in_many, String::from("1")); - - // Positive fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut context, "one.indexOf('a', 2)"); - assert_eq!(fromindex_greater_than_length, String::from("-1")); - // Positive fromIndex missed match - let fromindex_misses_match = forward(&mut context, "many.indexOf('a', 1)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Positive fromIndex matched - let fromindex_matches = forward(&mut context, "many.indexOf('b', 1)"); - assert_eq!(fromindex_matches, String::from("1")); - // Positive fromIndex with duplicates - let first_in_many = forward(&mut context, "duplicates.indexOf('a', 1)"); - assert_eq!(first_in_many, String::from("3")); - - // Negative fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut context, "one.indexOf('a', -2)"); - assert_eq!(fromindex_greater_than_length, String::from("0")); - // Negative fromIndex missed match - let fromindex_misses_match = forward(&mut context, "many.indexOf('b', -1)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Negative fromIndex matched - let fromindex_matches = forward(&mut context, "many.indexOf('c', -1)"); - assert_eq!(fromindex_matches, String::from("2")); - // Negative fromIndex with duplicates - let second_in_many = forward(&mut context, "duplicates.indexOf('b', -2)"); - assert_eq!(second_in_many, String::from("4")); + run_test([ + TestAction::run(indoc! {r#" + var one = ["a"]; + var many = ["a", "b", "c"]; + var duplicates = ["a", "b", "c", "a", "b"]; + "#}), + // Empty + TestAction::assert_eq("[].indexOf('a')", -1), + // One + TestAction::assert_eq("one.indexOf('a')", 0), + // Missing from one + TestAction::assert_eq("one.indexOf('b')", -1), + // First in many + TestAction::assert_eq("many.indexOf('a')", 0), + // Second in many + TestAction::assert_eq("many.indexOf('b')", 1), + // First in duplicates + TestAction::assert_eq("duplicates.indexOf('a')", 0), + // Second in duplicates + TestAction::assert_eq("duplicates.indexOf('b')", 1), + // Positive fromIndex greater than array length + TestAction::assert_eq("one.indexOf('a', 2)", -1), + // Positive fromIndex missed match + TestAction::assert_eq("many.indexOf('a', 1)", -1), + // Positive fromIndex matched + TestAction::assert_eq("many.indexOf('b', 1)", 1), + // Positive fromIndex with duplicates + TestAction::assert_eq("duplicates.indexOf('a', 1)", 3), + // Negative fromIndex greater than array length + TestAction::assert_eq("one.indexOf('a', -2)", 0), + // Negative fromIndex missed match + TestAction::assert_eq("many.indexOf('b', -1)", -1), + // Negative fromIndex matched + TestAction::assert_eq("many.indexOf('c', -1)", 2), + // Negative fromIndex with duplicates + TestAction::assert_eq("duplicates.indexOf('b', -2)", 4), + ]); } #[test] fn last_index_of() { - let mut context = Context::default(); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - var duplicates = ["a", "b", "c", "a", "b"]; - "#; - eprintln!("{}", forward(&mut context, init)); - - // Empty - let empty = forward(&mut context, "empty.lastIndexOf('a')"); - assert_eq!(empty, String::from("-1")); - - // One - let one = forward(&mut context, "one.lastIndexOf('a')"); - assert_eq!(one, String::from("0")); - // Missing from one - let missing_from_one = forward(&mut context, "one.lastIndexOf('b')"); - assert_eq!(missing_from_one, String::from("-1")); - - // First in many - let first_in_many = forward(&mut context, "many.lastIndexOf('a')"); - assert_eq!(first_in_many, String::from("0")); - // Second in many - let second_in_many = forward(&mut context, "many.lastIndexOf('b')"); - assert_eq!(second_in_many, String::from("1")); - - // 4th in duplicates - let first_in_many = forward(&mut context, "duplicates.lastIndexOf('a')"); - assert_eq!(first_in_many, String::from("3")); - // 5th in duplicates - let second_in_many = forward(&mut context, "duplicates.lastIndexOf('b')"); - assert_eq!(second_in_many, String::from("4")); - - // Positive fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut context, "one.lastIndexOf('a', 2)"); - assert_eq!(fromindex_greater_than_length, String::from("0")); - // Positive fromIndex missed match - let fromindex_misses_match = forward(&mut context, "many.lastIndexOf('c', 1)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Positive fromIndex matched - let fromindex_matches = forward(&mut context, "many.lastIndexOf('b', 1)"); - assert_eq!(fromindex_matches, String::from("1")); - // Positive fromIndex with duplicates - let first_in_many = forward(&mut context, "duplicates.lastIndexOf('a', 1)"); - assert_eq!(first_in_many, String::from("0")); - - // Negative fromIndex greater than array length - let fromindex_greater_than_length = forward(&mut context, "one.lastIndexOf('a', -2)"); - assert_eq!(fromindex_greater_than_length, String::from("-1")); - // Negative fromIndex missed match - let fromindex_misses_match = forward(&mut context, "many.lastIndexOf('c', -2)"); - assert_eq!(fromindex_misses_match, String::from("-1")); - // Negative fromIndex matched - let fromindex_matches = forward(&mut context, "many.lastIndexOf('c', -1)"); - assert_eq!(fromindex_matches, String::from("2")); - // Negative fromIndex with duplicates - let second_in_many = forward(&mut context, "duplicates.lastIndexOf('b', -2)"); - assert_eq!(second_in_many, String::from("1")); + run_test([ + TestAction::run(indoc! {r#" + var one = ["a"]; + var many = ["a", "b", "c"]; + var duplicates = ["a", "b", "c", "a", "b"]; + "#}), + // Empty + TestAction::assert_eq("[].lastIndexOf('a')", -1), + // One + TestAction::assert_eq("one.lastIndexOf('a')", 0), + // Missing from one + TestAction::assert_eq("one.lastIndexOf('b')", -1), + // First in many + TestAction::assert_eq("many.lastIndexOf('a')", 0), + // Second in many + TestAction::assert_eq("many.lastIndexOf('b')", 1), + // 4th in duplicates + TestAction::assert_eq("duplicates.lastIndexOf('a')", 3), + // 5th in duplicates + TestAction::assert_eq("duplicates.lastIndexOf('b')", 4), + // Positive fromIndex greater than array length + TestAction::assert_eq("one.lastIndexOf('a', 2)", 0), + // Positive fromIndex missed match + TestAction::assert_eq("many.lastIndexOf('c', 1)", -1), + // Positive fromIndex matched + TestAction::assert_eq("many.lastIndexOf('b', 1)", 1), + // Positive fromIndex with duplicates + TestAction::assert_eq("duplicates.lastIndexOf('a', 1)", 0), + // Negative fromIndex greater than array length + TestAction::assert_eq("one.lastIndexOf('a', -2)", -1), + // Negative fromIndex missed match + TestAction::assert_eq("many.lastIndexOf('c', -2)", -1), + // Negative fromIndex matched + TestAction::assert_eq("many.lastIndexOf('c', -1)", 2), + // Negative fromIndex with duplicates + TestAction::assert_eq("duplicates.lastIndexOf('b', -2)", 1), + ]); } #[test] fn fill_obj_ref() { - let mut context = Context::default(); - - // test object reference - forward(&mut context, "a = (new Array(3)).fill({});"); - forward(&mut context, "a[0].hi = 'hi';"); - assert_eq!(forward(&mut context, "a[0].hi"), "\"hi\""); + run_test([ + TestAction::run(indoc! {r#" + let obj = {}; + let a = new Array(3).fill(obj); + obj.hi = 'hi' + "#}), + TestAction::assert_eq("a[2].hi", "hi"), + ]); } #[test] fn fill() { - let mut context = Context::default(); - - forward(&mut context, "var a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4).join()"), - String::from("\"4,4,4\"") - ); - // make sure the array is modified - assert_eq!(forward(&mut context, "a.join()"), String::from("\"4,4,4\"")); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, '1').join()"), - String::from("\"1,4,4\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, 1, 2).join()"), - String::from("\"1,4,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, 1, 1).join()"), - String::from("\"1,2,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, 3, 3).join()"), - String::from("\"1,2,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, -3, -2).join()"), - String::from("\"4,2,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, NaN, NaN).join()"), - String::from("\"1,2,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, 3, 5).join()"), - String::from("\"1,2,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, '1.2', '2.5').join()"), - String::from("\"1,4,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, 'str').join()"), - String::from("\"4,4,4\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, 'str', 'str').join()"), - String::from("\"1,2,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, undefined, null).join()"), - String::from("\"1,2,3\"") - ); - - forward(&mut context, "a = [1, 2, 3];"); - assert_eq!( - forward(&mut context, "a.fill(4, undefined, undefined).join()"), - String::from("\"4,4,4\"") - ); - - assert_eq!( - forward(&mut context, "a.fill().join()"), - String::from("\",,\"") - ); - - // test object reference - forward(&mut context, "a = (new Array(3)).fill({});"); - forward(&mut context, "a[0].hi = 'hi';"); - assert_eq!(forward(&mut context, "a[0].hi"), String::from("\"hi\"")); + run_test([ + TestAction::run_harness(), + TestAction::run("var a = [1, 2, 3];"), + TestAction::assert("arrayEquals(a.fill(4), [4, 4, 4])"), + // make sure the array is modified + TestAction::assert("arrayEquals(a, [4, 4, 4])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, '1'), [1, 4, 4])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, 1, 2), [1, 4, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, 1, 1), [1, 2, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, 3, 3), [1, 2, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, -3, -2), [4, 2, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, NaN, NaN), [1, 2, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, 3, 5), [1, 2, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, '1.2', '2.5'), [1, 4, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, 'str'), [4, 4, 4])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, 'str', 'str'), [1, 2, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, undefined, null), [1, 2, 3])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(4, undefined, undefined), [4, 4, 4])"), + TestAction::assert("arrayEquals([1, 2, 3].fill(), [undefined, undefined, undefined])"), + ]); } #[test] fn includes_value() { - let mut context = Context::default(); - let init = r#" - var empty = [ ]; - var one = ["a"]; - var many = ["a", "b", "c"]; - var duplicates = ["a", "b", "c", "a", "b"]; - var undefined = [undefined]; - "#; - eprintln!("{}", forward(&mut context, init)); - - // Empty - let empty = forward(&mut context, "empty.includes('a')"); - assert_eq!(empty, String::from("false")); - - // One - let one = forward(&mut context, "one.includes('a')"); - assert_eq!(one, String::from("true")); - // Missing from one - let missing_from_one = forward(&mut context, "one.includes('b')"); - assert_eq!(missing_from_one, String::from("false")); - - // In many - let first_in_many = forward(&mut context, "many.includes('c')"); - assert_eq!(first_in_many, String::from("true")); - // Missing from many - let second_in_many = forward(&mut context, "many.includes('d')"); - assert_eq!(second_in_many, String::from("false")); - - // In duplicates - let first_in_many = forward(&mut context, "duplicates.includes('a')"); - assert_eq!(first_in_many, String::from("true")); - // Missing from duplicates - let second_in_many = forward(&mut context, "duplicates.includes('d')"); - assert_eq!(second_in_many, String::from("false")); + run_test([ + TestAction::run(indoc! {r#" + var one = ["a"]; + var many = ["a", "b", "c"]; + var duplicates = ["a", "b", "c", "a", "b"]; + "#}), + // Empty + TestAction::assert("![].includes('a')"), + // One + TestAction::assert("one.includes('a')"), + // Missing from one + TestAction::assert("!one.includes('b')"), + // In many + TestAction::assert("many.includes('b')"), + // Missing from many + TestAction::assert("!many.includes('d')"), + // In duplicates + TestAction::assert("duplicates.includes('a')"), + // Missing from duplicates + TestAction::assert("!duplicates.includes('d')"), + ]); } #[test] fn map() { - let mut context = Context::default(); - - let js = r#" - var empty = []; - var one = ["x"]; - var many = ["x", "y", "z"]; - - var _this = { answer: 42 }; - - function callbackThatUsesThis() { - return 'The answer to life is: ' + this.answer; - } - - var empty_mapped = empty.map(v => v + '_'); - var one_mapped = one.map(v => '_' + v); - var many_mapped = many.map(v => '_' + v + '_'); - "#; - - forward(&mut context, js); - - // assert the old arrays have not been modified - assert_eq!(forward(&mut context, "one[0]"), String::from("\"x\"")); - assert_eq!( - forward(&mut context, "many[2] + many[1] + many[0]"), - String::from("\"zyx\"") - ); - - // NB: These tests need to be rewritten once `Display` has been implemented for `Array` - // Empty - assert_eq!( - forward(&mut context, "empty_mapped.length"), - String::from("0") - ); - - // One - assert_eq!( - forward(&mut context, "one_mapped.length"), - String::from("1") - ); - assert_eq!( - forward(&mut context, "one_mapped[0]"), - String::from("\"_x\"") - ); - - // Many - assert_eq!( - forward(&mut context, "many_mapped.length"), - String::from("3") - ); - assert_eq!( - forward( - &mut context, - "many_mapped[0] + many_mapped[1] + many_mapped[2]" - ), - String::from("\"_x__y__z_\"") - ); - - // One but it uses `this` inside the callback - let one_with_this = forward(&mut context, "one.map(callbackThatUsesThis, _this)[0];"); - assert_eq!(one_with_this, String::from("\"The answer to life is: 42\"")); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var one = ["x"]; + var many = ["x", "y", "z"]; + "#}), + // Empty + TestAction::assert("arrayEquals([].map(v => v + '_'), [])"), + // One + TestAction::assert("arrayEquals(one.map(v => '_' + v), ['_x'])"), + // Many + TestAction::assert(indoc! {r#" + arrayEquals( + many.map(v => '_' + v + '_'), + ['_x_', '_y_', '_z_'] + ) + "#}), + // assert the old arrays have not been modified + TestAction::assert("arrayEquals(one, ['x'])"), + TestAction::assert("arrayEquals(many, ['x', 'y', 'z'])"), + // One but it uses `this` inside the callback + TestAction::assert(indoc! {r#" + var _this = { answer: 42 }; + + function callback() { + return 'The answer to life is: ' + this.answer; + } + + arrayEquals( + one.map(callback, _this), + ['The answer to life is: 42'] + ) + "#}), + ]); } #[test] fn slice() { - let mut context = Context::default(); - let init = r#" - var empty = [ ].slice(); - var one = ["a"].slice(); - var many1 = ["a", "b", "c", "d"].slice(1); - var many2 = ["a", "b", "c", "d"].slice(2, 3); - var many3 = ["a", "b", "c", "d"].slice(7); - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!(forward(&mut context, "empty.length"), "0"); - assert_eq!(forward(&mut context, "one[0]"), "\"a\""); - assert_eq!(forward(&mut context, "many1[0]"), "\"b\""); - assert_eq!(forward(&mut context, "many1[1]"), "\"c\""); - assert_eq!(forward(&mut context, "many1[2]"), "\"d\""); - assert_eq!(forward(&mut context, "many1.length"), "3"); - assert_eq!(forward(&mut context, "many2[0]"), "\"c\""); - assert_eq!(forward(&mut context, "many2.length"), "1"); - assert_eq!(forward(&mut context, "many3.length"), "0"); + run_test([ + TestAction::run_harness(), + TestAction::assert("arrayEquals([].slice(), [])"), + TestAction::assert("arrayEquals(['a'].slice(), ['a'])"), + TestAction::assert(indoc! {r#" + arrayEquals( + ["a", "b", "c", "d"].slice(1), + ["b", "c", "d"] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ["a", "b", "c", "d"].slice(2, 3), + ["c"] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ["a", "b", "c", "d"].slice(7), + [] + ) + "#}), + ]); } #[test] fn for_each() { - let mut context = Context::default(); - let init = r#" - var a = [2, 3, 4, 5]; - var sum = 0; - var indexSum = 0; - var listLengthSum = 0; - function callingCallback(item, index, list) { - sum += item; - indexSum += index; - listLengthSum += list.length; - } - a.forEach(callingCallback); - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!(forward(&mut context, "sum"), "14"); - assert_eq!(forward(&mut context, "indexSum"), "6"); - assert_eq!(forward(&mut context, "listLengthSum"), "16"); + run_test([ + TestAction::run(indoc! {r#" + var sum = 0; + var indexSum = 0; + var listLengthSum = 0; + function callingCallback(item, index, list) { + sum += item; + indexSum += index; + listLengthSum += list.length; + } + [2, 3, 4, 5].forEach(callingCallback); + "#}), + TestAction::assert_eq("sum", 14), + TestAction::assert_eq("indexSum", 6), + TestAction::assert_eq("listLengthSum", 16), + ]); } #[test] fn for_each_push_value() { - let mut context = Context::default(); - let init = r#" - var a = [1, 2, 3, 4]; - function callingCallback(item, index, list) { - list.push(item * 2); - } - a.forEach(callingCallback); - "#; - eprintln!("{}", forward(&mut context, init)); - - // [ 1, 2, 3, 4, 2, 4, 6, 8 ] - assert_eq!(forward(&mut context, "a.length"), "8"); - assert_eq!(forward(&mut context, "a[4]"), "2"); - assert_eq!(forward(&mut context, "a[5]"), "4"); - assert_eq!(forward(&mut context, "a[6]"), "6"); - assert_eq!(forward(&mut context, "a[7]"), "8"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var a = [1, 2, 3, 4]; + a.forEach((item, index, list) => list.push(item * 2)); + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + a, + [1, 2, 3, 4, 2, 4, 6, 8] + ) + "#}), + ]); } #[test] fn filter() { - let mut context = Context::default(); - - let js = r#" - var empty = []; - var one = ["1"]; - var many = ["1", "0", "1"]; - - var empty_filtered = empty.filter(v => v === "1"); - var one_filtered = one.filter(v => v === "1"); - var zero_filtered = one.filter(v => v === "0"); - var many_one_filtered = many.filter(v => v === "1"); - var many_zero_filtered = many.filter(v => v === "0"); - "#; - - forward(&mut context, js); - - // assert the old arrays have not been modified - assert_eq!(forward(&mut context, "one[0]"), String::from("\"1\"")); - assert_eq!( - forward(&mut context, "many[2] + many[1] + many[0]"), - String::from("\"101\"") - ); - - // NB: These tests need to be rewritten once `Display` has been implemented for `Array` - // Empty - assert_eq!( - forward(&mut context, "empty_filtered.length"), - String::from("0") - ); - - // One filtered on "1" - assert_eq!( - forward(&mut context, "one_filtered.length"), - String::from("1") - ); - assert_eq!( - forward(&mut context, "one_filtered[0]"), - String::from("\"1\"") - ); - - // One filtered on "0" - assert_eq!( - forward(&mut context, "zero_filtered.length"), - String::from("0") - ); - - // Many filtered on "1" - assert_eq!( - forward(&mut context, "many_one_filtered.length"), - String::from("2") - ); - assert_eq!( - forward(&mut context, "many_one_filtered[0] + many_one_filtered[1]"), - String::from("\"11\"") - ); - - // Many filtered on "0" - assert_eq!( - forward(&mut context, "many_zero_filtered.length"), - String::from("1") - ); - assert_eq!( - forward(&mut context, "many_zero_filtered[0]"), - String::from("\"0\"") - ); + run_test([ + TestAction::run_harness(), + TestAction::run("var empty = [], one = ['1'], many = ['1', '0', '1'];"), + // Empty + TestAction::assert(indoc! {r#" + arrayEquals( + empty.filter(v => v === "1"), + [] + ) + "#}), + // One filtered on "1" + TestAction::assert(indoc! {r#" + arrayEquals( + one.filter(v => v === "1"), + ["1"] + ) + "#}), + // One filtered on "0" + TestAction::assert(indoc! {r#" + arrayEquals( + one.filter(v => v === "0"), + [] + ) + "#}), + // Many filtered on "1" + TestAction::assert(indoc! {r#" + arrayEquals( + many.filter(v => v === "1"), + ["1", "1"] + ) + "#}), + // Many filtered on "0" + TestAction::assert(indoc! {r#" + arrayEquals( + many.filter(v => v === "0"), + ["0"] + ) + "#}), + // assert the old arrays have not been modified + TestAction::assert("arrayEquals(one, ['1'])"), + TestAction::assert("arrayEquals(many, ['1', '0', '1'])"), + ]); } #[test] fn some() { - let mut context = Context::default(); - let init = r#" - var empty = []; - - var array = [11, 23, 45]; - function lessThan10(element) { - return element > 10; - } - function greaterThan10(element) { - return element < 10; - } - - // Cases where callback mutates the array. - var appendArray = [1,2,3,4]; - function appendingCallback(elem,index,arr) { - arr.push('new'); - return elem !== "new"; - } - - var delArray = [1,2,3,4]; - function deletingCallback(elem,index,arr) { - arr.pop() - return elem < 3; - } - "#; - forward(&mut context, init); - let result = forward(&mut context, "array.some(lessThan10);"); - assert_eq!(result, "true"); - - let result = forward(&mut context, "empty.some(lessThan10);"); - assert_eq!(result, "false"); - - let result = forward(&mut context, "array.some(greaterThan10);"); - assert_eq!(result, "false"); - - let result = forward(&mut context, "appendArray.some(appendingCallback);"); - let append_array_length = forward(&mut context, "appendArray.length"); - assert_eq!(append_array_length, "5"); - assert_eq!(result, "true"); - - let result = forward(&mut context, "delArray.some(deletingCallback);"); - let del_array_length = forward(&mut context, "delArray.length"); - assert_eq!(del_array_length, "3"); - assert_eq!(result, "true"); + run_test([ + TestAction::run_harness(), + TestAction::run("var array = [11, 23, 45];"), + TestAction::assert("!array.some(e => e < 10)"), + TestAction::assert("![].some(e => e < 10)"), + TestAction::assert("array.some(e => e > 10)"), + TestAction::assert(indoc! {r#" + // Cases where callback mutates the array. + var appendArray = [1,2,3,4]; + function appendingCallback(elem, index, arr) { + arr.push('new'); + return elem !== "new"; + } + + appendArray.some(appendingCallback) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + appendArray, + [1, 2, 3, 4, "new"] + ) + "#}), + TestAction::assert(indoc! {r#" + var delArray = [1,2,3,4]; + function deletingCallback(elem,index,arr) { + arr.pop() + return elem < 3; + } + + delArray.some(deletingCallback) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + delArray, + [1, 2, 3] + ) + "#}), + ]); } #[test] fn reduce() { - let mut context = Context::default(); - - let init = r#" - var arr = [1, 2, 3, 4]; - function add(acc, x) { - return acc + x; - } - - function addIdx(acc, _, idx) { - return acc + idx; - } - - function addLen(acc, _x, _idx, arr) { - return acc + arr.length; - } - - function addResize(acc, x, idx, arr) { - if(idx == 0) { - arr.length = 3; - } - return acc + x; - } - var delArray = [1, 2, 3, 4, 5]; - delete delArray[0]; - delete delArray[1]; - delete delArray[3]; - - "#; - forward(&mut context, init); - - // empty array - let result = forward(&mut context, "[].reduce(add, 0)"); - assert_eq!(result, "0"); - - // simple with initial value - let result = forward(&mut context, "arr.reduce(add, 0)"); - assert_eq!(result, "10"); - - // without initial value - let result = forward(&mut context, "arr.reduce(add)"); - assert_eq!(result, "10"); - - // with some items missing - let result = forward(&mut context, "delArray.reduce(add, 0)"); - assert_eq!(result, "8"); - - // with index - let result = forward(&mut context, "arr.reduce(addIdx, 0)"); - assert_eq!(result, "6"); - - // with array - let result = forward(&mut context, "arr.reduce(addLen, 0)"); - assert_eq!(result, "16"); - - // resizing the array as reduce progresses - let result = forward(&mut context, "arr.reduce(addResize, 0)"); - assert_eq!(result, "6"); - - // Empty array - let result = forward( - &mut context, - r#" - try { - [].reduce((acc, x) => acc + x); - } catch(e) { - e.message - } - "#, - ); - assert_eq!( - result, - "\"Array.prototype.reduce: called on an empty array and with no initial value\"" - ); - - // Array with no defined elements - let result = forward( - &mut context, - r#" - try { - var arr = [0, 1]; - delete arr[0]; - delete arr[1]; - arr.reduce((acc, x) => acc + x); - } catch(e) { - e.message - } - "#, - ); - assert_eq!( - result, - "\"Array.prototype.reduce: called on an empty array and with no initial value\"" - ); - - // No callback - let result = forward( - &mut context, - r#" - try { - arr.reduce(""); - } catch(e) { - e.message - } - "#, - ); - assert_eq!( - result, - "\"Array.prototype.reduce: callback function is not callable\"" - ); + run_test([ + TestAction::run(indoc! {r#" + var arr = [1, 2, 3, 4]; + function add(acc, x) { + return acc + x; + } + + function addIdx(acc, _, idx) { + return acc + idx; + } + + function addLen(acc, _x, _idx, arr) { + return acc + arr.length; + } + + function addResize(acc, x, idx, arr) { + if(idx == 0) { + arr.length = 3; + } + return acc + x; + } + var delArray = [1, 2, 3, 4, 5]; + delete delArray[0]; + delete delArray[1]; + delete delArray[3]; + + "#}), + // empty array + TestAction::assert_eq("[].reduce(add, 0)", 0), + // simple with initial value + TestAction::assert_eq("arr.reduce(add, 0)", 10), + // without initial value + TestAction::assert_eq("arr.reduce(add)", 10), + // with some items missing + TestAction::assert_eq("delArray.reduce(add, 0)", 8), + // with index + TestAction::assert_eq("arr.reduce(addIdx, 0)", 6), + // with array + TestAction::assert_eq("arr.reduce(addLen, 0)", 16), + // resizing the array as reduce progresses + TestAction::assert_eq("arr.reduce(addResize, 0)", 6), + // Empty array + TestAction::assert_native_error( + "[].reduce((acc, x) => acc + x);", + ErrorKind::Type, + "Array.prototype.reduce: called on an empty array and with no initial value", + ), + // Array with no defined elements + TestAction::assert_native_error( + indoc! {r#" + var deleteArr = [0, 1]; + delete deleteArr[0]; + delete deleteArr[1]; + deleteArr.reduce((acc, x) => acc + x); + "#}, + ErrorKind::Type, + "Array.prototype.reduce: called on an empty array and with no initial value", + ), + // No callback + TestAction::assert_native_error( + indoc! {r#" + var someArr = [0, 1]; + someArr.reduce(''); + "#}, + ErrorKind::Type, + "Array.prototype.reduce: callback function is not callable", + ), + ]); } #[test] fn reduce_right() { - let mut context = Context::default(); - - let init = r#" - var arr = [1, 2, 3, 4]; - function sub(acc, x) { - return acc - x; - } - - function subIdx(acc, _, idx) { - return acc - idx; - } - - function subLen(acc, _x, _idx, arr) { - return acc - arr.length; - } - - function subResize(acc, x, idx, arr) { - if(idx == arr.length - 1) { - arr.length = 1; - } - return acc - x; - } - function subResize0(acc, x, idx, arr) { - if(idx == arr.length - 2) { - arr.length = 0; - } - return acc - x; - } - var delArray = [1, 2, 3, 4, 5]; - delete delArray[0]; - delete delArray[1]; - delete delArray[3]; - - "#; - forward(&mut context, init); - - // empty array - let result = forward(&mut context, "[].reduceRight(sub, 0)"); - assert_eq!(result, "0"); - - // simple with initial value - let result = forward(&mut context, "arr.reduceRight(sub, 0)"); - assert_eq!(result, "-10"); - - // without initial value - let result = forward(&mut context, "arr.reduceRight(sub)"); - assert_eq!(result, "-2"); - - // with some items missing - let result = forward(&mut context, "delArray.reduceRight(sub, 0)"); - assert_eq!(result, "-8"); - - // with index - let result = forward(&mut context, "arr.reduceRight(subIdx)"); - assert_eq!(result, "1"); - - // with array - let result = forward(&mut context, "arr.reduceRight(subLen)"); - assert_eq!(result, "-8"); - - // resizing the array as reduce progresses - let result = forward(&mut context, "arr.reduceRight(subResize, 0)"); - assert_eq!(result, "-5"); - - // reset array - forward(&mut context, "arr = [1, 2, 3, 4];"); - - // resizing the array to 0 as reduce progresses - let result = forward(&mut context, "arr.reduceRight(subResize0, 0)"); - assert_eq!(result, "-7"); - - // Empty array - let result = forward( - &mut context, - r#" - try { - [].reduceRight((acc, x) => acc + x); - } catch(e) { - e.message - } - "#, - ); - assert_eq!( - result, - "\"Array.prototype.reduceRight: called on an empty array and with no initial value\"" - ); - - // Array with no defined elements - let result = forward( - &mut context, - r#" - try { - var arr = [0, 1]; - delete arr[0]; - delete arr[1]; - arr.reduceRight((acc, x) => acc + x); - } catch(e) { - e.message - } - "#, - ); - assert_eq!( - result, - "\"Array.prototype.reduceRight: called on an empty array and with no initial value\"" - ); - - // No callback - let result = forward( - &mut context, - r#" - try { - arr.reduceRight(""); - } catch(e) { - e.message - } - "#, - ); - assert_eq!( - result, - "\"Array.prototype.reduceRight: callback function is not callable\"" - ); + run_test([ + TestAction::run(indoc! {r#" + var arr = [1, 2, 3, 4]; + function sub(acc, x) { + return acc - x; + } + + function subIdx(acc, _, idx) { + return acc - idx; + } + + function subLen(acc, _x, _idx, arr) { + return acc - arr.length; + } + + function subResize(acc, x, idx, arr) { + if(idx == arr.length - 1) { + arr.length = 1; + } + return acc - x; + } + function subResize0(acc, x, idx, arr) { + if(idx == arr.length - 2) { + arr.length = 0; + } + return acc - x; + } + var delArray = [1, 2, 3, 4, 5]; + delete delArray[0]; + delete delArray[1]; + delete delArray[3]; + "#}), + // empty array + TestAction::assert_eq("[].reduceRight(sub, 0)", 0), + // simple with initial value + TestAction::assert_eq("arr.reduceRight(sub, 0)", -10), + // without initial value + TestAction::assert_eq("arr.reduceRight(sub)", -2), + // with some items missing + TestAction::assert_eq("delArray.reduceRight(sub, 0)", -8), + // with index + TestAction::assert_eq("arr.reduceRight(subIdx)", 1), + // with array + TestAction::assert_eq("arr.reduceRight(subLen)", -8), + // resizing the array as reduce progresses + TestAction::assert_eq("arr.reduceRight(subResize, 0)", -5), + // reset array + TestAction::run("arr = [1, 2, 3, 4];"), + // resizing the array to 0 as reduce progresses + TestAction::assert_eq("arr.reduceRight(subResize0, 0)", -7), + // Empty array + TestAction::assert_native_error( + "[].reduceRight((acc, x) => acc + x);", + ErrorKind::Type, + "Array.prototype.reduceRight: called on an empty array and with no initial value", + ), + // Array with no defined elements + TestAction::assert_native_error( + indoc! {r#" + var deleteArr = [0, 1]; + delete deleteArr[0]; + delete deleteArr[1]; + deleteArr.reduceRight((acc, x) => acc + x); + "#}, + ErrorKind::Type, + "Array.prototype.reduceRight: called on an empty array and with no initial value", + ), + // No callback + TestAction::assert_native_error( + indoc! {r#" + var otherArr = [0, 1]; + otherArr.reduceRight(""); + "#}, + ErrorKind::Type, + "Array.prototype.reduceRight: callback function is not callable", + ), + ]); } #[test] fn call_array_constructor_with_one_argument() { - let mut context = Context::default(); - let init = r#" - var empty = new Array(0); - - var five = new Array(5); - - var one = new Array("Hello, world!"); - "#; - forward(&mut context, init); - // let result = forward(&mut context, "empty.length"); - // assert_eq!(result, "0"); - - // let result = forward(&mut context, "five.length"); - // assert_eq!(result, "5"); - - // let result = forward(&mut context, "one.length"); - // assert_eq!(result, "1"); + run_test([ + TestAction::run_harness(), + TestAction::assert("arrayEquals(new Array(0), [])"), + TestAction::assert("arrayEquals(new Array(5), [,,,,,])"), + TestAction::assert("arrayEquals(new Array('Hello, world!'), ['Hello, world!'])"), + ]); } #[test] fn array_values_simple() { - let mut context = Context::default(); - let init = r#" - var iterator = [1, 2, 3].values(); - var next = iterator.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "1"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "2"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "3"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from([1, 2, 3].values()), + [1, 2, 3] + ) + "#}), + ]); } #[test] fn array_keys_simple() { - let mut context = Context::default(); - let init = r#" - var iterator = [1, 2, 3].keys(); - var next = iterator.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "0"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "1"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "2"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from([1, 2, 3].keys()), + [0, 1, 2] + ) + "#}), + ]); } #[test] fn array_entries_simple() { - let mut context = Context::default(); - let init = r#" - var iterator = [1, 2, 3].entries(); - var next = iterator.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "[ 0, 1 ]"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "[ 1, 2 ]"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "[ 2, 3 ]"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from([1, 2, 3].entries()), + [ + [0, 1], + [1, 2], + [2, 3] + ] + ) + "#}), + ]); } #[test] fn array_values_empty() { - let mut context = Context::default(); - let init = r#" - var iterator = [].values(); - var next = iterator.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from([].values()), + [] + ) + "#}), + ]); } #[test] fn array_values_sparse() { - let mut context = Context::default(); - let init = r#" - var array = Array(); - array[3] = 5; - var iterator = array.values(); - var next = iterator.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "5"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + var array = Array(); + array[3] = 5; + arrayEquals( + Array.from(array.values()), + [undefined, undefined, undefined, 5] + ) + "#}), + ]); } #[test] fn array_symbol_iterator() { - let mut context = Context::default(); - let init = r#" - var iterator = [1, 2, 3][Symbol.iterator](); - var next = iterator.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "1"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "2"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "3"); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iterator.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from([1, 2, 3][Symbol.iterator]()), + [1, 2, 3] + ) + "#}), + ]); } #[test] fn array_values_symbol_iterator() { - let mut context = Context::default(); - let init = r#" - var iterator = [1, 2, 3].values(); - iterator === iterator[Symbol.iterator](); - "#; - assert_eq!(forward(&mut context, init), "true"); + run_test([TestAction::assert(indoc! {r#" + var iterator = [1, 2, 3].values(); + iterator === iterator[Symbol.iterator](); + "#})]); } #[test] fn array_spread_arrays() { - let mut context = Context::default(); - let init = r#" - const array1 = [2, 3]; - const array2 = [1, ...array1]; - array2[0] === 1 && array2[1] === 2 && array2[2] === 3; - "#; - assert_eq!(forward(&mut context, init), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + [1, ...[2, 3]], + [1, 2, 3] + ) + "#}), + ]); } #[test] fn array_spread_non_iterable() { - let mut context = Context::default(); - let init = r#" - try { - const array2 = [...5]; - } catch (err) { - err.name === "TypeError" && err.message === "value with type `number` is not iterable" - } - "#; - assert_eq!(forward(&mut context, init), "true"); + run_test([TestAction::assert_native_error( + "const array2 = [...5];", + ErrorKind::Type, + "value with type `number` is not iterable", + )]); } #[test] fn get_relative_start() { - let mut context = Context::default(); - - assert_eq!( - Array::get_relative_start(&mut context, None, 10).unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::undefined()), 10).unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::NEG_INFINITY)), 10) - .unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::INFINITY)), 10).unwrap(), - 10 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(-1)), 10).unwrap(), - 9 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(1)), 10).unwrap(), - 1 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(-11)), 10).unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(11)), 10).unwrap(), - 10 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::MIN)), 10).unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_start( - &mut context, - Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), - 10 - ) - .unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_start(&mut context, Some(&JsValue::new(f64::MAX)), 10).unwrap(), - 10 - ); - - // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) - assert_eq!( - Array::get_relative_start( - &mut context, - Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), - 10 - ) - .unwrap(), - 10 - ); + #[track_caller] + fn assert(context: &mut Context<'_>, arg: Option<&JsValue>, len: u64, expected: u64) { + assert_eq!( + Array::get_relative_start(context, arg, len).unwrap(), + expected + ); + } + run_test([TestAction::inspect_context(|ctx| { + assert(ctx, None, 10, 0); + assert(ctx, Some(&JsValue::undefined()), 10, 0); + assert(ctx, Some(&JsValue::new(f64::NEG_INFINITY)), 10, 0); + assert(ctx, Some(&JsValue::new(f64::INFINITY)), 10, 10); + assert(ctx, Some(&JsValue::new(-1)), 10, 9); + assert(ctx, Some(&JsValue::new(1)), 10, 1); + assert(ctx, Some(&JsValue::new(-11)), 10, 0); + assert(ctx, Some(&JsValue::new(11)), 10, 10); + assert(ctx, Some(&JsValue::new(f64::MIN)), 10, 0); + assert(ctx, Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), 10, 0); + assert(ctx, Some(&JsValue::new(f64::MAX)), 10, 10); + // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) + assert(ctx, Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), 10, 10); + })]); } #[test] fn get_relative_end() { - let mut context = Context::default(); - - assert_eq!(Array::get_relative_end(&mut context, None, 10).unwrap(), 10); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::undefined()), 10).unwrap(), - 10 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::NEG_INFINITY)), 10).unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::INFINITY)), 10).unwrap(), - 10 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(-1)), 10).unwrap(), - 9 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(1)), 10).unwrap(), - 1 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(-11)), 10).unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(11)), 10).unwrap(), - 10 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::MIN)), 10).unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_end( - &mut context, - Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), - 10 - ) - .unwrap(), - 0 - ); - assert_eq!( - Array::get_relative_end(&mut context, Some(&JsValue::new(f64::MAX)), 10).unwrap(), - 10 - ); - - // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) - assert_eq!( - Array::get_relative_end( - &mut context, - Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), - 10 - ) - .unwrap(), - 10 - ); + #[track_caller] + fn assert(context: &mut Context<'_>, arg: Option<&JsValue>, len: u64, expected: u64) { + assert_eq!( + Array::get_relative_end(context, arg, len).unwrap(), + expected + ); + } + run_test([TestAction::inspect_context(|ctx| { + assert(ctx, None, 10, 10); + assert(ctx, Some(&JsValue::undefined()), 10, 10); + assert(ctx, Some(&JsValue::new(f64::NEG_INFINITY)), 10, 0); + assert(ctx, Some(&JsValue::new(f64::INFINITY)), 10, 10); + assert(ctx, Some(&JsValue::new(-1)), 10, 9); + assert(ctx, Some(&JsValue::new(1)), 10, 1); + assert(ctx, Some(&JsValue::new(-11)), 10, 0); + assert(ctx, Some(&JsValue::new(11)), 10, 10); + assert(ctx, Some(&JsValue::new(f64::MIN)), 10, 0); + assert(ctx, Some(&JsValue::new(Number::MIN_SAFE_INTEGER)), 10, 0); + assert(ctx, Some(&JsValue::new(f64::MAX)), 10, 10); + // This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32) + assert(ctx, Some(&JsValue::new(Number::MAX_SAFE_INTEGER)), 10, 10); + })]); } #[test] fn array_length_is_not_enumerable() { - let mut context = Context::default(); - - let array = - Array::array_create(0, None, &mut context).expect("could not create an empty array"); - let desc = array - .__get_own_property__(&"length".into(), &mut context) - .expect("accessing length property on array should not throw") - .expect("there should always be a length property on arrays"); - assert!(!desc.expect_enumerable()); + run_test([TestAction::assert( + "!Object.getOwnPropertyDescriptor([], 'length').enumerable", + )]); } #[test] fn array_sort() { - let mut context = Context::default(); - let init = r#" - let arr = ['80', '9', '700', 40, 1, 5, 200]; - - function compareNumbers(a, b) { - return a - b; - } - "#; - forward(&mut context, init); - assert_eq!( - forward(&mut context, "arr.sort().join()"), - "\"1,200,40,5,700,80,9\"" - ); - assert_eq!( - forward(&mut context, "arr.sort(compareNumbers).join()"), - "\"1,5,9,40,80,200,700\"" - ); + run_test([ + TestAction::run_harness(), + TestAction::run("let arr = ['80', '9', '700', 40, 1, 5, 200];"), + TestAction::assert(indoc! {r#" + arrayEquals( + arr.sort(), + [1, 200, 40, 5, "700", "80", "9"] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + arr.sort((a, b) => a - b), + [1, 5, "9", 40, "80", 200, "700"] + ) + "#}), + ]); } diff --git a/boa_engine/src/builtins/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index 6290f590c78..aacdb674809 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -114,7 +114,7 @@ impl BigInt { // 1. If IsIntegralNumber(number) is false, throw a RangeError exception. if number.is_nan() || number.is_infinite() || number.fract() != 0.0 { return Err(JsNativeError::range() - .with_message(format!("Cannot convert {number} to BigInt")) + .with_message(format!("cannot convert {number} to a BigInt")) .into()); } diff --git a/boa_engine/src/builtins/bigint/tests.rs b/boa_engine/src/builtins/bigint/tests.rs index 9e54b9636b2..fcb2571eb32 100644 --- a/boa_engine/src/builtins/bigint/tests.rs +++ b/boa_engine/src/builtins/bigint/tests.rs @@ -1,413 +1,314 @@ -use crate::{forward, Context}; +use crate::{builtins::error::ErrorKind, run_test, JsBigInt, TestAction}; #[test] fn equality() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "0n == 0n"), "true"); - assert_eq!(forward(&mut context, "1n == 0n"), "false"); - assert_eq!( - forward( - &mut context, - "1000000000000000000000000000000000n == 1000000000000000000000000000000000n" - ), - "true" - ); - - assert_eq!(forward(&mut context, "0n == ''"), "true"); - assert_eq!(forward(&mut context, "100n == '100'"), "true"); - assert_eq!(forward(&mut context, "100n == '100.5'"), "false"); - assert_eq!( - forward(&mut context, "10000000000000000n == '10000000000000000'"), - "true" - ); - - assert_eq!(forward(&mut context, "'' == 0n"), "true"); - assert_eq!(forward(&mut context, "'100' == 100n"), "true"); - assert_eq!(forward(&mut context, "'100.5' == 100n"), "false"); - assert_eq!( - forward(&mut context, "'10000000000000000' == 10000000000000000n"), - "true" - ); - - assert_eq!(forward(&mut context, "0n == 0"), "true"); - assert_eq!(forward(&mut context, "0n == 0.0"), "true"); - assert_eq!(forward(&mut context, "100n == 100"), "true"); - assert_eq!(forward(&mut context, "100n == 100.0"), "true"); - assert_eq!(forward(&mut context, "100n == '100.5'"), "false"); - assert_eq!(forward(&mut context, "100n == '1005'"), "false"); - assert_eq!( - forward(&mut context, "10000000000000000n == 10000000000000000"), - "true" - ); - - assert_eq!(forward(&mut context, "0 == 0n"), "true"); - assert_eq!(forward(&mut context, "0.0 == 0n"), "true"); - assert_eq!(forward(&mut context, "100 == 100n"), "true"); - assert_eq!(forward(&mut context, "100.0 == 100n"), "true"); - assert_eq!(forward(&mut context, "100.5 == 100n"), "false"); - assert_eq!(forward(&mut context, "1005 == 100n"), "false"); - assert_eq!( - forward(&mut context, "10000000000000000 == 10000000000000000n"), - "true" - ); + run_test([ + TestAction::assert("0n == 0n"), + TestAction::assert("!(1n == 0n)"), + TestAction::assert( + "1000000000000000000000000000000000n == 1000000000000000000000000000000000n", + ), + TestAction::assert("0n == ''"), + TestAction::assert("100n == '100'"), + TestAction::assert("!(100n == '100.5')"), + TestAction::assert("10000000000000000n == '10000000000000000'"), + TestAction::assert("'' == 0n"), + TestAction::assert("'100' == 100n"), + TestAction::assert("!('100.5' == 100n)"), + TestAction::assert("'10000000000000000' == 10000000000000000n"), + TestAction::assert("0n == 0"), + TestAction::assert("0n == 0.0"), + TestAction::assert("100n == 100"), + TestAction::assert("100n == 100.0"), + TestAction::assert("!(100n == '100.5')"), + TestAction::assert("!(100n == '1005')"), + TestAction::assert("10000000000000000n == 10000000000000000"), + TestAction::assert("0 == 0n"), + TestAction::assert("0.0 == 0n"), + TestAction::assert("100 == 100n"), + TestAction::assert("100.0 == 100n"), + TestAction::assert("!(100.5 == 100n)"), + TestAction::assert("!(1005 == 100n)"), + TestAction::assert("10000000000000000 == 10000000000000000n"), + ]); } #[test] fn bigint_function_conversion_from_integer() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "BigInt(1000)"), "1000n"); - assert_eq!( - forward(&mut context, "BigInt(20000000000000000)"), - "20000000000000000n" - ); - assert_eq!( - forward(&mut context, "BigInt(1000000000000000000000000000000000)"), - "999999999999999945575230987042816n" - ); + run_test([ + TestAction::assert_eq("BigInt(1000)", JsBigInt::from(1000)), + TestAction::assert_eq( + "BigInt(20000000000000000)", + JsBigInt::from_string("20000000000000000").unwrap(), + ), + TestAction::assert_eq( + "BigInt(1000000000000000000000000000000000)", + JsBigInt::from_string("999999999999999945575230987042816").unwrap(), + ), + ]); } #[test] fn bigint_function_conversion_from_rational() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "BigInt(0.0)"), "0n"); - assert_eq!(forward(&mut context, "BigInt(1.0)"), "1n"); - assert_eq!(forward(&mut context, "BigInt(10000.0)"), "10000n"); -} - -#[test] -fn bigint_function_conversion_from_rational_with_fractional_part() { - let mut context = Context::default(); - - let scenario = r#" - try { - BigInt(0.1); - } catch (e) { - e.toString(); - } - "#; - assert_eq!( - forward(&mut context, scenario), - "\"RangeError: Cannot convert 0.1 to BigInt\"" - ); + run_test([ + TestAction::assert_eq("BigInt(0.0)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt(1.0)", JsBigInt::from(1)), + TestAction::assert_eq("BigInt(10000.0)", JsBigInt::from(10_000)), + ]); } #[test] -fn bigint_function_conversion_from_null() { - let mut context = Context::default(); - - let scenario = r#" - try { - BigInt(null); - } catch (e) { - e.toString(); - } - "#; - assert_eq!( - forward(&mut context, scenario), - "\"TypeError: cannot convert null to a BigInt\"" - ); -} - -#[test] -fn bigint_function_conversion_from_undefined() { - let mut context = Context::default(); - - let scenario = r#" - try { - BigInt(undefined); - } catch (e) { - e.toString(); - } - "#; - assert_eq!( - forward(&mut context, scenario), - "\"TypeError: cannot convert undefined to a BigInt\"" - ); +fn bigint_function_throws() { + run_test([ + TestAction::assert_native_error( + "BigInt(0.1)", + ErrorKind::Range, + "cannot convert 0.1 to a BigInt", + ), + TestAction::assert_native_error( + "BigInt(null)", + ErrorKind::Type, + "cannot convert null to a BigInt", + ), + TestAction::assert_native_error( + "BigInt(undefined)", + ErrorKind::Type, + "cannot convert undefined to a BigInt", + ), + ]); } #[test] fn bigint_function_conversion_from_string() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "BigInt('')"), "0n"); - assert_eq!(forward(&mut context, "BigInt(' ')"), "0n"); - assert_eq!( - forward(&mut context, "BigInt('200000000000000000')"), - "200000000000000000n" - ); - assert_eq!( - forward(&mut context, "BigInt('1000000000000000000000000000000000')"), - "1000000000000000000000000000000000n" - ); - assert_eq!(forward(&mut context, "BigInt('0b1111')"), "15n"); - assert_eq!(forward(&mut context, "BigInt('0o70')"), "56n"); - assert_eq!(forward(&mut context, "BigInt('0xFF')"), "255n"); -} - -#[test] -fn add() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "10000n + 1000n"), "11000n"); -} - -#[test] -fn sub() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "10000n - 1000n"), "9000n"); -} - -#[test] -fn mul() { - let mut context = Context::default(); - - assert_eq!( - forward(&mut context, "123456789n * 102030n"), - "12596296181670n" - ); -} - -#[test] -fn div() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "15000n / 50n"), "300n"); -} - -#[test] -fn div_with_truncation() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "15001n / 50n"), "300n"); -} - -#[test] -fn r#mod() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "15007n % 10n"), "7n"); -} - -#[test] -fn pow() { - let mut context = Context::default(); - - assert_eq!( - forward(&mut context, "100n ** 10n"), - "100000000000000000000n" - ); -} - -#[test] -fn pow_negative_exponent() { - let mut context = Context::default(); - - assert_throws(&mut context, "10n ** (-10n)", "RangeError"); -} - -#[test] -fn shl() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "8n << 2n"), "32n"); -} - -#[test] -fn shl_out_of_range() { - let mut context = Context::default(); - - assert_throws(&mut context, "1000n << 1000000000000000n", "RangeError"); -} - -#[test] -fn shr() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "8n >> 2n"), "2n"); + run_test([ + TestAction::assert_eq("BigInt('')", JsBigInt::from(0)), + TestAction::assert_eq("BigInt(' ')", JsBigInt::from(0)), + TestAction::assert_eq( + "BigInt('200000000000000000')", + JsBigInt::from_string("200000000000000000").unwrap(), + ), + TestAction::assert_eq( + "BigInt('1000000000000000000000000000000000')", + JsBigInt::from_string("1000000000000000000000000000000000").unwrap(), + ), + TestAction::assert_eq("BigInt('0b1111')", JsBigInt::from(15)), + TestAction::assert_eq("BigInt('0o70')", JsBigInt::from(56)), + TestAction::assert_eq("BigInt('0xFF')", JsBigInt::from(255)), + ]); } #[test] -fn shr_out_of_range() { - let mut context = Context::default(); - - assert_throws(&mut context, "1000n >> 1000000000000000n", "RangeError"); +fn operations() { + run_test([ + TestAction::assert_eq("10000n + 1000n", JsBigInt::from(11_000)), + TestAction::assert_eq("10000n - 1000n", JsBigInt::from(9_000)), + TestAction::assert_eq( + "123456789n * 102030n", + JsBigInt::from_string("12596296181670").unwrap(), + ), + TestAction::assert_eq("15000n / 50n", JsBigInt::from(300)), + TestAction::assert_eq("15001n / 50n", JsBigInt::from(300)), + TestAction::assert_native_error("1n/0n", ErrorKind::Range, "BigInt division by zero"), + TestAction::assert_eq("15007n % 10n", JsBigInt::from(7)), + TestAction::assert_native_error("1n % 0n", ErrorKind::Range, "BigInt division by zero"), + TestAction::assert_eq( + "100n ** 10n", + JsBigInt::from_string("100000000000000000000").unwrap(), + ), + TestAction::assert_native_error( + "10n ** (-10n)", + ErrorKind::Range, + "BigInt negative exponent", + ), + TestAction::assert_eq("8n << 2n", JsBigInt::from(32)), + TestAction::assert_native_error( + "1000n << 1000000000000000n", + ErrorKind::Range, + "Maximum BigInt size exceeded", + ), + TestAction::assert_eq("8n >> 2n", JsBigInt::from(2)), + // TODO: this should return 0n instead of throwing + TestAction::assert_native_error( + "1000n >> 1000000000000000n", + ErrorKind::Range, + "Maximum BigInt size exceeded", + ), + ]); } #[test] fn to_string() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "1000n.toString()"), "\"1000\""); - assert_eq!(forward(&mut context, "1000n.toString(2)"), "\"1111101000\""); - assert_eq!(forward(&mut context, "255n.toString(16)"), "\"ff\""); - assert_eq!(forward(&mut context, "1000n.toString(36)"), "\"rs\""); + run_test([ + TestAction::assert_eq("1000n.toString()", "1000"), + TestAction::assert_eq("1000n.toString(2)", "1111101000"), + TestAction::assert_eq("255n.toString(16)", "ff"), + TestAction::assert_eq("1000n.toString(36)", "rs"), + ]); } #[test] fn to_string_invalid_radix() { - let mut context = Context::default(); - - assert_throws(&mut context, "10n.toString(null)", "RangeError"); - assert_throws(&mut context, "10n.toString(-1)", "RangeError"); - assert_throws(&mut context, "10n.toString(37)", "RangeError"); + run_test([ + TestAction::assert_native_error( + "10n.toString(null)", + ErrorKind::Range, + "radix must be an integer at least 2 and no greater than 36", + ), + TestAction::assert_native_error( + "10n.toString(-1)", + ErrorKind::Range, + "radix must be an integer at least 2 and no greater than 36", + ), + TestAction::assert_native_error( + "10n.toString(37)", + ErrorKind::Range, + "radix must be an integer at least 2 and no greater than 36", + ), + ]); } #[test] fn as_int_n() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "BigInt.asIntN(0, 1n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asIntN(1, 1n)"), "-1n"); - assert_eq!(forward(&mut context, "BigInt.asIntN(3, 10n)"), "2n"); - assert_eq!(forward(&mut context, "BigInt.asIntN({}, 1n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asIntN(2, 0n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asIntN(2, -0n)"), "0n"); - - assert_eq!( - forward(&mut context, "BigInt.asIntN(2, -123456789012345678901n)"), - "-1n" - ); - assert_eq!( - forward(&mut context, "BigInt.asIntN(2, -123456789012345678900n)"), - "0n" - ); - - assert_eq!( - forward(&mut context, "BigInt.asIntN(2, 123456789012345678900n)"), - "0n" - ); - assert_eq!( - forward(&mut context, "BigInt.asIntN(2, 123456789012345678901n)"), - "1n" - ); - - assert_eq!( - forward( - &mut context, - "BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)" - ), - "-1n" - ); - assert_eq!( - forward( - &mut context, - "BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)" - ), - "1606938044258990275541962092341162602522202993782792835301375n" - ); - - assert_eq!( - forward( - &mut context, - "BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)" - ), - "-741470203160010616172516490008037905920749803227695190508460n" - ); - assert_eq!( - forward( - &mut context, - "BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)" - ), - "865467841098979659369445602333124696601453190555097644792916n" - ); + run_test([ + TestAction::assert_eq("BigInt.asIntN(0, 1n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asIntN(1, 1n)", JsBigInt::from(-1)), + TestAction::assert_eq("BigInt.asIntN(3, 10n)", JsBigInt::from(2)), + TestAction::assert_eq("BigInt.asIntN({}, 1n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asIntN(2, 0n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asIntN(2, -0n)", JsBigInt::from(0)), + TestAction::assert_eq( + "BigInt.asIntN(2, -123456789012345678901n)", + JsBigInt::from(-1), + ), + TestAction::assert_eq( + "BigInt.asIntN(2, -123456789012345678900n)", + JsBigInt::from(0), + ), + TestAction::assert_eq( + "BigInt.asIntN(2, 123456789012345678900n)", + JsBigInt::from(0), + ), + TestAction::assert_eq( + "BigInt.asIntN(2, 123456789012345678901n)", + JsBigInt::from(1), + ), + TestAction::assert_eq( + "BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)", + JsBigInt::from(-1), + ), + TestAction::assert_eq( + "BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)", + JsBigInt::from_string("1606938044258990275541962092341162602522202993782792835301375") + .unwrap(), + ), + TestAction::assert_eq( + "BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)", + JsBigInt::from_string("-741470203160010616172516490008037905920749803227695190508460") + .unwrap(), + ), + TestAction::assert_eq( + "BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)", + JsBigInt::from_string("865467841098979659369445602333124696601453190555097644792916") + .unwrap(), + ), + ]); } #[test] fn as_int_n_errors() { - let mut context = Context::default(); - - assert_throws(&mut context, "BigInt.asIntN(-1, 0n)", "RangeError"); - assert_throws(&mut context, "BigInt.asIntN(-2.5, 0n)", "RangeError"); - assert_throws( - &mut context, - "BigInt.asIntN(9007199254740992, 0n)", - "RangeError", - ); - assert_throws(&mut context, "BigInt.asIntN(0n, 0n)", "TypeError"); + run_test([ + TestAction::assert_native_error( + "BigInt.asIntN(-1, 0n)", + ErrorKind::Range, + "Index must be between 0 and 2^53 - 1", + ), + TestAction::assert_native_error( + "BigInt.asIntN(-2.5, 0n)", + ErrorKind::Range, + "Index must be between 0 and 2^53 - 1", + ), + TestAction::assert_native_error( + "BigInt.asIntN(9007199254740992, 0n)", + ErrorKind::Range, + "Index must be between 0 and 2^53 - 1", + ), + TestAction::assert_native_error( + "BigInt.asIntN(0n, 0n)", + ErrorKind::Type, + "argument must not be a bigint", + ), + ]); } #[test] fn as_uint_n() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "BigInt.asUintN(0, -2n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(0, -1n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(0, 0n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(0, 1n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(0, 2n)"), "0n"); - - assert_eq!(forward(&mut context, "BigInt.asUintN(1, -3n)"), "1n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(1, -2n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(1, -1n)"), "1n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(1, 0n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(1, 1n)"), "1n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(1, 2n)"), "0n"); - assert_eq!(forward(&mut context, "BigInt.asUintN(1, 3n)"), "1n"); - - assert_eq!( - forward(&mut context, "BigInt.asUintN(1, -123456789012345678901n)"), - "1n" - ); - assert_eq!( - forward(&mut context, "BigInt.asUintN(1, -123456789012345678900n)"), - "0n" - ); - assert_eq!( - forward(&mut context, "BigInt.asUintN(1, 123456789012345678900n)"), - "0n" - ); - assert_eq!( - forward(&mut context, "BigInt.asUintN(1, 123456789012345678901n)"), - "1n" - ); - - assert_eq!( - forward( - &mut context, - "BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)" - ), - "1606938044258990275541962092341162602522202993782792835301375n" - ); - assert_eq!( - forward( - &mut context, - "BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)" - ), - "3213876088517980551083924184682325205044405987565585670602751n" - ); + run_test([ + TestAction::assert_eq("BigInt.asUintN(0, -2n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asUintN(0, -1n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asUintN(0, 0n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asUintN(0, 1n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asUintN(0, 2n)", JsBigInt::from(0)), + // + TestAction::assert_eq("BigInt.asUintN(1, -3n)", JsBigInt::from(1)), + TestAction::assert_eq("BigInt.asUintN(1, -2n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asUintN(1, -1n)", JsBigInt::from(1)), + TestAction::assert_eq("BigInt.asUintN(1, 0n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asUintN(1, 1n)", JsBigInt::from(1)), + TestAction::assert_eq("BigInt.asUintN(1, 2n)", JsBigInt::from(0)), + TestAction::assert_eq("BigInt.asUintN(1, 3n)", JsBigInt::from(1)), + // + TestAction::assert_eq( + "BigInt.asUintN(1, -123456789012345678901n)", + JsBigInt::from(1), + ), + TestAction::assert_eq( + "BigInt.asUintN(1, -123456789012345678900n)", + JsBigInt::from(0), + ), + TestAction::assert_eq( + "BigInt.asUintN(1, 123456789012345678900n)", + JsBigInt::from(0), + ), + TestAction::assert_eq( + "BigInt.asUintN(1, 123456789012345678901n)", + JsBigInt::from(1), + ), + // + TestAction::assert_eq( + "BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)", + JsBigInt::from_string("1606938044258990275541962092341162602522202993782792835301375") + .unwrap(), + ), + TestAction::assert_eq( + "BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)", + JsBigInt::from_string("3213876088517980551083924184682325205044405987565585670602751") + .unwrap(), + ), + ]); } #[test] fn as_uint_n_errors() { - let mut context = Context::default(); - - assert_throws(&mut context, "BigInt.asUintN(-1, 0n)", "RangeError"); - assert_throws(&mut context, "BigInt.asUintN(-2.5, 0n)", "RangeError"); - assert_throws( - &mut context, - "BigInt.asUintN(9007199254740992, 0n)", - "RangeError", - ); - assert_throws(&mut context, "BigInt.asUintN(0n, 0n)", "TypeError"); -} - -fn assert_throws(context: &mut Context<'_>, src: &str, error_type: &str) { - let result = forward(context, src); - assert!(result.contains(error_type)); -} - -#[test] -fn division_by_zero() { - let mut context = Context::default(); - assert_throws(&mut context, "1n/0n", "RangeError"); -} - -#[test] -fn remainder_by_zero() { - let mut context = Context::default(); - assert_throws(&mut context, "1n % 0n", "RangeError"); + run_test([ + TestAction::assert_native_error( + "BigInt.asUintN(-1, 0n)", + ErrorKind::Range, + "Index must be between 0 and 2^53 - 1", + ), + TestAction::assert_native_error( + "BigInt.asUintN(-2.5, 0n)", + ErrorKind::Range, + "Index must be between 0 and 2^53 - 1", + ), + TestAction::assert_native_error( + "BigInt.asUintN(9007199254740992, 0n)", + ErrorKind::Range, + "Index must be between 0 and 2^53 - 1", + ), + TestAction::assert_native_error( + "BigInt.asUintN(0n, 0n)", + ErrorKind::Type, + "argument must not be a bigint", + ), + ]); } diff --git a/boa_engine/src/builtins/boolean/tests.rs b/boa_engine/src/builtins/boolean/tests.rs index 3d6ae4a3dec..54fe42145e3 100644 --- a/boa_engine/src/builtins/boolean/tests.rs +++ b/boa_engine/src/builtins/boolean/tests.rs @@ -1,65 +1,41 @@ -use crate::{forward, forward_val, Context}; +use crate::{run_test, TestAction}; +use indoc::indoc; /// Test the correct type is returned from call and construct #[allow(clippy::unwrap_used)] #[test] fn construct_and_call() { - let mut context = Context::default(); - let init = r#" - var one = new Boolean(1); - var zero = Boolean(0); - "#; - eprintln!("{}", forward(&mut context, init)); - let one = forward_val(&mut context, "one").unwrap(); - let zero = forward_val(&mut context, "zero").unwrap(); - - assert!(one.is_object()); - assert!(zero.is_boolean()); + run_test([ + TestAction::assert_with_op("new Boolean(1)", |val, _| val.is_object()), + TestAction::assert_with_op("Boolean(0)", |val, _| val.is_boolean()), + ]); } #[test] fn constructor_gives_true_instance() { - let mut context = Context::default(); - let init = r#" - var trueVal = new Boolean(true); - var trueNum = new Boolean(1); - var trueString = new Boolean("true"); - var trueBool = new Boolean(trueVal); - "#; - - eprintln!("{}", forward(&mut context, init)); - let true_val = forward_val(&mut context, "trueVal").expect("value expected"); - let true_num = forward_val(&mut context, "trueNum").expect("value expected"); - let true_string = forward_val(&mut context, "trueString").expect("value expected"); - let true_bool = forward_val(&mut context, "trueBool").expect("value expected"); - - // Values should all be objects - assert!(true_val.is_object()); - assert!(true_num.is_object()); - assert!(true_string.is_object()); - assert!(true_bool.is_object()); - - // Values should all be truthy - assert!(true_val.to_boolean()); - assert!(true_num.to_boolean()); - assert!(true_string.to_boolean()); - assert!(true_bool.to_boolean()); + run_test([ + TestAction::run(indoc! {r#" + var trueVal = new Boolean(true); + var trueNum = new Boolean(1); + var trueString = new Boolean("true"); + var trueBool = new Boolean(trueVal); + "#}), + // Values should all be objects + TestAction::assert_with_op("trueVal", |val, _| val.is_object()), + TestAction::assert_with_op("trueNum", |val, _| val.is_object()), + TestAction::assert_with_op("trueString", |val, _| val.is_object()), + TestAction::assert_with_op("trueBool", |val, _| val.is_object()), + // Values should all be truthy + TestAction::assert("trueVal.valueOf()"), + TestAction::assert("trueNum.valueOf()"), + TestAction::assert("trueString.valueOf()"), + TestAction::assert("trueBool.valueOf()"), + ]); } #[test] fn instances_have_correct_proto_set() { - let mut context = Context::default(); - let init = r#" - var boolInstance = new Boolean(true); - var boolProto = Boolean.prototype; - "#; - - eprintln!("{}", forward(&mut context, init)); - let bool_instance = forward_val(&mut context, "boolInstance").expect("value expected"); - let bool_prototype = forward_val(&mut context, "boolProto").expect("value expected"); - - assert_eq!( - &*bool_instance.as_object().unwrap().prototype(), - &bool_prototype.as_object().cloned() - ); + run_test([TestAction::assert( + "Object.getPrototypeOf(new Boolean(true)) === Boolean.prototype", + )]); } diff --git a/boa_engine/src/builtins/date/tests.rs b/boa_engine/src/builtins/date/tests.rs index 07324b8b534..91ba3100e7a 100644 --- a/boa_engine/src/builtins/date/tests.rs +++ b/boa_engine/src/builtins/date/tests.rs @@ -1,10 +1,11 @@ -use crate::{forward, forward_val, Context, JsValue}; -use chrono::{prelude::*, Duration}; +use crate::{builtins::error::ErrorKind, run_test, TestAction}; +use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone}; +use indoc::indoc; // NOTE: Javascript Uses 0-based months, where chrono uses 1-based months. Many of the assertions look wrong because of // this. -fn datetime_from_local( +fn timestamp_from_local( year: i32, month: u32, date: u32, @@ -12,7 +13,7 @@ fn datetime_from_local( minute: u32, second: u32, millisecond: u32, -) -> NaiveDateTime { +) -> i64 { Local .from_local_datetime( &NaiveDate::from_ymd_opt(year, month, date) @@ -23,13 +24,23 @@ fn datetime_from_local( .earliest() .unwrap() .naive_utc() + .timestamp_millis() } -fn forward_dt(context: &mut Context<'_>, src: &str) -> Option { - let date_time = forward_val(context, src).unwrap(); - let date_time = date_time.as_object().unwrap(); - let date_time = date_time.borrow(); - date_time.as_date().unwrap().0 +fn timestamp_from_utc( + year: i32, + month: u32, + date: u32, + hour: u32, + minute: u32, + second: u32, + millisecond: u32, +) -> i64 { + NaiveDate::from_ymd_opt(year, month, date) + .unwrap() + .and_hms_milli_opt(hour, minute, second, millisecond) + .unwrap() + .timestamp_millis() } #[test] @@ -49,1362 +60,802 @@ fn date_display() { #[test] fn date_this_time_value() { - let mut context = Context::default(); - - let error = forward_val( - &mut context, + run_test([TestAction::assert_native_error( "({toString: Date.prototype.toString}).toString()", - ) - .unwrap_err(); - let error = error.as_native().unwrap(); - assert_eq!("\'this\' is not a Date", error.message()); + ErrorKind::Type, + "'this' is not a Date", + )]); } #[test] fn date_ctor_call() { - let mut context = Context::default(); - - let dt1 = forward_dt(&mut context, "new Date()"); - - std::thread::sleep(std::time::Duration::from_millis(1)); - - let dt2 = forward_dt(&mut context, "new Date()"); - - assert_ne!(dt1, dt2); + run_test([ + TestAction::run("let a = new Date()"), + TestAction::inspect_context(|_| std::thread::sleep(std::time::Duration::from_millis(1))), + TestAction::assert("a.getTime() != new Date().getTime()"), + ]); } #[test] fn date_ctor_call_string() { - let mut context = Context::default(); - - let date_time = forward_dt(&mut context, "new Date('2020-06-08T09:16:15.779-06:30')"); - - // Internal date is expressed as UTC - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 6, 8) - .unwrap() - .and_hms_milli_opt(15, 46, 15, 779) - .unwrap() - ), - date_time - ); + run_test([TestAction::assert_eq( + "new Date('2020-06-08T09:16:15.779-06:30').getTime()", + timestamp_from_utc(2020, 6, 8, 15, 46, 15, 779), + )]); } #[test] fn date_ctor_call_string_invalid() { - let mut context = Context::default(); - - let date_time = forward_dt(&mut context, "new Date('nope')"); - assert_eq!(None, date_time); + run_test([TestAction::assert_eq( + "new Date('nope').getTime()", + f64::NAN, + )]); } #[test] fn date_ctor_call_number() { - let mut context = Context::default(); - - let date_time = forward_dt(&mut context, "new Date(1594199775779)"); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() - ), - date_time - ); + run_test([TestAction::assert_eq( + "new Date(1594199775779).getTime()", + timestamp_from_utc(2020, 7, 8, 9, 16, 15, 779), + )]); } #[test] fn date_ctor_call_date() { - let mut context = Context::default(); - - let date_time = forward_dt(&mut context, "new Date(new Date(1594199775779))"); - - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() - ), - date_time - ); + run_test([TestAction::assert_eq( + "new Date(new Date(1594199775779)).getTime()", + timestamp_from_utc(2020, 7, 8, 9, 16, 15, 779), + )]); } #[test] fn date_ctor_call_multiple() { - let mut context = Context::default(); - - let date_time = forward_dt(&mut context, "new Date(2020, 6, 8, 9, 16, 15, 779)"); - - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 16, 15, 779)), - date_time - ); + run_test([TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).getTime()", + timestamp_from_local(2020, 7, 8, 9, 16, 15, 779), + )]); } #[test] fn date_ctor_call_multiple_90s() { - let mut context = Context::default(); - - let date_time = forward_dt(&mut context, "new Date(99, 6, 8, 9, 16, 15, 779)"); - - assert_eq!( - Some(datetime_from_local(1999, 7, 8, 9, 16, 15, 779)), - date_time - ); + run_test([TestAction::assert_eq( + "new Date(99, 6, 8, 9, 16, 15, 779).getTime()", + timestamp_from_local(1999, 7, 8, 9, 16, 15, 779), + )]); } #[test] fn date_ctor_call_multiple_nan() { - fn check(src: &str) { - let mut context = Context::default(); - let date_time = forward_dt(&mut context, src); - assert_eq!(None, date_time); - } - - check("new Date(1/0, 6, 8, 9, 16, 15, 779)"); - check("new Date(2020, 1/0, 8, 9, 16, 15, 779)"); - check("new Date(2020, 6, 1/0, 9, 16, 15, 779)"); - check("new Date(2020, 6, 8, 1/0, 16, 15, 779)"); - check("new Date(2020, 6, 8, 9, 1/0, 15, 779)"); - check("new Date(2020, 6, 8, 9, 16, 1/0, 779)"); - check("new Date(2020, 6, 8, 9, 16, 15, 1/0)"); + run_test([ + TestAction::assert_eq("new Date(1/0, 6, 8, 9, 16, 15, 779).getTime()", f64::NAN), + TestAction::assert_eq("new Date(2020, 1/0, 8, 9, 16, 15, 779).getTime()", f64::NAN), + TestAction::assert_eq("new Date(2020, 6, 1/0, 9, 16, 15, 779).getTime()", f64::NAN), + TestAction::assert_eq("new Date(2020, 6, 8, 1/0, 16, 15, 779).getTime()", f64::NAN), + TestAction::assert_eq("new Date(2020, 6, 8, 9, 1/0, 15, 779).getTime()", f64::NAN), + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 1/0, 779).getTime()", f64::NAN), + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 1/0).getTime()", f64::NAN), + ]); } #[test] fn date_ctor_now_call() { - let mut context = Context::default(); - - let date_time = forward(&mut context, "Date.now()"); - let dt1 = date_time.parse::().unwrap(); - - std::thread::sleep(std::time::Duration::from_millis(1)); - - let date_time = forward(&mut context, "Date.now()"); - let dt2 = date_time.parse::().unwrap(); - - assert_ne!(dt1, dt2); + run_test([ + TestAction::run("let a = Date.now()"), + TestAction::inspect_context(|_| std::thread::sleep(std::time::Duration::from_millis(1))), + TestAction::assert("a != Date.now()"), + ]); } #[test] fn date_ctor_parse_call() { - let mut context = Context::default(); - - let date_time = forward_val(&mut context, "Date.parse('2020-06-08T09:16:15.779-07:30')"); - - assert_eq!(JsValue::new(1_591_634_775_779_i64), date_time.unwrap()); + run_test([TestAction::assert_eq( + "Date.parse('2020-06-08T09:16:15.779-07:30')", + 1_591_634_775_779_i64, + )]); } #[test] fn date_ctor_utc_call() { - let mut context = Context::default(); - - let date_time = forward_val(&mut context, "Date.UTC(2020, 6, 8, 9, 16, 15, 779)"); - - assert_eq!(JsValue::new(1_594_199_775_779_i64), date_time.unwrap()); + run_test([TestAction::assert_eq( + "Date.UTC(2020, 6, 8, 9, 16, 15, 779)", + 1_594_199_775_779_i64, + )]); } #[test] fn date_ctor_utc_call_nan() { - fn check(src: &str) { - let mut context = Context::default(); - let date_time = forward_val(&mut context, src).unwrap(); - assert_eq!(JsValue::nan(), date_time); - } - - check("Date.UTC(1/0, 6, 8, 9, 16, 15, 779)"); - check("Date.UTC(2020, 1/0, 8, 9, 16, 15, 779)"); - check("Date.UTC(2020, 6, 1/0, 9, 16, 15, 779)"); - check("Date.UTC(2020, 6, 8, 1/0, 16, 15, 779)"); - check("Date.UTC(2020, 6, 8, 9, 1/0, 15, 779)"); - check("Date.UTC(2020, 6, 8, 9, 16, 1/0, 779)"); - check("Date.UTC(2020, 6, 8, 9, 16, 15, 1/0)"); + run_test([ + TestAction::assert_eq("Date.UTC(1/0, 6, 8, 9, 16, 15, 779)", f64::NAN), + TestAction::assert_eq("Date.UTC(2020, 1/0, 8, 9, 16, 15, 779)", f64::NAN), + TestAction::assert_eq("Date.UTC(2020, 6, 1/0, 9, 16, 15, 779)", f64::NAN), + TestAction::assert_eq("Date.UTC(2020, 6, 8, 1/0, 16, 15, 779)", f64::NAN), + TestAction::assert_eq("Date.UTC(2020, 6, 8, 9, 1/0, 15, 779)", f64::NAN), + TestAction::assert_eq("Date.UTC(2020, 6, 8, 9, 16, 1/0, 779)", f64::NAN), + TestAction::assert_eq("Date.UTC(2020, 6, 8, 9, 16, 15, 1/0)", f64::NAN), + ]); } #[test] fn date_proto_get_date_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getDate()", - ); - assert_eq!(JsValue::new(8), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getDate()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getDate()", 8), + TestAction::assert_eq("new Date(1/0).getDate()", f64::NAN), + ]); } #[test] fn date_proto_get_day_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getDay()", - ); - assert_eq!(JsValue::new(3), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getDay()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getDay()", 3), + TestAction::assert_eq("new Date(1/0).getDay()", f64::NAN), + ]); } #[test] fn date_proto_get_full_year_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getFullYear()", - ); - assert_eq!(JsValue::new(2020), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getFullYear()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getFullYear()", 2020), + TestAction::assert_eq("new Date(1/0).getFullYear()", f64::NAN), + ]); } #[test] fn date_proto_get_hours_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getHours()", - ); - assert_eq!(JsValue::new(9), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getHours()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getHours()", 9), + TestAction::assert_eq("new Date(1/0).getHours()", f64::NAN), + ]); } #[test] fn date_proto_get_milliseconds_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getMilliseconds()", - ); - assert_eq!(JsValue::new(779), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getMilliseconds()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).getMilliseconds()", + 779, + ), + TestAction::assert_eq("new Date(1/0).getMilliseconds()", f64::NAN), + ]); } #[test] fn date_proto_get_minutes_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getMinutes()", - ); - assert_eq!(JsValue::new(16), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getMinutes()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getMinutes()", 16), + TestAction::assert_eq("new Date(1/0).getMinutes()", f64::NAN), + ]); } #[test] fn date_proto_get_month() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getMonth()", - ); - assert_eq!(JsValue::new(6), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getMonth()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getMonth()", 6), + TestAction::assert_eq("new Date(1/0).getMonth()", f64::NAN), + ]); } #[test] fn date_proto_get_seconds() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getSeconds()", - ); - assert_eq!(JsValue::new(15), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getSeconds()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getSeconds()", 15), + TestAction::assert_eq("new Date(1/0).getSeconds()", f64::NAN), + ]); } #[test] fn date_proto_get_time() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getTime()", - ); - - let ts = Local.with_ymd_and_hms(2020, 7, 8, 9, 16, 15).unwrap() + Duration::milliseconds(779); - - assert_eq!(JsValue::new(ts.timestamp_millis()), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getTime()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).getTime()", + timestamp_from_local(2020, 7, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq("new Date(1/0).getTime()", f64::NAN), + ]); } #[test] fn date_proto_get_year() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(2020, 6, 8, 9, 16, 15, 779).getYear()", - ); - assert_eq!(JsValue::new(120), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getYear()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq("new Date(2020, 6, 8, 9, 16, 15, 779).getYear()", 120), + TestAction::assert_eq("new Date(1/0).getYear()", f64::NAN), + ]); } #[test] fn date_proto_get_timezone_offset() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset() === new Date('1975-08-19T23:15:30-02:00').getTimezoneOffset()", - ); - - // NB: Host Settings, not TZ specified in the DateTime. - assert_eq!(JsValue::new(true), actual.unwrap()); - - let actual = forward_val( - &mut context, - "new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset()", - ); - - // The value of now().offset() depends on the host machine, so we have to replicate the method code here. - let offset_seconds = chrono::Local::now().offset().local_minus_utc(); - let offset_minutes = -offset_seconds / 60; - assert_eq!(JsValue::new(offset_minutes), actual.unwrap()); - - let actual = forward_val( - &mut context, - "new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset()", - ); - assert_eq!(JsValue::new(offset_minutes), actual.unwrap()); + run_test([ + TestAction::assert(indoc! {r#" + new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset() === + new Date('1975-08-19T23:15:30-02:00').getTimezoneOffset() + "#}), + // NB: Host Settings, not TZ specified in the DateTime. + TestAction::assert_eq( + "new Date('1975-08-19T23:15:30+07:00').getTimezoneOffset()", + { + // The value of now().offset() depends on the host machine, so we have to replicate the method code here. + let offset_seconds = chrono::Local::now().offset().local_minus_utc(); + -offset_seconds / 60 + }, + ), + ]); } #[test] fn date_proto_get_utc_date_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCDate()", - ); - assert_eq!(JsValue::new(8), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCDate()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCDate()", + 8, + ), + TestAction::assert_eq("new Date(1/0).getUTCDate()", f64::NAN), + ]); } #[test] fn date_proto_get_utc_day_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCDay()", - ); - assert_eq!(JsValue::new(3), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCDay()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCDay()", + 3, + ), + TestAction::assert_eq("new Date(1/0).getUTCDay()", f64::NAN), + ]); } #[test] fn date_proto_get_utc_full_year_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCFullYear()", - ); - assert_eq!(JsValue::new(2020), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCFullYear()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCFullYear()", + 2020, + ), + TestAction::assert_eq("new Date(1/0).getUTCFullYear()", f64::NAN), + ]); } #[test] fn date_proto_get_utc_hours_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCHours()", - ); - assert_eq!(JsValue::new(9), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCHours()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCHours()", + 9, + ), + TestAction::assert_eq("new Date(1/0).getUTCHours()", f64::NAN), + ]); } #[test] fn date_proto_get_utc_milliseconds_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMilliseconds()", - ); - assert_eq!(JsValue::new(779), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCMilliseconds()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMilliseconds()", + 779, + ), + TestAction::assert_eq("new Date(1/0).getUTCMilliseconds()", f64::NAN), + ]); } #[test] fn date_proto_get_utc_minutes_call() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMinutes()", - ); - assert_eq!(JsValue::new(16), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCMinutes()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMinutes()", + 16, + ), + TestAction::assert_eq("new Date(1/0).getUTCMinutes()", f64::NAN), + ]); } #[test] fn date_proto_get_utc_month() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMonth()", - ); - assert_eq!(JsValue::new(6), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCMonth()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCMonth()", + 6, + ), + TestAction::assert_eq("new Date(1/0).getUTCMonth()", f64::NAN), + ]); } #[test] fn date_proto_get_utc_seconds() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCSeconds()", - ); - assert_eq!(JsValue::new(15), actual.unwrap()); - - let actual = forward_val(&mut context, "new Date(1/0).getUTCSeconds()"); - assert_eq!(JsValue::nan(), actual.unwrap()); + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).getUTCSeconds()", + 15, + ), + TestAction::assert_eq("new Date(1/0).getUTCSeconds()", f64::NAN), + ]); } #[test] fn date_proto_set_date() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setDate(21); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 21, 9, 16, 15, 779)), - actual - ); - - // Date wraps to previous month for 0. - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setDate(0); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 6, 30, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setDate(1/0); dt", - ); - assert_eq!(None, actual); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setDate(21)", + timestamp_from_local(2020, 7, 21, 9, 16, 15, 779), + ), + // Date wraps to previous month for 0. + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setDate(0)", + timestamp_from_local(2020, 6, 30, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setDate(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_full_year() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setFullYear(2012); dt", - ); - assert_eq!( - Some(datetime_from_local(2012, 7, 8, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setFullYear(2012, 8); dt", - ); - assert_eq!( - Some(datetime_from_local(2012, 9, 8, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setFullYear(2012, 8, 10); dt", - ); - assert_eq!( - Some(datetime_from_local(2012, 9, 10, 9, 16, 15, 779)), - actual - ); - - // Out-of-bounds - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 7, 8, 9, 16, 15, 779); dt.setFullYear(2012, 35); dt", - ); - assert_eq!( - Some(datetime_from_local(2014, 12, 8, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 7, 8, 9, 16, 15, 779); dt.setFullYear(2012, -35); dt", - ); - assert_eq!( - Some(datetime_from_local(2009, 2, 8, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 7, 8, 9, 16, 15, 779); dt.setFullYear(2012, 9, 950); dt", - ); - assert_eq!( - Some(datetime_from_local(2015, 5, 8, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 7, 8, 9, 16, 15, 779); dt.setFullYear(2012, 9, -950); dt", - ); - assert_eq!( - Some(datetime_from_local(2010, 2, 23, 9, 16, 15, 779)), - actual - ); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012)", + timestamp_from_local(2012, 7, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 8)", + timestamp_from_local(2012, 9, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 8, 10)", + timestamp_from_local(2012, 9, 10, 9, 16, 15, 779), + ), + // Out-of-bounds + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 35)", + timestamp_from_local(2014, 12, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, -35)", + timestamp_from_local(2009, 2, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 9, 950)", + timestamp_from_local(2015, 5, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(2012, 9, -950)", + timestamp_from_local(2010, 2, 23, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setFullYear(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_hours() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setHours(11); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 11, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setHours(11, 35); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 11, 35, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setHours(11, 35, 23); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 11, 35, 23, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setHours(11, 35, 23, 537); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 11, 35, 23, 537)), - actual - ); - - // Out-of-bounds - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setHours(10000, 20000, 30000, 40123); dt", - ); - assert_eq!( - Some(datetime_from_local(2021, 9, 11, 21, 40, 40, 123)), - actual - ); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11)", + timestamp_from_local(2020, 7, 8, 11, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11, 35)", + timestamp_from_local(2020, 7, 8, 11, 35, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11, 35, 23)", + timestamp_from_local(2020, 7, 8, 11, 35, 23, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(11, 35, 23, 537)", + timestamp_from_local(2020, 7, 8, 11, 35, 23, 537), + ), + // Out-of-bounds + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(10000, 20000, 30000, 40123)", + timestamp_from_local(2021, 9, 11, 21, 40, 40, 123), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setHours(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_milliseconds() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMilliseconds(597); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 16, 15, 597)), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setHours - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMilliseconds(40123); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 16, 55, 123)), - actual - ); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMilliseconds(597)", + timestamp_from_local(2020, 7, 8, 9, 16, 15, 597), + ), + // Out-of-bounds + // Thorough tests are done by setHours + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMilliseconds(40123)", + timestamp_from_local(2020, 7, 8, 9, 16, 55, 123), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMilliseconds(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_minutes() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMinutes(11); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 11, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMinutes(11, 35); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 11, 35, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMinutes(11, 35, 537); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 11, 35, 537)), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setHours - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMinutes(600000, 30000, 40123); dt", - ); - assert_eq!( - Some(datetime_from_local(2021, 8, 29, 9, 20, 40, 123)), - actual - ); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(11)", + timestamp_from_local(2020, 7, 8, 9, 11, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(11, 35)", + timestamp_from_local(2020, 7, 8, 9, 11, 35, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(11, 35, 537)", + timestamp_from_local(2020, 7, 8, 9, 11, 35, 537), + ), + // Out-of-bounds + // Thorough tests are done by setHours + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(600000, 30000, 40123)", + timestamp_from_local(2021, 8, 29, 9, 20, 40, 123), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMinutes(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_month() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMonth(11); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 12, 8, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setMonth(11, 16); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 12, 16, 9, 16, 15, 779)), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setFullYear - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 7, 8, 9, 16, 15, 779); dt.setMonth(40, 83); dt", - ); - assert_eq!( - Some(datetime_from_local(2023, 7, 22, 9, 16, 15, 779)), - actual - ); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(11)", + timestamp_from_local(2020, 12, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(11, 16)", + timestamp_from_local(2020, 12, 16, 9, 16, 15, 779), + ), + // Out-of-bounds + // Thorough tests are done by setFullYear + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(40, 83)", + timestamp_from_local(2023, 7, 22, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setMonth(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_seconds() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setSeconds(11); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 16, 11, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setSeconds(11, 487); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 16, 11, 487)), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setHour - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 7, 8, 9, 16, 15, 779); dt.setSeconds(40000000, 40123); dt", - ); - assert_eq!( - Some(datetime_from_local(2021, 11, 14, 8, 23, 20, 123)), - actual - ); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(11)", + timestamp_from_local(2020, 7, 8, 9, 16, 11, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(11, 487)", + timestamp_from_local(2020, 7, 8, 9, 16, 11, 487), + ), + // Out-of-bounds + // Thorough tests are done by setHour + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(40000000, 40123)", + timestamp_from_local(2021, 10, 14, 8, 23, 20, 123), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setSeconds(1/0)", + f64::NAN, + ), + ]); } #[test] fn set_year() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setYear(98); dt", - ); - assert_eq!( - Some(datetime_from_local(1998, 7, 8, 9, 16, 15, 779)), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.setYear(2001); dt", - ); - assert_eq!( - Some(datetime_from_local(2001, 7, 8, 9, 16, 15, 779)), - actual - ); + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setYear(98)", + timestamp_from_local(1998, 7, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setYear(2001)", + timestamp_from_local(2001, 7, 8, 9, 16, 15, 779), + ), + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setYear(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_time() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(); dt.setTime(new Date(2020, 6, 8, 9, 16, 15, 779).getTime()); dt", - ); - assert_eq!( - Some(datetime_from_local(2020, 7, 8, 9, 16, 15, 779)), - actual - ); + run_test([TestAction::assert_eq( + "new Date().setTime(new Date(2020, 6, 8, 9, 16, 15, 779).getTime())", + timestamp_from_local(2020, 7, 8, 9, 16, 15, 779), + )]); } #[test] fn date_proto_set_utc_date() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCDate(21); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 21) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCDate(21)", + timestamp_from_utc(2020, 7, 21, 9, 16, 15, 779), ), - actual - ); - - // Date wraps to previous month for 0. - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCDate(0); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 6, 30) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + // Date wraps to previous month for 0. + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCDate(0)", + timestamp_from_utc(2020, 6, 30, 9, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCDate(1/0); dt", - ); - assert_eq!(None, actual); + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCDate(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_utc_full_year() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCFullYear(2012); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2012, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012)", + timestamp_from_utc(2012, 7, 8, 9, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCFullYear(2012, 8); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2012, 9, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 8)", + timestamp_from_utc(2012, 9, 8, 9, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCFullYear(2012, 8, 10); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2012, 9, 10) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 8, 10)", + timestamp_from_utc(2012, 9, 10, 9, 16, 15, 779), ), - actual - ); - - // Out-of-bounds - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 7, 8, 9, 16, 15, 779)); dt.setUTCFullYear(2012, 35); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2014, 12, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + // Out-of-bounds + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 35)", + timestamp_from_utc(2014, 12, 8, 9, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 7, 8, 9, 16, 15, 779)); dt.setUTCFullYear(2012, -35); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2009, 2, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, -35)", + timestamp_from_utc(2009, 2, 8, 9, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 7, 8, 9, 16, 15, 779)); dt.setUTCFullYear(2012, 9, 950); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2015, 5, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 9, 950)", + timestamp_from_utc(2015, 5, 8, 9, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 7, 8, 9, 16, 15, 779)); dt.setUTCFullYear(2012, 9, -950); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2010, 2, 23) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(2012, 9, -950)", + timestamp_from_utc(2010, 2, 23, 9, 16, 15, 779), ), - actual - ); + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCFullYear(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_utc_hours() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCHours(11); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(11, 16, 15, 779) - .unwrap() + run_test([ + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11)", + timestamp_from_utc(2020, 7, 8, 11, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCHours(11, 35); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(11, 35, 15, 779) - .unwrap() + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11, 35)", + timestamp_from_utc(2020, 7, 8, 11, 35, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCHours(11, 35, 23); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(11, 35, 23, 779) - .unwrap() + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11, 35, 23)", + timestamp_from_utc(2020, 7, 8, 11, 35, 23, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCHours(11, 35, 23, 537); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(11, 35, 23, 537) - .unwrap() + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(11, 35, 23, 537)", + timestamp_from_utc(2020, 7, 8, 11, 35, 23, 537), ), - actual - ); - - // Out-of-bounds - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCHours(10000, 20000, 30000, 40123); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2021, 9, 11) - .unwrap() - .and_hms_milli_opt(21, 40, 40, 123) - .unwrap() + // Out-of-bounds + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(10000, 20000, 30000, 40123)", + timestamp_from_utc(2021, 9, 11, 21, 40, 40, 123), ), - actual - ); + TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).setUTCHours(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_utc_milliseconds() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMilliseconds(597); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 597) - .unwrap() + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMilliseconds(597)", + timestamp_from_utc(2020, 7, 8, 9, 16, 15, 597), ), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setHours - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMilliseconds(40123); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 55, 123) - .unwrap() + // Out-of-bounds + // Thorough tests are done by setHours + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMilliseconds(40123)", + timestamp_from_utc(2020, 7, 8, 9, 16, 55, 123), ), - actual - ); + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMilliseconds(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_utc_minutes() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMinutes(11); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 11, 15, 779) - .unwrap() + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(11)", + timestamp_from_utc(2020, 7, 8, 9, 11, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMinutes(11, 35); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 11, 35, 779) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(11, 35)", + timestamp_from_utc(2020, 7, 8, 9, 11, 35, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMinutes(11, 35, 537); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 11, 35, 537) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(11, 35, 537)", + timestamp_from_utc(2020, 7, 8, 9, 11, 35, 537), ), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setHours - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMinutes(600000, 30000, 40123); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2021, 8, 29) - .unwrap() - .and_hms_milli_opt(9, 20, 40, 123) - .unwrap() + // Out-of-bounds + // Thorough tests are done by setHours + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(600000, 30000, 40123)", + timestamp_from_utc(2021, 8, 29, 9, 20, 40, 123), ), - actual - ); + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMinutes(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_utc_month() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMonth(11); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 12, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(11)", + timestamp_from_utc(2020, 12, 8, 9, 16, 15, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCMonth(11, 16); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 12, 16) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(11, 16)", + timestamp_from_utc(2020, 12, 16, 9, 16, 15, 779), ), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setFullYear - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 7, 8, 9, 16, 15, 779)); dt.setUTCMonth(40, 83); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2023, 7, 22) - .unwrap() - .and_hms_milli_opt(9, 16, 15, 779) - .unwrap() + // Out-of-bounds + // Thorough tests are done by setFullYear + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(40, 83)", + timestamp_from_utc(2023, 7, 22, 9, 16, 15, 779), ), - actual - ); + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCMonth(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_set_utc_seconds() { - let mut context = Context::default(); - - let actual = forward_dt( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCSeconds(11); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 11, 779) - .unwrap() + run_test([ + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(11)", + timestamp_from_utc(2020, 7, 8, 9, 16, 11, 779), ), - actual - ); - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.setUTCSeconds(11, 487); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2020, 7, 8) - .unwrap() - .and_hms_milli_opt(9, 16, 11, 487) - .unwrap() + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(11, 487)", + timestamp_from_utc(2020, 7, 8, 9, 16, 11, 487), ), - actual - ); - - // Out-of-bounds - // Thorough tests are done by setHour - - let actual = forward_dt( - &mut context, - "dt = new Date(Date.UTC(2020, 7, 8, 9, 16, 15, 779)); dt.setUTCSeconds(40000000, 40123); dt", - ); - assert_eq!( - Some( - NaiveDate::from_ymd_opt(2021, 11, 14) - .unwrap() - .and_hms_milli_opt(8, 23, 20, 123) - .unwrap() + // Out-of-bounds + // Thorough tests are done by setHour + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(40000000, 40123)", + timestamp_from_utc(2021, 10, 14, 8, 23, 20, 123), ), - actual - ); + TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).setUTCSeconds(1/0)", + f64::NAN, + ), + ]); } #[test] fn date_proto_to_date_string() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.toDateString()", - ) - .unwrap(); - assert_eq!(JsValue::new("Wed Jul 08 2020"), actual); + run_test([TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).toDateString()", + "Wed Jul 08 2020", + )]); } #[test] fn date_proto_to_gmt_string() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.toGMTString()", - ) - .unwrap(); - assert_eq!(JsValue::new("Wed, 08 Jul 2020 09:16:15 GMT"), actual); + run_test([TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toGMTString()", + "Wed, 08 Jul 2020 09:16:15 GMT", + )]); } #[test] fn date_proto_to_iso_string() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.toISOString()", - ) - .unwrap(); - assert_eq!(JsValue::new("2020-07-08T09:16:15.779Z"), actual); + run_test([TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toISOString()", + "2020-07-08T09:16:15.779Z", + )]); } #[test] fn date_proto_to_json() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.toJSON()", - ) - .unwrap(); - assert_eq!(JsValue::new("2020-07-08T09:16:15.779Z"), actual); + run_test([TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toJSON()", + "2020-07-08T09:16:15.779Z", + )]); } #[test] fn date_proto_to_string() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.toString()", - ) - .ok(); - - assert_eq!( - Some(JsValue::new( - Local - .from_local_datetime(&NaiveDateTime::new( - NaiveDate::from_ymd_opt(2020, 7, 8).unwrap(), - NaiveTime::from_hms_milli_opt(9, 16, 15, 779).unwrap() - )) - .earliest() - .unwrap() - .format("Wed Jul 08 2020 09:16:15 GMT%z") - .to_string() - )), - actual - ); + run_test([TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).toString()", + Local + .from_local_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(2020, 7, 8).unwrap(), + NaiveTime::from_hms_milli_opt(9, 16, 15, 779).unwrap(), + )) + .earliest() + .unwrap() + .format("Wed Jul 08 2020 09:16:15 GMT%z") + .to_string(), + )]); } #[test] fn date_proto_to_time_string() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "let dt = new Date(2020, 6, 8, 9, 16, 15, 779); dt.toTimeString()", - ) - .ok(); - - assert_eq!( - Some(JsValue::new( - Local - .from_local_datetime(&NaiveDateTime::new( - NaiveDate::from_ymd_opt(2020, 7, 8).unwrap(), - NaiveTime::from_hms_milli_opt(9, 16, 15, 779).unwrap() - )) - .earliest() - .unwrap() - .format("09:16:15 GMT%z") - .to_string() - )), - actual - ); + run_test([TestAction::assert_eq( + "new Date(2020, 6, 8, 9, 16, 15, 779).toTimeString()", + Local + .from_local_datetime(&NaiveDateTime::new( + NaiveDate::from_ymd_opt(2020, 7, 8).unwrap(), + NaiveTime::from_hms_milli_opt(9, 16, 15, 779).unwrap(), + )) + .earliest() + .unwrap() + .format("09:16:15 GMT%z") + .to_string(), + )]); } #[test] fn date_proto_to_utc_string() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, - "let dt = new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)); dt.toUTCString()", - ) - .unwrap(); - assert_eq!(JsValue::new("Wed, 08 Jul 2020 09:16:15 GMT"), actual); + run_test([TestAction::assert_eq( + "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).toUTCString()", + "Wed, 08 Jul 2020 09:16:15 GMT", + )]); } #[test] fn date_proto_value_of() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, + run_test([TestAction::assert_eq( "new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)).valueOf()", - ) - .unwrap(); - assert_eq!(JsValue::new(1_594_199_775_779_i64), actual); + 1_594_199_775_779_i64, + )]); } #[test] fn date_neg() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, + run_test([TestAction::assert_eq( "-new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779))", - ) - .unwrap(); - assert_eq!(JsValue::new(-1_594_199_775_779_i64), actual); + -1_594_199_775_779_i64, + )]); } #[test] fn date_json() { - let mut context = Context::default(); - - let actual = forward_val( - &mut context, + run_test([TestAction::assert_eq( "JSON.stringify({ date: new Date(Date.UTC(2020, 6, 8, 9, 16, 15, 779)) })", - ) - .unwrap(); - assert_eq!( - JsValue::new(r#"{"date":"2020-07-08T09:16:15.779Z"}"#), - actual - ); + r#"{"date":"2020-07-08T09:16:15.779Z"}"#, + )]); } diff --git a/boa_engine/src/builtins/error/tests.rs b/boa_engine/src/builtins/error/tests.rs index e082ba9666f..814fcd0a798 100644 --- a/boa_engine/src/builtins/error/tests.rs +++ b/boa_engine/src/builtins/error/tests.rs @@ -1,86 +1,60 @@ -use crate::{forward, Context}; +use crate::{run_test, TestAction}; +use indoc::indoc; #[test] fn error_to_string() { - let mut context = Context::default(); - let init = r#" - let e = new Error('1'); - let name = new Error(); - let message = new Error('message'); - message.name = ''; - let range_e = new RangeError('2'); - let ref_e = new ReferenceError('3'); - let syntax_e = new SyntaxError('4'); - let type_e = new TypeError('5'); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "e.toString()"), "\"Error: 1\""); - assert_eq!(forward(&mut context, "name.toString()"), "\"Error\""); - assert_eq!(forward(&mut context, "message.toString()"), "\"message\""); - assert_eq!( - forward(&mut context, "range_e.toString()"), - "\"RangeError: 2\"" - ); - assert_eq!( - forward(&mut context, "ref_e.toString()"), - "\"ReferenceError: 3\"" - ); - assert_eq!( - forward(&mut context, "syntax_e.toString()"), - "\"SyntaxError: 4\"" - ); - assert_eq!( - forward(&mut context, "type_e.toString()"), - "\"TypeError: 5\"" - ); + run_test([ + TestAction::assert_eq("(new Error('1')).toString()", "Error: 1"), + TestAction::assert_eq("(new RangeError('2')).toString()", "RangeError: 2"), + TestAction::assert_eq("(new ReferenceError('3')).toString()", "ReferenceError: 3"), + TestAction::assert_eq("(new SyntaxError('4')).toString()", "SyntaxError: 4"), + TestAction::assert_eq("(new TypeError('5')).toString()", "TypeError: 5"), + TestAction::assert_eq("(new EvalError('6')).toString()", "EvalError: 6"), + TestAction::assert_eq("(new URIError('7')).toString()", "URIError: 7"), + // no message + TestAction::assert_eq("(new Error()).toString()", "Error"), + TestAction::assert_eq("(new RangeError()).toString()", "RangeError"), + TestAction::assert_eq("(new ReferenceError()).toString()", "ReferenceError"), + TestAction::assert_eq("(new SyntaxError()).toString()", "SyntaxError"), + TestAction::assert_eq("(new TypeError()).toString()", "TypeError"), + TestAction::assert_eq("(new EvalError()).toString()", "EvalError"), + TestAction::assert_eq("(new URIError()).toString()", "URIError"), + // no name + TestAction::assert_eq( + indoc! {r#" + let message = new Error('message'); + message.name = ''; + message.toString() + "#}, + "message", + ), + ]); } #[test] -fn eval_error_name() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "EvalError.name"), "\"EvalError\""); +fn error_names() { + run_test([ + TestAction::assert_eq("Error.name", "Error"), + TestAction::assert_eq("EvalError.name", "EvalError"), + TestAction::assert_eq("RangeError.name", "RangeError"), + TestAction::assert_eq("ReferenceError.name", "ReferenceError"), + TestAction::assert_eq("SyntaxError.name", "SyntaxError"), + TestAction::assert_eq("URIError.name", "URIError"), + TestAction::assert_eq("TypeError.name", "TypeError"), + TestAction::assert_eq("AggregateError.name", "AggregateError"), + ]); } #[test] -fn eval_error_length() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "EvalError.length"), "1"); -} - -#[test] -fn eval_error_to_string() { - let mut context = Context::default(); - assert_eq!( - forward(&mut context, "new EvalError('hello').toString()"), - "\"EvalError: hello\"" - ); - assert_eq!( - forward(&mut context, "new EvalError().toString()"), - "\"EvalError\"" - ); -} - -#[test] -fn uri_error_name() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "URIError.name"), "\"URIError\""); -} - -#[test] -fn uri_error_length() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "URIError.length"), "1"); -} - -#[test] -fn uri_error_to_string() { - let mut context = Context::default(); - assert_eq!( - forward(&mut context, "new URIError('hello').toString()"), - "\"URIError: hello\"" - ); - assert_eq!( - forward(&mut context, "new URIError().toString()"), - "\"URIError\"" - ); +fn error_lengths() { + run_test([ + TestAction::assert_eq("Error.length", 1), + TestAction::assert_eq("EvalError.length", 1), + TestAction::assert_eq("RangeError.length", 1), + TestAction::assert_eq("ReferenceError.length", 1), + TestAction::assert_eq("SyntaxError.length", 1), + TestAction::assert_eq("URIError.length", 1), + TestAction::assert_eq("TypeError.length", 1), + TestAction::assert_eq("AggregateError.length", 2), + ]); } diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index 76f786d50e0..157e0a33601 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -1,275 +1,177 @@ +use indoc::indoc; + use crate::{ + builtins::error::ErrorKind, error::JsNativeError, - forward, forward_val, js_string, + js_string, native_function::NativeFunction, object::{FunctionObjectBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, - string::utf16, - Context, JsNativeErrorKind, + run_test, JsValue, TestAction, }; #[allow(clippy::float_cmp)] #[test] fn arguments_object() { - let mut context = Context::default(); - - let init = r#" - function jason(a, b) { - return arguments[0]; - } - var val = jason(100, 6); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let return_val = forward_val(&mut context, "val").expect("value expected"); - assert!(return_val.is_integer()); - assert_eq!( - return_val - .to_i32(&mut context) - .expect("Could not convert value to i32"), - 100 - ); + run_test([ + TestAction::run(indoc! {r#" + function jason(a, b) { + return arguments[0]; + } + "#}), + TestAction::assert_eq("jason(100, 6)", 100), + ]); } #[test] fn self_mutating_function_when_calling() { - let mut context = Context::default(); - let func = r#" - function x() { - x.y = 3; - } - x(); - "#; - eprintln!("{}", forward(&mut context, func)); - let y = forward_val(&mut context, "x.y").expect("value expected"); - assert!(y.is_integer()); - assert_eq!( - y.to_i32(&mut context) - .expect("Could not convert value to i32"), - 3 - ); + run_test([ + TestAction::run(indoc! {r#" + function x() { + x.y = 3; + } + x(); + "#}), + TestAction::assert_eq("x.y", 3), + ]); } #[test] fn self_mutating_function_when_constructing() { - let mut context = Context::default(); - let func = r#" - function x() { - x.y = 3; - } - new x(); - "#; - eprintln!("{}", forward(&mut context, func)); - let y = forward_val(&mut context, "x.y").expect("value expected"); - assert!(y.is_integer()); - assert_eq!( - y.to_i32(&mut context) - .expect("Could not convert value to i32"), - 3 - ); -} - -#[test] -fn call_function_prototype() { - let mut context = Context::default(); - let func = r#" - Function.prototype() - "#; - let value = forward_val(&mut context, func).unwrap(); - assert!(value.is_undefined()); -} - -#[test] -fn call_function_prototype_with_arguments() { - let mut context = Context::default(); - let func = r#" - Function.prototype(1, "", new String("")) - "#; - let value = forward_val(&mut context, func).unwrap(); - assert!(value.is_undefined()); -} - -#[test] -fn call_function_prototype_with_new() { - let mut context = Context::default(); - let func = r#" - new Function.prototype() - "#; - let value = forward_val(&mut context, func); - assert!(value.is_err()); -} - -#[test] -fn function_prototype_name() { - let mut context = Context::default(); - let func = r#" - Function.prototype.name - "#; - let value = forward_val(&mut context, func).unwrap(); - assert!(value.is_string()); - assert!(value.as_string().unwrap().is_empty()); + run_test([ + TestAction::run(indoc! {r#" + function x() { + x.y = 3; + } + new x(); + "#}), + TestAction::assert_eq("x.y", 3), + ]); } #[test] -#[allow(clippy::float_cmp)] -fn function_prototype_length() { - let mut context = Context::default(); - let func = r#" - Function.prototype.length - "#; - let value = forward_val(&mut context, func).unwrap(); - assert!(value.is_number()); - assert_eq!(value.as_number().unwrap(), 0.0); +fn function_prototype() { + run_test([ + TestAction::assert_eq("Function.prototype.name", ""), + TestAction::assert_eq("Function.prototype.length", 0), + TestAction::assert_eq("Function.prototype()", JsValue::undefined()), + TestAction::assert_eq( + "Function.prototype(1, '', new String(''))", + JsValue::undefined(), + ), + TestAction::assert_native_error( + "new Function.prototype()", + ErrorKind::Type, + "not a constructor", + ), + ]); } #[test] fn function_prototype_call() { - let mut context = Context::default(); - let func = r#" - let e = new Error() - Object.prototype.toString.call(e) - "#; - let value = forward_val(&mut context, func).unwrap(); - assert!(value.is_string()); - assert_eq!(value.as_string().unwrap(), utf16!("[object Error]")); + run_test([TestAction::assert_eq( + "Object.prototype.toString.call(new Error())", + "[object Error]", + )]); } #[test] fn function_prototype_call_throw() { - let mut context = Context::default(); - let throw = r#" - let call = Function.prototype.call; - call(call) - "#; - let err = forward_val(&mut context, throw).unwrap_err(); - let err = err.as_native().unwrap(); - assert!(matches!( - err, - JsNativeError { - kind: JsNativeErrorKind::Type, - .. - } - )); + run_test([TestAction::assert_native_error( + indoc! {r#" + let call = Function.prototype.call; + call(call) + "#}, + ErrorKind::Type, + "undefined is not a function", + )]); } #[test] fn function_prototype_call_multiple_args() { - let mut context = Context::default(); - let init = r#" - function f(a, b) { - this.a = a; - this.b = b; - } - let o = {a: 0, b: 0}; - f.call(o, 1, 2); - "#; - forward_val(&mut context, init).unwrap(); - let boolean = forward_val(&mut context, "o.a == 1") - .unwrap() - .as_boolean() - .unwrap(); - assert!(boolean); - let boolean = forward_val(&mut context, "o.b == 2") - .unwrap() - .as_boolean() - .unwrap(); - assert!(boolean); + run_test([ + TestAction::run(indoc! {r#" + function f(a, b) { + this.a = a; + this.b = b; + } + let o = {a: 0, b: 0}; + f.call(o, 1, 2); + "#}), + TestAction::assert_eq("o.a", 1), + TestAction::assert_eq("o.b", 2), + ]); } #[test] fn function_prototype_apply() { - let mut context = Context::default(); - let init = r#" - const numbers = [6, 7, 3, 4, 2]; - const max = Math.max.apply(null, numbers); - const min = Math.min.apply(null, numbers); - "#; - forward_val(&mut context, init).unwrap(); - - let boolean = forward_val(&mut context, "max == 7") - .unwrap() - .as_boolean() - .unwrap(); - assert!(boolean); - - let boolean = forward_val(&mut context, "min == 2") - .unwrap() - .as_boolean() - .unwrap(); - assert!(boolean); + run_test([ + TestAction::run("const numbers = [6, 7, 3, 4, 2]"), + TestAction::assert_eq("Math.max.apply(null, numbers)", 7), + TestAction::assert_eq("Math.min.apply(null, numbers)", 2), + ]); } #[test] fn function_prototype_apply_on_object() { - let mut context = Context::default(); - let init = r#" - function f(a, b) { - this.a = a; - this.b = b; - } - let o = {a: 0, b: 0}; - f.apply(o, [1, 2]); - "#; - forward_val(&mut context, init).unwrap(); - - let boolean = forward_val(&mut context, "o.a == 1") - .unwrap() - .as_boolean() - .unwrap(); - assert!(boolean); - - let boolean = forward_val(&mut context, "o.b == 2") - .unwrap() - .as_boolean() - .unwrap(); - assert!(boolean); + run_test([ + TestAction::run(indoc! {r#" + function f(a, b) { + this.a = a; + this.b = b; + } + let o = {a: 0, b: 0}; + f.apply(o, [1, 2]); + "#}), + TestAction::assert_eq("o.a", 1), + TestAction::assert_eq("o.b", 2), + ]); } #[test] fn closure_capture_clone() { - let mut context = Context::default(); - - let string = js_string!("Hello"); - let object = JsObject::with_object_proto(&mut context); - - object - .define_property_or_throw( - "key", - PropertyDescriptor::builder() - .value(" world!") - .writable(false) - .enumerable(false) - .configurable(false), - &mut context, - ) - .unwrap(); - - let func = FunctionObjectBuilder::new( - &mut context, - NativeFunction::from_copy_closure_with_captures( - |_, _, captures, context| { - let (string, object) = &captures; - - let hw = js_string!( - string, - &object - .__get_own_property__(&"key".into(), context)? - .and_then(|prop| prop.value().cloned()) - .and_then(|val| val.as_string().cloned()) - .ok_or_else( - || JsNativeError::typ().with_message("invalid `key` property") - )? - ); - Ok(hw.into()) - }, - (string, object), - ), - ) - .name("closure") - .build(); - - context.register_global_property("closure", func, Attribute::default()); - - assert_eq!(forward(&mut context, "closure()"), "\"Hello world!\""); + run_test([ + TestAction::inspect_context(|ctx| { + let string = js_string!("Hello"); + let object = JsObject::with_object_proto(ctx); + object + .define_property_or_throw( + "key", + PropertyDescriptor::builder() + .value(" world!") + .writable(false) + .enumerable(false) + .configurable(false), + ctx, + ) + .unwrap(); + + let func = FunctionObjectBuilder::new( + ctx, + NativeFunction::from_copy_closure_with_captures( + |_, _, captures, context| { + let (string, object) = &captures; + + let hw = js_string!( + string, + &object + .__get_own_property__(&"key".into(), context)? + .and_then(|prop| prop.value().cloned()) + .and_then(|val| val.as_string().cloned()) + .ok_or_else( + || JsNativeError::typ().with_message("invalid `key` property") + )? + ); + Ok(hw.into()) + }, + (string, object), + ), + ) + .name("closure") + .build(); + + ctx.register_global_property("closure", func, Attribute::default()); + }), + TestAction::assert_eq("closure()", "Hello world!"), + ]); } diff --git a/boa_engine/src/builtins/json/tests.rs b/boa_engine/src/builtins/json/tests.rs index 0fa74a01a5b..65626e5d35a 100644 --- a/boa_engine/src/builtins/json/tests.rs +++ b/boa_engine/src/builtins/json/tests.rs @@ -1,468 +1,313 @@ -use crate::{forward, forward_val, Context}; +use indoc::indoc; + +use crate::{builtins::error::ErrorKind, run_test, JsValue, TestAction}; #[test] fn json_sanity() { - let mut context = Context::default(); - assert_eq!( - forward(&mut context, r#"JSON.parse('{"aaa":"bbb"}').aaa == 'bbb'"#), - "true" - ); - assert_eq!( - forward( - &mut context, - r#"JSON.stringify({aaa: 'bbb'}) == '{"aaa":"bbb"}'"# - ), - "true" - ); + run_test([ + TestAction::assert_eq(r#"JSON.parse('{"aaa":"bbb"}').aaa"#, "bbb"), + TestAction::assert_eq(r#"JSON.stringify({aaa: 'bbb'})"#, r#"{"aaa":"bbb"}"#), + ]); } #[test] fn json_stringify_remove_undefined_values_from_objects() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({ aaa: undefined, bbb: 'ccc' })"#, - ); - let expected = r#""{"bbb":"ccc"}""#; - - assert_eq!(actual, expected); + r#"{"bbb":"ccc"}"#, + )]); } #[test] fn json_stringify_remove_function_values_from_objects() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({ aaa: () => {}, bbb: 'ccc' })"#, - ); - let expected = r#""{"bbb":"ccc"}""#; - - assert_eq!(actual, expected); + r#"{"bbb":"ccc"}"#, + )]); } #[test] fn json_stringify_remove_symbols_from_objects() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({ aaa: Symbol(), bbb: 'ccc' })"#, - ); - let expected = r#""{"bbb":"ccc"}""#; - - assert_eq!(actual, expected); + r#"{"bbb":"ccc"}"#, + )]); } #[test] fn json_stringify_replacer_array_strings() { - let mut context = Context::default(); - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({aaa: 'bbb', bbb: 'ccc', ccc: 'ddd'}, ['aaa', 'bbb'])"#, - ); - let expected = forward(&mut context, r#"'{"aaa":"bbb","bbb":"ccc"}'"#); - assert_eq!(actual, expected); + r#"{"aaa":"bbb","bbb":"ccc"}"#, + )]); } #[test] fn json_stringify_replacer_array_numbers() { - let mut context = Context::default(); - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({ 0: 'aaa', 1: 'bbb', 2: 'ccc'}, [1, 2])"#, - ); - let expected = forward(&mut context, r#"'{"1":"bbb","2":"ccc"}'"#); - assert_eq!(actual, expected); + r#"{"1":"bbb","2":"ccc"}"#, + )]); } #[test] fn json_stringify_replacer_function() { - let mut context = Context::default(); - let actual = forward( - &mut context, - r#"JSON.stringify({ aaa: 1, bbb: 2}, (key, value) => { - if (key === 'aaa') { - return undefined; - } - - return value; - })"#, - ); - let expected = forward(&mut context, r#"'{"bbb":2}'"#); - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + indoc! {r#" + JSON.stringify({ aaa: 1, bbb: 2}, (key, value) => { + if (key === 'aaa') { + return undefined; + } + + return value; + }) + "#}, + r#"{"bbb":2}"#, + )]); } #[test] fn json_stringify_arrays() { - let mut context = Context::default(); - let actual = forward(&mut context, r#"JSON.stringify(['a', 'b'])"#); - let expected = forward(&mut context, r#"'["a","b"]'"#); - - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + "JSON.stringify(['a', 'b'])", + r#"["a","b"]"#, + )]); } #[test] fn json_stringify_object_array() { - let mut context = Context::default(); - let actual = forward(&mut context, r#"JSON.stringify([{a: 'b'}, {b: 'c'}])"#); - let expected = forward(&mut context, r#"'[{"a":"b"},{"b":"c"}]'"#); - - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + "JSON.stringify([{a: 'b'}, {b: 'c'}])", + r#"[{"a":"b"},{"b":"c"}]"#, + )]); } #[test] fn json_stringify_array_converts_undefined_to_null() { - let mut context = Context::default(); - let actual = forward(&mut context, r#"JSON.stringify([undefined])"#); - let expected = forward(&mut context, r#"'[null]'"#); - - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + "JSON.stringify([undefined])", + "[null]", + )]); } #[test] fn json_stringify_array_converts_function_to_null() { - let mut context = Context::default(); - let actual = forward(&mut context, r#"JSON.stringify([() => {}])"#); - let expected = forward(&mut context, r#"'[null]'"#); - - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + "JSON.stringify([() => {}])", + "[null]", + )]); } #[test] fn json_stringify_array_converts_symbol_to_null() { - let mut context = Context::default(); - let actual = forward(&mut context, r#"JSON.stringify([Symbol()])"#); - let expected = forward(&mut context, r#"'[null]'"#); - - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + "JSON.stringify([Symbol()])", + "[null]", + )]); } #[test] fn json_stringify_function_replacer_propagate_error() { - let mut context = Context::default(); - - let actual = forward( - &mut context, - r#" - let thrown = 0; - try { - JSON.stringify({x: 1}, (key, value) => { throw 1 }) - } catch (err) { - thrown = err; - } - thrown - "#, - ); - let expected = forward(&mut context, "1"); - - assert_eq!(actual, expected); + run_test([TestAction::assert_opaque_error( + "JSON.stringify({x: 1}, (key, value) => { throw 1 })", + 1, + )]); } #[test] fn json_stringify_function() { - let mut context = Context::default(); - - let actual_function = forward(&mut context, r#"JSON.stringify(() => {})"#); - let expected = forward(&mut context, r#"undefined"#); - - assert_eq!(actual_function, expected); + run_test([TestAction::assert_eq( + "JSON.stringify(() => {})", + JsValue::undefined(), + )]); } #[test] fn json_stringify_undefined() { - let mut context = Context::default(); - let actual_undefined = forward(&mut context, r#"JSON.stringify(undefined)"#); - let expected = forward(&mut context, r#"undefined"#); - - assert_eq!(actual_undefined, expected); + run_test([TestAction::assert_eq( + "JSON.stringify(undefined)", + JsValue::undefined(), + )]); } #[test] fn json_stringify_symbol() { - let mut context = Context::default(); - - let actual_symbol = forward(&mut context, r#"JSON.stringify(Symbol())"#); - let expected = forward(&mut context, r#"undefined"#); - - assert_eq!(actual_symbol, expected); + run_test([TestAction::assert_eq( + "JSON.stringify(Symbol())", + JsValue::undefined(), + )]); } #[test] fn json_stringify_no_args() { - let mut context = Context::default(); - - let actual_no_args = forward(&mut context, r#"JSON.stringify()"#); - let expected = forward(&mut context, r#"undefined"#); - - assert_eq!(actual_no_args, expected); + run_test([TestAction::assert_eq( + "JSON.stringify()", + JsValue::undefined(), + )]); } #[test] fn json_stringify_fractional_numbers() { - let mut context = Context::default(); - - let actual = forward(&mut context, r#"JSON.stringify(Math.round(1.0))"#); - let expected = forward(&mut context, r#""1""#); - assert_eq!(actual, expected); + run_test([TestAction::assert_eq("JSON.stringify(1.2)", "1.2")]); } #[test] fn json_stringify_pretty_print() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({a: "b", b: "c"}, undefined, 4)"#, - ); - let expected = forward( - &mut context, - r#"'{\n' - +' "a": "b",\n' - +' "b": "c"\n' - +'}'"#, - ); - assert_eq!(actual, expected); + indoc! {r#" + { + "a": "b", + "b": "c" + }"# + }, + )]); } #[test] fn json_stringify_pretty_print_four_spaces() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({a: "b", b: "c"}, undefined, 4.3)"#, - ); - let expected = forward( - &mut context, - r#"'{\n' - +' "a": "b",\n' - +' "b": "c"\n' - +'}'"#, - ); - assert_eq!(actual, expected); + indoc! {r#" + { + "a": "b", + "b": "c" + }"# + }, + )]); } #[test] fn json_stringify_pretty_print_twenty_spaces() { - let mut context = Context::default(); - - let actual = forward( - &mut context, - r#"JSON.stringify({a: "b", b: "c"}, ["a", "b"], 20)"#, - ); - let expected = forward( - &mut context, - r#"'{\n' - +' "a": "b",\n' - +' "b": "c"\n' - +'}'"#, - ); - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + r#"JSON.stringify({a: "b", b: "c"}, undefined, 20)"#, + indoc! {r#" + { + "a": "b", + "b": "c" + }"# + }, + )]); } #[test] fn json_stringify_pretty_print_with_number_object() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({a: "b", b: "c"}, undefined, new Number(10))"#, - ); - let expected = forward( - &mut context, - r#"'{\n' - +' "a": "b",\n' - +' "b": "c"\n' - +'}'"#, - ); - assert_eq!(actual, expected); + indoc! {r#" + { + "a": "b", + "b": "c" + }"# + }, + )]); } #[test] fn json_stringify_pretty_print_bad_space_argument() { - let mut context = Context::default(); - - let actual = forward( - &mut context, - r#"JSON.stringify({a: "b", b: "c"}, ["a", "b"], [])"#, - ); - let expected = forward(&mut context, r#"'{"a":"b","b":"c"}'"#); - assert_eq!(actual, expected); + run_test([TestAction::assert_eq( + r#"JSON.stringify({a: "b", b: "c"}, undefined, [])"#, + r#"{"a":"b","b":"c"}"#, + )]); } #[test] fn json_stringify_pretty_print_with_too_long_string() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({a: "b", b: "c"}, undefined, "abcdefghijklmn")"#, - ); - let expected = forward( - &mut context, - r#"'{\n' - +'abcdefghij"a": "b",\n' - +'abcdefghij"b": "c"\n' - +'}'"#, - ); - assert_eq!(actual, expected); + indoc! {r#" + { + abcdefghij"a": "b", + abcdefghij"b": "c" + }"# + }, + )]); } #[test] fn json_stringify_pretty_print_with_string_object() { - let mut context = Context::default(); - - let actual = forward( - &mut context, + run_test([TestAction::assert_eq( r#"JSON.stringify({a: "b", b: "c"}, undefined, new String("abcd"))"#, - ); - let expected = forward( - &mut context, - r#"'{\n' - +'abcd"a": "b",\n' - +'abcd"b": "c"\n' - +'}'"#, - ); - assert_eq!(actual, expected); + indoc! {r#" + { + abcd"a": "b", + abcd"b": "c" + }"# + }, + )]); } #[test] fn json_parse_array_with_reviver() { - let mut context = Context::default(); - let result = forward_val( - &mut context, - r#"JSON.parse('[1,2,3,4]', function(k, v){ - if (typeof v == 'number') { - return v * 2; - } else { - return v; - } - })"#, - ) - .unwrap(); - assert_eq!( - result - .get_v("0", &mut context) - .unwrap() - .to_number(&mut context) - .unwrap() as u8, - 2u8 - ); - assert_eq!( - result - .get_v("1", &mut context) - .unwrap() - .to_number(&mut context) - .unwrap() as u8, - 4u8 - ); - assert_eq!( - result - .get_v("2", &mut context) - .unwrap() - .to_number(&mut context) - .unwrap() as u8, - 6u8 - ); - assert_eq!( - result - .get_v("3", &mut context) - .unwrap() - .to_number(&mut context) - .unwrap() as u8, - 8u8 - ); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + function reviver(k, v){ + if (typeof v == 'number') { + return v * 2; + } else { + return v; + } + } + "#}), + TestAction::assert("arrayEquals(JSON.parse('[1,2,3,4]', reviver), [2,4,6,8])"), + ]); } #[test] fn json_parse_object_with_reviver() { - let mut context = Context::default(); - let result = forward( - &mut context, - r#" - var myObj = new Object(); - myObj.firstname = "boa"; - myObj.lastname = "snake"; - var jsonString = JSON.stringify(myObj); - - function dataReviver(key, value) { - if (key == 'lastname') { - return 'interpreter'; - } else { - return value; - } - } - - var jsonObj = JSON.parse(jsonString, dataReviver); - - JSON.stringify(jsonObj);"#, - ); - assert_eq!(result, r#""{"firstname":"boa","lastname":"interpreter"}""#); + run_test([ + TestAction::run(indoc! {r#" + var jsonString = JSON.stringify({ + firstname: "boa", + lastname: "snake" + }); + + function dataReviver(key, value) { + if (key == 'lastname') { + return 'interpreter'; + } else { + return value; + } + } + + var jsonObj = JSON.parse(jsonString, dataReviver); + "#}), + TestAction::assert_eq("jsonObj.firstname", "boa"), + TestAction::assert_eq("jsonObj.lastname", "interpreter"), + ]); } #[test] fn json_parse_sets_prototypes() { - let mut context = Context::default(); - let init = r#" - const jsonString = "{\"ob\":{\"ject\":1},\"arr\": [0,1]}"; - const jsonObj = JSON.parse(jsonString); - "#; - eprintln!("{}", forward(&mut context, init)); - let object_prototype = forward_val(&mut context, r#"jsonObj.ob"#) - .unwrap() - .as_object() - .unwrap() - .prototype() - .clone(); - let array_prototype = forward_val(&mut context, r#"jsonObj.arr"#) - .unwrap() - .as_object() - .unwrap() - .prototype() - .clone(); - let global_object_prototype = context - .intrinsics() - .constructors() - .object() - .prototype() - .into(); - let global_array_prototype = context - .intrinsics() - .constructors() - .array() - .prototype() - .into(); - assert_eq!(object_prototype, global_object_prototype); - assert_eq!(array_prototype, global_array_prototype); + run_test([ + TestAction::run(indoc! {r#" + const jsonString = "{\"ob\":{\"ject\":1},\"arr\": [0,1]}"; + const jsonObj = JSON.parse(jsonString); + "#}), + TestAction::assert("Object.getPrototypeOf(jsonObj.ob) === Object.prototype"), + TestAction::assert("Object.getPrototypeOf(jsonObj.arr) === Array.prototype"), + ]); } #[test] fn json_fields_should_be_enumerable() { - let mut context = Context::default(); - let actual_object = forward( - &mut context, - r#" - var a = JSON.parse('{"x":0}'); - a.propertyIsEnumerable('x'); - "#, - ); - let actual_array_index = forward( - &mut context, - r#" - var b = JSON.parse('[0, 1]'); - b.propertyIsEnumerable('0'); - "#, - ); - let expected = forward(&mut context, r#"true"#); - - assert_eq!(actual_object, expected); - assert_eq!(actual_array_index, expected); + run_test([ + TestAction::assert(indoc! {r#" + var a = JSON.parse('{"x":0}'); + a.propertyIsEnumerable('x') + "#}), + TestAction::assert(indoc! {r#" + var b = JSON.parse('[0, 1]'); + b.propertyIsEnumerable('0'); + "#}), + ]); } #[test] fn json_parse_with_no_args_throws_syntax_error() { - let mut context = Context::default(); - let result = forward(&mut context, "JSON.parse();"); - assert!(result.contains("SyntaxError")); + run_test([TestAction::assert_native_error( + "JSON.parse();", + ErrorKind::Syntax, + "expected value at line 1 column 1", + )]); } diff --git a/boa_engine/src/builtins/map/tests.rs b/boa_engine/src/builtins/map/tests.rs index 23469e91310..534ae795573 100644 --- a/boa_engine/src/builtins/map/tests.rs +++ b/boa_engine/src/builtins/map/tests.rs @@ -1,406 +1,306 @@ -use crate::{forward, Context}; +use crate::{builtins::error::ErrorKind, run_test, JsValue, TestAction}; +use indoc::indoc; #[test] -fn construct_empty() { - let mut context = Context::default(); - let init = r#" - var empty = new Map(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "empty.size"); - assert_eq!(result, "0"); -} - -#[test] -fn construct_from_array() { - let mut context = Context::default(); - let init = r#" - let map = new Map([["1", "one"], ["2", "two"]]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map.size"); - assert_eq!(result, "2"); +fn construct() { + run_test([ + TestAction::assert_eq("(new Map()).size", 0), + TestAction::assert_eq("(new Map([['1', 'one'], ['2', 'two']])).size", 2), + ]); } #[test] fn clone() { - let mut context = Context::default(); - let init = r#" - let original = new Map([["1", "one"], ["2", "two"]]); - let clone = new Map(original); - "#; - forward(&mut context, init); - let result = forward(&mut context, "clone.size"); - assert_eq!(result, "2"); - let result = forward( - &mut context, - r#" - original.set("3", "three"); - original.size"#, - ); - assert_eq!(result, "3"); - let result = forward(&mut context, "clone.size"); - assert_eq!(result, "2"); + run_test([ + TestAction::run(indoc! {r#" + let original = new Map([["1", "one"], ["2", "two"]]); + let clone = new Map(original); + "#}), + TestAction::assert_eq("clone.size", 2), + TestAction::assert_eq("original.set('3', 'three'); original.size", 3), + TestAction::assert_eq("clone.size", 2), + ]); } #[test] fn symbol_iterator() { - let mut context = Context::default(); - let init = r#" - const map1 = new Map(); - map1.set('0', 'foo'); - map1.set(1, 'bar'); - const iterator = map1[Symbol.iterator](); - let item1 = iterator.next(); - let item2 = iterator.next(); - let item3 = iterator.next(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "item1.value.length"); - assert_eq!(result, "2"); - let result = forward(&mut context, "item1.value[0]"); - assert_eq!(result, "\"0\""); - let result = forward(&mut context, "item1.value[1]"); - assert_eq!(result, "\"foo\""); - let result = forward(&mut context, "item1.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item2.value.length"); - assert_eq!(result, "2"); - let result = forward(&mut context, "item2.value[0]"); - assert_eq!(result, "1"); - let result = forward(&mut context, "item2.value[1]"); - assert_eq!(result, "\"bar\""); - let result = forward(&mut context, "item2.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item3.value"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "item3.done"); - assert_eq!(result, "true"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + const map1 = new Map(); + map1.set('0', 'foo'); + map1.set(1, 'bar'); + const iterator = map1[Symbol.iterator](); + let item1 = iterator.next(); + let item2 = iterator.next(); + let item3 = iterator.next(); + "#}), + TestAction::assert("arrayEquals(item1.value, ['0', 'foo'])"), + TestAction::assert("arrayEquals(item2.value, [1, 'bar'])"), + TestAction::assert_eq("item3.value", JsValue::undefined()), + TestAction::assert("item3.done"), + ]); } // Should behave the same as symbol_iterator #[test] fn entries() { - let mut context = Context::default(); - let init = r#" - const map1 = new Map(); - map1.set('0', 'foo'); - map1.set(1, 'bar'); - const entriesIterator = map1.entries(); - let item1 = entriesIterator.next(); - let item2 = entriesIterator.next(); - let item3 = entriesIterator.next(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "item1.value.length"); - assert_eq!(result, "2"); - let result = forward(&mut context, "item1.value[0]"); - assert_eq!(result, "\"0\""); - let result = forward(&mut context, "item1.value[1]"); - assert_eq!(result, "\"foo\""); - let result = forward(&mut context, "item1.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item2.value.length"); - assert_eq!(result, "2"); - let result = forward(&mut context, "item2.value[0]"); - assert_eq!(result, "1"); - let result = forward(&mut context, "item2.value[1]"); - assert_eq!(result, "\"bar\""); - let result = forward(&mut context, "item2.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item3.value"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "item3.done"); - assert_eq!(result, "true"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + const map1 = new Map(); + map1.set('0', 'foo'); + map1.set(1, 'bar'); + const iterator = map1.entries(); + let item1 = iterator.next(); + let item2 = iterator.next(); + let item3 = iterator.next(); + "#}), + TestAction::assert("arrayEquals(item1.value, ['0', 'foo'])"), + TestAction::assert("arrayEquals(item2.value, [1, 'bar'])"), + TestAction::assert_eq("item3.value", JsValue::undefined()), + TestAction::assert("item3.done"), + ]); } #[test] fn merge() { - let mut context = Context::default(); - let init = r#" - let first = new Map([["1", "one"], ["2", "two"]]); - let second = new Map([["2", "second two"], ["3", "three"]]); - let third = new Map([["4", "four"], ["5", "five"]]); - let merged1 = new Map([...first, ...second]); - let merged2 = new Map([...second, ...third]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "merged1.size"); - assert_eq!(result, "3"); - let result = forward(&mut context, "merged1.get('2')"); - assert_eq!(result, "\"second two\""); - let result = forward(&mut context, "merged2.size"); - assert_eq!(result, "4"); + run_test([ + TestAction::run(indoc! {r#" + let first = new Map([["1", "one"], ["2", "two"]]); + let second = new Map([["2", "second two"], ["3", "three"]]); + let third = new Map([["4", "four"], ["5", "five"]]); + let merged1 = new Map([...first, ...second]); + let merged2 = new Map([...second, ...third]); + "#}), + TestAction::assert_eq("merged1.size", 3), + TestAction::assert_eq("merged1.get('2')", "second two"), + TestAction::assert_eq("merged2.size", 4), + ]); } #[test] fn get() { - let mut context = Context::default(); - let init = r#" - let map = new Map([["1", "one"], ["2", "two"]]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map.get('1')"); - assert_eq!(result, "\"one\""); - let result = forward(&mut context, "map.get('2')"); - assert_eq!(result, "\"two\""); - let result = forward(&mut context, "map.get('3')"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "map.get()"); - assert_eq!(result, "undefined"); + run_test([ + TestAction::run(indoc! {r#" + let map = new Map([["1", "one"], ["2", "two"]]); + "#}), + TestAction::assert_eq("map.get('1')", "one"), + TestAction::assert_eq("map.get('2')", "two"), + TestAction::assert_eq("map.get('3')", JsValue::undefined()), + TestAction::assert_eq("map.get()", JsValue::undefined()), + ]); } #[test] fn set() { - let mut context = Context::default(); - let init = r#" - let map = new Map(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map.set()"); - assert_eq!(result, "Map { undefined → undefined }"); - let result = forward(&mut context, "map.set('1', 'one')"); - assert_eq!(result, "Map { undefined → undefined, \"1\" → \"one\" }"); - let result = forward(&mut context, "map.set('2')"); - assert_eq!( - result, - "Map { undefined → undefined, \"1\" → \"one\", \"2\" → undefined }" - ); + run_test([ + TestAction::run("let map = new Map();"), + TestAction::assert("map.set(); map.has(undefined)"), + TestAction::assert_eq("map.get()", JsValue::undefined()), + TestAction::assert_eq("map.set('1', 'one'); map.get('1')", "one"), + TestAction::assert("map.set('2'); map.has('2')"), + TestAction::assert_eq("map.get('2')", JsValue::undefined()), + ]); } #[test] fn clear() { - let mut context = Context::default(); - let init = r#" - let map = new Map([["1", "one"], ["2", "two"]]); - map.clear(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map.size"); - assert_eq!(result, "0"); + run_test([ + TestAction::run(indoc! {r#" + let map = new Map([["1", "one"], ["2", "two"]]); + map.clear(); + "#}), + TestAction::assert_eq("map.size", 0), + ]); } #[test] fn delete() { - let mut context = Context::default(); - let init = r#" - let map = new Map([["1", "one"], ["2", "two"]]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map.delete('1')"); - assert_eq!(result, "true"); - let result = forward(&mut context, "map.size"); - assert_eq!(result, "1"); - let result = forward(&mut context, "map.delete('1')"); - assert_eq!(result, "false"); + run_test([ + TestAction::run("let map = new Map([['1', 'one'], ['2', 'two']]);"), + TestAction::assert_eq("map.size", 2), + TestAction::assert("map.delete('1')"), + TestAction::assert("!map.has('1')"), + TestAction::assert("!map.delete('1')"), + ]); } #[test] fn has() { - let mut context = Context::default(); - let init = r#" - let map = new Map([["1", "one"]]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map.has()"); - assert_eq!(result, "false"); - let result = forward(&mut context, "map.has('1')"); - assert_eq!(result, "true"); - let result = forward(&mut context, "map.has('2')"); - assert_eq!(result, "false"); + run_test([ + TestAction::run("let map = new Map([['1', 'one']]);"), + TestAction::assert("!map.has()"), + TestAction::assert("map.has('1')"), + TestAction::assert("!map.has('2')"), + ]); } #[test] fn keys() { - let mut context = Context::default(); - let init = r#" - const map1 = new Map(); - map1.set('0', 'foo'); - map1.set(1, 'bar'); - const keysIterator = map1.keys(); - let item1 = keysIterator.next(); - let item2 = keysIterator.next(); - let item3 = keysIterator.next(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "item1.value"); - assert_eq!(result, "\"0\""); - let result = forward(&mut context, "item1.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item2.value"); - assert_eq!(result, "1"); - let result = forward(&mut context, "item2.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item3.value"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "item3.done"); - assert_eq!(result, "true"); + run_test([ + TestAction::run(indoc! {r#" + const map1 = new Map(); + map1.set('0', 'foo'); + map1.set(1, 'bar'); + const keysIterator = map1.keys(); + let item1 = keysIterator.next(); + let item2 = keysIterator.next(); + let item3 = keysIterator.next(); + "#}), + TestAction::assert_eq("item1.value", "0"), + TestAction::assert_eq("item2.value", 1), + TestAction::assert("item3.done"), + ]); } #[test] fn for_each() { - let mut context = Context::default(); - let init = r#" - let map = new Map([[1, 5], [2, 10], [3, 15]]); - let valueSum = 0; - let keySum = 0; - let sizeSum = 0; - function callingCallback(value, key, map) { - valueSum += value; - keySum += key; - sizeSum += map.size; - } - map.forEach(callingCallback); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "valueSum"), "30"); - assert_eq!(forward(&mut context, "keySum"), "6"); - assert_eq!(forward(&mut context, "sizeSum"), "9"); + run_test([ + TestAction::run(indoc! {r#" + let map = new Map([[1, 5], [2, 10], [3, 15]]); + let valueSum = 0; + let keySum = 0; + let sizeSum = 0; + function callingCallback(value, key, map) { + valueSum += value; + keySum += key; + sizeSum += map.size; + } + map.forEach(callingCallback); + "#}), + TestAction::assert_eq("valueSum", 30), + TestAction::assert_eq("keySum", 6), + TestAction::assert_eq("sizeSum", 9), + ]); } #[test] fn values() { - let mut context = Context::default(); - let init = r#" - const map1 = new Map(); - map1.set('0', 'foo'); - map1.set(1, 'bar'); - const valuesIterator = map1.values(); - let item1 = valuesIterator.next(); - let item2 = valuesIterator.next(); - let item3 = valuesIterator.next(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "item1.value"); - assert_eq!(result, "\"foo\""); - let result = forward(&mut context, "item1.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item2.value"); - assert_eq!(result, "\"bar\""); - let result = forward(&mut context, "item2.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item3.value"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "item3.done"); - assert_eq!(result, "true"); + run_test([ + TestAction::run(indoc! {r#" + const map1 = new Map(); + map1.set('0', 'foo'); + map1.set(1, 'bar'); + const valuesIterator = map1.values(); + let item1 = valuesIterator.next(); + let item2 = valuesIterator.next(); + let item3 = valuesIterator.next(); + "#}), + TestAction::assert_eq("item1.value", "foo"), + TestAction::assert_eq("item2.value", "bar"), + TestAction::assert("item3.done"), + ]); } #[test] fn modify_key() { - let mut context = Context::default(); - let init = r#" - let obj = new Object(); - let map = new Map([[obj, "one"]]); - obj.field = "Value"; - "#; - forward(&mut context, init); - let result = forward(&mut context, "map.get(obj)"); - assert_eq!(result, "\"one\""); + run_test([ + TestAction::run(indoc! {r#" + let obj = new Object(); + let map = new Map([[obj, "one"]]); + obj.field = "Value"; + "#}), + TestAction::assert_eq("map.get(obj)", "one"), + ]); } #[test] fn order() { - let mut context = Context::default(); - let init = r#" - let map = new Map([[1, "one"]]); - map.set(2, "two"); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map"); - assert_eq!(result, "Map { 1 → \"one\", 2 → \"two\" }"); - let result = forward(&mut context, "map.set(1, \"five\");map"); - assert_eq!(result, "Map { 1 → \"five\", 2 → \"two\" }"); - let result = forward(&mut context, "map.set();map"); - assert_eq!( - result, - "Map { 1 → \"five\", 2 → \"two\", undefined → undefined }" - ); - let result = forward(&mut context, "map.delete(2);map"); - assert_eq!(result, "Map { 1 → \"five\", undefined → undefined }"); - let result = forward(&mut context, "map.set(2, \"two\");map"); - assert_eq!( - result, - "Map { 1 → \"five\", undefined → undefined, 2 → \"two\" }" - ); + run_test([ + TestAction::run_harness(), + TestAction::run("let map = new Map([[1, 'one'], [2, 'two']]);"), + TestAction::assert("arrayEquals(Array.from(map.keys()), [1, 2])"), + TestAction::run("map.set(1, 'five')"), + TestAction::assert("arrayEquals(Array.from(map.keys()), [1, 2])"), + TestAction::run("map.set()"), + TestAction::assert("arrayEquals(Array.from(map.keys()), [1, 2, undefined])"), + TestAction::run("map.delete(2)"), + TestAction::assert("arrayEquals(Array.from(map.keys()), [1, undefined])"), + TestAction::run("map.set(2, 'two')"), + TestAction::assert("arrayEquals(Array.from(map.keys()), [1, undefined, 2])"), + ]); } #[test] fn recursive_display() { - let mut context = Context::default(); - let init = r#" - let map = new Map(); - let array = new Array([map]); - map.set("y", map); - "#; - forward(&mut context, init); - let result = forward(&mut context, "map"); - assert_eq!(result, "Map { \"y\" → Map(1) }"); - let result = forward(&mut context, "map.set(\"z\", array)"); - assert_eq!(result, "Map { \"y\" → Map(2), \"z\" → Array(1) }"); + run_test([ + TestAction::run(indoc! {r#" + let map = new Map(); + let array = new Array([map]); + "#}), + TestAction::assert_with_op("map.set('y', map)", |v, _| { + v.display().to_string() == r#"Map { "y" → Map(1) }"# + }), + TestAction::assert_with_op("map.set('z', array)", |v, _| { + v.display().to_string() == r#"Map { "y" → Map(2), "z" → Array(1) }"# + }), + ]); } #[test] fn not_a_function() { - let mut context = Context::default(); - let init = r" - try { - let map = Map() - } catch(e) { - e.toString() - } - "; - assert_eq!( - forward(&mut context, init), - "\"TypeError: calling a builtin Map constructor without new is forbidden\"" - ); + run_test([TestAction::assert_native_error( + "let map = Map()", + ErrorKind::Type, + "calling a builtin Map constructor without new is forbidden", + )]); } #[test] fn for_each_delete() { - let mut context = Context::default(); - let init = r#" - let map = new Map([[0, "a"], [1, "b"], [2, "c"]]); - let result = []; - map.forEach(function(value, key) { - if (key === 0) { - map.delete(0); - map.set(3, "d"); - } - result.push([key, value]); - }) - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "result[0][0]"), "0"); - assert_eq!(forward(&mut context, "result[0][1]"), "\"a\""); - assert_eq!(forward(&mut context, "result[1][0]"), "1"); - assert_eq!(forward(&mut context, "result[1][1]"), "\"b\""); - assert_eq!(forward(&mut context, "result[2][0]"), "2"); - assert_eq!(forward(&mut context, "result[2][1]"), "\"c\""); - assert_eq!(forward(&mut context, "result[3][0]"), "3"); - assert_eq!(forward(&mut context, "result[3][1]"), "\"d\""); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let map = new Map([[0, "a"], [1, "b"], [2, "c"]]); + let result = []; + map.forEach(function(value, key) { + if (key === 0) { + map.delete(0); + map.set(3, "d"); + } + result.push([key, value]); + }) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + result, + [ + [0, "a"], + [1, "b"], + [2, "c"], + [3, "d"] + ] + ) + "#}), + ]); } #[test] fn for_of_delete() { - let mut context = Context::default(); - let init = r#" - let map = new Map([[0, "a"], [1, "b"], [2, "c"]]); - let result = []; - for (a of map) { - if (a[0] === 0) { - map.delete(0); - map.set(3, "d"); - } - result.push([a[0], a[1]]); - } - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "result[0][0]"), "0"); - assert_eq!(forward(&mut context, "result[0][1]"), "\"a\""); - assert_eq!(forward(&mut context, "result[1][0]"), "1"); - assert_eq!(forward(&mut context, "result[1][1]"), "\"b\""); - assert_eq!(forward(&mut context, "result[2][0]"), "2"); - assert_eq!(forward(&mut context, "result[2][1]"), "\"c\""); - assert_eq!(forward(&mut context, "result[3][0]"), "3"); - assert_eq!(forward(&mut context, "result[3][1]"), "\"d\""); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let map = new Map([[0, "a"], [1, "b"], [2, "c"]]); + let result = []; + for (a of map) { + if (a[0] === 0) { + map.delete(0); + map.set(3, "d"); + } + result.push([a[0], a[1]]); + } + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + result, + [ + [0, "a"], + [1, "b"], + [2, "c"], + [3, "d"] + ] + ) + "#}), + ]); } diff --git a/boa_engine/src/builtins/math/tests.rs b/boa_engine/src/builtins/math/tests.rs index 582a6610639..7482fab2530 100644 --- a/boa_engine/src/builtins/math/tests.rs +++ b/boa_engine/src/builtins/math/tests.rs @@ -1,729 +1,314 @@ #![allow(clippy::float_cmp)] -use crate::{forward, forward_val, Context}; -use std::f64; +use crate::{run_test, TestAction}; #[test] fn abs() { - let mut context = Context::default(); - let init = r#" - var a = Math.abs(3 - 5); - var b = Math.abs(1.23456 - 7.89012); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 2.0); - assert_eq!(b.to_number(&mut context).unwrap(), 6.655_559_999_999_999_5); + run_test([ + TestAction::assert_eq("Math.abs(3 - 5)", 2.0), + TestAction::assert_eq("Math.abs(1.23456 - 7.89012)", 6.655_559_999_999_999_5), + ]); } #[test] fn acos() { - let mut context = Context::default(); - let init = r#" - var a = Math.acos(8 / 10); - var b = Math.acos(5 / 3); - var c = Math.acos(1); - var d = Math.acos(2); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward(&mut context, "b"); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward(&mut context, "d"); - - assert_eq!(a.to_number(&mut context).unwrap(), 0.643_501_108_793_284_3); - assert_eq!(b, "NaN"); - assert_eq!(c.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(d, "NaN"); + run_test([ + TestAction::assert_eq("Math.acos(8 / 10)", 0.643_501_108_793_284_3), + TestAction::assert_eq("Math.acos(5 / 3)", f64::NAN), + TestAction::assert_eq("Math.acos(1)", 0.0), + TestAction::assert_eq("Math.acos(2)", f64::NAN), + ]); } #[test] fn acosh() { - let mut context = Context::default(); - let init = r#" - var a = Math.acosh(2); - var b = Math.acosh(-1); - var c = Math.acosh(0.5); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward(&mut context, "b"); - let c = forward(&mut context, "c"); - - assert_eq!(a.to_number(&mut context).unwrap(), 1.316_957_896_924_816_6); - assert_eq!(b, "NaN"); - assert_eq!(c, "NaN"); + run_test([ + TestAction::assert_eq("Math.acosh(2)", 1.316_957_896_924_816_6), + TestAction::assert_eq("Math.acosh(-1)", f64::NAN), + TestAction::assert_eq("Math.acosh(0.5)", f64::NAN), + ]); } #[test] fn asin() { - let mut context = Context::default(); - let init = r#" - var a = Math.asin(6 / 10); - var b = Math.asin(5 / 3); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward(&mut context, "b"); - - assert_eq!(a.to_number(&mut context).unwrap(), 0.643_501_108_793_284_4); - assert_eq!(b, String::from("NaN")); + run_test([ + TestAction::assert_eq("Math.asin(6 / 10)", 0.643_501_108_793_284_4), + TestAction::assert_eq("Math.asin(5 / 3)", f64::NAN), + ]); } #[test] fn asinh() { - let mut context = Context::default(); - let init = r#" - var a = Math.asinh(1); - var b = Math.asinh(0); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 0.881_373_587_019_543); - assert_eq!(b.to_number(&mut context).unwrap(), 0_f64); + run_test([ + TestAction::assert_eq("Math.asinh(1)", 0.881_373_587_019_543), + TestAction::assert_eq("Math.asinh(0)", 0.0), + ]); } #[test] fn atan() { - let mut context = Context::default(); - let init = r#" - var a = Math.atan(1); - var b = Math.atan(0); - var c = Math.atan(-0); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), f64::consts::FRAC_PI_4); - assert_eq!(b.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(c.to_number(&mut context).unwrap(), f64::from(-0)); + run_test([ + TestAction::assert_eq("Math.atan(1)", std::f64::consts::FRAC_PI_4), + TestAction::assert_eq("Math.atan(0)", 0.0), + TestAction::assert_eq("Math.atan(-0)", -0.0), + ]); } #[test] fn atan2() { - let mut context = Context::default(); - let init = r#" - var a = Math.atan2(90, 15); - var b = Math.atan2(15, 90); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 1.405_647_649_380_269_9); - assert_eq!(b.to_number(&mut context).unwrap(), 0.165_148_677_414_626_83); + run_test([ + TestAction::assert_eq("Math.atan2(90, 15)", 1.405_647_649_380_269_9), + TestAction::assert_eq("Math.atan2(15, 90)", 0.165_148_677_414_626_83), + ]); } #[test] fn cbrt() { - let mut context = Context::default(); - let init = r#" - var a = Math.cbrt(64); - var b = Math.cbrt(-1); - var c = Math.cbrt(1); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 4_f64); - assert_eq!(b.to_number(&mut context).unwrap(), -1_f64); - assert_eq!(c.to_number(&mut context).unwrap(), 1_f64); + run_test([ + TestAction::assert_eq("Math.cbrt(64)", 4.0), + TestAction::assert_eq("Math.cbrt(-1)", -1.0), + TestAction::assert_eq("Math.cbrt(1)", 1.0), + ]); } #[test] fn ceil() { - let mut context = Context::default(); - let init = r#" - var a = Math.ceil(1.95); - var b = Math.ceil(4); - var c = Math.ceil(-7.004); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 2_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 4_f64); - assert_eq!(c.to_number(&mut context).unwrap(), -7_f64); + run_test([ + TestAction::assert_eq("Math.ceil(1.95)", 2.0), + TestAction::assert_eq("Math.ceil(4)", 4.0), + TestAction::assert_eq("Math.ceil(-7.004)", -7.0), + ]); } #[test] -#[allow(clippy::many_single_char_names)] fn clz32() { - let mut context = Context::default(); - let init = r#" - var a = Math.clz32(); - var b = Math.clz32({}); - var c = Math.clz32(-173); - var d = Math.clz32("1"); - var e = Math.clz32(2147483647); - var f = Math.clz32(Infinity); - var g = Math.clz32(true); - var h = Math.clz32(0); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward_val(&mut context, "d").unwrap(); - let e = forward_val(&mut context, "e").unwrap(); - let f = forward_val(&mut context, "f").unwrap(); - let g = forward_val(&mut context, "g").unwrap(); - let h = forward_val(&mut context, "h").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 32_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 32_f64); - assert_eq!(c.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(d.to_number(&mut context).unwrap(), 31_f64); - assert_eq!(e.to_number(&mut context).unwrap(), 1_f64); - assert_eq!(f.to_number(&mut context).unwrap(), 32_f64); - assert_eq!(g.to_number(&mut context).unwrap(), 31_f64); - assert_eq!(h.to_number(&mut context).unwrap(), 32_f64); + run_test([ + TestAction::assert_eq("Math.clz32()", 32), + TestAction::assert_eq("Math.clz32({})", 32), + TestAction::assert_eq("Math.clz32(-173)", 0), + TestAction::assert_eq("Math.clz32('1')", 31), + TestAction::assert_eq("Math.clz32(2147483647)", 1), + TestAction::assert_eq("Math.clz32(Infinity)", 32), + TestAction::assert_eq("Math.clz32(true)", 31), + TestAction::assert_eq("Math.clz32(0)", 32), + ]); } #[test] fn cos() { - let mut context = Context::default(); - let init = r#" - var a = Math.cos(0); - var b = Math.cos(1); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 1_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 0.540_302_305_868_139_8); + run_test([ + TestAction::assert_eq("Math.cos(0)", 1.0), + TestAction::assert_eq("Math.cos(1)", 0.540_302_305_868_139_8), + ]); } #[test] fn cosh() { - let mut context = Context::default(); - let init = r#" - var a = Math.cosh(0); - var b = Math.cosh(1); - var c = Math.cosh(-1); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 1_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 1.543_080_634_815_243_7); - assert_eq!(c.to_number(&mut context).unwrap(), 1.543_080_634_815_243_7); + run_test([ + TestAction::assert_eq("Math.cosh(0)", 1.0), + TestAction::assert_eq("Math.cosh(1)", 1.543_080_634_815_243_7), + TestAction::assert_eq("Math.cosh(-1)", 1.543_080_634_815_243_7), + ]); } #[test] fn exp() { - let mut context = Context::default(); - let init = r#" - var a = Math.exp(0); - var b = Math.exp(-1); - var c = Math.exp(2); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 1_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 0.367_879_441_171_442_33); - assert_eq!(c.to_number(&mut context).unwrap(), 7.389_056_098_930_65); + run_test([ + TestAction::assert_eq("Math.exp(0)", 1.0), + TestAction::assert_eq("Math.exp(-1)", 0.367_879_441_171_442_33), + TestAction::assert_eq("Math.exp(2)", 7.389_056_098_930_65), + ]); } #[test] -#[allow(clippy::many_single_char_names)] fn expm1() { - let mut context = Context::default(); - let init = r#" - var a = Math.expm1(); - var b = Math.expm1({}); - var c = Math.expm1(1); - var d = Math.expm1(-1); - var e = Math.expm1(0); - var f = Math.expm1(2); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward(&mut context, "a"); - let b = forward(&mut context, "b"); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward_val(&mut context, "d").unwrap(); - let e = forward_val(&mut context, "e").unwrap(); - let f = forward_val(&mut context, "f").unwrap(); - - assert_eq!(a, String::from("NaN")); - assert_eq!(b, String::from("NaN")); - assert!(float_cmp::approx_eq!( - f64, - c.to_number(&mut context).unwrap(), - 1.718_281_828_459_045 - )); - assert!(float_cmp::approx_eq!( - f64, - d.to_number(&mut context).unwrap(), - -0.632_120_558_828_557_7 - )); - assert!(float_cmp::approx_eq!( - f64, - e.to_number(&mut context).unwrap(), - 0_f64 - )); - assert!(float_cmp::approx_eq!( - f64, - f.to_number(&mut context).unwrap(), - 6.389_056_098_930_65 - )); + run_test([ + TestAction::assert_eq("Math.expm1()", f64::NAN), + TestAction::assert_eq("Math.expm1({})", f64::NAN), + TestAction::assert_eq("Math.expm1(1)", 1.718_281_828_459_045), + TestAction::assert_eq("Math.expm1(-1)", -0.632_120_558_828_557_7), + TestAction::assert_eq("Math.expm1(0)", 0.0), + TestAction::assert_eq("Math.expm1(2)", 6.389_056_098_930_65), + ]); } #[test] fn floor() { - let mut context = Context::default(); - let init = r#" - var a = Math.floor(1.95); - var b = Math.floor(-3.01); - var c = Math.floor(3.01); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 1_f64); - assert_eq!(b.to_number(&mut context).unwrap(), -4_f64); - assert_eq!(c.to_number(&mut context).unwrap(), 3_f64); + run_test([ + TestAction::assert_eq("Math.floor(1.95)", 1.0), + TestAction::assert_eq("Math.floor(-3.01)", -4.0), + TestAction::assert_eq("Math.floor(3.01)", 3.0), + ]); } #[test] -#[allow(clippy::many_single_char_names)] fn fround() { - let mut context = Context::default(); - let init = r#" - var a = Math.fround(NaN); - var b = Math.fround(Infinity); - var c = Math.fround(5); - var d = Math.fround(5.5); - var e = Math.fround(5.05); - var f = Math.fround(-5.05); - var g = Math.fround(); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward(&mut context, "a"); - let b = forward(&mut context, "b"); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward_val(&mut context, "d").unwrap(); - let e = forward_val(&mut context, "e").unwrap(); - let f = forward_val(&mut context, "f").unwrap(); - let g = forward(&mut context, "g"); - - assert_eq!(a, String::from("NaN")); - assert_eq!(b, String::from("Infinity")); - assert_eq!(c.to_number(&mut context).unwrap(), 5f64); - assert_eq!(d.to_number(&mut context).unwrap(), 5.5f64); - assert_eq!(e.to_number(&mut context).unwrap(), 5.050_000_190_734_863); - assert_eq!(f.to_number(&mut context).unwrap(), -5.050_000_190_734_863); - assert_eq!(g, String::from("NaN")); -} - -#[test] -#[allow(clippy::many_single_char_names)] + run_test([ + TestAction::assert_eq("Math.fround(NaN)", f64::NAN), + TestAction::assert_eq("Math.fround(Infinity)", f64::INFINITY), + TestAction::assert_eq("Math.fround(5)", 5.0), + TestAction::assert_eq("Math.fround(5.5)", 5.5), + TestAction::assert_eq("Math.fround(5.05)", 5.050_000_190_734_863), + TestAction::assert_eq("Math.fround(-5.05)", -5.050_000_190_734_863), + TestAction::assert_eq("Math.fround()", f64::NAN), + ]); +} + +#[test] fn hypot() { - let mut context = Context::default(); - let init = r#" - var a = Math.hypot(); - var b = Math.hypot(3, 4); - var c = Math.hypot(5, 12); - var d = Math.hypot(3, 4, -5); - var e = Math.hypot(4, [5], 6); - var f = Math.hypot(3, -Infinity); - var g = Math.hypot(12); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward_val(&mut context, "d").unwrap(); - let e = forward_val(&mut context, "e").unwrap(); - let f = forward_val(&mut context, "f").unwrap(); - let g = forward_val(&mut context, "g").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 0f64); - assert_eq!(b.to_number(&mut context).unwrap(), 5f64); - assert_eq!(c.to_number(&mut context).unwrap(), 13f64); - assert_eq!(d.to_number(&mut context).unwrap(), 7.071_067_811_865_475_5); - assert_eq!(e.to_number(&mut context).unwrap(), 8.774_964_387_392_123); - assert!(f.to_number(&mut context).unwrap().is_infinite()); - assert_eq!(g.to_number(&mut context).unwrap(), 12f64); -} - -#[test] -#[allow(clippy::many_single_char_names)] + run_test([ + TestAction::assert_eq("Math.hypot()", 0.0), + TestAction::assert_eq("Math.hypot(3, 4)", 5.0), + TestAction::assert_eq("Math.hypot(5, 12)", 13.0), + TestAction::assert_eq("Math.hypot(3, 4, -5)", 7.071_067_811_865_475_5), + TestAction::assert_eq("Math.hypot(4, [5], 6)", 8.774_964_387_392_123), + TestAction::assert_eq("Math.hypot(3, -Infinity)", f64::INFINITY), + TestAction::assert_eq("Math.hypot(12)", 12.0), + ]); +} + +#[test] fn imul() { - let mut context = Context::default(); - let init = r#" - var a = Math.imul(3, 4); - var b = Math.imul(-5, 12); - var c = Math.imul(0xffffffff, 5); - var d = Math.imul(0xfffffffe, 5); - var e = Math.imul(12); - var f = Math.imul(); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward_val(&mut context, "d").unwrap(); - let e = forward_val(&mut context, "e").unwrap(); - let f = forward_val(&mut context, "f").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 12f64); - assert_eq!(b.to_number(&mut context).unwrap(), -60f64); - assert_eq!(c.to_number(&mut context).unwrap(), -5f64); - assert_eq!(d.to_number(&mut context).unwrap(), -10f64); - assert_eq!(e.to_number(&mut context).unwrap(), 0f64); - assert_eq!(f.to_number(&mut context).unwrap(), 0f64); + run_test([ + TestAction::assert_eq("Math.imul(3, 4)", 12), + TestAction::assert_eq("Math.imul(-5, 12)", -60), + TestAction::assert_eq("Math.imul(0xffffffff, 5)", -5), + TestAction::assert_eq("Math.imul(0xfffffffe, 5)", -10), + TestAction::assert_eq("Math.imul(12)", 0), + TestAction::assert_eq("Math.imul()", 0), + ]); } #[test] fn log() { - let mut context = Context::default(); - let init = r#" - var a = Math.log(1); - var b = Math.log(10); - var c = Math.log(-1); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward(&mut context, "c"); - - assert_eq!(a.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(b.to_number(&mut context).unwrap(), f64::consts::LN_10); - assert_eq!(c, String::from("NaN")); + run_test([ + TestAction::assert_eq("Math.log(1)", 0.0), + TestAction::assert_eq("Math.log(10)", std::f64::consts::LN_10), + TestAction::assert_eq("Math.log(-1)", f64::NAN), + ]); } #[test] -#[allow(clippy::many_single_char_names)] fn log1p() { - let mut context = Context::default(); - let init = r#" - var a = Math.log1p(1); - var b = Math.log1p(0); - var c = Math.log1p(-0.9999999999999999); - var d = Math.log1p(-1); - var e = Math.log1p(-1.000000000000001); - var f = Math.log1p(-2); - var g = Math.log1p(); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward(&mut context, "d"); - let e = forward(&mut context, "e"); - let f = forward(&mut context, "f"); - let g = forward(&mut context, "g"); - - assert_eq!(a.to_number(&mut context).unwrap(), f64::consts::LN_2); - assert_eq!(b.to_number(&mut context).unwrap(), 0f64); - assert_eq!(c.to_number(&mut context).unwrap(), -36.736_800_569_677_1); - assert_eq!(d, "-Infinity"); - assert_eq!(e, String::from("NaN")); - assert_eq!(f, String::from("NaN")); - assert_eq!(g, String::from("NaN")); + run_test([ + TestAction::assert_eq("Math.log1p(1)", std::f64::consts::LN_2), + TestAction::assert_eq("Math.log1p(0)", 0.0), + TestAction::assert_eq("Math.log1p(-0.9999999999999999)", -36.736_800_569_677_1), + TestAction::assert_eq("Math.log1p(-1)", f64::NEG_INFINITY), + TestAction::assert_eq("Math.log1p(-1.000000000000001)", f64::NAN), + TestAction::assert_eq("Math.log1p(-2)", f64::NAN), + TestAction::assert_eq("Math.log1p()", f64::NAN), + ]); } #[test] fn log10() { - let mut context = Context::default(); - let init = r#" - var a = Math.log10(2); - var b = Math.log10(1); - var c = Math.log10(-2); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward(&mut context, "c"); - - assert_eq!(a.to_number(&mut context).unwrap(), f64::consts::LOG10_2); - assert_eq!(b.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(c, String::from("NaN")); + run_test([ + TestAction::assert_eq("Math.log10(2)", std::f64::consts::LOG10_2), + TestAction::assert_eq("Math.log10(1)", 0.0), + TestAction::assert_eq("Math.log10(-2)", f64::NAN), + ]); } #[test] fn log2() { - let mut context = Context::default(); - let init = r#" - var a = Math.log2(3); - var b = Math.log2(1); - var c = Math.log2(-2); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward(&mut context, "c"); - - assert_eq!(a.to_number(&mut context).unwrap(), 1.584_962_500_721_156); - assert_eq!(b.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(c, String::from("NaN")); + run_test([ + TestAction::assert_eq("Math.log2(3)", 1.584_962_500_721_156), + TestAction::assert_eq("Math.log2(1)", 0.0), + TestAction::assert_eq("Math.log2(-2)", f64::NAN), + ]); } #[test] fn max() { - let mut context = Context::default(); - let init = r#" - var a = Math.max(10, 20); - var b = Math.max(-10, -20); - var c = Math.max(-10, 20); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 20_f64); - assert_eq!(b.to_number(&mut context).unwrap(), -10_f64); - assert_eq!(c.to_number(&mut context).unwrap(), 20_f64); + run_test([ + TestAction::assert_eq("Math.max(10, 20)", 20.0), + TestAction::assert_eq("Math.max(-10, -20)", -10.0), + TestAction::assert_eq("Math.max(-10, 20)", 20.0), + ]); } #[test] fn min() { - let mut context = Context::default(); - let init = r#" - var a = Math.min(10, 20); - var b = Math.min(-10, -20); - var c = Math.min(-10, 20); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 10_f64); - assert_eq!(b.to_number(&mut context).unwrap(), -20_f64); - assert_eq!(c.to_number(&mut context).unwrap(), -10_f64); + run_test([ + TestAction::assert_eq("Math.min(10, 20)", 10.0), + TestAction::assert_eq("Math.min(-10, -20)", -20.0), + TestAction::assert_eq("Math.min(-10, 20)", -10.0), + ]); } #[test] fn pow() { - let mut context = Context::default(); - let init = r#" - var a = Math.pow(2, 10); - var b = Math.pow(-7, 2); - var c = Math.pow(4, 0.5); - var d = Math.pow(7, -2); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - let d = forward_val(&mut context, "d").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 1_024_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 49_f64); - assert_eq!(c.to_number(&mut context).unwrap(), 2.0); - assert_eq!(d.to_number(&mut context).unwrap(), 0.020_408_163_265_306_12); + run_test([ + TestAction::assert_eq("Math.pow(2, 10)", 1_024.0), + TestAction::assert_eq("Math.pow(-7, 2)", 49.0), + TestAction::assert_eq("Math.pow(4, 0.5)", 2.0), + TestAction::assert_eq("Math.pow(7, -2)", 0.020_408_163_265_306_12), + ]); } #[test] fn round() { - let mut context = Context::default(); - let init = r#" - var a = Math.round(20.5); - var b = Math.round(-20.3); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 21.0); - assert_eq!(b.to_number(&mut context).unwrap(), -20.0); + run_test([ + TestAction::assert_eq("Math.round(20.5)", 21.0), + TestAction::assert_eq("Math.round(-20.3)", -20.0), + ]); } #[test] fn sign() { - let mut context = Context::default(); - let init = r#" - var a = Math.sign(3); - var b = Math.sign(-3); - var c = Math.sign(0); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 1_f64); - assert_eq!(b.to_number(&mut context).unwrap(), -1_f64); - assert_eq!(c.to_number(&mut context).unwrap(), 0_f64); + run_test([ + TestAction::assert_eq("Math.sign(3)", 1.0), + TestAction::assert_eq("Math.sign(-3)", -1.0), + TestAction::assert_eq("Math.sign(0)", 0.0), + ]); } #[test] fn sin() { - let mut context = Context::default(); - let init = r#" - var a = Math.sin(0); - var b = Math.sin(1); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 0.841_470_984_807_896_5); + run_test([ + TestAction::assert_eq("Math.sin(0)", 0.0), + TestAction::assert_eq("Math.sin(1)", 0.841_470_984_807_896_5), + ]); } #[test] fn sinh() { - let mut context = Context::default(); - let init = r#" - var a = Math.sinh(0); - var b = Math.sinh(1); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 1.175_201_193_643_801_4); + run_test([ + TestAction::assert_eq("Math.sinh(0)", 0.0), + TestAction::assert_eq("Math.sinh(1)", 1.175_201_193_643_801_4), + ]); } #[test] fn sqrt() { - let mut context = Context::default(); - let init = r#" - var a = Math.sqrt(0); - var b = Math.sqrt(2); - var c = Math.sqrt(9); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - let c = forward_val(&mut context, "c").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(b.to_number(&mut context).unwrap(), f64::consts::SQRT_2); - assert_eq!(c.to_number(&mut context).unwrap(), 3_f64); + run_test([ + TestAction::assert_eq("Math.sqrt(0)", 0.0), + TestAction::assert_eq("Math.sqrt(2)", std::f64::consts::SQRT_2), + TestAction::assert_eq("Math.sqrt(9)", 3.0), + ]); } #[test] fn tan() { - let mut context = Context::default(); - let init = r#" - var a = Math.tan(1.1); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - - assert!(float_cmp::approx_eq!( - f64, - a.to_number(&mut context).unwrap(), - 1.964_759_657_248_652_5 - )); + run_test([TestAction::assert_eq( + "Math.tan(1.1)", + 1.964_759_657_248_652_3, + )]); } #[test] fn tanh() { - let mut context = Context::default(); - let init = r#" - var a = Math.tanh(1); - var b = Math.tanh(0); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 0.761_594_155_955_764_9); - assert_eq!(b.to_number(&mut context).unwrap(), 0_f64); + run_test([ + TestAction::assert_eq("Math.tanh(1)", 0.761_594_155_955_764_9), + TestAction::assert_eq("Math.tanh(0)", 0.0), + ]); } #[test] fn trunc() { - let mut context = Context::default(); - let init = r#" - var a = Math.trunc(13.37); - var b = Math.trunc(0.123); - "#; - - eprintln!("{}", forward(&mut context, init)); - - let a = forward_val(&mut context, "a").unwrap(); - let b = forward_val(&mut context, "b").unwrap(); - - assert_eq!(a.to_number(&mut context).unwrap(), 13_f64); - assert_eq!(b.to_number(&mut context).unwrap(), 0_f64); + run_test([ + TestAction::assert_eq("Math.trunc(13.37)", 13.0), + TestAction::assert_eq("Math.trunc(0.123)", 0.0), + ]); } diff --git a/boa_engine/src/builtins/number/tests.rs b/boa_engine/src/builtins/number/tests.rs index a513364a105..5439572d649 100644 --- a/boa_engine/src/builtins/number/tests.rs +++ b/boa_engine/src/builtins/number/tests.rs @@ -1,436 +1,221 @@ #![allow(clippy::float_cmp)] -use crate::{builtins::Number, forward, forward_val, value::AbstractRelation, Context}; +use crate::{ + builtins::{error::ErrorKind, Number}, + run_test, + value::AbstractRelation, + TestAction, +}; #[test] fn integer_number_primitive_to_number_object() { - let mut context = Context::default(); - - let scenario = r#" - (100).toString() === "100" - "#; - - assert_eq!(forward(&mut context, scenario), "true"); + run_test([TestAction::assert_eq("(100).toString()", "100")]); } #[test] fn call_number() { - let mut context = Context::default(); - let init = r#" - var default_zero = Number(); - var int_one = Number(1); - var float_two = Number(2.1); - var str_three = Number('3.2'); - var bool_one = Number(true); - var bool_zero = Number(false); - var invalid_nan = Number("I am not a number"); - var from_exp = Number("2.34e+2"); - "#; - - eprintln!("{}", forward(&mut context, init)); - let default_zero = forward_val(&mut context, "default_zero").unwrap(); - let int_one = forward_val(&mut context, "int_one").unwrap(); - let float_two = forward_val(&mut context, "float_two").unwrap(); - let str_three = forward_val(&mut context, "str_three").unwrap(); - let bool_one = forward_val(&mut context, "bool_one").unwrap(); - let bool_zero = forward_val(&mut context, "bool_zero").unwrap(); - let invalid_nan = forward_val(&mut context, "invalid_nan").unwrap(); - let from_exp = forward_val(&mut context, "from_exp").unwrap(); - - assert_eq!(default_zero.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(int_one.to_number(&mut context).unwrap(), 1_f64); - assert_eq!(float_two.to_number(&mut context).unwrap(), 2.1); - assert_eq!(str_three.to_number(&mut context).unwrap(), 3.2); - assert_eq!(bool_one.to_number(&mut context).unwrap(), 1_f64); - assert!(invalid_nan.to_number(&mut context).unwrap().is_nan()); - assert_eq!(bool_zero.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(from_exp.to_number(&mut context).unwrap(), 234_f64); + run_test([ + TestAction::assert_eq("Number()", 0), + TestAction::assert_eq("Number(1)", 1), + TestAction::assert_eq("Number(2.1)", 2.1), + TestAction::assert_eq("Number('3.2')", 3.2), + TestAction::assert_eq("Number(true)", 1), + TestAction::assert_eq("Number(false)", 0), + TestAction::assert_eq("Number('I am not a number')", f64::NAN), + TestAction::assert_eq("Number('2.34e+2')", 234), + ]); } #[test] fn to_exponential() { - let mut context = Context::default(); - let init = r#" - var default_exp = Number().toExponential(); - var int_exp = Number(5).toExponential(); - var float_exp = Number(1.234).toExponential(); - var big_exp = Number(1234).toExponential(); - var nan_exp = Number("I am also not a number").toExponential(); - var noop_exp = Number("1.23e+2").toExponential(); - "#; - - eprintln!("{}", forward(&mut context, init)); - let default_exp = forward(&mut context, "default_exp"); - let int_exp = forward(&mut context, "int_exp"); - let float_exp = forward(&mut context, "float_exp"); - let big_exp = forward(&mut context, "big_exp"); - let nan_exp = forward(&mut context, "nan_exp"); - let noop_exp = forward(&mut context, "noop_exp"); - - assert_eq!(default_exp, "\"0e+0\""); - assert_eq!(int_exp, "\"5e+0\""); - assert_eq!(float_exp, "\"1.234e+0\""); - assert_eq!(big_exp, "\"1.234e+3\""); - assert_eq!(nan_exp, "\"NaN\""); - assert_eq!(noop_exp, "\"1.23e+2\""); + run_test([ + TestAction::assert_eq("Number().toExponential()", "0e+0"), + TestAction::assert_eq("Number(5).toExponential()", "5e+0"), + TestAction::assert_eq("Number(1.234).toExponential()", "1.234e+0"), + TestAction::assert_eq("Number(1234).toExponential()", "1.234e+3"), + TestAction::assert_eq("Number('I am also not a number').toExponential()", "NaN"), + TestAction::assert_eq("Number('1.23e+2').toExponential()", "1.23e+2"), + ]); } #[test] fn to_fixed() { - let mut context = Context::default(); - let init = r#" - var default_fixed = Number().toFixed(); - var pos_fixed = Number("3.456e+4").toFixed(); - var neg_fixed = Number("3.456e-4").toFixed(); - var noop_fixed = Number(5).toFixed(); - var nan_fixed = Number("I am not a number").toFixed(); - "#; - - eprintln!("{}", forward(&mut context, init)); - let default_fixed = forward(&mut context, "default_fixed"); - let pos_fixed = forward(&mut context, "pos_fixed"); - let neg_fixed = forward(&mut context, "neg_fixed"); - let noop_fixed = forward(&mut context, "noop_fixed"); - let nan_fixed = forward(&mut context, "nan_fixed"); - - assert_eq!(default_fixed, "\"0\""); - assert_eq!(pos_fixed, "\"34560\""); - assert_eq!(neg_fixed, "\"0\""); - assert_eq!(noop_fixed, "\"5\""); - assert_eq!(nan_fixed, "\"NaN\""); + run_test([ + TestAction::assert_eq("Number().toFixed()", "0"), + TestAction::assert_eq("Number('3.456e+4').toFixed()", "34560"), + TestAction::assert_eq("Number('3.456e-4').toFixed()", "0"), + TestAction::assert_eq("Number(5).toFixed()", "5"), + TestAction::assert_eq("Number('I am also not a number').toFixed()", "NaN"), + ]); } #[test] fn to_locale_string() { - let mut context = Context::default(); - let init = r#" - var default_locale = Number().toLocaleString(); - var small_locale = Number(5).toLocaleString(); - var big_locale = Number("345600").toLocaleString(); - var neg_locale = Number(-25).toLocaleString(); - "#; - // TODO: We don't actually do any locale checking here // To honor the spec we should print numbers according to user locale. - - eprintln!("{}", forward(&mut context, init)); - let default_locale = forward(&mut context, "default_locale"); - let small_locale = forward(&mut context, "small_locale"); - let big_locale = forward(&mut context, "big_locale"); - let neg_locale = forward(&mut context, "neg_locale"); - - assert_eq!(default_locale, "\"0\""); - assert_eq!(small_locale, "\"5\""); - assert_eq!(big_locale, "\"345600\""); - assert_eq!(neg_locale, "\"-25\""); + run_test([ + TestAction::assert_eq("Number().toLocaleString()", "0"), + TestAction::assert_eq("Number(5).toLocaleString()", "5"), + TestAction::assert_eq("Number('345600').toLocaleString()", "345600"), + TestAction::assert_eq("Number(-25).toLocaleString()", "-25"), + ]); } #[test] fn to_precision() { - let mut context = Context::default(); - let init = r#" - var infinity = (1/0).toPrecision(3); - var default_precision = Number().toPrecision(); - var explicit_ud_precision = Number().toPrecision(undefined); - var low_precision = (123456789).toPrecision(1); - var more_precision = (123456789).toPrecision(4); - var exact_precision = (123456789).toPrecision(9); - var over_precision = (123456789).toPrecision(50); - var neg_precision = (-123456789).toPrecision(4); - var neg_exponent = (0.1).toPrecision(4); - var ieee754_limits = (1/3).toPrecision(60); - "#; - - eprintln!("{}", forward(&mut context, init)); - let infinity = forward(&mut context, "infinity"); - let default_precision = forward(&mut context, "default_precision"); - let explicit_ud_precision = forward(&mut context, "explicit_ud_precision"); - let low_precision = forward(&mut context, "low_precision"); - let more_precision = forward(&mut context, "more_precision"); - let exact_precision = forward(&mut context, "exact_precision"); - let over_precision = forward(&mut context, "over_precision"); - let neg_precision = forward(&mut context, "neg_precision"); - let neg_exponent = forward(&mut context, "neg_exponent"); - let ieee754_limits = forward(&mut context, "ieee754_limits"); - - assert_eq!(infinity, String::from("\"Infinity\"")); - assert_eq!(default_precision, String::from("\"0\"")); - assert_eq!(explicit_ud_precision, String::from("\"0\"")); - assert_eq!(low_precision, String::from("\"1e+8\"")); - assert_eq!(more_precision, String::from("\"1.235e+8\"")); - assert_eq!(exact_precision, String::from("\"123456789\"")); - assert_eq!(neg_precision, String::from("\"-1.235e+8\"")); - assert_eq!( - over_precision, - String::from("\"123456789.00000000000000000000000000000000000000000\"") - ); - assert_eq!(neg_exponent, String::from("\"0.1000\"")); - assert_eq!( - ieee754_limits, - String::from("\"0.333333333333333314829616256247390992939472198486328125000000\"") - ); - - let expected = - "Uncaught RangeError: precision must be an integer at least 1 and no greater than 100"; - - let range_error_1 = r#"(1).toPrecision(101);"#; - let range_error_2 = r#"(1).toPrecision(0);"#; - let range_error_3 = r#"(1).toPrecision(-2000);"#; - let range_error_4 = r#"(1).toPrecision('%');"#; - - assert_eq!(forward(&mut context, range_error_1), expected); - assert_eq!(forward(&mut context, range_error_2), expected); - assert_eq!(forward(&mut context, range_error_3), expected); - assert_eq!(forward(&mut context, range_error_4), expected); + const ERROR: &str = "precision must be an integer at least 1 and no greater than 100"; + run_test([ + TestAction::assert_eq("(1/0).toPrecision(3)", "Infinity"), + TestAction::assert_eq("Number().toPrecision()", "0"), + TestAction::assert_eq("Number().toPrecision(undefined)", "0"), + TestAction::assert_eq("(123456789).toPrecision(1)", "1e+8"), + TestAction::assert_eq("(123456789).toPrecision(4)", "1.235e+8"), + TestAction::assert_eq("(123456789).toPrecision(9)", "123456789"), + TestAction::assert_eq("(-123456789).toPrecision(4)", "-1.235e+8"), + TestAction::assert_eq( + "(123456789).toPrecision(50)", + "123456789.00000000000000000000000000000000000000000", + ), + TestAction::assert_eq("(0.1).toPrecision(4)", "0.1000"), + TestAction::assert_eq( + "(1/3).toPrecision(60)", + "0.333333333333333314829616256247390992939472198486328125000000", + ), + TestAction::assert_native_error("(1).toPrecision(101)", ErrorKind::Range, ERROR), + TestAction::assert_native_error("(1).toPrecision(0)", ErrorKind::Range, ERROR), + TestAction::assert_native_error("(1).toPrecision(-2000)", ErrorKind::Range, ERROR), + TestAction::assert_native_error("(1).toPrecision('%')", ErrorKind::Range, ERROR), + ]); } #[test] fn to_string() { - let mut context = Context::default(); - - assert_eq!("\"NaN\"", &forward(&mut context, "Number(NaN).toString()")); - assert_eq!( - "\"Infinity\"", - &forward(&mut context, "Number(1/0).toString()") - ); - assert_eq!( - "\"-Infinity\"", - &forward(&mut context, "Number(-1/0).toString()") - ); - assert_eq!("\"0\"", &forward(&mut context, "Number(0).toString()")); - assert_eq!("\"9\"", &forward(&mut context, "Number(9).toString()")); - assert_eq!("\"90\"", &forward(&mut context, "Number(90).toString()")); - assert_eq!( - "\"90.12\"", - &forward(&mut context, "Number(90.12).toString()") - ); - assert_eq!("\"0.1\"", &forward(&mut context, "Number(0.1).toString()")); - assert_eq!( - "\"0.01\"", - &forward(&mut context, "Number(0.01).toString()") - ); - assert_eq!( - "\"0.0123\"", - &forward(&mut context, "Number(0.0123).toString()") - ); - assert_eq!( - "\"0.00001\"", - &forward(&mut context, "Number(0.00001).toString()") - ); - assert_eq!( - "\"0.000001\"", - &forward(&mut context, "Number(0.000001).toString()") - ); - assert_eq!( - "\"NaN\"", - &forward(&mut context, "Number(NaN).toString(16)") - ); - assert_eq!( - "\"Infinity\"", - &forward(&mut context, "Number(1/0).toString(16)") - ); - assert_eq!( - "\"-Infinity\"", - &forward(&mut context, "Number(-1/0).toString(16)") - ); - assert_eq!("\"0\"", &forward(&mut context, "Number(0).toString(16)")); - assert_eq!("\"9\"", &forward(&mut context, "Number(9).toString(16)")); - assert_eq!("\"5a\"", &forward(&mut context, "Number(90).toString(16)")); - assert_eq!( - "\"5a.1eb851eb852\"", - &forward(&mut context, "Number(90.12).toString(16)") - ); - assert_eq!( - "\"0.1999999999999a\"", - &forward(&mut context, "Number(0.1).toString(16)") - ); - assert_eq!( - "\"0.028f5c28f5c28f6\"", - &forward(&mut context, "Number(0.01).toString(16)") - ); - assert_eq!( - "\"0.032617c1bda511a\"", - &forward(&mut context, "Number(0.0123).toString(16)") - ); - assert_eq!( - "\"605f9f6dd18bc8000\"", - &forward(&mut context, "Number(111111111111111111111).toString(16)") - ); - assert_eq!( - "\"3c3bc3a4a2f75c0000\"", - &forward(&mut context, "Number(1111111111111111111111).toString(16)") - ); - assert_eq!( - "\"25a55a46e5da9a00000\"", - &forward(&mut context, "Number(11111111111111111111111).toString(16)") - ); - assert_eq!( - "\"0.0000a7c5ac471b4788\"", - &forward(&mut context, "Number(0.00001).toString(16)") - ); - assert_eq!( - "\"0.000010c6f7a0b5ed8d\"", - &forward(&mut context, "Number(0.000001).toString(16)") - ); - assert_eq!( - "\"0.000001ad7f29abcaf48\"", - &forward(&mut context, "Number(0.0000001).toString(16)") - ); - assert_eq!( - "\"0.000002036565348d256\"", - &forward(&mut context, "Number(0.00000012).toString(16)") - ); - assert_eq!( - "\"0.0000021047ee22aa466\"", - &forward(&mut context, "Number(0.000000123).toString(16)") - ); - assert_eq!( - "\"0.0000002af31dc4611874\"", - &forward(&mut context, "Number(0.00000001).toString(16)") - ); - assert_eq!( - "\"0.000000338a23b87483be\"", - &forward(&mut context, "Number(0.000000012).toString(16)") - ); - assert_eq!( - "\"0.00000034d3fe36aaa0a2\"", - &forward(&mut context, "Number(0.0000000123).toString(16)") - ); - - assert_eq!("\"0\"", &forward(&mut context, "Number(-0).toString(16)")); - assert_eq!("\"-9\"", &forward(&mut context, "Number(-9).toString(16)")); - assert_eq!( - "\"-5a\"", - &forward(&mut context, "Number(-90).toString(16)") - ); - assert_eq!( - "\"-5a.1eb851eb852\"", - &forward(&mut context, "Number(-90.12).toString(16)") - ); - assert_eq!( - "\"-0.1999999999999a\"", - &forward(&mut context, "Number(-0.1).toString(16)") - ); - assert_eq!( - "\"-0.028f5c28f5c28f6\"", - &forward(&mut context, "Number(-0.01).toString(16)") - ); - assert_eq!( - "\"-0.032617c1bda511a\"", - &forward(&mut context, "Number(-0.0123).toString(16)") - ); - assert_eq!( - "\"-605f9f6dd18bc8000\"", - &forward(&mut context, "Number(-111111111111111111111).toString(16)") - ); - assert_eq!( - "\"-3c3bc3a4a2f75c0000\"", - &forward(&mut context, "Number(-1111111111111111111111).toString(16)") - ); - assert_eq!( - "\"-25a55a46e5da9a00000\"", - &forward( - &mut context, - "Number(-11111111111111111111111).toString(16)" - ) - ); - assert_eq!( - "\"-0.0000a7c5ac471b4788\"", - &forward(&mut context, "Number(-0.00001).toString(16)") - ); - assert_eq!( - "\"-0.000010c6f7a0b5ed8d\"", - &forward(&mut context, "Number(-0.000001).toString(16)") - ); - assert_eq!( - "\"-0.000001ad7f29abcaf48\"", - &forward(&mut context, "Number(-0.0000001).toString(16)") - ); - assert_eq!( - "\"-0.000002036565348d256\"", - &forward(&mut context, "Number(-0.00000012).toString(16)") - ); - assert_eq!( - "\"-0.0000021047ee22aa466\"", - &forward(&mut context, "Number(-0.000000123).toString(16)") - ); - assert_eq!( - "\"-0.0000002af31dc4611874\"", - &forward(&mut context, "Number(-0.00000001).toString(16)") - ); - assert_eq!( - "\"-0.000000338a23b87483be\"", - &forward(&mut context, "Number(-0.000000012).toString(16)") - ); - assert_eq!( - "\"-0.00000034d3fe36aaa0a2\"", - &forward(&mut context, "Number(-0.0000000123).toString(16)") - ); + run_test([ + TestAction::assert_eq("Number(NaN).toString()", "NaN"), + TestAction::assert_eq("Number(1/0).toString()", "Infinity"), + TestAction::assert_eq("Number(-1/0).toString()", "-Infinity"), + TestAction::assert_eq("Number(0).toString()", "0"), + TestAction::assert_eq("Number(9).toString()", "9"), + TestAction::assert_eq("Number(90).toString()", "90"), + TestAction::assert_eq("Number(90.12).toString()", "90.12"), + TestAction::assert_eq("Number(0.1).toString()", "0.1"), + TestAction::assert_eq("Number(0.01).toString()", "0.01"), + TestAction::assert_eq("Number(0.0123).toString()", "0.0123"), + TestAction::assert_eq("Number(0.00001).toString()", "0.00001"), + TestAction::assert_eq("Number(0.000001).toString()", "0.000001"), + TestAction::assert_eq("Number(NaN).toString(16)", "NaN"), + TestAction::assert_eq("Number(1/0).toString(16)", "Infinity"), + TestAction::assert_eq("Number(-1/0).toString(16)", "-Infinity"), + TestAction::assert_eq("Number(0).toString(16)", "0"), + TestAction::assert_eq("Number(9).toString(16)", "9"), + TestAction::assert_eq("Number(90).toString(16)", "5a"), + TestAction::assert_eq("Number(90.12).toString(16)", "5a.1eb851eb852"), + TestAction::assert_eq("Number(0.1).toString(16)", "0.1999999999999a"), + TestAction::assert_eq("Number(0.01).toString(16)", "0.028f5c28f5c28f6"), + TestAction::assert_eq("Number(0.0123).toString(16)", "0.032617c1bda511a"), + TestAction::assert_eq( + "Number(111111111111111111111).toString(16)", + "605f9f6dd18bc8000", + ), + TestAction::assert_eq( + "Number(1111111111111111111111).toString(16)", + "3c3bc3a4a2f75c0000", + ), + TestAction::assert_eq( + "Number(11111111111111111111111).toString(16)", + "25a55a46e5da9a00000", + ), + TestAction::assert_eq("Number(0.00001).toString(16)", "0.0000a7c5ac471b4788"), + TestAction::assert_eq("Number(0.000001).toString(16)", "0.000010c6f7a0b5ed8d"), + TestAction::assert_eq("Number(0.0000001).toString(16)", "0.000001ad7f29abcaf48"), + TestAction::assert_eq("Number(0.00000012).toString(16)", "0.000002036565348d256"), + TestAction::assert_eq("Number(0.000000123).toString(16)", "0.0000021047ee22aa466"), + TestAction::assert_eq("Number(0.00000001).toString(16)", "0.0000002af31dc4611874"), + TestAction::assert_eq("Number(0.000000012).toString(16)", "0.000000338a23b87483be"), + TestAction::assert_eq( + "Number(0.0000000123).toString(16)", + "0.00000034d3fe36aaa0a2", + ), + TestAction::assert_eq("Number(-0).toString(16)", "0"), + TestAction::assert_eq("Number(-9).toString(16)", "-9"), + // + TestAction::assert_eq("Number(-90).toString(16)", "-5a"), + TestAction::assert_eq("Number(-90.12).toString(16)", "-5a.1eb851eb852"), + TestAction::assert_eq("Number(-0.1).toString(16)", "-0.1999999999999a"), + TestAction::assert_eq("Number(-0.01).toString(16)", "-0.028f5c28f5c28f6"), + TestAction::assert_eq("Number(-0.0123).toString(16)", "-0.032617c1bda511a"), + TestAction::assert_eq( + "Number(-111111111111111111111).toString(16)", + "-605f9f6dd18bc8000", + ), + TestAction::assert_eq( + "Number(-1111111111111111111111).toString(16)", + "-3c3bc3a4a2f75c0000", + ), + TestAction::assert_eq( + "Number(-11111111111111111111111).toString(16)", + "-25a55a46e5da9a00000", + ), + TestAction::assert_eq("Number(-0.00001).toString(16)", "-0.0000a7c5ac471b4788"), + TestAction::assert_eq("Number(-0.000001).toString(16)", "-0.000010c6f7a0b5ed8d"), + TestAction::assert_eq("Number(-0.0000001).toString(16)", "-0.000001ad7f29abcaf48"), + TestAction::assert_eq("Number(-0.00000012).toString(16)", "-0.000002036565348d256"), + TestAction::assert_eq( + "Number(-0.000000123).toString(16)", + "-0.0000021047ee22aa466", + ), + TestAction::assert_eq( + "Number(-0.00000001).toString(16)", + "-0.0000002af31dc4611874", + ), + TestAction::assert_eq( + "Number(-0.000000012).toString(16)", + "-0.000000338a23b87483be", + ), + TestAction::assert_eq( + "Number(-0.0000000123).toString(16)", + "-0.00000034d3fe36aaa0a2", + ), + ]); } #[test] fn num_to_string_exponential() { - let mut context = Context::default(); - - assert_eq!("\"0\"", forward(&mut context, "(0).toString()")); - assert_eq!("\"0\"", forward(&mut context, "(-0).toString()")); - assert_eq!( - "\"111111111111111110000\"", - forward(&mut context, "(111111111111111111111).toString()") - ); - assert_eq!( - "\"1.1111111111111111e+21\"", - forward(&mut context, "(1111111111111111111111).toString()") - ); - assert_eq!( - "\"1.1111111111111111e+22\"", - forward(&mut context, "(11111111111111111111111).toString()") - ); - assert_eq!("\"1e-7\"", forward(&mut context, "(0.0000001).toString()")); - assert_eq!( - "\"1.2e-7\"", - forward(&mut context, "(0.00000012).toString()") - ); - assert_eq!( - "\"1.23e-7\"", - forward(&mut context, "(0.000000123).toString()") - ); - assert_eq!("\"1e-8\"", forward(&mut context, "(0.00000001).toString()")); - assert_eq!( - "\"1.2e-8\"", - forward(&mut context, "(0.000000012).toString()") - ); - assert_eq!( - "\"1.23e-8\"", - forward(&mut context, "(0.0000000123).toString()") - ); + run_test([ + TestAction::assert_eq("(0).toString()", "0"), + TestAction::assert_eq("(-0).toString()", "0"), + TestAction::assert_eq( + "(111111111111111111111).toString()", + "111111111111111110000", + ), + TestAction::assert_eq( + "(1111111111111111111111).toString()", + "1.1111111111111111e+21", + ), + TestAction::assert_eq( + "(11111111111111111111111).toString()", + "1.1111111111111111e+22", + ), + TestAction::assert_eq("(0.0000001).toString()", "1e-7"), + TestAction::assert_eq("(0.00000012).toString()", "1.2e-7"), + TestAction::assert_eq("(0.000000123).toString()", "1.23e-7"), + TestAction::assert_eq("(0.00000001).toString()", "1e-8"), + TestAction::assert_eq("(0.000000012).toString()", "1.2e-8"), + TestAction::assert_eq("(0.0000000123).toString()", "1.23e-8"), + ]); } #[test] fn value_of() { - let mut context = Context::default(); // TODO: In addition to parsing numbers from strings, parse them bare As of October 2019 // the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation. - let init = r#" - var default_val = Number().valueOf(); - var int_val = Number("123").valueOf(); - var float_val = Number(1.234).valueOf(); - var exp_val = Number("1.2e+4").valueOf() - var neg_val = Number("-1.2e+4").valueOf() - "#; - - eprintln!("{}", forward(&mut context, init)); - let default_val = forward_val(&mut context, "default_val").unwrap(); - let int_val = forward_val(&mut context, "int_val").unwrap(); - let float_val = forward_val(&mut context, "float_val").unwrap(); - let exp_val = forward_val(&mut context, "exp_val").unwrap(); - let neg_val = forward_val(&mut context, "neg_val").unwrap(); - - assert_eq!(default_val.to_number(&mut context).unwrap(), 0_f64); - assert_eq!(int_val.to_number(&mut context).unwrap(), 123_f64); - assert_eq!(float_val.to_number(&mut context).unwrap(), 1.234); - assert_eq!(exp_val.to_number(&mut context).unwrap(), 12_000_f64); - assert_eq!(neg_val.to_number(&mut context).unwrap(), -12_000_f64); + run_test([ + TestAction::assert_eq("Number().valueOf()", 0), + TestAction::assert_eq("Number('123').valueOf()", 123), + TestAction::assert_eq("Number(1.234).valueOf()", 1.234), + TestAction::assert_eq("Number('1.2e+4').valueOf()", 12_000), + TestAction::assert_eq("Number('-1.2e+4').valueOf()", -12_000), + ]); } #[test] @@ -494,394 +279,199 @@ fn same_value_zero() { #[test] fn from_bigint() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "Number(0n)"), "0",); - assert_eq!(&forward(&mut context, "Number(100000n)"), "100000",); - assert_eq!(&forward(&mut context, "Number(100000n)"), "100000",); - assert_eq!(&forward(&mut context, "Number(1n << 1240n)"), "Infinity",); + run_test([ + TestAction::assert_eq("Number(0n)", 0), + TestAction::assert_eq("Number(100000n)", 100_000), + TestAction::assert_eq("Number(100000n)", 100_000), + TestAction::assert_eq("Number(1n << 1240n)", f64::INFINITY), + ]); } #[test] fn number_constants() { - let mut context = Context::default(); - - assert!(!forward_val(&mut context, "Number.EPSILON") - .unwrap() - .is_null_or_undefined()); - assert!(!forward_val(&mut context, "Number.MAX_SAFE_INTEGER") - .unwrap() - .is_null_or_undefined()); - assert!(!forward_val(&mut context, "Number.MIN_SAFE_INTEGER") - .unwrap() - .is_null_or_undefined()); - assert!(!forward_val(&mut context, "Number.MAX_VALUE") - .unwrap() - .is_null_or_undefined()); - assert!(!forward_val(&mut context, "Number.MIN_VALUE") - .unwrap() - .is_null_or_undefined()); - assert!(!forward_val(&mut context, "Number.NEGATIVE_INFINITY") - .unwrap() - .is_null_or_undefined()); - assert!(!forward_val(&mut context, "Number.POSITIVE_INFINITY") - .unwrap() - .is_null_or_undefined()); -} - -#[test] -fn parse_int_simple() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(\"6\")"), "6"); -} - -#[test] -fn parse_int_negative() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(\"-9\")"), "-9"); -} - -#[test] -fn parse_int_already_int() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(100)"), "100"); -} - -#[test] -fn parse_int_float() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(100.5)"), "100"); -} - -#[test] -fn parse_int_float_str() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(\"100.5\")"), "100"); -} - -#[test] -fn parse_int_inferred_hex() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(\"0xA\")"), "10"); -} - -/// This test demonstrates that this version of parseInt treats strings starting with 0 to be parsed with -/// a radix 10 if no radix is specified. Some alternative implementations default to a radix of 8. -#[test] -fn parse_int_zero_start() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(\"018\")"), "18"); + run_test([ + TestAction::assert_eq("Number.EPSILON", f64::EPSILON), + TestAction::assert_eq("Number.MAX_SAFE_INTEGER", Number::MAX_SAFE_INTEGER), + TestAction::assert_eq("Number.MIN_SAFE_INTEGER", Number::MIN_SAFE_INTEGER), + TestAction::assert_eq("Number.MAX_VALUE", f64::MAX), + TestAction::assert_eq("Number.MIN_VALUE", f64::MIN_POSITIVE), + TestAction::assert_eq("Number.POSITIVE_INFINITY", f64::INFINITY), + TestAction::assert_eq("Number.NEGATIVE_INFINITY", -f64::INFINITY), + ]); +} + +#[test] +fn parse_int() { + run_test([ + TestAction::assert_eq("parseInt('6')", 6), + TestAction::assert_eq("parseInt('-9')", -9), + TestAction::assert_eq("parseInt(100)", 100), + TestAction::assert_eq("parseInt(100.5)", 100), + TestAction::assert_eq("parseInt('0xA')", 10), + // This test demonstrates that this version of parseInt treats strings starting with 0 to be parsed with + // a radix 10 if no radix is specified. Some alternative implementations default to a radix of 8. + TestAction::assert_eq("parseInt('018')", 18), + TestAction::assert_eq("parseInt('hello')", f64::NAN), + TestAction::assert_eq("parseInt(undefined)", f64::NAN), + // Shows that no arguments to parseInt is treated the same as if undefined was + // passed as the first argument. + TestAction::assert_eq("parseInt()", f64::NAN), + // Shows that extra arguments to parseInt are ignored. + TestAction::assert_eq("parseInt('100', 10, 10)", 100), + ]); } #[test] fn parse_int_varying_radix() { - let mut context = Context::default(); - let base_str = "1000"; - - for radix in 2..36 { + let tests = (2..36).flat_map(|radix| { let expected = i32::from_str_radix(base_str, radix).unwrap(); + [ + TestAction::assert_eq(format!("parseInt('{base_str}', {radix} )"), expected), + TestAction::assert_eq(format!("parseInt('-{base_str}', {radix} )"), -expected), + ] + }); - assert_eq!( - forward(&mut context, &format!("parseInt(\"{base_str}\", {radix} )")), - expected.to_string() - ); - } + run_test(tests); } #[test] -fn parse_int_negative_varying_radix() { - let mut context = Context::default(); - - let base_str = "-1000"; - - for radix in 2..36 { - let expected = i32::from_str_radix(base_str, radix).unwrap(); - - assert_eq!( - forward(&mut context, &format!("parseInt(\"{base_str}\", {radix} )")), - expected.to_string() - ); - } -} - -#[test] -fn parse_int_malformed_str() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(\"hello\")"), "NaN"); -} - -#[test] -fn parse_int_undefined() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(undefined)"), "NaN"); -} - -/// Shows that no arguments to parseInt is treated the same as if undefined was -/// passed as the first argument. -#[test] -fn parse_int_no_args() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt()"), "NaN"); -} - -/// Shows that extra arguments to parseInt are ignored. -#[test] -fn parse_int_too_many_args() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseInt(\"100\", 10, 10)"), "100"); -} - -#[test] -fn parse_float_simple() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(\"6.5\")"), "6.5"); -} - -#[test] -fn parse_float_int() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(10)"), "10"); -} - -#[test] -fn parse_float_int_str() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(\"8\")"), "8"); -} - -#[test] -fn parse_float_already_float() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(17.5)"), "17.5"); -} - -#[test] -fn parse_float_negative() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(\"-99.7\")"), "-99.7"); -} - -#[test] -fn parse_float_malformed_str() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(\"hello\")"), "NaN"); -} - -#[test] -fn parse_float_undefined() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(undefined)"), "NaN"); -} - -/// No arguments to parseFloat is treated the same as passing undefined as the first argument. -#[test] -fn parse_float_no_args() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat()"), "NaN"); -} - -/// Shows that the parseFloat function ignores extra arguments. -#[test] -fn parse_float_too_many_args() { - let mut context = Context::default(); - - assert_eq!(&forward(&mut context, "parseFloat(\"100.5\", 10)"), "100.5"); +fn parse_float() { + run_test([ + TestAction::assert_eq("parseFloat('6.5')", 6.5), + TestAction::assert_eq("parseFloat(10)", 10), + TestAction::assert_eq("parseFloat('8')", 8), + TestAction::assert_eq("parseFloat(17.5)", 17.5), + TestAction::assert_eq("parseFloat('-99.7')", -99.7), + TestAction::assert_eq("parseFloat('hello')", f64::NAN), + TestAction::assert_eq("parseFloat(undefined)", f64::NAN), + // No arguments to parseFloat is treated the same as passing undefined as the first argument. + TestAction::assert_eq("parseFloat()", f64::NAN), + // Shows that the parseFloat function ignores extra arguments. + TestAction::assert_eq("parseFloat('100.5', 10)", 100.5), + ]); } #[test] fn global_is_finite() { - let mut context = Context::default(); - - assert_eq!("false", &forward(&mut context, "isFinite(Infinity)")); - assert_eq!("false", &forward(&mut context, "isFinite(NaN)")); - assert_eq!("false", &forward(&mut context, "isFinite(-Infinity)")); - assert_eq!("true", &forward(&mut context, "isFinite(0)")); - assert_eq!("true", &forward(&mut context, "isFinite(2e64)")); - assert_eq!("true", &forward(&mut context, "isFinite(910)")); - assert_eq!("true", &forward(&mut context, "isFinite(null)")); - assert_eq!("true", &forward(&mut context, "isFinite('0')")); - assert_eq!("false", &forward(&mut context, "isFinite()")); + run_test([ + TestAction::assert("!isFinite(Infinity)"), + TestAction::assert("!isFinite(NaN)"), + TestAction::assert("!isFinite(-Infinity)"), + TestAction::assert("isFinite(0)"), + TestAction::assert("isFinite(2e64)"), + TestAction::assert("isFinite(910)"), + TestAction::assert("isFinite(null)"), + TestAction::assert("isFinite('0')"), + TestAction::assert("!isFinite()"), + ]); } #[test] fn global_is_nan() { - let mut context = Context::default(); - - assert_eq!("true", &forward(&mut context, "isNaN(NaN)")); - assert_eq!("true", &forward(&mut context, "isNaN('NaN')")); - assert_eq!("true", &forward(&mut context, "isNaN(undefined)")); - assert_eq!("true", &forward(&mut context, "isNaN({})")); - assert_eq!("false", &forward(&mut context, "isNaN(true)")); - assert_eq!("false", &forward(&mut context, "isNaN(null)")); - assert_eq!("false", &forward(&mut context, "isNaN(37)")); - assert_eq!("false", &forward(&mut context, "isNaN('37')")); - assert_eq!("false", &forward(&mut context, "isNaN('37.37')")); - assert_eq!("true", &forward(&mut context, "isNaN('37,5')")); - assert_eq!("true", &forward(&mut context, "isNaN('123ABC')")); - // Incorrect due to ToNumber implementation inconsistencies. - //assert_eq!("false", &forward(&mut context, "isNaN('')")); - //assert_eq!("false", &forward(&mut context, "isNaN(' ')")); - assert_eq!("true", &forward(&mut context, "isNaN('blabla')")); + run_test([ + TestAction::assert("isNaN(NaN)"), + TestAction::assert("isNaN('NaN')"), + TestAction::assert("isNaN(undefined)"), + TestAction::assert("isNaN({})"), + TestAction::assert("!isNaN(true)"), + TestAction::assert("!isNaN(null)"), + TestAction::assert("!isNaN(37)"), + TestAction::assert("!isNaN('37')"), + TestAction::assert("!isNaN('37.37')"), + TestAction::assert("isNaN('37,5')"), + TestAction::assert("isNaN('123ABC')"), + // Incorrect due to ToNumber implementation inconsistencies. + // TestAction::assert("isNaN('')"), + // TestAction::assert("isNaN(' ')"), + TestAction::assert("isNaN('blabla')"), + ]); } #[test] fn number_is_finite() { - let mut context = Context::default(); - - assert_eq!("false", &forward(&mut context, "Number.isFinite(Infinity)")); - assert_eq!("false", &forward(&mut context, "Number.isFinite(NaN)")); - assert_eq!( - "false", - &forward(&mut context, "Number.isFinite(-Infinity)") - ); - assert_eq!("true", &forward(&mut context, "Number.isFinite(0)")); - assert_eq!("true", &forward(&mut context, "Number.isFinite(2e64)")); - assert_eq!("true", &forward(&mut context, "Number.isFinite(910)")); - assert_eq!("false", &forward(&mut context, "Number.isFinite(null)")); - assert_eq!("false", &forward(&mut context, "Number.isFinite('0')")); - assert_eq!("false", &forward(&mut context, "Number.isFinite()")); - assert_eq!("false", &forward(&mut context, "Number.isFinite({})")); - assert_eq!("true", &forward(&mut context, "Number.isFinite(Number(5))")); - assert_eq!( - "false", - &forward(&mut context, "Number.isFinite(new Number(5))") - ); - assert_eq!( - "false", - &forward(&mut context, "Number.isFinite(new Number(NaN))") - ); - assert_eq!( - "false", - &forward(&mut context, "Number.isFinite(BigInt(5))") - ); + run_test([ + TestAction::assert("!Number.isFinite(Infinity)"), + TestAction::assert("!Number.isFinite(NaN)"), + TestAction::assert("!Number.isFinite(-Infinity)"), + TestAction::assert("Number.isFinite(0)"), + TestAction::assert("Number.isFinite(2e64)"), + TestAction::assert("Number.isFinite(910)"), + TestAction::assert("!Number.isFinite(null)"), + TestAction::assert("!Number.isFinite('0')"), + TestAction::assert("!Number.isFinite()"), + TestAction::assert("!Number.isFinite({})"), + TestAction::assert("Number.isFinite(Number(5))"), + TestAction::assert("!Number.isFinite(new Number(5))"), + TestAction::assert("!Number.isFinite(new Number(5))"), + TestAction::assert("!Number.isFinite(BigInt(5))"), + ]); } #[test] fn number_is_integer() { - let mut context = Context::default(); - - assert_eq!("true", &forward(&mut context, "Number.isInteger(0)")); - assert_eq!("true", &forward(&mut context, "Number.isInteger(1)")); - assert_eq!("true", &forward(&mut context, "Number.isInteger(-100000)")); - assert_eq!( - "true", - &forward(&mut context, "Number.isInteger(99999999999999999999999)") - ); - assert_eq!("false", &forward(&mut context, "Number.isInteger(0.1)")); - assert_eq!("false", &forward(&mut context, "Number.isInteger(Math.PI)")); - assert_eq!("false", &forward(&mut context, "Number.isInteger(NaN)")); - assert_eq!( - "false", - &forward(&mut context, "Number.isInteger(Infinity)") - ); - assert_eq!( - "false", - &forward(&mut context, "Number.isInteger(-Infinity)") - ); - assert_eq!("false", &forward(&mut context, "Number.isInteger('10')")); - assert_eq!("false", &forward(&mut context, "Number.isInteger(true)")); - assert_eq!("false", &forward(&mut context, "Number.isInteger(false)")); - assert_eq!("false", &forward(&mut context, "Number.isInteger([1])")); - assert_eq!("true", &forward(&mut context, "Number.isInteger(5.0)")); - assert_eq!( - "false", - &forward(&mut context, "Number.isInteger(5.000000000000001)") - ); - assert_eq!( - "true", - &forward(&mut context, "Number.isInteger(5.0000000000000001)") - ); - assert_eq!( - "false", - &forward(&mut context, "Number.isInteger(Number(5.000000000000001))") - ); - assert_eq!( - "true", - &forward(&mut context, "Number.isInteger(Number(5.0000000000000001))") - ); - assert_eq!("false", &forward(&mut context, "Number.isInteger()")); - assert_eq!( - "false", - &forward(&mut context, "Number.isInteger(new Number(5))") - ); + run_test([ + TestAction::assert("Number.isInteger(0)"), + TestAction::assert("Number.isInteger(1)"), + TestAction::assert("Number.isInteger(-100000)"), + TestAction::assert("Number.isInteger(99999999999999999999999)"), + TestAction::assert("!Number.isInteger(0.1)"), + TestAction::assert("!Number.isInteger(Math.PI)"), + TestAction::assert("!Number.isInteger(NaN)"), + TestAction::assert("!Number.isInteger(Infinity)"), + TestAction::assert("!Number.isInteger(-Infinity)"), + TestAction::assert("!Number.isInteger('10')"), + TestAction::assert("!Number.isInteger(true)"), + TestAction::assert("!Number.isInteger(false)"), + TestAction::assert("!Number.isInteger([1])"), + TestAction::assert("Number.isInteger(5.0)"), + TestAction::assert("!Number.isInteger(5.000000000000001)"), + TestAction::assert("Number.isInteger(5.0000000000000001)"), + TestAction::assert("!Number.isInteger(Number(5.000000000000001))"), + TestAction::assert("Number.isInteger(Number(5.0000000000000001))"), + TestAction::assert("!Number.isInteger()"), + TestAction::assert("!Number.isInteger(new Number(5))"), + ]); } #[test] fn number_is_nan() { - let mut context = Context::default(); - - assert_eq!("true", &forward(&mut context, "Number.isNaN(NaN)")); - assert_eq!("true", &forward(&mut context, "Number.isNaN(Number.NaN)")); - assert_eq!("true", &forward(&mut context, "Number.isNaN(0 / 0)")); - assert_eq!("false", &forward(&mut context, "Number.isNaN(undefined)")); - assert_eq!("false", &forward(&mut context, "Number.isNaN({})")); - assert_eq!("false", &forward(&mut context, "Number.isNaN(true)")); - assert_eq!("false", &forward(&mut context, "Number.isNaN(null)")); - assert_eq!("false", &forward(&mut context, "Number.isNaN(37)")); - assert_eq!("false", &forward(&mut context, "Number.isNaN('37')")); - assert_eq!("false", &forward(&mut context, "Number.isNaN('37.37')")); - assert_eq!("false", &forward(&mut context, "Number.isNaN('37,5')")); - assert_eq!("false", &forward(&mut context, "Number.isNaN('123ABC')")); - // Incorrect due to ToNumber implementation inconsistencies. - //assert_eq!("false", &forward(&mut context, "Number.isNaN('')")); - //assert_eq!("false", &forward(&mut context, "Number.isNaN(' ')")); - assert_eq!("false", &forward(&mut context, "Number.isNaN('blabla')")); - assert_eq!("false", &forward(&mut context, "Number.isNaN(Number(5))")); - assert_eq!("true", &forward(&mut context, "Number.isNaN(Number(NaN))")); - assert_eq!("false", &forward(&mut context, "Number.isNaN(BigInt(5))")); - assert_eq!( - "false", - &forward(&mut context, "Number.isNaN(new Number(5))") - ); - assert_eq!( - "false", - &forward(&mut context, "Number.isNaN(new Number(NaN))") - ); + run_test([ + TestAction::assert("Number.isNaN(NaN)"), + TestAction::assert("Number.isNaN(Number.NaN)"), + TestAction::assert("Number.isNaN(0 / 0)"), + TestAction::assert("!Number.isNaN(undefined)"), + TestAction::assert("!Number.isNaN({})"), + TestAction::assert("!Number.isNaN(true)"), + TestAction::assert("!Number.isNaN(null)"), + TestAction::assert("!Number.isNaN(37)"), + TestAction::assert("!Number.isNaN('37')"), + TestAction::assert("!Number.isNaN('37.37')"), + TestAction::assert("!Number.isNaN('37,5')"), + TestAction::assert("!Number.isNaN('123ABC')"), + // Incorrect due to ToNumber implementation inconsistencies. + //TestAction::assert("!Number.isNaN('')"), + //TestAction::assert("!Number.isNaN(' ')"), + TestAction::assert("!Number.isNaN('blabla')"), + TestAction::assert("!Number.isNaN(Number(5))"), + TestAction::assert("Number.isNaN(Number(NaN))"), + TestAction::assert("!Number.isNaN(BigInt(5))"), + TestAction::assert("!Number.isNaN(new Number(5))"), + TestAction::assert("!Number.isNaN(new Number(NaN))"), + ]); } #[test] fn number_is_safe_integer() { - let mut context = Context::default(); - - assert_eq!("true", &forward(&mut context, "Number.isSafeInteger(3)")); - assert_eq!( - "false", - &forward(&mut context, "Number.isSafeInteger(Math.pow(2, 53))") - ); - assert_eq!( - "true", - &forward(&mut context, "Number.isSafeInteger(Math.pow(2, 53) - 1)") - ); - assert_eq!("false", &forward(&mut context, "Number.isSafeInteger(NaN)")); - assert_eq!( - "false", - &forward(&mut context, "Number.isSafeInteger(Infinity)") - ); - assert_eq!("false", &forward(&mut context, "Number.isSafeInteger('3')")); - assert_eq!("false", &forward(&mut context, "Number.isSafeInteger(3.1)")); - assert_eq!("true", &forward(&mut context, "Number.isSafeInteger(3.0)")); - assert_eq!( - "false", - &forward(&mut context, "Number.isSafeInteger(new Number(5))") - ); + run_test([ + TestAction::assert("Number.isSafeInteger(3)"), + TestAction::assert("!Number.isSafeInteger(Math.pow(2, 53))"), + TestAction::assert("Number.isSafeInteger(Math.pow(2, 53) - 1)"), + TestAction::assert("!Number.isSafeInteger(NaN)"), + TestAction::assert("!Number.isSafeInteger(Infinity)"), + TestAction::assert("!Number.isSafeInteger('3')"), + TestAction::assert("!Number.isSafeInteger(3.1)"), + TestAction::assert("Number.isSafeInteger(3.0)"), + TestAction::assert("!Number.isSafeInteger(new Number(5))"), + ]); } diff --git a/boa_engine/src/builtins/object/tests.rs b/boa_engine/src/builtins/object/tests.rs index 866ae123f58..a999f1844de 100644 --- a/boa_engine/src/builtins/object/tests.rs +++ b/boa_engine/src/builtins/object/tests.rs @@ -1,430 +1,408 @@ -use boa_parser::Source; +use crate::{builtins::error::ErrorKind, run_test, JsValue, TestAction}; +use indoc::indoc; -use crate::{check_output, forward, Context, JsValue, TestAction}; +#[test] +fn object_create_length() { + run_test([TestAction::assert_eq("Object.create.length", 2)]); +} #[test] fn object_create_with_regular_object() { - let mut context = Context::default(); - - let init = r#" - const foo = { a: 5 }; - const bar = Object.create(foo); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "bar.a"), "5"); - assert_eq!(forward(&mut context, "Object.create.length"), "2"); + run_test([TestAction::assert_eq("Object.create({ a: 5 }).a", 5)]); } #[test] fn object_create_with_undefined() { - let mut context = Context::default(); - - let init = r#" - try { - const bar = Object.create(); - } catch (err) { - err.toString() - } - "#; - - let result = forward(&mut context, init); - assert_eq!( - result, - "\"TypeError: Object prototype may only be an Object or null: undefined\"" - ); + run_test([TestAction::assert_native_error( + "Object.create()", + ErrorKind::Type, + "Object prototype may only be an Object or null: undefined", + )]); } #[test] fn object_create_with_number() { - let mut context = Context::default(); - - let init = r#" - try { - const bar = Object.create(5); - } catch (err) { - err.toString() - } - "#; - - let result = forward(&mut context, init); - assert_eq!( - result, - "\"TypeError: Object prototype may only be an Object or null: 5\"" - ); + run_test([TestAction::assert_native_error( + "Object.create(5)", + ErrorKind::Type, + "Object prototype may only be an Object or null: 5", + )]); } #[test] -#[ignore] -// TODO: to test on __proto__ somehow. __proto__ getter is not working as expected currently fn object_create_with_function() { - let mut context = Context::default(); - - let init = r#" - const x = function (){}; - const bar = Object.create(5); - bar.__proto__ - "#; - - let result = forward(&mut context, init); - assert_eq!(result, "...something on __proto__..."); + run_test([TestAction::assert(indoc! {r#" + const x = function (){}; + const bar = Object.create(x); + bar.__proto__ === x + "#})]); } #[test] fn object_is() { - let mut context = Context::default(); - - let init = r#" - var foo = { a: 1}; - var bar = { a: 1}; - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "Object.is('foo', 'foo')"), "true"); - assert_eq!(forward(&mut context, "Object.is('foo', 'bar')"), "false"); - assert_eq!(forward(&mut context, "Object.is([], [])"), "false"); - assert_eq!(forward(&mut context, "Object.is(foo, foo)"), "true"); - assert_eq!(forward(&mut context, "Object.is(foo, bar)"), "false"); - assert_eq!(forward(&mut context, "Object.is(null, null)"), "true"); - assert_eq!(forward(&mut context, "Object.is(0, -0)"), "false"); - assert_eq!(forward(&mut context, "Object.is(-0, -0)"), "true"); - assert_eq!(forward(&mut context, "Object.is(NaN, 0/0)"), "true"); - assert_eq!(forward(&mut context, "Object.is()"), "true"); - assert_eq!(forward(&mut context, "Object.is(undefined)"), "true"); - assert!(context.global_object().is_global()); + run_test([ + TestAction::run(indoc! {r#" + var foo = { a: 1}; + var bar = { a: 1}; + "#}), + TestAction::assert("Object.is('foo', 'foo')"), + TestAction::assert("!Object.is('foo', 'bar')"), + TestAction::assert("!Object.is([], [])"), + TestAction::assert("Object.is(foo, foo)"), + TestAction::assert("!Object.is(foo, bar)"), + TestAction::assert("Object.is(null, null)"), + TestAction::assert("!Object.is(0, -0)"), + TestAction::assert("Object.is(-0, -0)"), + TestAction::assert("Object.is(NaN, 0/0)"), + TestAction::assert("Object.is()"), + TestAction::assert("Object.is(undefined)"), + ]); } #[test] fn object_has_own_property() { - let scenario = r#" - let symA = Symbol('a'); - let symB = Symbol('b'); - - let x = { - undefinedProp: undefined, - nullProp: null, - someProp: 1, - [symA]: 2, - 100: 3, - }; - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("x.hasOwnProperty('hasOwnProperty')", "false"), - TestAction::TestEq("x.hasOwnProperty('undefinedProp')", "true"), - TestAction::TestEq("x.hasOwnProperty('nullProp')", "true"), - TestAction::TestEq("x.hasOwnProperty('someProp')", "true"), - TestAction::TestEq("x.hasOwnProperty(symB)", "false"), - TestAction::TestEq("x.hasOwnProperty(symA)", "true"), - TestAction::TestEq("x.hasOwnProperty(1000)", "false"), - TestAction::TestEq("x.hasOwnProperty(100)", "true"), + run_test([ + TestAction::run(indoc! {r#" + let symA = Symbol('a'); + let symB = Symbol('b'); + + let x = { + undefinedProp: undefined, + nullProp: null, + someProp: 1, + [symA]: 2, + 100: 3, + }; + "#}), + TestAction::assert("!x.hasOwnProperty('hasOwnProperty')"), + TestAction::assert("x.hasOwnProperty('undefinedProp')"), + TestAction::assert("x.hasOwnProperty('nullProp')"), + TestAction::assert("x.hasOwnProperty('someProp')"), + TestAction::assert("!x.hasOwnProperty(symB)"), + TestAction::assert("x.hasOwnProperty(symA)"), + TestAction::assert("!x.hasOwnProperty(1000)"), + TestAction::assert("x.hasOwnProperty(100)"), ]); } #[test] fn object_has_own() { - let scenario = r#" - let symA = Symbol('a'); - let symB = Symbol('b'); - - let x = { - undefinedProp: undefined, - nullProp: null, - someProp: 1, - [symA]: 2, - 100: 3, - }; - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("Object.hasOwn(x, 'hasOwnProperty')", "false"), - TestAction::TestEq("Object.hasOwn(x, 'undefinedProp')", "true"), - TestAction::TestEq("Object.hasOwn(x, 'nullProp')", "true"), - TestAction::TestEq("Object.hasOwn(x, 'someProp')", "true"), - TestAction::TestEq("Object.hasOwn(x, symB)", "false"), - TestAction::TestEq("Object.hasOwn(x, symA)", "true"), - TestAction::TestEq("Object.hasOwn(x, 1000)", "false"), - TestAction::TestEq("Object.hasOwn(x, 100)", "true"), + run_test([ + TestAction::run(indoc! {r#" + let symA = Symbol('a'); + let symB = Symbol('b'); + + let x = { + undefinedProp: undefined, + nullProp: null, + someProp: 1, + [symA]: 2, + 100: 3, + }; + "#}), + TestAction::assert("!Object.hasOwn(x, 'hasOwnProperty')"), + TestAction::assert("Object.hasOwn(x, 'undefinedProp')"), + TestAction::assert("Object.hasOwn(x, 'nullProp')"), + TestAction::assert("Object.hasOwn(x, 'someProp')"), + TestAction::assert("!Object.hasOwn(x, symB)"), + TestAction::assert("Object.hasOwn(x, symA)"), + TestAction::assert("!Object.hasOwn(x, 1000)"), + TestAction::assert("Object.hasOwn(x, 100)"), ]); } #[test] fn object_property_is_enumerable() { - let mut context = Context::default(); - let init = r#" - let x = { enumerableProp: 'yes' }; - "#; - eprintln!("{}", forward(&mut context, init)); - assert_eq!( - forward(&mut context, r#"x.propertyIsEnumerable('enumerableProp')"#), - "true" - ); - assert_eq!( - forward(&mut context, r#"x.propertyIsEnumerable('hasOwnProperty')"#), - "false" - ); - assert_eq!( - forward(&mut context, r#"x.propertyIsEnumerable('not_here')"#), - "false", - ); - assert_eq!( - forward(&mut context, r#"x.propertyIsEnumerable()"#), - "false", - ); + run_test([ + TestAction::run("let x = { enumerableProp: 'yes' };"), + TestAction::assert("x.propertyIsEnumerable('enumerableProp')"), + TestAction::assert("!x.propertyIsEnumerable('hasOwnProperty')"), + TestAction::assert("!x.propertyIsEnumerable('not_here')"), + TestAction::assert("!x.propertyIsEnumerable()"), + ]); } #[test] fn object_to_string() { - let mut context = Context::default(); - let init = r#" - let u = undefined; - let n = null; - let a = []; - Array.prototype.toString = Object.prototype.toString; - let f = () => {}; - Function.prototype.toString = Object.prototype.toString; - let e = new Error('test'); - Error.prototype.toString = Object.prototype.toString; - let b = Boolean(); - Boolean.prototype.toString = Object.prototype.toString; - let i = Number(42); - Number.prototype.toString = Object.prototype.toString; - let s = String('boa'); - String.prototype.toString = Object.prototype.toString; - let d = new Date(Date.now()); - Date.prototype.toString = Object.prototype.toString; - let re = /boa/; - RegExp.prototype.toString = Object.prototype.toString; - let o = Object(); - "#; - eprintln!("{}", forward(&mut context, init)); - assert_eq!( - forward(&mut context, "Object.prototype.toString.call(u)"), - "\"[object Undefined]\"" - ); - assert_eq!( - forward(&mut context, "Object.prototype.toString.call(n)"), - "\"[object Null]\"" - ); - assert_eq!(forward(&mut context, "a.toString()"), "\"[object Array]\""); - assert_eq!( - forward(&mut context, "f.toString()"), - "\"[object Function]\"" - ); - assert_eq!(forward(&mut context, "e.toString()"), "\"[object Error]\""); - assert_eq!( - forward(&mut context, "b.toString()"), - "\"[object Boolean]\"" - ); - assert_eq!(forward(&mut context, "i.toString()"), "\"[object Number]\""); - assert_eq!(forward(&mut context, "s.toString()"), "\"[object String]\""); - assert_eq!(forward(&mut context, "d.toString()"), "\"[object Date]\""); - assert_eq!( - forward(&mut context, "re.toString()"), - "\"[object RegExp]\"" - ); - assert_eq!(forward(&mut context, "o.toString()"), "\"[object Object]\""); + run_test([ + TestAction::run(indoc! {r#" + Array.prototype.toString = Object.prototype.toString; + Function.prototype.toString = Object.prototype.toString; + Error.prototype.toString = Object.prototype.toString; + Boolean.prototype.toString = Object.prototype.toString; + Number.prototype.toString = Object.prototype.toString; + String.prototype.toString = Object.prototype.toString; + Date.prototype.toString = Object.prototype.toString; + RegExp.prototype.toString = Object.prototype.toString; + "#}), + TestAction::assert_eq( + "Object.prototype.toString.call(undefined)", + "[object Undefined]", + ), + TestAction::assert_eq("Object.prototype.toString.call(null)", "[object Null]"), + TestAction::assert_eq("[].toString()", "[object Array]"), + TestAction::assert_eq("(() => {}).toString()", "[object Function]"), + TestAction::assert_eq("(new Error('')).toString()", "[object Error]"), + TestAction::assert_eq("Boolean().toString()", "[object Boolean]"), + TestAction::assert_eq("Number(42).toString()", "[object Number]"), + TestAction::assert_eq("String('boa').toString()", "[object String]"), + TestAction::assert_eq("(new Date()).toString()", "[object Date]"), + TestAction::assert_eq("/boa/.toString()", "[object RegExp]"), + TestAction::assert_eq("({}).toString()", "[object Object]"), + ]); } #[test] fn define_symbol_property() { - let mut context = Context::default(); - - let init = r#" - let obj = {}; - let sym = Symbol("key"); - Object.defineProperty(obj, sym, { value: "val" }); - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!(forward(&mut context, "obj[sym]"), "\"val\""); + run_test([ + TestAction::run(indoc! {r#" + let obj = {}; + let sym = Symbol("key"); + Object.defineProperty(obj, sym, { value: "val" }); + "#}), + TestAction::assert_eq("obj[sym]", "val"), + ]); } #[test] fn get_own_property_descriptor_1_arg_returns_undefined() { - let mut context = Context::default(); - let code = r#" - let obj = {a: 2}; - Object.getOwnPropertyDescriptor(obj) - "#; - assert_eq!( - context.eval_script(Source::from_bytes(code)).unwrap(), - JsValue::undefined() - ); + run_test([TestAction::assert_eq( + "Object.getOwnPropertyDescriptor({a: 2})", + JsValue::undefined(), + )]); } #[test] fn get_own_property_descriptor() { - let mut context = Context::default(); - forward( - &mut context, - r#" - let obj = {a: 2}; - let result = Object.getOwnPropertyDescriptor(obj, "a"); - "#, - ); - - assert_eq!(forward(&mut context, "result.enumerable"), "true"); - assert_eq!(forward(&mut context, "result.writable"), "true"); - assert_eq!(forward(&mut context, "result.configurable"), "true"); - assert_eq!(forward(&mut context, "result.value"), "2"); + run_test([ + TestAction::run(indoc! {r#" + let obj = {a: 2}; + let prop = Object.getOwnPropertyDescriptor(obj, "a"); + "#}), + TestAction::assert("prop.enumerable"), + TestAction::assert("prop.writable"), + TestAction::assert("prop.configurable"), + TestAction::assert_eq("prop.value", 2), + ]); } #[test] fn get_own_property_descriptors() { - let mut context = Context::default(); - forward( - &mut context, - r#" - let obj = {a: 1, b: 2}; - let result = Object.getOwnPropertyDescriptors(obj); - "#, - ); - - assert_eq!(forward(&mut context, "result.a.enumerable"), "true"); - assert_eq!(forward(&mut context, "result.a.writable"), "true"); - assert_eq!(forward(&mut context, "result.a.configurable"), "true"); - assert_eq!(forward(&mut context, "result.a.value"), "1"); - - assert_eq!(forward(&mut context, "result.b.enumerable"), "true"); - assert_eq!(forward(&mut context, "result.b.writable"), "true"); - assert_eq!(forward(&mut context, "result.b.configurable"), "true"); - assert_eq!(forward(&mut context, "result.b.value"), "2"); + run_test([ + TestAction::run(indoc! {r#" + let obj = {a: 1, b: 2}; + let props = Object.getOwnPropertyDescriptors(obj); + "#}), + TestAction::assert("props.a.enumerable"), + TestAction::assert("props.a.writable"), + TestAction::assert("props.a.configurable"), + TestAction::assert_eq("props.a.value", 1), + TestAction::assert("props.b.enumerable"), + TestAction::assert("props.b.writable"), + TestAction::assert("props.b.configurable"), + TestAction::assert_eq("props.b.value", 2), + ]); } #[test] fn object_define_properties() { - let mut context = Context::default(); - - let init = r#" - const obj = {}; - - Object.defineProperties(obj, { - p: { - value: 42, - writable: true - } - }); - "#; - eprintln!("{}", forward(&mut context, init)); - - assert_eq!(forward(&mut context, "obj.p"), "42"); + run_test([ + TestAction::run(indoc! {r#" + const obj = {}; + + Object.defineProperties(obj, { + p: { + value: 42, + writable: true + } + }); + + const prop = Object.getOwnPropertyDescriptor(obj, 'p'); + "#}), + TestAction::assert("!prop.enumerable"), + TestAction::assert("prop.writable"), + TestAction::assert("!prop.configurable"), + TestAction::assert_eq("prop.value", 42), + ]); } #[test] fn object_is_prototype_of() { - let mut context = Context::default(); - - let init = r#" - Object.prototype.isPrototypeOf(String.prototype) - "#; - - assert_eq!( - context.eval_script(Source::from_bytes(init)).unwrap(), - JsValue::new(true) - ); + run_test([TestAction::assert( + "Object.prototype.isPrototypeOf(String.prototype)", + )]); } #[test] fn object_get_own_property_names_invalid_args() { - let error_message = "Uncaught TypeError: cannot convert 'null' or 'undefined' to object"; - - check_output(&[ - TestAction::TestEq("Object.getOwnPropertyNames()", error_message), - TestAction::TestEq("Object.getOwnPropertyNames(null)", error_message), - TestAction::TestEq("Object.getOwnPropertyNames(undefined)", error_message), + const ERROR: &str = "cannot convert 'null' or 'undefined' to object"; + + run_test([ + TestAction::assert_native_error("Object.getOwnPropertyNames()", ErrorKind::Type, ERROR), + TestAction::assert_native_error("Object.getOwnPropertyNames(null)", ErrorKind::Type, ERROR), + TestAction::assert_native_error( + "Object.getOwnPropertyNames(undefined)", + ErrorKind::Type, + ERROR, + ), ]); } #[test] fn object_get_own_property_names() { - check_output(&[ - TestAction::TestEq("Object.getOwnPropertyNames(0)", "[]"), - TestAction::TestEq("Object.getOwnPropertyNames(false)", "[]"), - TestAction::TestEq(r#"Object.getOwnPropertyNames(Symbol("a"))"#, "[]"), - TestAction::TestEq("Object.getOwnPropertyNames({})", "[]"), - TestAction::TestEq("Object.getOwnPropertyNames(NaN)", "[]"), - TestAction::TestEq( - "Object.getOwnPropertyNames([1, 2, 3])", - r#"[ "0", "1", "2", "length" ]"#, - ), - TestAction::TestEq( - r#"Object.getOwnPropertyNames({ - "a": 1, - "b": 2, - [ Symbol("c") ]: 3, - [ Symbol("d") ]: 4, - })"#, - r#"[ "a", "b" ]"#, - ), + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertyNames(0), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertyNames(false), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertyNames(Symbol("a")), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertyNames({}), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertyNames(NaN), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertyNames([1, 2, 3]), + ["0", "1", "2", "length"] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertyNames({ + "a": 1, + "b": 2, + [ Symbol("c") ]: 3, + [ Symbol("d") ]: 4, + }), + ["a", "b"] + ) + "#}), ]); } #[test] fn object_get_own_property_symbols_invalid_args() { - let error_message = "Uncaught TypeError: cannot convert 'null' or 'undefined' to object"; - - check_output(&[ - TestAction::TestEq("Object.getOwnPropertySymbols()", error_message), - TestAction::TestEq("Object.getOwnPropertySymbols(null)", error_message), - TestAction::TestEq("Object.getOwnPropertySymbols(undefined)", error_message), + const ERROR: &str = "cannot convert 'null' or 'undefined' to object"; + + run_test([ + TestAction::assert_native_error("Object.getOwnPropertySymbols()", ErrorKind::Type, ERROR), + TestAction::assert_native_error( + "Object.getOwnPropertySymbols(null)", + ErrorKind::Type, + ERROR, + ), + TestAction::assert_native_error( + "Object.getOwnPropertySymbols(undefined)", + ErrorKind::Type, + ERROR, + ), ]); } #[test] fn object_get_own_property_symbols() { - check_output(&[ - TestAction::TestEq("Object.getOwnPropertySymbols(0)", "[]"), - TestAction::TestEq("Object.getOwnPropertySymbols(false)", "[]"), - TestAction::TestEq(r#"Object.getOwnPropertySymbols(Symbol("a"))"#, "[]"), - TestAction::TestEq("Object.getOwnPropertySymbols({})", "[]"), - TestAction::TestEq("Object.getOwnPropertySymbols(NaN)", "[]"), - TestAction::TestEq("Object.getOwnPropertySymbols([1, 2, 3])", "[]"), - TestAction::TestEq( - r#" - Object.getOwnPropertySymbols({ - "a": 1, - "b": 2, - [ Symbol("c") ]: 3, - [ Symbol("d") ]: 4, - })"#, - "[ Symbol(c), Symbol(d) ]", - ), + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertySymbols(0), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertySymbols(false), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertySymbols(Symbol("a")), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertySymbols({}), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertySymbols(NaN), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Object.getOwnPropertySymbols([1, 2, 3]), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + let c = Symbol("c"); + let d = Symbol("d"); + arrayEquals( + Object.getOwnPropertySymbols({ + "a": 1, + "b": 2, + [ c ]: 3, + [ d ]: 4, + }), + [c, d] + ) + "#}), ]); } #[test] fn object_from_entries_invalid_args() { - let error_message = "Uncaught TypeError: cannot convert null or undefined to Object"; + const ERROR: &str = "cannot convert null or undefined to Object"; - check_output(&[ - TestAction::TestEq("Object.fromEntries()", error_message), - TestAction::TestEq("Object.fromEntries(null)", error_message), - TestAction::TestEq("Object.fromEntries(undefined)", error_message), + run_test([ + TestAction::assert_native_error("Object.fromEntries()", ErrorKind::Type, ERROR), + TestAction::assert_native_error("Object.fromEntries(null)", ErrorKind::Type, ERROR), + TestAction::assert_native_error("Object.fromEntries(undefined)", ErrorKind::Type, ERROR), ]); } #[test] fn object_from_entries() { - let scenario = r#" - let sym = Symbol("sym"); - let map = Object.fromEntries([ - ["long key", 1], - ["short", 2], - [sym, 3], - [5, 4], - ]); - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("map['long key']", "1"), - TestAction::TestEq("map.short", "2"), - TestAction::TestEq("map[sym]", "3"), - TestAction::TestEq("map[5]", "4"), + run_test([ + TestAction::run(indoc! {r#" + let sym = Symbol("sym"); + let map = Object.fromEntries([ + ["long key", 1], + ["short", 2], + [sym, 3], + [5, 4], + ]); + "#}), + TestAction::assert_eq("map['long key']", 1), + TestAction::assert_eq("map.short", 2), + TestAction::assert_eq("map[sym]", 3), + TestAction::assert_eq("map[5]", 4), ]); } diff --git a/boa_engine/src/builtins/promise/tests.rs b/boa_engine/src/builtins/promise/tests.rs index 2cc340a6465..beeefb65309 100644 --- a/boa_engine/src/builtins/promise/tests.rs +++ b/boa_engine/src/builtins/promise/tests.rs @@ -1,23 +1,25 @@ -use boa_parser::Source; - -use crate::{context::ContextBuilder, forward, job::SimpleJobQueue}; +use crate::{context::ContextBuilder, job::SimpleJobQueue, run_test_with, TestAction}; +use indoc::indoc; #[test] fn promise() { let queue = SimpleJobQueue::new(); - let mut context = ContextBuilder::new().job_queue(&queue).build().unwrap(); - let init = r#" - let count = 0; - const promise = new Promise((resolve, reject) => { - count += 1; - resolve(undefined); - }).then((_) => (count += 1)); - count += 1; - count; - "#; - let result = context.eval_script(Source::from_bytes(init)).unwrap(); - assert_eq!(result.as_number(), Some(2_f64)); - context.run_jobs(); - let after_completion = forward(&mut context, "count"); - assert_eq!(after_completion, String::from("3")); + let context = &mut ContextBuilder::new().job_queue(&queue).build().unwrap(); + run_test_with( + [ + TestAction::run(indoc! {r#" + let count = 0; + const promise = new Promise((resolve, reject) => { + count += 1; + resolve(undefined); + }).then((_) => (count += 1)); + count += 1; + "#}), + TestAction::assert_eq("count", 2), + #[allow(clippy::redundant_closure_for_method_calls)] + TestAction::inspect_context(|ctx| ctx.run_jobs()), + TestAction::assert_eq("count", 3), + ], + context, + ); } diff --git a/boa_engine/src/builtins/reflect/tests.rs b/boa_engine/src/builtins/reflect/tests.rs index 87ad4cc4282..8941d89962a 100644 --- a/boa_engine/src/builtins/reflect/tests.rs +++ b/boa_engine/src/builtins/reflect/tests.rs @@ -1,191 +1,136 @@ -use crate::{forward, Context}; +use crate::{run_test, JsValue, TestAction}; +use indoc::indoc; #[test] fn apply() { - let mut context = Context::default(); - - let init = r#" - var called = {}; - function f(n) { called.result = n }; - Reflect.apply(f, undefined, [42]); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "called.result"), "42"); + run_test([ + TestAction::run(indoc! {r#" + var called = {}; + function f(n) { called.result = n }; + Reflect.apply(f, undefined, [42]); + "#}), + TestAction::assert_eq("called.result", 42), + ]); } #[test] fn construct() { - let mut context = Context::default(); - - let init = r#" - var called = {}; - function f(n) { called.result = n }; - Reflect.construct(f, [42]); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "called.result"), "42"); + run_test([ + TestAction::run(indoc! {r#" + var called = {}; + function f(n) { called.result = n }; + Reflect.construct(f, [42]); + "#}), + TestAction::assert_eq("called.result", 42), + ]); } #[test] fn define_property() { - let mut context = Context::default(); - - let init = r#" - let obj = {}; - Reflect.defineProperty(obj, 'p', { value: 42 }); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "obj.p"), "42"); + run_test([ + TestAction::run(indoc! {r#" + let obj = {}; + Reflect.defineProperty(obj, 'p', { value: 42 }); + "#}), + TestAction::assert_eq("obj.p", 42), + ]); } #[test] fn delete_property() { - let mut context = Context::default(); - - let init = r#" - let obj = { p: 42 }; - let deleted = Reflect.deleteProperty(obj, 'p'); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "obj.p"), "undefined"); - assert_eq!(forward(&mut context, "deleted"), "true"); + run_test([ + TestAction::run("let obj = { p: 42 };"), + TestAction::assert("Reflect.deleteProperty(obj, 'p')"), + TestAction::assert_eq("obj.p", JsValue::undefined()), + ]); } #[test] fn get() { - let mut context = Context::default(); - - let init = r#" - let obj = { p: 42 } - let p = Reflect.get(obj, 'p'); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "p"), "42"); + run_test([ + TestAction::run("let obj = { p: 42 };"), + TestAction::assert_eq("Reflect.get(obj, 'p')", 42), + ]); } #[test] fn get_own_property_descriptor() { - let mut context = Context::default(); - - let init = r#" - let obj = { p: 42 }; - let desc = Reflect.getOwnPropertyDescriptor(obj, 'p'); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "desc.value"), "42"); + run_test([ + TestAction::run("let obj = { p: 42 };"), + TestAction::assert_eq("Reflect.getOwnPropertyDescriptor(obj, 'p').value", 42), + ]); } #[test] fn get_prototype_of() { - let mut context = Context::default(); - - let init = r#" - function F() { this.p = 42 }; - let f = new F(); - let proto = Reflect.getPrototypeOf(f); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "proto.constructor.name"), "\"F\""); + run_test([ + TestAction::run(indoc! {r#" + function F() { this.p = 42 }; + let f = new F(); + "#}), + TestAction::assert_eq("Reflect.getPrototypeOf(f).constructor.name", "F"), + ]); } #[test] fn has() { - let mut context = Context::default(); - - let init = r#" - let obj = { p: 42 }; - let hasP = Reflect.has(obj, 'p'); - let hasP2 = Reflect.has(obj, 'p2'); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "hasP"), "true"); - assert_eq!(forward(&mut context, "hasP2"), "false"); + run_test([ + TestAction::run("let obj = { p: 42 };"), + TestAction::assert("Reflect.has(obj, 'p')"), + TestAction::assert("!Reflect.has(obj, 'p2')"), + ]); } #[test] fn is_extensible() { - let mut context = Context::default(); - - let init = r#" - let obj = { p: 42 }; - let isExtensible = Reflect.isExtensible(obj); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "isExtensible"), "true"); + run_test([ + TestAction::run("let obj = { p: 42 };"), + TestAction::assert("Reflect.isExtensible(obj)"), + ]); } #[test] fn own_keys() { - let mut context = Context::default(); - - let init = r#" - let obj = { p: 42 }; - let ownKeys = Reflect.ownKeys(obj); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "ownKeys"), r#"[ "p" ]"#); + run_test([ + TestAction::run_harness(), + TestAction::run("let obj = { p: 42 };"), + TestAction::assert(indoc! {r#" + arrayEquals( + Reflect.ownKeys(obj), + ["p"] + ) + "#}), + ]); } #[test] fn prevent_extensions() { - let mut context = Context::default(); - - let init = r#" - let obj = { p: 42 }; - let r = Reflect.preventExtensions(obj); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "r"), "true"); + run_test([ + TestAction::run("let obj = { p: 42 };"), + TestAction::assert("Reflect.preventExtensions(obj)"), + TestAction::assert("!Reflect.isExtensible(obj)"), + ]); } #[test] fn set() { - let mut context = Context::default(); - - let init = r#" - let obj = {}; - Reflect.set(obj, 'p', 42); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "obj.p"), "42"); + run_test([ + TestAction::run(indoc! {r#" + let obj = {}; + Reflect.set(obj, 'p', 42); + "#}), + TestAction::assert_eq("obj.p", 42), + ]); } #[test] fn set_prototype_of() { - let mut context = Context::default(); - - let init = r#" - function F() { this.p = 42 }; - let obj = {} - Reflect.setPrototypeOf(obj, F); - let p = Reflect.getPrototypeOf(obj); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "p.name"), "\"F\""); + run_test([ + TestAction::run(indoc! {r#" + function F() { this.p = 42 }; + let obj = {} + Reflect.setPrototypeOf(obj, F); + "#}), + TestAction::assert_eq("Reflect.getPrototypeOf(obj).name", "F"), + ]); } diff --git a/boa_engine/src/builtins/regexp/tests.rs b/boa_engine/src/builtins/regexp/tests.rs index 3dd09c6b307..437ab4244ec 100644 --- a/boa_engine/src/builtins/regexp/tests.rs +++ b/boa_engine/src/builtins/regexp/tests.rs @@ -1,240 +1,186 @@ -use crate::{forward, Context}; +use crate::{builtins::error::ErrorKind, object::JsObject, run_test, JsValue, TestAction}; +use indoc::indoc; #[test] fn constructors() { - let mut context = Context::default(); - let init = r#" - var constructed = new RegExp("[0-9]+(\\.[0-9]+)?"); - var literal = /[0-9]+(\.[0-9]+)?/; - var ctor_literal = new RegExp(/[0-9]+(\.[0-9]+)?/); - "#; - - eprintln!("{}", forward(&mut context, init)); - assert_eq!(forward(&mut context, "constructed.test('1.0')"), "true"); - assert_eq!(forward(&mut context, "literal.test('1.0')"), "true"); - assert_eq!(forward(&mut context, "ctor_literal.test('1.0')"), "true"); + run_test([ + TestAction::run(indoc! {r#" + var constructed = new RegExp("[0-9]+(\\.[0-9]+)?"); + var literal = /[0-9]+(\.[0-9]+)?/; + var ctor_literal = new RegExp(/[0-9]+(\.[0-9]+)?/); + "#}), + TestAction::assert("constructed.test('1.0')"), + TestAction::assert("literal.test('1.0')"), + TestAction::assert("ctor_literal.test('1.0')"), + ]); } #[test] fn species() { - let mut context = Context::default(); - - let init = r#" + run_test([ + TestAction::run(indoc! {r#" var descriptor = Object.getOwnPropertyDescriptor(RegExp, Symbol.species); - var accessor = Object.getOwnPropertyDescriptor(RegExp, Symbol.species).get; - var name = Object.getOwnPropertyDescriptor(descriptor.get, "name"); - var length = Object.getOwnPropertyDescriptor(descriptor.get, "length"); + var accessor = descriptor.get; + var name = Object.getOwnPropertyDescriptor(accessor, "name"); + var length = Object.getOwnPropertyDescriptor(accessor, "length"); var thisVal = {}; - "#; - eprintln!("{}", forward(&mut context, init)); - - // length - assert_eq!(forward(&mut context, "descriptor.get.length"), "0"); - assert_eq!(forward(&mut context, "length.enumerable"), "false"); - assert_eq!(forward(&mut context, "length.writable"), "false"); - assert_eq!(forward(&mut context, "length.configurable"), "true"); - - // return-value - assert_eq!( - forward(&mut context, "Object.is(accessor.call(thisVal), thisVal)"), - "true" - ); - - // symbol-species-name - assert_eq!( - forward(&mut context, "descriptor.get.name"), - "\"get [Symbol.species]\"" - ); - assert_eq!(forward(&mut context, "name.enumerable"), "false"); - assert_eq!(forward(&mut context, "name.writable"), "false"); - assert_eq!(forward(&mut context, "name.configurable"), "true"); - - // symbol-species - assert_eq!(forward(&mut context, "descriptor.set"), "undefined"); - assert_eq!( - forward(&mut context, "typeof descriptor.get"), - "\"function\"" - ); - assert_eq!(forward(&mut context, "descriptor.enumerable"), "false"); - assert_eq!(forward(&mut context, "descriptor.configurable"), "true"); + "#}), + // length + TestAction::assert_eq("length.value", 0), + TestAction::assert("!length.enumerable"), + TestAction::assert("!length.writable"), + TestAction::assert("length.configurable"), + // return-value + TestAction::assert("Object.is(accessor.call(thisVal), thisVal)"), + // symbol-species-name + TestAction::assert_eq("name.value", "get [Symbol.species]"), + TestAction::assert("!name.enumerable"), + TestAction::assert("!name.writable"), + TestAction::assert("name.configurable"), + // symbol-species + TestAction::assert_eq("descriptor.set", JsValue::undefined()), + TestAction::assert_with_op("accessor", |v, _| { + v.as_object().map_or(false, JsObject::is_function) + }), + TestAction::assert("!descriptor.enumerable"), + TestAction::assert("descriptor.configurable"), + ]); } -// TODO: uncomment this test when property getters are supported - -// #[test] -// fn flags() { -// let mut context = Context::default(); -// let init = r#" -// var re_gi = /test/gi; -// var re_sm = /test/sm; -// "#; -// -// eprintln!("{}", forward(&mut context, init)); -// assert_eq!(forward(&mut context, "re_gi.global"), "true"); -// assert_eq!(forward(&mut context, "re_gi.ignoreCase"), "true"); -// assert_eq!(forward(&mut context, "re_gi.multiline"), "false"); -// assert_eq!(forward(&mut context, "re_gi.dotAll"), "false"); -// assert_eq!(forward(&mut context, "re_gi.unicode"), "false"); -// assert_eq!(forward(&mut context, "re_gi.sticky"), "false"); -// assert_eq!(forward(&mut context, "re_gi.flags"), "gi"); -// -// assert_eq!(forward(&mut context, "re_sm.global"), "false"); -// assert_eq!(forward(&mut context, "re_sm.ignoreCase"), "false"); -// assert_eq!(forward(&mut context, "re_sm.multiline"), "true"); -// assert_eq!(forward(&mut context, "re_sm.dotAll"), "true"); -// assert_eq!(forward(&mut context, "re_sm.unicode"), "false"); -// assert_eq!(forward(&mut context, "re_sm.sticky"), "false"); -// assert_eq!(forward(&mut context, "re_sm.flags"), "ms"); -// } +#[test] +fn flags() { + run_test([ + TestAction::run(indoc! {r#" + var re_gi = /test/gi; + var re_sm = /test/sm; + "#}), + TestAction::assert("re_gi.global"), + TestAction::assert("re_gi.ignoreCase"), + TestAction::assert("!re_gi.multiline"), + TestAction::assert("!re_gi.dotAll"), + TestAction::assert("!re_gi.unicode"), + TestAction::assert("!re_gi.sticky"), + TestAction::assert_eq("re_gi.flags", "gi"), + // + TestAction::assert("!re_sm.global"), + TestAction::assert("!re_sm.ignoreCase"), + TestAction::assert("re_sm.multiline"), + TestAction::assert("re_sm.dotAll"), + TestAction::assert("!re_sm.unicode"), + TestAction::assert("!re_sm.sticky"), + TestAction::assert_eq("re_sm.flags", "ms"), + ]); +} #[test] fn last_index() { - let mut context = Context::default(); - let init = r#" - var regex = /[0-9]+(\.[0-9]+)?/g; - "#; - - eprintln!("{}", forward(&mut context, init)); - assert_eq!(forward(&mut context, "regex.lastIndex"), "0"); - assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "true"); - assert_eq!(forward(&mut context, "regex.lastIndex"), "3"); - assert_eq!(forward(&mut context, "regex.test('1.0foo')"), "false"); - assert_eq!(forward(&mut context, "regex.lastIndex"), "0"); + run_test([ + TestAction::run(r"var regex = /[0-9]+(\.[0-9]+)?/g;"), + TestAction::assert_eq("regex.lastIndex", 0), + TestAction::assert("regex.test('1.0foo')"), + TestAction::assert_eq("regex.lastIndex", 3), + TestAction::assert("!regex.test('1.0foo')"), + TestAction::assert_eq("regex.lastIndex", 0), + ]); } #[test] fn exec() { - let mut context = Context::default(); - let init = r#" - var re = /quick\s(brown).+?(jumps)/ig; - var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); - "#; - - eprintln!("{}", forward(&mut context, init)); - assert_eq!( - forward(&mut context, "result[0]"), - "\"Quick Brown Fox Jumps\"" - ); - assert_eq!(forward(&mut context, "result[1]"), "\"Brown\""); - assert_eq!(forward(&mut context, "result[2]"), "\"Jumps\""); - assert_eq!(forward(&mut context, "result.index"), "4"); - assert_eq!( - forward(&mut context, "result.input"), - "\"The Quick Brown Fox Jumps Over The Lazy Dog\"" - ); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var re = /quick\s(brown).+?(jumps)/ig; + var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog'); + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + result, + ["Quick Brown Fox Jumps", "Brown", "Jumps"] + ) + "#}), + TestAction::assert_eq("result.index", 4), + TestAction::assert_eq( + "result.input", + "The Quick Brown Fox Jumps Over The Lazy Dog", + ), + ]); } #[test] fn to_string() { - let mut context = Context::default(); - - assert_eq!( - forward(&mut context, "(new RegExp('a+b+c')).toString()"), - "\"/a+b+c/\"" - ); - assert_eq!( - forward(&mut context, "(new RegExp('bar', 'g')).toString()"), - "\"/bar/g\"" - ); - assert_eq!( - forward(&mut context, "(new RegExp('\\\\n', 'g')).toString()"), - "\"/\\n/g\"" - ); - assert_eq!(forward(&mut context, "/\\n/g.toString()"), "\"/\\n/g\""); + run_test([ + TestAction::assert_eq("(new RegExp('a+b+c')).toString()", "/a+b+c/"), + TestAction::assert_eq("(new RegExp('bar', 'g')).toString()", "/bar/g"), + TestAction::assert_eq(r"(new RegExp('\\n', 'g')).toString()", r"/\n/g"), + TestAction::assert_eq(r"/\n/g.toString()", r"/\n/g"), + ]); } #[test] fn no_panic_on_invalid_character_escape() { - let mut context = Context::default(); - // This used to panic, we now return an error // The line below should not cause Boa to panic - forward(&mut context, r"const a = /,\;/"); + run_test([TestAction::assert_native_error( + r"const a = /,\;/", + ErrorKind::Syntax, + "Invalid regular expression literal: Invalid character escape at position: 1:11", + )]); } #[test] fn search() { - let mut context = Context::default(); - - // coerce-string - assert_eq!( - forward( - &mut context, - r#" - var obj = { - toString: function() { - return 'toString value'; - } - }; - /ring/[Symbol.search](obj) - "# + const ERROR: &str = "RegExp.prototype[Symbol.search] method called on incompatible value"; + run_test([ + TestAction::run(indoc! {r#" + var search = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.search); + var length = Object.getOwnPropertyDescriptor(search.value, 'length'); + var name = Object.getOwnPropertyDescriptor(search.value, 'name'); + "#}), + // prop-desc + TestAction::assert("!search.enumerable"), + TestAction::assert("search.writable"), + TestAction::assert("search.configurable"), + // length + TestAction::assert_eq("length.value", 1), + TestAction::assert("!length.enumerable"), + TestAction::assert("!length.writable"), + TestAction::assert("length.configurable"), + // name + TestAction::assert_eq("name.value", "[Symbol.search]"), + TestAction::assert("!name.enumerable"), + TestAction::assert("!name.writable"), + TestAction::assert("name.configurable"), + // success-return-val + TestAction::assert_eq("/a/[Symbol.search]('abc')", 0), + TestAction::assert_eq("/b/[Symbol.search]('abc')", 1), + TestAction::assert_eq("/c/[Symbol.search]('abc')", 2), + // failure-return-val + TestAction::assert_eq("/z/[Symbol.search]('a')", -1), + // coerce-string + TestAction::assert_eq( + indoc! {r#" + /ring/[Symbol.search]({ + toString: function() { + return 'toString value'; + } + }); + "#}, + 4, ), - "4" - ); - - // failure-return-val - assert_eq!(forward(&mut context, "/z/[Symbol.search]('a')"), "-1"); - - // length - assert_eq!( - forward(&mut context, "RegExp.prototype[Symbol.search].length"), - "1" - ); - - let init = - "var obj = Object.getOwnPropertyDescriptor(RegExp.prototype[Symbol.search], \"length\")"; - eprintln!("{}", forward(&mut context, init)); - assert_eq!(forward(&mut context, "obj.enumerable"), "false"); - assert_eq!(forward(&mut context, "obj.writable"), "false"); - assert_eq!(forward(&mut context, "obj.configurable"), "true"); - - // name - assert_eq!( - forward(&mut context, "RegExp.prototype[Symbol.search].name"), - "\"[Symbol.search]\"" - ); - - let init = - "var obj = Object.getOwnPropertyDescriptor(RegExp.prototype[Symbol.search], \"name\")"; - eprintln!("{}", forward(&mut context, init)); - assert_eq!(forward(&mut context, "obj.enumerable"), "false"); - assert_eq!(forward(&mut context, "obj.writable"), "false"); - assert_eq!(forward(&mut context, "obj.configurable"), "true"); - - // prop-desc - let init = "var obj = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.search)"; - eprintln!("{}", forward(&mut context, init)); - assert_eq!(forward(&mut context, "obj.enumerable"), "false"); - assert_eq!(forward(&mut context, "obj.writable"), "true"); - assert_eq!(forward(&mut context, "obj.configurable"), "true"); - - // success-return-val - assert_eq!(forward(&mut context, "/a/[Symbol.search]('abc')"), "0"); - assert_eq!(forward(&mut context, "/b/[Symbol.search]('abc')"), "1"); - assert_eq!(forward(&mut context, "/c/[Symbol.search]('abc')"), "2"); - - // this-val-non-obj - let error = - "Uncaught TypeError: RegExp.prototype[Symbol.search] method called on incompatible value"; - let init = "var search = RegExp.prototype[Symbol.search]"; - eprintln!("{}", forward(&mut context, init)); - assert_eq!(forward(&mut context, "search.call()"), error); - assert_eq!(forward(&mut context, "search.call(undefined)"), error); - assert_eq!(forward(&mut context, "search.call(null)"), error); - assert_eq!(forward(&mut context, "search.call(true)"), error); - assert_eq!(forward(&mut context, "search.call('string')"), error); - assert_eq!(forward(&mut context, "search.call(Symbol.search)"), error); - assert_eq!(forward(&mut context, "search.call(86)"), error); - - // u-lastindex-advance - assert_eq!( - forward(&mut context, "/\\udf06/u[Symbol.search]('\\ud834\\udf06')"), - "-1" - ); - - assert_eq!(forward(&mut context, "/a/[Symbol.search](\"a\")"), "0"); - assert_eq!(forward(&mut context, "/a/[Symbol.search](\"ba\")"), "1"); - assert_eq!(forward(&mut context, "/a/[Symbol.search](\"bb\")"), "-1"); - assert_eq!(forward(&mut context, "/u/[Symbol.search](null)"), "1"); - assert_eq!(forward(&mut context, "/d/[Symbol.search](undefined)"), "2"); + // this-val-non-obj + TestAction::assert_native_error("search.value.call()", ErrorKind::Type, ERROR), + TestAction::assert_native_error("search.value.call(undefined)", ErrorKind::Type, ERROR), + TestAction::assert_native_error("search.value.call(null)", ErrorKind::Type, ERROR), + TestAction::assert_native_error("search.value.call(true)", ErrorKind::Type, ERROR), + TestAction::assert_native_error("search.value.call('string')", ErrorKind::Type, ERROR), + TestAction::assert_native_error("search.value.call(Symbol.search)", ErrorKind::Type, ERROR), + TestAction::assert_native_error("search.value.call(86)", ErrorKind::Type, ERROR), + // u-lastindex-advance + TestAction::assert_eq(r"/\udf06/u[Symbol.search]('\ud834\udf06')", -1), + TestAction::assert_eq("/a/[Symbol.search](\"a\")", 0), + TestAction::assert_eq("/a/[Symbol.search](\"ba\")", 1), + TestAction::assert_eq("/a/[Symbol.search](\"bb\")", -1), + TestAction::assert_eq("/u/[Symbol.search](null)", 1), + TestAction::assert_eq("/d/[Symbol.search](undefined)", 2), + ]); } diff --git a/boa_engine/src/builtins/set/tests.rs b/boa_engine/src/builtins/set/tests.rs index 1a29c07f82b..bc0e8c3d1a8 100644 --- a/boa_engine/src/builtins/set/tests.rs +++ b/boa_engine/src/builtins/set/tests.rs @@ -1,248 +1,176 @@ -use crate::{forward, Context}; +use crate::{builtins::error::ErrorKind, run_test, TestAction}; +use indoc::indoc; #[test] -fn construct_empty() { - let mut context = Context::default(); - let init = r#" - var empty = new Set(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "empty.size"); - assert_eq!(result, "0"); -} - -#[test] -fn construct_from_array() { - let mut context = Context::default(); - let init = r#" - let set = new Set(["one", "two"]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "set.size"); - assert_eq!(result, "2"); +fn construct() { + run_test([ + TestAction::assert_eq("(new Set()).size", 0), + TestAction::assert_eq("(new Set(['one', 'two'])).size", 2), + ]); } #[test] fn clone() { - let mut context = Context::default(); - let init = r#" - let original = new Set(["one", "two"]); - let clone = new Set(original); - "#; - forward(&mut context, init); - let result = forward(&mut context, "clone.size"); - assert_eq!(result, "2"); - let result = forward( - &mut context, - r#" - original.add("three"); - original.size"#, - ); - assert_eq!(result, "3"); - let result = forward(&mut context, "clone.size"); - assert_eq!(result, "2"); + run_test([ + TestAction::run(indoc! {r#" + let original = new Set(["one", "two"]); + let clone = new Set(original); + "#}), + TestAction::assert_eq("clone.size", 2), + TestAction::assert_eq( + indoc! {r#" + original.add("three"); + original.size + "#}, + 3, + ), + TestAction::assert_eq("clone.size", 2), + ]); } #[test] fn symbol_iterator() { - let mut context = Context::default(); - let init = r#" - const set1 = new Set(); - set1.add('foo'); - set1.add('bar'); - const iterator = set1[Symbol.iterator](); - let item1 = iterator.next(); - let item2 = iterator.next(); - let item3 = iterator.next(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "item1.value"); - assert_eq!(result, "\"foo\""); - let result = forward(&mut context, "item1.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item2.value"); - assert_eq!(result, "\"bar\""); - let result = forward(&mut context, "item2.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item3.value"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "item3.done"); - assert_eq!(result, "true"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + const set1 = new Set(); + set1.add('foo'); + set1.add('bar'); + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from(set1[Symbol.iterator]()), + ["foo", "bar"] + ) + "#}), + ]); } #[test] fn entries() { - let mut context = Context::default(); - let init = r#" - const set1 = new Set(); - set1.add('foo'); - set1.add('bar'); - const entriesIterator = set1.entries(); - let item1 = entriesIterator.next(); - let item2 = entriesIterator.next(); - let item3 = entriesIterator.next(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "item1.value.length"); - assert_eq!(result, "2"); - let result = forward(&mut context, "item1.value[0]"); - assert_eq!(result, "\"foo\""); - let result = forward(&mut context, "item1.value[1]"); - assert_eq!(result, "\"foo\""); - let result = forward(&mut context, "item1.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item2.value.length"); - assert_eq!(result, "2"); - let result = forward(&mut context, "item2.value[0]"); - assert_eq!(result, "\"bar\""); - let result = forward(&mut context, "item2.value[1]"); - assert_eq!(result, "\"bar\""); - let result = forward(&mut context, "item2.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item3.value"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "item3.done"); - assert_eq!(result, "true"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + const set1 = new Set(); + set1.add('foo'); + set1.add('bar') + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from(set1.entries()), + [["foo", "foo"], ["bar", "bar"]] + ) + "#}), + ]); } #[test] fn merge() { - let mut context = Context::default(); - let init = r#" - let first = new Set(["one", "two"]); - let second = new Set(["three", "four"]); - let third = new Set(["four", "five"]); - let merged1 = new Set([...first, ...second]); - let merged2 = new Set([...second, ...third]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "merged1.size"); - assert_eq!(result, "4"); - let result = forward(&mut context, "merged2.size"); - assert_eq!(result, "3"); + run_test([ + TestAction::run(indoc! {r#" + let first = new Set(["one", "two"]); + let second = new Set(["three", "four"]); + let third = new Set(["four", "five"]); + let merged1 = new Set([...first, ...second]); + let merged2 = new Set([...second, ...third]); + "#}), + TestAction::assert_eq("merged1.size", 4), + TestAction::assert_eq("merged2.size", 3), + ]); } #[test] fn clear() { - let mut context = Context::default(); - let init = r#" - let set = new Set(["one", "two"]); - set.clear(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "set.size"); - assert_eq!(result, "0"); + run_test([ + TestAction::run(indoc! {r#" + let set = new Set(["one", "two"]); + set.clear(); + "#}), + TestAction::assert_eq("set.size", 0), + ]); } #[test] fn delete() { - let mut context = Context::default(); - let init = r#" - let set = new Set(["one", "two"]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "set.delete('one')"); - assert_eq!(result, "true"); - let result = forward(&mut context, "set.size"); - assert_eq!(result, "1"); - let result = forward(&mut context, "set.delete('one')"); - assert_eq!(result, "false"); + run_test([ + TestAction::run("let set = new Set(['one', 'two'])"), + TestAction::assert("set.delete('one')"), + TestAction::assert_eq("set.size", 1), + TestAction::assert("!set.delete('one')"), + ]); } #[test] fn has() { - let mut context = Context::default(); - let init = r#" - let set = new Set(["one", "two"]); - "#; - forward(&mut context, init); - let result = forward(&mut context, "set.has('one')"); - assert_eq!(result, "true"); - let result = forward(&mut context, "set.has('two')"); - assert_eq!(result, "true"); - let result = forward(&mut context, "set.has('three')"); - assert_eq!(result, "false"); - let result = forward(&mut context, "set.has()"); - assert_eq!(result, "false"); + run_test([ + TestAction::run("let set = new Set(['one', 'two']);"), + TestAction::assert("set.has('one')"), + TestAction::assert("set.has('two')"), + TestAction::assert("!set.has('three')"), + TestAction::assert("!set.has()"), + ]); } #[test] fn values_and_keys() { - let mut context = Context::default(); - let init = r#" - const set1 = new Set(); - set1.add('foo'); - set1.add('bar'); - const valuesIterator = set1.values(); - let item1 = valuesIterator.next(); - let item2 = valuesIterator.next(); - let item3 = valuesIterator.next(); - "#; - forward(&mut context, init); - let result = forward(&mut context, "item1.value"); - assert_eq!(result, "\"foo\""); - let result = forward(&mut context, "item1.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item2.value"); - assert_eq!(result, "\"bar\""); - let result = forward(&mut context, "item2.done"); - assert_eq!(result, "false"); - let result = forward(&mut context, "item3.value"); - assert_eq!(result, "undefined"); - let result = forward(&mut context, "item3.done"); - assert_eq!(result, "true"); - let result = forward(&mut context, "set1.values == set1.keys"); - assert_eq!(result, "true"); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + const set1 = new Set(); + set1.add('foo'); + set1.add('bar'); + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from(set1.values()), + ["foo", "bar"] + ) + "#}), + TestAction::assert("set1.values == set1.keys"), + ]); } #[test] fn for_each() { - let mut context = Context::default(); - let init = r#" - let set = new Set([5, 10, 15]); - let value1Sum = 0; - let value2Sum = 0; - let sizeSum = 0; - function callingCallback(value1, value2, set) { - value1Sum += value1; - value2Sum += value2; - sizeSum += set.size; - } - set.forEach(callingCallback); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "value1Sum"), "30"); - assert_eq!(forward(&mut context, "value2Sum"), "30"); - assert_eq!(forward(&mut context, "sizeSum"), "9"); + run_test([ + TestAction::run(indoc! {r#" + let set = new Set([5, 10, 15]); + let value1Sum = 0; + let value2Sum = 0; + let sizeSum = 0; + function callingCallback(value1, value2, set) { + value1Sum += value1; + value2Sum += value2; + sizeSum += set.size; + } + set.forEach(callingCallback); + "#}), + TestAction::assert_eq("value1Sum", 30), + TestAction::assert_eq("value2Sum", 30), + TestAction::assert_eq("sizeSum", 9), + ]); } #[test] fn recursive_display() { - let mut context = Context::default(); - let init = r#" - let set = new Set(); - let array = new Array([set]); - set.add(set); - "#; - forward(&mut context, init); - let result = forward(&mut context, "set"); - assert_eq!(result, "Set { Set(1) }"); - let result = forward(&mut context, "set.add(array)"); - assert_eq!(result, "Set { Set(2), Array(1) }"); + run_test([ + TestAction::run(indoc! {r#" + let set = new Set(); + let array = new Array([set]); + set.add(set); + "#}), + TestAction::assert_with_op("set", |v, _| v.display().to_string() == "Set { Set(1) }"), + TestAction::assert_with_op("set.add(array)", |v, _| { + v.display().to_string() == "Set { Set(2), Array(1) }" + }), + ]); } #[test] fn not_a_function() { - let mut context = Context::default(); - let init = r" - try { - let set = Set() - } catch(e) { - e.toString() - } - "; - assert_eq!( - forward(&mut context, init), - "\"TypeError: calling a builtin Set constructor without new is forbidden\"" - ); + run_test([TestAction::assert_native_error( + "Set()", + ErrorKind::Type, + "calling a builtin Set constructor without new is forbidden", + )]); } diff --git a/boa_engine/src/builtins/string/tests.rs b/boa_engine/src/builtins/string/tests.rs index 96e303a1170..0136c716c8d 100644 --- a/boa_engine/src/builtins/string/tests.rs +++ b/boa_engine/src/builtins/string/tests.rs @@ -1,1153 +1,858 @@ -use crate::{forward, forward_val, Context}; +use indoc::indoc; + +use crate::{builtins::error::ErrorKind, js_string, run_test, JsValue, TestAction}; #[test] fn length() { //TEST262: https://github.com/tc39/test262/blob/master/test/built-ins/String/length.js - let mut context = Context::default(); - let init = r#" - const a = new String(' '); - const b = new String('\ud834\udf06'); - const c = new String(' \b '); - const d = new String('中文长度') - "#; - eprintln!("{}", forward(&mut context, init)); - let a = forward(&mut context, "a.length"); - assert_eq!(a, "1"); - let b = forward(&mut context, "b.length"); - // unicode surrogate pair length should be 1 - // utf16/usc2 length should be 2 - // utf8 length should be 4 - assert_eq!(b, "2"); - let c = forward(&mut context, "c.length"); - assert_eq!(c, "3"); - let d = forward(&mut context, "d.length"); - assert_eq!(d, "4"); + run_test([ + TestAction::run(indoc! {r#" + const a = new String(' '); + const b = new String('\ud834\udf06'); + const c = new String(' \b '); + const d = new String('中文长度') + "#}), + // unicode surrogate pair length should be 1 + // utf16/usc2 length should be 2 + // utf8 length should be 4 + TestAction::assert_eq("a.length", 1), + TestAction::assert_eq("b.length", 2), + TestAction::assert_eq("c.length", 3), + TestAction::assert_eq("d.length", 4), + ]); } #[test] fn new_string_has_length() { - let mut context = Context::default(); - let init = r#" - let a = new String("1234"); - a - "#; - - forward(&mut context, init); - assert_eq!(forward(&mut context, "a.length"), "4"); + run_test([ + TestAction::run("let a = new String(\"1234\");"), + TestAction::assert_eq("a.length", 4), + ]); } #[test] fn new_string_has_length_not_enumerable() { - let mut context = Context::default(); - let init = r#" - let a = new String("1234"); - "#; - - forward(&mut context, init); - assert_eq!( - forward(&mut context, "a.propertyIsEnumerable('length')"), - "false" - ); + run_test([ + TestAction::run("let a = new String(\"1234\");"), + TestAction::assert("!a.propertyIsEnumerable('length')"), + ]); } #[test] fn new_utf8_string_has_length() { - let mut context = Context::default(); - let init = r#" - let a = new String("中文"); - a - "#; - - forward(&mut context, init); - assert_eq!(forward(&mut context, "a.length"), "2"); + run_test([ + TestAction::run("let a = new String(\"中文\");"), + TestAction::assert_eq("a.length", 2), + ]); } #[test] fn concat() { - let mut context = Context::default(); - let init = r#" - var hello = new String('Hello, '); - var world = new String('world! '); - var nice = new String('Have a nice day.'); - "#; - eprintln!("{}", forward(&mut context, init)); - - let a = forward(&mut context, "hello.concat(world, nice)"); - assert_eq!(a, "\"Hello, world! Have a nice day.\""); - - let b = forward(&mut context, "hello + world + nice"); - assert_eq!(b, "\"Hello, world! Have a nice day.\""); + run_test([ + TestAction::run(indoc! {r#" + var hello = new String('Hello, '); + var world = new String('world! '); + var nice = new String('Have a nice day.'); + "#}), + TestAction::assert_eq( + "hello.concat(world, nice)", + "Hello, world! Have a nice day.", + ), + TestAction::assert_eq("hello + world + nice", "Hello, world! Have a nice day."), + ]); } #[test] fn generic_concat() { - let mut context = Context::default(); - let init = r#" - Number.prototype.concat = String.prototype.concat; - let number = new Number(100); - "#; - eprintln!("{}", forward(&mut context, init)); - - let a = forward(&mut context, "number.concat(' - 50', ' = 50')"); - assert_eq!(a, "\"100 - 50 = 50\""); + run_test([ + TestAction::run(indoc! {r#" + Number.prototype.concat = String.prototype.concat; + let number = new Number(100); + "#}), + TestAction::assert_eq("number.concat(' - 50', ' = 50')", "100 - 50 = 50"), + ]); } #[allow(clippy::unwrap_used)] #[test] /// Test the correct type is returned from call and construct fn construct_and_call() { - let mut context = Context::default(); - let init = r#" - var hello = new String('Hello'); - var world = String('world'); - "#; - - forward(&mut context, init); - - let hello = forward_val(&mut context, "hello").unwrap(); - let world = forward_val(&mut context, "world").unwrap(); - - assert!(hello.is_object()); - assert!(world.is_string()); + run_test([ + TestAction::assert_with_op("new String('Hello')", |v, _| v.is_object()), + TestAction::assert_with_op("String('world')", |v, _| v.is_string()), + ]); } #[test] fn repeat() { - let mut context = Context::default(); - let init = r#" - var empty = new String(''); - var en = new String('english'); - var zh = new String('中文'); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "empty.repeat(0)"), "\"\""); - assert_eq!(forward(&mut context, "empty.repeat(1)"), "\"\""); - - assert_eq!(forward(&mut context, "en.repeat(0)"), "\"\""); - assert_eq!(forward(&mut context, "zh.repeat(0)"), "\"\""); - - assert_eq!(forward(&mut context, "en.repeat(1)"), "\"english\""); - assert_eq!(forward(&mut context, "zh.repeat(2)"), "\"中文中文\""); + run_test([ + TestAction::run(indoc! {r#" + var empty = new String(''); + var en = new String('english'); + var zh = new String('中文'); + "#}), + TestAction::assert_eq("empty.repeat(0)", ""), + TestAction::assert_eq("empty.repeat(1)", ""), + TestAction::assert_eq("en.repeat(0)", ""), + TestAction::assert_eq("zh.repeat(0)", ""), + TestAction::assert_eq("en.repeat(1)", "english"), + TestAction::assert_eq("zh.repeat(2)", "中文中文"), + ]); } #[test] fn repeat_throws_when_count_is_negative() { - let mut context = Context::default(); - - assert_eq!( - forward( - &mut context, - r#" - try { - 'x'.repeat(-1) - } catch (e) { - e.toString() - } - "# - ), - "\"RangeError: repeat count must be a positive finite number \ - that doesn't overflow the maximum string length (2^32 - 1)\"" - ); + run_test([TestAction::assert_native_error( + "'x'.repeat(-1)", + ErrorKind::Range, + "repeat count must be a positive finite number \ + that doesn't overflow the maximum string length (2^32 - 1)", + )]); } #[test] fn repeat_throws_when_count_is_infinity() { - let mut context = Context::default(); - - assert_eq!( - forward( - &mut context, - r#" - try { - 'x'.repeat(Infinity) - } catch (e) { - e.toString() - } - "# - ), - "\"RangeError: repeat count must be a positive finite number \ - that doesn't overflow the maximum string length (2^32 - 1)\"" - ); + run_test([TestAction::assert_native_error( + "'x'.repeat(Infinity)", + ErrorKind::Range, + "repeat count must be a positive finite number \ + that doesn't overflow the maximum string length (2^32 - 1)", + )]); } #[test] fn repeat_throws_when_count_overflows_max_length() { - let mut context = Context::default(); - - assert_eq!( - forward( - &mut context, - r#" - try { - 'x'.repeat(2 ** 64) - } catch (e) { - e.toString() - } - "# - ), - "\"RangeError: repeat count must be a positive finite number \ - that doesn't overflow the maximum string length (2^32 - 1)\"" - ); + run_test([TestAction::assert_native_error( + "'x'.repeat(2 ** 64)", + ErrorKind::Range, + "repeat count must be a positive finite number \ + that doesn't overflow the maximum string length (2^32 - 1)", + )]); } #[test] fn repeat_generic() { - let mut context = Context::default(); - let init = "Number.prototype.repeat = String.prototype.repeat;"; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "(0).repeat(0)"), "\"\""); - assert_eq!(forward(&mut context, "(1).repeat(1)"), "\"1\""); - - assert_eq!(forward(&mut context, "(1).repeat(5)"), "\"11111\""); - assert_eq!(forward(&mut context, "(12).repeat(3)"), "\"121212\""); + run_test([ + TestAction::run("Number.prototype.repeat = String.prototype.repeat;"), + TestAction::assert_eq("(0).repeat(0)", ""), + TestAction::assert_eq("(1).repeat(1)", "1"), + TestAction::assert_eq("(1).repeat(5)", "11111"), + TestAction::assert_eq("(12).repeat(3)", "121212"), + ]); } #[test] fn replace() { - let mut context = Context::default(); - let init = r#" - var a = "abc"; - a = a.replace("a", "2"); - a - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "a"), "\"2bc\""); + run_test([TestAction::assert_eq( + indoc! {r#" + "abc".replace("a", "2") + "#}, + "2bc", + )]); } #[test] fn replace_no_match() { - let mut context = Context::default(); - let init = r#" - var a = "abc"; - a = a.replace(/d/, "$&$&"); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "a"), "\"abc\""); + run_test([TestAction::assert_eq( + indoc! {r#" + "abc".replace(/d/, "$&$&") + "#}, + "abc", + )]); } #[test] fn replace_with_capture_groups() { - let mut context = Context::default(); - let init = r#" - var re = /(\w+)\s(\w+)/; - var a = "John Smith"; - a = a.replace(re, '$2, $1'); - a - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "a"), "\"Smith, John\""); + run_test([TestAction::assert_eq( + indoc! {r#" + "John Smith".replace(/(\w+)\s(\w+)/, '$2, $1') + "#}, + "Smith, John", + )]); } #[test] fn replace_with_tenth_capture_group() { - let mut context = Context::default(); - let init = r#" - var re = /(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)/; - var a = "0123456789"; - let res = a.replace(re, '$10'); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "res"), "\"9\""); + run_test([TestAction::assert_eq( + indoc! {r#" + var re = /(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)(\d)/; + "0123456789".replace(re, '$10') + "#}, + "9", + )]); } #[test] fn replace_substitutions() { - let mut context = Context::default(); - let init = r#" - var re = / two /; - var a = "one two three"; - var dollar = a.replace(re, " $$ "); - var matched = a.replace(re, "$&$&"); - var start = a.replace(re, " $` "); - var end = a.replace(re, " $' "); - var no_sub = a.replace(re, " $_ "); - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "dollar"), "\"one $ three\""); - assert_eq!(forward(&mut context, "matched"), "\"one two two three\""); - assert_eq!(forward(&mut context, "start"), "\"one one three\""); - assert_eq!(forward(&mut context, "end"), "\"one three three\""); - assert_eq!(forward(&mut context, "no_sub"), "\"one $_ three\""); + run_test([ + TestAction::run(indoc! {r#" + var re = / two /; + var a = "one two three"; + var dollar = a.replace(re, " $$ "); + var matched = a.replace(re, "$&$&"); + var start = a.replace(re, " $` "); + var end = a.replace(re, " $' "); + var no_sub = a.replace(re, " $_ "); + "#}), + TestAction::assert_eq("a.replace(re, \" $$ \")", "one $ three"), + TestAction::assert_eq("a.replace(re, \"$&$&\")", "one two two three"), + TestAction::assert_eq("a.replace(re, \" $` \")", "one one three"), + TestAction::assert_eq("a.replace(re, \" $' \")", "one three three"), + TestAction::assert_eq("a.replace(re, \" $_ \")", "one $_ three"), + ]); } #[test] fn replace_with_function() { - let mut context = Context::default(); - let init = r#" - var a = "ecmascript is cool"; - var p1, p2, p3, length; - var replacer = (match, cap1, cap2, cap3, len) => { - p1 = cap1; - p2 = cap2; - p3 = cap3; - length = len; - return "awesome!"; - }; - - a = a.replace(/c(o)(o)(l)/, replacer); - a; - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "a"), "\"ecmascript is awesome!\""); - - assert_eq!(forward(&mut context, "p1"), "\"o\""); - assert_eq!(forward(&mut context, "p2"), "\"o\""); - assert_eq!(forward(&mut context, "p3"), "\"l\""); - assert_eq!(forward(&mut context, "length"), "14"); + run_test([ + TestAction::run(indoc! {r#" + var p1, p2, p3, length; + var replacer = (match, cap1, cap2, cap3, len) => { + p1 = cap1; + p2 = cap2; + p3 = cap3; + length = len; + return "awesome!"; + }; + "#}), + TestAction::assert_eq( + "\"ecmascript is cool\".replace(/c(o)(o)(l)/, replacer)", + "ecmascript is awesome!", + ), + TestAction::assert_eq("p1", "o"), + TestAction::assert_eq("p2", "o"), + TestAction::assert_eq("p3", "l"), + TestAction::assert_eq("length", 14), + ]); } #[test] fn starts_with() { - let mut context = Context::default(); - let init = r#" - var empty = new String(''); - var en = new String('english'); - var zh = new String('中文'); - - var emptyLiteral = ''; - var enLiteral = 'english'; - var zhLiteral = '中文'; - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "empty.startsWith('')"), "true"); - assert_eq!(forward(&mut context, "en.startsWith('e')"), "true"); - assert_eq!(forward(&mut context, "zh.startsWith('中')"), "true"); - - assert_eq!(forward(&mut context, "emptyLiteral.startsWith('')"), "true"); - assert_eq!(forward(&mut context, "enLiteral.startsWith('e')"), "true"); - assert_eq!(forward(&mut context, "zhLiteral.startsWith('中')"), "true"); + run_test([ + TestAction::run(indoc! {r#" + var empty = ''; + var en = 'english'; + var zh = '中文'; + "#}), + TestAction::assert("empty.startsWith('')"), + TestAction::assert("en.startsWith('e')"), + TestAction::assert("zh.startsWith('中')"), + TestAction::assert("(new String(empty)).startsWith('')"), + TestAction::assert("(new String(en)).startsWith('e')"), + TestAction::assert("(new String(zh)).startsWith('中')"), + ]); } #[test] fn starts_with_with_regex_arg() { - let mut context = Context::default(); - - let scenario = r#" - try { - 'Saturday night'.startsWith(/Saturday/); - } catch (e) { - e.toString(); - } - "#; - - assert_eq!( - forward( - &mut context, scenario - ), - "\"TypeError: First argument to String.prototype.startsWith must not be a regular expression\"" - ); + run_test([TestAction::assert_native_error( + "'Saturday night'.startsWith(/Saturday/)", + ErrorKind::Type, + "First argument to String.prototype.startsWith must not be a regular expression", + )]); } #[test] fn ends_with() { - let mut context = Context::default(); - let init = r#" - var empty = new String(''); - var en = new String('english'); - var zh = new String('中文'); - - var emptyLiteral = ''; - var enLiteral = 'english'; - var zhLiteral = '中文'; - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "empty.endsWith('')"), "true"); - assert_eq!(forward(&mut context, "en.endsWith('h')"), "true"); - assert_eq!(forward(&mut context, "zh.endsWith('文')"), "true"); - - assert_eq!(forward(&mut context, "emptyLiteral.endsWith('')"), "true"); - assert_eq!(forward(&mut context, "enLiteral.endsWith('h')"), "true"); - assert_eq!(forward(&mut context, "zhLiteral.endsWith('文')"), "true"); + run_test([ + TestAction::run(indoc! {r#" + var empty = ''; + var en = 'english'; + var zh = '中文'; + "#}), + TestAction::assert("empty.endsWith('')"), + TestAction::assert("en.endsWith('h')"), + TestAction::assert("zh.endsWith('文')"), + TestAction::assert("(new String(empty)).endsWith('')"), + TestAction::assert("(new String(en)).endsWith('h')"), + TestAction::assert("(new String(zh)).endsWith('文')"), + ]); } #[test] fn ends_with_with_regex_arg() { - let mut context = Context::default(); - - let scenario = r#" - try { - 'Saturday night'.endsWith(/night/); - } catch (e) { - e.toString(); - } - "#; - - assert_eq!( - forward( - &mut context, scenario - ), - "\"TypeError: First argument to String.prototype.endsWith must not be a regular expression\"" - ); + run_test([TestAction::assert_native_error( + "'Saturday night'.endsWith(/night/)", + ErrorKind::Type, + "First argument to String.prototype.endsWith must not be a regular expression", + )]); } #[test] fn includes() { - let mut context = Context::default(); - let init = r#" - var empty = new String(''); - var en = new String('english'); - var zh = new String('中文'); - - var emptyLiteral = ''; - var enLiteral = 'english'; - var zhLiteral = '中文'; - "#; - - forward(&mut context, init); - - assert_eq!(forward(&mut context, "empty.includes('')"), "true"); - assert_eq!(forward(&mut context, "en.includes('g')"), "true"); - assert_eq!(forward(&mut context, "zh.includes('文')"), "true"); - - assert_eq!(forward(&mut context, "emptyLiteral.includes('')"), "true"); - assert_eq!(forward(&mut context, "enLiteral.includes('g')"), "true"); - assert_eq!(forward(&mut context, "zhLiteral.includes('文')"), "true"); + run_test([ + TestAction::run(indoc! {r#" + var empty = ''; + var en = 'english'; + var zh = '中文'; + "#}), + TestAction::assert("empty.includes('')"), + TestAction::assert("en.includes('g')"), + TestAction::assert("zh.includes('文')"), + TestAction::assert("(new String(empty)).includes('')"), + TestAction::assert("(new String(en)).includes('g')"), + TestAction::assert("(new String(zh)).includes('文')"), + ]); } #[test] fn includes_with_regex_arg() { - let mut context = Context::default(); - - let scenario = r#" - try { - 'Saturday night'.includes(/day/); - } catch (e) { - e.toString(); - } - "#; - - assert_eq!( - forward( - &mut context, scenario - ), - "\"TypeError: First argument to String.prototype.includes must not be a regular expression\"" - ); -} - -#[test] -fn match_all() { - let mut context = Context::default(); - - forward( - &mut context, - r#" - var groupMatches = 'test1test2'.matchAll(/t(e)(st(\d?))/g); - var m1 = groupMatches.next(); - var m2 = groupMatches.next(); - var m3 = groupMatches.next(); - "#, - ); - - assert_eq!(forward(&mut context, "m1.done"), "false"); - assert_eq!(forward(&mut context, "m2.done"), "false"); - assert_eq!(forward(&mut context, "m3.done"), "true"); - - assert_eq!(forward(&mut context, "m1.value[0]"), "\"test1\""); - assert_eq!(forward(&mut context, "m1.value[1]"), "\"e\""); - assert_eq!(forward(&mut context, "m1.value[2]"), "\"st1\""); - assert_eq!(forward(&mut context, "m1.value[3]"), "\"1\""); - assert_eq!(forward(&mut context, "m1.value[4]"), "undefined"); - assert_eq!(forward(&mut context, "m1.value.index"), "0"); - assert_eq!(forward(&mut context, "m1.value.input"), "\"test1test2\""); - assert_eq!(forward(&mut context, "m1.value.groups"), "undefined"); - - assert_eq!(forward(&mut context, "m2.value[0]"), "\"test2\""); - assert_eq!(forward(&mut context, "m2.value[1]"), "\"e\""); - assert_eq!(forward(&mut context, "m2.value[2]"), "\"st2\""); - assert_eq!(forward(&mut context, "m2.value[3]"), "\"2\""); - assert_eq!(forward(&mut context, "m2.value[4]"), "undefined"); - assert_eq!(forward(&mut context, "m2.value.index"), "5"); - assert_eq!(forward(&mut context, "m2.value.input"), "\"test1test2\""); - assert_eq!(forward(&mut context, "m2.value.groups"), "undefined"); - - assert_eq!(forward(&mut context, "m3.value"), "undefined"); - - forward( - &mut context, - r#" - var regexp = RegExp('foo[a-z]*','g'); - var str = 'table football, foosball'; - var matches = str.matchAll(regexp); - var m1 = matches.next(); - var m2 = matches.next(); - var m3 = matches.next(); - "#, - ); - - assert_eq!(forward(&mut context, "m1.done"), "false"); - assert_eq!(forward(&mut context, "m2.done"), "false"); - assert_eq!(forward(&mut context, "m3.done"), "true"); - - assert_eq!(forward(&mut context, "m1.value[0]"), "\"football\""); - assert_eq!(forward(&mut context, "m1.value[1]"), "undefined"); - assert_eq!(forward(&mut context, "m1.value.index"), "6"); - assert_eq!( - forward(&mut context, "m1.value.input"), - "\"table football, foosball\"" - ); - assert_eq!(forward(&mut context, "m1.value.groups"), "undefined"); - - assert_eq!(forward(&mut context, "m2.value[0]"), "\"foosball\""); - assert_eq!(forward(&mut context, "m2.value[1]"), "undefined"); - assert_eq!(forward(&mut context, "m2.value.index"), "16"); - assert_eq!( - forward(&mut context, "m1.value.input"), - "\"table football, foosball\"" - ); - assert_eq!(forward(&mut context, "m2.value.groups"), "undefined"); - - assert_eq!(forward(&mut context, "m3.value"), "undefined"); + run_test([TestAction::assert_native_error( + "'Saturday night'.includes(/day/)", + ErrorKind::Type, + "First argument to String.prototype.includes must not be a regular expression", + )]); +} + +#[test] +fn match_all_one() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var groupMatches = 'test1test2'.matchAll(/t(e)(st(\d?))/g); + var m1 = groupMatches.next(); + var m2 = groupMatches.next(); + var m3 = groupMatches.next(); + "#}), + TestAction::assert("!m1.done"), + TestAction::assert("!m2.done"), + TestAction::assert("m3.done"), + TestAction::assert(indoc! {r#" + arrayEquals( + m1.value, + ["test1", "e", "st1", "1"] + ) + "#}), + TestAction::assert_eq("m1.value.index", 0), + TestAction::assert_eq("m1.value.input", "test1test2"), + TestAction::assert_eq("m1.value.groups", JsValue::undefined()), + TestAction::assert(indoc! {r#" + arrayEquals( + m2.value, + ["test2", "e", "st2", "2"] + ) + "#}), + TestAction::assert_eq("m2.value.index", 5), + TestAction::assert_eq("m2.value.input", "test1test2"), + TestAction::assert_eq("m2.value.groups", JsValue::undefined()), + TestAction::assert_eq("m3.value", JsValue::undefined()), + ]); +} + +#[test] +fn match_all_two() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var regexp = RegExp('foo[a-z]*','g'); + var str = 'table football, foosball'; + var matches = str.matchAll(regexp); + var m1 = matches.next(); + var m2 = matches.next(); + var m3 = matches.next(); + "#}), + TestAction::assert("!m1.done"), + TestAction::assert("!m2.done"), + TestAction::assert("m3.done"), + TestAction::assert(indoc! {r#" + arrayEquals( + m1.value, + ["football"] + ) + "#}), + TestAction::assert_eq("m1.value.index", 6), + TestAction::assert_eq("m1.value.input", "table football, foosball"), + TestAction::assert_eq("m1.value.groups", JsValue::undefined()), + TestAction::assert(indoc! {r#" + arrayEquals( + m2.value, + ["foosball"] + ) + "#}), + TestAction::assert_eq("m2.value.index", 16), + TestAction::assert_eq("m2.value.input", "table football, foosball"), + TestAction::assert_eq("m2.value.groups", JsValue::undefined()), + TestAction::assert_eq("m3.value", JsValue::undefined()), + ]); } #[test] fn test_match() { - let mut context = Context::default(); - let init = r#" - var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog'); - var result1 = str.match(/quick\s(brown).+?(jumps)/i); - var result2 = str.match(/[A-Z]/g); - var result3 = str.match("T"); - var result4 = str.match(RegExp("B", 'g')); - "#; - - forward(&mut context, init); - - assert_eq!( - forward(&mut context, "result1[0]"), - "\"Quick Brown Fox Jumps\"" - ); - assert_eq!(forward(&mut context, "result1[1]"), "\"Brown\""); - assert_eq!(forward(&mut context, "result1[2]"), "\"Jumps\""); - assert_eq!(forward(&mut context, "result1.index"), "4"); - assert_eq!( - forward(&mut context, "result1.input"), - "\"The Quick Brown Fox Jumps Over The Lazy Dog\"" - ); - - assert_eq!(forward(&mut context, "result2[0]"), "\"T\""); - assert_eq!(forward(&mut context, "result2[1]"), "\"Q\""); - assert_eq!(forward(&mut context, "result2[2]"), "\"B\""); - assert_eq!(forward(&mut context, "result2[3]"), "\"F\""); - assert_eq!(forward(&mut context, "result2[4]"), "\"J\""); - assert_eq!(forward(&mut context, "result2[5]"), "\"O\""); - assert_eq!(forward(&mut context, "result2[6]"), "\"T\""); - assert_eq!(forward(&mut context, "result2[7]"), "\"L\""); - assert_eq!(forward(&mut context, "result2[8]"), "\"D\""); - - assert_eq!(forward(&mut context, "result3[0]"), "\"T\""); - assert_eq!(forward(&mut context, "result3.index"), "0"); - assert_eq!( - forward(&mut context, "result3.input"), - "\"The Quick Brown Fox Jumps Over The Lazy Dog\"" - ); - assert_eq!(forward(&mut context, "result4[0]"), "\"B\""); + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var str = new String('The Quick Brown Fox Jumps Over The Lazy Dog'); + var result1 = str.match(/quick\s(brown).+?(jumps)/i); + var result2 = str.match(/[A-Z]/g); + var result3 = str.match("T"); + var result4 = str.match(RegExp("B", 'g')); + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + result1, + ["Quick Brown Fox Jumps", "Brown", "Jumps"] + ) + "#}), + TestAction::assert_eq("result1.index", 4), + TestAction::assert_eq( + "result1.input", + "The Quick Brown Fox Jumps Over The Lazy Dog", + ), + TestAction::assert(indoc! {r#" + arrayEquals( + result2, + ["T", "Q", "B", "F", "J", "O", "T", "L", "D"] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + result3, + ["T"] + ) + "#}), + TestAction::assert_eq("result3.index", 0), + TestAction::assert_eq( + "result3.input", + "The Quick Brown Fox Jumps Over The Lazy Dog", + ), + TestAction::assert(indoc! {r#" + arrayEquals( + result4, + ["B"] + ) + "#}), + ]); } #[test] fn trim() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, r#"'Hello'.trim()"#), "\"Hello\""); - assert_eq!(forward(&mut context, r#"' \nHello'.trim()"#), "\"Hello\""); - assert_eq!(forward(&mut context, r#"'Hello \n\r'.trim()"#), "\"Hello\""); - assert_eq!(forward(&mut context, r#"' Hello '.trim()"#), "\"Hello\""); + run_test([ + TestAction::assert_eq(r"'Hello'.trim()", "Hello"), + TestAction::assert_eq(r"' \nHello'.trim()", "Hello"), + TestAction::assert_eq(r"'Hello \n\r'.trim()", "Hello"), + TestAction::assert_eq(r"' Hello '.trim()", "Hello"), + ]); } #[test] fn trim_start() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, r#"'Hello'.trimStart()"#), "\"Hello\""); - assert_eq!( - forward(&mut context, r#"' \nHello'.trimStart()"#), - "\"Hello\"" - ); - assert_eq!( - forward(&mut context, r#"'Hello \n'.trimStart()"#), - "\"Hello \n\"" - ); - assert_eq!( - forward(&mut context, r#"' Hello '.trimStart()"#), - "\"Hello \"" - ); + run_test([ + TestAction::assert_eq(r"'Hello'.trimStart()", "Hello"), + TestAction::assert_eq(r"' \nHello'.trimStart()", "Hello"), + TestAction::assert_eq(r"'Hello \n\r'.trimStart()", "Hello \n\r"), + TestAction::assert_eq(r"' Hello '.trimStart()", "Hello "), + ]); } #[test] fn trim_end() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, r#"'Hello'.trimEnd()"#), "\"Hello\""); - assert_eq!( - forward(&mut context, r#"' \nHello'.trimEnd()"#), - "\" \nHello\"" - ); - assert_eq!( - forward(&mut context, r#"'Hello \n'.trimEnd()"#), - "\"Hello\"" - ); - assert_eq!( - forward(&mut context, r#"' Hello '.trimEnd()"#), - "\" Hello\"" - ); + run_test([ + TestAction::assert_eq(r"'Hello'.trimEnd()", "Hello"), + TestAction::assert_eq(r"' \nHello'.trimEnd()", " \nHello"), + TestAction::assert_eq(r"'Hello \n\r'.trimEnd()", "Hello"), + TestAction::assert_eq(r"' Hello '.trimEnd()", " Hello"), + ]); } #[test] fn split() { - let mut context = Context::default(); - assert_eq!( - forward(&mut context, "'Hello'.split()"), - forward(&mut context, "['Hello']") - ); - assert_eq!( - forward(&mut context, "'Hello'.split(null)"), - forward(&mut context, "['Hello']") - ); - assert_eq!( - forward(&mut context, "'Hello'.split(undefined)"), - forward(&mut context, "['Hello']") - ); - assert_eq!( - forward(&mut context, "'Hello'.split('')"), - forward(&mut context, "['H','e','l','l','o']") - ); - - assert_eq!( - forward(&mut context, "'x1x2'.split('x')"), - forward(&mut context, "['','1','2']") - ); - assert_eq!( - forward(&mut context, "'x1x2x'.split('x')"), - forward(&mut context, "['','1','2','']") - ); - - assert_eq!( - forward(&mut context, "'x1x2x'.split('x', 0)"), - forward(&mut context, "[]") - ); - assert_eq!( - forward(&mut context, "'x1x2x'.split('x', 2)"), - forward(&mut context, "['','1']") - ); - assert_eq!( - forward(&mut context, "'x1x2x'.split('x', 10)"), - forward(&mut context, "['','1','2','']") - ); - - assert_eq!( - forward(&mut context, "'x1x2x'.split(1)"), - forward(&mut context, "['x','x2x']") - ); - - assert_eq!( - forward(&mut context, "'Hello'.split(null, 0)"), - forward(&mut context, "[]") - ); - assert_eq!( - forward(&mut context, "'Hello'.split(undefined, 0)"), - forward(&mut context, "[]") - ); - - assert_eq!( - forward(&mut context, "''.split()"), - forward(&mut context, "['']") - ); - assert_eq!( - forward(&mut context, "''.split(undefined)"), - forward(&mut context, "['']") - ); - assert_eq!( - forward(&mut context, "''.split('')"), - forward(&mut context, "[]") - ); - assert_eq!( - forward(&mut context, "''.split('1')"), - forward(&mut context, "['']") - ); - - assert_eq!( - forward( - &mut context, - "\'\u{1d7d8}\u{1d7d9}\u{1d7da}\u{1d7db}\'.split(\'\')" - ), - // TODO: modify interner to store UTF-16 surrogates from string literals - // forward(&mut context, "['�','�','�','�','�','�','�','�']") - "[ \"\\uD835\", \"\\uDFD8\", \"\\uD835\", \"\\uDFD9\", \"\\uD835\", \"\\uDFDA\", \"\\uD835\", \"\\uDFDB\" ]" - ); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + "Hello".split(), + ["Hello"] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'Hello'.split(null), + ['Hello'] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'Hello'.split(undefined), + ['Hello'] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'Hello'.split(''), + ['H','e','l','l','o'] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'x1x2'.split('x'), + ['','1','2'] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'x1x2x'.split('x'), + ['','1','2',''] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'x1x2x'.split('x', 0), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'x1x2x'.split('x', 2), + ['','1'] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'x1x2x'.split('x', 10), + ['','1','2',''] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'x1x2x'.split(1), + ['x','x2x'] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'Hello'.split(null, 0), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + 'Hello'.split(undefined, 0), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ''.split(), + [''] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ''.split(undefined), + [''] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ''.split(''), + [] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + ''.split('1'), + [''] + ) + "#}), + TestAction::assert(indoc! {r#" + arrayEquals( + '\u{1D7D8}\u{1D7D9}\u{1D7DA}\u{1D7DB}'.split(''), + ['\uD835', '\uDFD8', '\uD835', '\uDFD9', '\uD835', '\uDFDA', '\uD835', '\uDFDB'] + ) + "#}), + ]); } #[test] fn split_with_symbol_split_method() { - assert_eq!( - forward( - &mut Context::default(), - r#" - let sep = {}; - sep[Symbol.split] = function(s, limit) { return s + limit.toString(); }; - 'hello'.split(sep, 10) - "# - ), - "\"hello10\"" - ); - - assert_eq!( - forward( - &mut Context::default(), - r#" - let sep = {}; - sep[Symbol.split] = undefined; - 'hello'.split(sep) - "# + run_test([ + TestAction::run_harness(), + TestAction::assert_eq( + indoc! {r#" + let sep = {}; + sep[Symbol.split] = function(s, limit) { return s + limit.toString(); }; + 'hello'.split(sep, 10) + "#}, + "hello10", ), - "[ \"hello\" ]" - ); - - assert_eq!( - forward( - &mut Context::default(), - r#" - try { + TestAction::assert(indoc! {r#" + let sep = {}; + sep[Symbol.split] = undefined; + arrayEquals( + 'hello'.split(sep), + ['hello'] + ) + "#}), + TestAction::assert_native_error( + indoc! {r#" let sep = {}; sep[Symbol.split] = 10; 'hello'.split(sep, 10); - } catch(e) { - e.toString() - } - "# + "#}, + ErrorKind::Type, + "value returned for property of object is not a function", ), - "\"TypeError: value returned for property of object is not a function\"" - ); + ]); } #[test] fn index_of_with_no_arguments() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.indexOf()"), "-1"); - assert_eq!(forward(&mut context, "'undefined'.indexOf()"), "0"); - assert_eq!(forward(&mut context, "'a1undefined'.indexOf()"), "2"); - assert_eq!(forward(&mut context, "'a1undefined1a'.indexOf()"), "2"); - assert_eq!(forward(&mut context, "'µµµundefined'.indexOf()"), "3"); - assert_eq!(forward(&mut context, "'µµµundefinedµµµ'.indexOf()"), "3"); + run_test([ + TestAction::assert_eq("''.indexOf()", -1), + TestAction::assert_eq("'undefined'.indexOf()", 0), + TestAction::assert_eq("'a1undefined'.indexOf()", 2), + TestAction::assert_eq("'a1undefined1a'.indexOf()", 2), + TestAction::assert_eq("'µµµundefined'.indexOf()", 3), + TestAction::assert_eq("'µµµundefinedµµµ'.indexOf()", 3), + ]); } #[test] fn index_of_with_string_search_string_argument() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.indexOf('hello')"), "-1"); - assert_eq!( - forward(&mut context, "'undefined'.indexOf('undefined')"), - "0" - ); - assert_eq!( - forward(&mut context, "'a1undefined'.indexOf('undefined')"), - "2" - ); - assert_eq!( - forward(&mut context, "'a1undefined1a'.indexOf('undefined')"), - "2" - ); - assert_eq!( - forward(&mut context, "'µµµundefined'.indexOf('undefined')"), - "3" - ); - assert_eq!( - forward(&mut context, "'µµµundefinedµµµ'.indexOf('undefined')"), - "3" - ); + run_test([ + TestAction::assert_eq("''.indexOf('undefined')", -1), + TestAction::assert_eq("'undefined'.indexOf('undefined')", 0), + TestAction::assert_eq("'a1undefined'.indexOf('undefined')", 2), + TestAction::assert_eq("'a1undefined1a'.indexOf('undefined')", 2), + TestAction::assert_eq("'µµµundefined'.indexOf('undefined')", 3), + TestAction::assert_eq("'µµµundefinedµµµ'.indexOf('undefined')", 3), + ]); } #[test] fn index_of_with_non_string_search_string_argument() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.indexOf(1)"), "-1"); - assert_eq!(forward(&mut context, "'1'.indexOf(1)"), "0"); - assert_eq!(forward(&mut context, "'true'.indexOf(true)"), "0"); - assert_eq!(forward(&mut context, "'ab100ba'.indexOf(100)"), "2"); - assert_eq!(forward(&mut context, "'µµµfalse'.indexOf(true)"), "-1"); - assert_eq!(forward(&mut context, "'µµµ5µµµ'.indexOf(5)"), "3"); + run_test([ + TestAction::assert_eq("''.indexOf(1)", -1), + TestAction::assert_eq("'1'.indexOf(1)", 0), + TestAction::assert_eq("'true'.indexOf(true)", 0), + TestAction::assert_eq("'ab100ba'.indexOf(100)", 2), + TestAction::assert_eq("'µµµfalse'.indexOf(true)", -1), + TestAction::assert_eq("'µµµ5µµµ'.indexOf(5)", 3), + ]); } #[test] fn index_of_with_from_index_argument() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.indexOf('x', 2)"), "-1"); - assert_eq!(forward(&mut context, "'x'.indexOf('x', 2)"), "-1"); - assert_eq!(forward(&mut context, "'abcx'.indexOf('x', 2)"), "3"); - assert_eq!(forward(&mut context, "'x'.indexOf('x', 2)"), "-1"); - assert_eq!(forward(&mut context, "'µµµxµµµ'.indexOf('x', 2)"), "3"); - - assert_eq!( - forward(&mut context, "'µµµxµµµ'.indexOf('x', 10000000)"), - "-1" - ); + run_test([ + TestAction::assert_eq("''.indexOf('x', 2)", -1), + TestAction::assert_eq("'x'.indexOf('x', 2)", -1), + TestAction::assert_eq("'abcx'.indexOf('x', 2)", 3), + TestAction::assert_eq("'µµµxµµµ'.indexOf('x', 2)", 3), + TestAction::assert_eq("'µµµxµµµ'.indexOf('x', 10000000)", -1), + ]); } #[test] fn generic_index_of() { - let mut context = Context::default(); - forward_val( - &mut context, - "Number.prototype.indexOf = String.prototype.indexOf", - ) - .unwrap(); - - assert_eq!(forward(&mut context, "(10).indexOf(9)"), "-1"); - assert_eq!(forward(&mut context, "(10).indexOf(0)"), "1"); - assert_eq!(forward(&mut context, "(10).indexOf('0')"), "1"); + run_test([ + TestAction::run("Number.prototype.indexOf = String.prototype.indexOf"), + TestAction::assert_eq("'10'.indexOf(9)", -1), + TestAction::assert_eq("'10'.indexOf(0)", 1), + TestAction::assert_eq("'10'.indexOf('0')", 1), + ]); } #[test] fn index_of_empty_search_string() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "''.indexOf('')"), "0"); - assert_eq!(forward(&mut context, "''.indexOf('', 10)"), "0"); - assert_eq!(forward(&mut context, "'ABC'.indexOf('', 1)"), "1"); - assert_eq!(forward(&mut context, "'ABC'.indexOf('', 2)"), "2"); - assert_eq!(forward(&mut context, "'ABC'.indexOf('', 10)"), "3"); + run_test([ + TestAction::assert_eq("''.indexOf('')", 0), + TestAction::assert_eq("''.indexOf('', 10)", 0), + TestAction::assert_eq("'ABC'.indexOf('', 1)", 1), + TestAction::assert_eq("'ABC'.indexOf('', 2)", 2), + TestAction::assert_eq("'ABC'.indexOf('', 10)", 3), + ]); } #[test] fn last_index_of_with_no_arguments() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.lastIndexOf()"), "-1"); - assert_eq!(forward(&mut context, "'undefined'.lastIndexOf()"), "0"); - assert_eq!(forward(&mut context, "'a1undefined'.lastIndexOf()"), "2"); - assert_eq!( - forward(&mut context, "'a1undefined1aundefined'.lastIndexOf()"), - "13" - ); - assert_eq!( - forward(&mut context, "'µµµundefinedundefined'.lastIndexOf()"), - "12" - ); - assert_eq!( - forward(&mut context, "'µµµundefinedµµµundefined'.lastIndexOf()"), - "15" - ); + run_test([ + TestAction::assert_eq("''.lastIndexOf()", -1), + TestAction::assert_eq("'undefined'.lastIndexOf()", 0), + TestAction::assert_eq("'a1undefined'.lastIndexOf()", 2), + TestAction::assert_eq("'a1undefined1aundefined'.lastIndexOf()", 13), + TestAction::assert_eq("'µµµundefinedundefined'.lastIndexOf()", 12), + TestAction::assert_eq("'µµµundefinedµµµundefined'.lastIndexOf()", 15), + ]); } #[test] fn last_index_of_with_string_search_string_argument() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.lastIndexOf('hello')"), "-1"); - assert_eq!( - forward(&mut context, "'undefined'.lastIndexOf('undefined')"), - "0" - ); - assert_eq!( - forward(&mut context, "'a1undefined'.lastIndexOf('undefined')"), - "2" - ); - assert_eq!( - forward( - &mut context, - "'a1undefined1aundefined'.lastIndexOf('undefined')" - ), - "13" - ); - assert_eq!( - forward( - &mut context, - "'µµµundefinedundefined'.lastIndexOf('undefined')" - ), - "12" - ); - assert_eq!( - forward( - &mut context, - "'µµµundefinedµµµundefined'.lastIndexOf('undefined')" - ), - "15" - ); + run_test([ + TestAction::assert_eq("''.lastIndexOf('hello')", -1), + TestAction::assert_eq("'undefined'.lastIndexOf('undefined')", 0), + TestAction::assert_eq("'a1undefined'.lastIndexOf('undefined')", 2), + TestAction::assert_eq("'a1undefined1aundefined'.lastIndexOf('undefined')", 13), + TestAction::assert_eq("'µµµundefinedundefined'.lastIndexOf('undefined')", 12), + TestAction::assert_eq("'µµµundefinedµµµundefined'.lastIndexOf('undefined')", 15), + ]); } #[test] fn last_index_of_with_non_string_search_string_argument() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.lastIndexOf(1)"), "-1"); - assert_eq!(forward(&mut context, "'1'.lastIndexOf(1)"), "0"); - assert_eq!(forward(&mut context, "'11'.lastIndexOf(1)"), "1"); - assert_eq!( - forward(&mut context, "'truefalsetrue'.lastIndexOf(true)"), - "9" - ); - assert_eq!(forward(&mut context, "'ab100ba'.lastIndexOf(100)"), "2"); - assert_eq!(forward(&mut context, "'µµµfalse'.lastIndexOf(true)"), "-1"); - assert_eq!(forward(&mut context, "'µµµ5µµµ65µ'.lastIndexOf(5)"), "8"); + run_test([ + TestAction::assert_eq("''.lastIndexOf(1)", -1), + TestAction::assert_eq("'1'.lastIndexOf(1)", 0), + TestAction::assert_eq("'11'.lastIndexOf(1)", 1), + TestAction::assert_eq("'truefalsetrue'.lastIndexOf(true)", 9), + TestAction::assert_eq("'ab100ba'.lastIndexOf(100)", 2), + TestAction::assert_eq("'µµµfalse'.lastIndexOf(true)", -1), + TestAction::assert_eq("'µµµ5µµµ65µ'.lastIndexOf(5)", 8), + ]); } #[test] fn last_index_of_with_from_index_argument() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.lastIndexOf('x', 2)"), "-1"); - assert_eq!(forward(&mut context, "'x'.lastIndexOf('x', 2)"), "0"); - assert_eq!(forward(&mut context, "'abcxx'.lastIndexOf('x', 2)"), "-1"); - assert_eq!(forward(&mut context, "'x'.lastIndexOf('x', 2)"), "0"); - assert_eq!(forward(&mut context, "'µµµxµµµ'.lastIndexOf('x', 2)"), "-1"); - - assert_eq!( - forward(&mut context, "'µµµxµµµ'.lastIndexOf('x', 10000000)"), - "3" - ); + run_test([ + TestAction::assert_eq("''.lastIndexOf('x', 2)", -1), + TestAction::assert_eq("'x'.lastIndexOf('x', 2)", 0), + TestAction::assert_eq("'abcxx'.lastIndexOf('x', 2)", -1), + TestAction::assert_eq("'µµµxµµµ'.lastIndexOf('x', 2)", -1), + TestAction::assert_eq("'µµµxµµµ'.lastIndexOf('x', 10000000)", 3), + ]); } #[test] fn last_index_with_empty_search_string() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "''.lastIndexOf('')"), "0"); - assert_eq!(forward(&mut context, "'x'.lastIndexOf('', 2)"), "1"); - assert_eq!(forward(&mut context, "'abcxx'.lastIndexOf('', 4)"), "4"); - assert_eq!(forward(&mut context, "'µµµxµµµ'.lastIndexOf('', 2)"), "2"); - - assert_eq!( - forward(&mut context, "'abc'.lastIndexOf('', 10000000)"), - "3" - ); + run_test([ + TestAction::assert_eq("''.lastIndexOf('')", 0), + TestAction::assert_eq("'x'.lastIndexOf('', 2)", 1), + TestAction::assert_eq("'abcxx'.lastIndexOf('', 4)", 4), + TestAction::assert_eq("'µµµxµµµ'.lastIndexOf('', 2)", 2), + TestAction::assert_eq("'µµµxµµµ'.lastIndexOf('', 10000000)", 7), + ]); } #[test] fn generic_last_index_of() { - let mut context = Context::default(); - forward_val( - &mut context, - "Number.prototype.lastIndexOf = String.prototype.lastIndexOf", - ) - .unwrap(); - - assert_eq!(forward(&mut context, "(1001).lastIndexOf(9)"), "-1"); - assert_eq!(forward(&mut context, "(1001).lastIndexOf(0)"), "2"); - assert_eq!(forward(&mut context, "(1001).lastIndexOf('0')"), "2"); + run_test([ + TestAction::run("Number.prototype.lastIndexOf = String.prototype.lastIndexOf"), + TestAction::assert_eq("(1001).lastIndexOf(9)", -1), + TestAction::assert_eq("(1001).lastIndexOf(0)", 2), + TestAction::assert_eq("(1001).lastIndexOf('0')", 2), + ]); } #[test] fn last_index_non_integer_position_argument() { - let mut context = Context::default(); - assert_eq!( - forward(&mut context, "''.lastIndexOf('x', new Number(4))"), - "-1" - ); - assert_eq!( - forward(&mut context, "'abc'.lastIndexOf('b', new Number(1))"), - "1" - ); - assert_eq!( - forward(&mut context, "'abcx'.lastIndexOf('x', new String('1'))"), - "-1" - ); - assert_eq!( - forward(&mut context, "'abcx'.lastIndexOf('x', new String('100'))"), - "3" - ); - assert_eq!(forward(&mut context, "'abcx'.lastIndexOf('x', null)"), "-1"); + run_test([ + TestAction::assert_eq("''.lastIndexOf('x', new Number(4))", -1), + TestAction::assert_eq("'abc'.lastIndexOf('b', new Number(1))", 1), + TestAction::assert_eq("'abcx'.lastIndexOf('x', new String('1'))", -1), + TestAction::assert_eq("'abcx'.lastIndexOf('x', new String('100'))", 3), + TestAction::assert_eq("'abcx'.lastIndexOf('x', null)", -1), + ]); } #[test] fn char_at() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "'abc'.charAt(-1)"), "\"\""); - assert_eq!(forward(&mut context, "'abc'.charAt(1)"), "\"b\""); - assert_eq!(forward(&mut context, "'abc'.charAt(9)"), "\"\""); - assert_eq!(forward(&mut context, "'abc'.charAt()"), "\"a\""); - assert_eq!(forward(&mut context, "'abc'.charAt(null)"), "\"a\""); - assert_eq!(forward(&mut context, "'\\uDBFF'.charAt(0)"), r#""\uDBFF""#); + run_test([ + TestAction::assert_eq("'abc'.charAt(-1)", ""), + TestAction::assert_eq("'abc'.charAt(1)", "b"), + TestAction::assert_eq("'abc'.charAt(9)", ""), + TestAction::assert_eq("'abc'.charAt()", "a"), + TestAction::assert_eq("'abc'.charAt(null)", "a"), + TestAction::assert_eq(r"'\uDBFF'.charAt(0)", js_string!(&[0xDBFFu16])), + ]); } #[test] fn char_code_at() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "'abc'.charCodeAt(-1)"), "NaN"); - assert_eq!(forward(&mut context, "'abc'.charCodeAt(1)"), "98"); - assert_eq!(forward(&mut context, "'abc'.charCodeAt(9)"), "NaN"); - assert_eq!(forward(&mut context, "'abc'.charCodeAt()"), "97"); - assert_eq!(forward(&mut context, "'abc'.charCodeAt(null)"), "97"); - assert_eq!(forward(&mut context, "'\\uFFFF'.charCodeAt(0)"), "65535"); + run_test([ + TestAction::assert_eq("'abc'.charCodeAt-1", f64::NAN), + TestAction::assert_eq("'abc'.charCodeAt(1)", 98), + TestAction::assert_eq("'abc'.charCodeAt(9)", f64::NAN), + TestAction::assert_eq("'abc'.charCodeAt()", 97), + TestAction::assert_eq("'abc'.charCodeAt(null)", 97), + TestAction::assert_eq("'\\uFFFF'.charCodeAt(0)", 65535), + ]); } #[test] fn code_point_at() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "'abc'.codePointAt(-1)"), "undefined"); - assert_eq!(forward(&mut context, "'abc'.codePointAt(1)"), "98"); - assert_eq!(forward(&mut context, "'abc'.codePointAt(9)"), "undefined"); - assert_eq!(forward(&mut context, "'abc'.codePointAt()"), "97"); - assert_eq!(forward(&mut context, "'abc'.codePointAt(null)"), "97"); - assert_eq!( - forward(&mut context, "'\\uD800\\uDC00'.codePointAt(0)"), - "65536" - ); - assert_eq!( - forward(&mut context, "'\\uD800\\uDFFF'.codePointAt(0)"), - "66559" - ); - assert_eq!( - forward(&mut context, "'\\uDBFF\\uDC00'.codePointAt(0)"), - "1113088" - ); - assert_eq!( - forward(&mut context, "'\\uDBFF\\uDFFF'.codePointAt(0)"), - "1114111" - ); - assert_eq!( - forward(&mut context, "'\\uD800\\uDC00'.codePointAt(1)"), - "56320" - ); - assert_eq!( - forward(&mut context, "'\\uD800\\uDFFF'.codePointAt(1)"), - "57343" - ); - assert_eq!( - forward(&mut context, "'\\uDBFF\\uDC00'.codePointAt(1)"), - "56320" - ); - assert_eq!( - forward(&mut context, "'\\uDBFF\\uDFFF'.codePointAt(1)"), - "57343" - ); + run_test([ + TestAction::assert_eq("'abc'.codePointAt(-1)", JsValue::undefined()), + TestAction::assert_eq("'abc'.codePointAt(1)", 98), + TestAction::assert_eq("'abc'.codePointAt(9)", JsValue::undefined()), + TestAction::assert_eq("'abc'.codePointAt()", 97), + TestAction::assert_eq("'abc'.codePointAt(null)", 97), + TestAction::assert_eq(r"'\uD800\uDC00'.codePointAt(0)", 65_536), + TestAction::assert_eq(r"'\uD800\uDFFF'.codePointAt(0)", 66_559), + TestAction::assert_eq(r"'\uDBFF\uDC00'.codePointAt(0)", 1_113_088), + TestAction::assert_eq(r"'\uDBFF\uDFFF'.codePointAt(0)", 1_114_111), + TestAction::assert_eq(r"'\uD800\uDC00'.codePointAt(1)", 56_320), + TestAction::assert_eq(r"'\uD800\uDFFF'.codePointAt(1)", 57_343), + TestAction::assert_eq(r"'\uDBFF\uDC00'.codePointAt(1)", 56_320), + TestAction::assert_eq(r"'\uDBFF\uDFFF'.codePointAt(1)", 57_343), + ]); } #[test] fn slice() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "'abc'.slice()"), "\"abc\""); - assert_eq!(forward(&mut context, "'abc'.slice(1)"), "\"bc\""); - assert_eq!(forward(&mut context, "'abc'.slice(-1)"), "\"c\""); - assert_eq!(forward(&mut context, "'abc'.slice(0, 9)"), "\"abc\""); - assert_eq!(forward(&mut context, "'abc'.slice(9, 10)"), "\"\""); + run_test([ + TestAction::assert_eq("'abc'.slice()", "abc"), + TestAction::assert_eq("'abc'.slice(1)", "bc"), + TestAction::assert_eq("'abc'.slice(-1)", "c"), + TestAction::assert_eq("'abc'.slice(0, 9)", "abc"), + TestAction::assert_eq("'abc'.slice(9, 10)", ""), + ]); } #[test] fn empty_iter() { - let mut context = Context::default(); - let init = r#" - let iter = new String()[Symbol.iterator](); - let next = iter.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run(indoc! {r#" + let iter = new String()[Symbol.iterator](); + let next = iter.next(); + "#}), + TestAction::assert_eq("next.value", JsValue::undefined()), + TestAction::assert("next.done"), + ]); } #[test] fn ascii_iter() { - let mut context = Context::default(); - let init = r#" - let iter = new String("Hello World")[Symbol.iterator](); - let next = iter.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "\"H\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"e\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"l\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"l\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"o\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\" \""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"W\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"o\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"r\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"l\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"d\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from(new String("Hello World")[Symbol.iterator]()), + ["H", "e", "l", "l", "o", " ", "W", "o", "r", "l", "d"] + ) + "#}), + ]); } #[test] fn unicode_iter() { - let mut context = Context::default(); - let init = r#" - let iter = new String("C🙂🙂l W🙂rld")[Symbol.iterator](); - let next = iter.next(); - "#; - forward(&mut context, init); - assert_eq!(forward(&mut context, "next.value"), "\"C\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"🙂\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"🙂\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"l\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\" \""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"W\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"🙂\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"r\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"l\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "\"d\""); - assert_eq!(forward(&mut context, "next.done"), "false"); - forward(&mut context, "next = iter.next()"); - assert_eq!(forward(&mut context, "next.value"), "undefined"); - assert_eq!(forward(&mut context, "next.done"), "true"); + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + arrayEquals( + Array.from(new String("C🙂🙂l W🙂rld")[Symbol.iterator]()), + ["C", "🙂", "🙂", "l", " ", "W", "🙂", "r", "l", "d"] + ) + "#}), + ]); } #[test] fn string_get_property() { - let mut context = Context::default(); - assert_eq!(forward(&mut context, "'abc'[-1]"), "undefined"); - assert_eq!(forward(&mut context, "'abc'[1]"), "\"b\""); - assert_eq!(forward(&mut context, "'abc'[2]"), "\"c\""); - assert_eq!(forward(&mut context, "'abc'[3]"), "undefined"); - assert_eq!(forward(&mut context, "'abc'['foo']"), "undefined"); - assert_eq!(forward(&mut context, "'😀'[0]"), "\"\\uD83D\""); + run_test([ + TestAction::assert_eq("'abc'[-1]", JsValue::undefined()), + TestAction::assert_eq("'abc'[1]", "b"), + TestAction::assert_eq("'abc'[2]", "c"), + TestAction::assert_eq("'abc'[3]", JsValue::undefined()), + TestAction::assert_eq("'abc'['foo']", JsValue::undefined()), + TestAction::assert_eq("'😀'[0]", js_string!(&[0xD83D])), + ]); } #[test] fn search() { - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "'aa'.search(/b/)"), "-1"); - assert_eq!(forward(&mut context, "'aa'.search(/a/)"), "0"); - assert_eq!(forward(&mut context, "'aa'.search(/a/g)"), "0"); - assert_eq!(forward(&mut context, "'ba'.search(/a/)"), "1"); + run_test([ + TestAction::assert_eq("'aa'.search(/b/)", -1), + TestAction::assert_eq("'aa'.search(/a/)", 0), + TestAction::assert_eq("'aa'.search(/a/g)", 0), + TestAction::assert_eq("'ba'.search(/a/)", 1), + ]); } diff --git a/boa_engine/src/builtins/symbol/tests.rs b/boa_engine/src/builtins/symbol/tests.rs index 6c68304200c..3481435bbfd 100644 --- a/boa_engine/src/builtins/symbol/tests.rs +++ b/boa_engine/src/builtins/symbol/tests.rs @@ -1,40 +1,33 @@ -use crate::{check_output, forward, forward_val, Context, TestAction}; +use crate::{run_test, JsValue, TestAction}; +use indoc::indoc; #[test] fn call_symbol_and_check_return_type() { - let mut context = Context::default(); - let init = r#" - var sym = Symbol(); - "#; - eprintln!("{}", forward(&mut context, init)); - let sym = forward_val(&mut context, "sym").unwrap(); - assert!(sym.is_symbol()); + run_test([TestAction::assert_with_op("Symbol()", |val, _| { + val.is_symbol() + })]); } #[test] fn print_symbol_expect_description() { - let mut context = Context::default(); - let init = r#" - var sym = Symbol("Hello"); - "#; - eprintln!("{}", forward(&mut context, init)); - let sym = forward_val(&mut context, "sym.toString()").unwrap(); - assert_eq!(sym.display().to_string(), "\"Symbol(Hello)\""); + run_test([TestAction::assert_eq( + "String(Symbol('Hello'))", + "Symbol(Hello)", + )]); } #[test] fn symbol_access() { - let init = r#" - var x = {}; - var sym1 = Symbol("Hello"); - var sym2 = Symbol("Hello"); - x[sym1] = 10; - x[sym2] = 20; - "#; - check_output(&[ - TestAction::Execute(init), - TestAction::TestEq("x[sym1]", "10"), - TestAction::TestEq("x[sym2]", "20"), - TestAction::TestEq("x['Symbol(Hello)']", "undefined"), + run_test([ + TestAction::run(indoc! {r#" + var x = {}; + var sym1 = Symbol("Hello"); + var sym2 = Symbol("Hello"); + x[sym1] = 10; + x[sym2] = 20; + "#}), + TestAction::assert_eq("x[sym1]", 10), + TestAction::assert_eq("x[sym2]", 20), + TestAction::assert_eq("x['Symbol(Hello)']", JsValue::undefined()), ]); } diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 40b3b0da9b8..e8695c17956 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -131,7 +131,6 @@ impl Default for Context<'_> { } // ==== Public API ==== - impl Context<'_> { /// Create a new [`ContextBuilder`] to specify the [`Interner`] and/or /// the icu data provider. diff --git a/boa_engine/src/environments/tests.rs b/boa_engine/src/environments/tests.rs index 920e788bef2..d74b6226cd6 100644 --- a/boa_engine/src/environments/tests.rs +++ b/boa_engine/src/environments/tests.rs @@ -1,92 +1,79 @@ -use crate::exec; +use crate::{builtins::error::ErrorKind, run_test, TestAction}; +use indoc::indoc; #[test] fn let_is_block_scoped() { - let scenario = r#" - { - let bar = "bar"; - } - - try{ + run_test([TestAction::assert_native_error( + indoc! {r#" + { + let bar = "bar"; + } bar; - } catch (err) { - err.message - } - "#; - - assert_eq!(&exec(scenario), "\"bar is not defined\""); + "#}, + ErrorKind::Reference, + "bar is not defined", + )]); } #[test] fn const_is_block_scoped() { - let scenario = r#" - { + run_test([TestAction::assert_native_error( + indoc! {r#" + { const bar = "bar"; - } - - try{ + } bar; - } catch (err) { - err.message - } - "#; - - assert_eq!(&exec(scenario), "\"bar is not defined\""); + "#}, + ErrorKind::Reference, + "bar is not defined", + )]); } #[test] fn var_not_block_scoped() { - let scenario = r#" - { - var bar = "bar"; - } - bar == "bar"; - "#; - - assert_eq!(&exec(scenario), "true"); + run_test([TestAction::assert(indoc! {r#" + { + var bar = "bar"; + } + bar == "bar"; + "#})]); } #[test] fn functions_use_declaration_scope() { - let scenario = r#" - function foo() { - try { + run_test([TestAction::assert_native_error( + indoc! {r#" + function foo() { bar; - } catch (err) { - return err.message; } - } - { - let bar = "bar"; - foo(); - } - "#; - - assert_eq!(&exec(scenario), "\"bar is not defined\""); + { + let bar = "bar"; + foo(); + } + "#}, + ErrorKind::Reference, + "bar is not defined", + )]); } #[test] fn set_outer_var_in_block_scope() { - let scenario = r#" - var bar; - { - bar = "foo"; - } - bar == "foo"; - "#; - - assert_eq!(&exec(scenario), "true"); + run_test([TestAction::assert(indoc! {r#" + var bar; + { + bar = "foo"; + } + bar == "foo"; + "#})]); } #[test] fn set_outer_let_in_block_scope() { - let scenario = r#" - let bar; - { - bar = "foo"; - } - bar == "foo"; - "#; - - assert_eq!(&exec(scenario), "true"); + run_test([TestAction::assert(indoc! {r#" + let bar; + { + bar = "foo"; + } + bar == "foo"; + "#})]); } diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 20de717f15b..c5f02ea446a 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -801,6 +801,22 @@ pub enum JsNativeErrorKind { NoInstructionsRemain, } +impl PartialEq for JsNativeErrorKind { + fn eq(&self, other: &ErrorKind) -> bool { + matches!( + (self, other), + (Self::Aggregate(_), ErrorKind::Aggregate) + | (Self::Error, ErrorKind::Error) + | (Self::Eval, ErrorKind::Eval) + | (Self::Range, ErrorKind::Range) + | (Self::Reference, ErrorKind::Reference) + | (Self::Syntax, ErrorKind::Syntax) + | (Self::Type, ErrorKind::Type) + | (Self::Uri, ErrorKind::Uri) + ) + } +} + impl std::fmt::Display for JsNativeErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 7ac37de1910..5fcd9846696 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -182,85 +182,268 @@ impl JsArgs for [JsValue] { } } -/// Execute the code using an existing `Context`. -/// -/// The state of the `Context` is changed, and a string representation of the result is returned. #[cfg(test)] -pub(crate) fn forward(context: &mut Context<'_>, src: &S) -> String -where - S: AsRef<[u8]> + ?Sized, -{ - context - .eval_script(Source::from_bytes(src)) - .map_or_else(|e| format!("Uncaught {e}"), |v| v.display().to_string()) +use std::borrow::Cow; + +/// A test action executed in a test function. +#[cfg(test)] +#[derive(Clone)] +pub(crate) struct TestAction(Inner); + +#[cfg(test)] +#[derive(Clone)] +enum Inner { + RunHarness, + Run { + source: Cow<'static, str>, + }, + InspectContext { + op: fn(&mut Context<'_>), + }, + Assert { + source: Cow<'static, str>, + }, + AssertEq { + source: Cow<'static, str>, + expected: JsValue, + }, + AssertWithOp { + source: Cow<'static, str>, + op: fn(JsValue, &mut Context<'_>) -> bool, + }, + AssertOpaqueError { + source: Cow<'static, str>, + expected: JsValue, + }, + AssertNativeError { + source: Cow<'static, str>, + kind: builtins::error::ErrorKind, + message: &'static str, + }, + AssertContext { + op: fn(&mut Context<'_>) -> bool, + }, } -/// Execute the code using an existing Context. -/// The str is consumed and the state of the Context is changed -/// Similar to `forward`, except the current value is returned instead of the string -/// If the interpreter fails parsing an error value is returned instead (error object) -#[allow(clippy::unit_arg, clippy::drop_copy)] #[cfg(test)] -pub(crate) fn forward_val + ?Sized>( - context: &mut Context<'_>, - src: &T, -) -> JsResult { - use boa_profiler::Profiler; +impl TestAction { + /// Evaluates some utility functions used in tests. + pub(crate) const fn run_harness() -> Self { + Self(Inner::RunHarness) + } - let main_timer = Profiler::global().start_event("Main", "Main"); + /// Runs `source`, panickinf if the execution throws. + pub(crate) fn run(source: impl Into>) -> Self { + Self(Inner::Run { + source: source.into(), + }) + } - let result = context.eval_script(Source::from_bytes(src)); + /// Executes `op` in the currently executing context. + /// + /// Useful to make custom assertions that must be done from Rust code. + pub(crate) fn inspect_context(op: fn(&mut Context<'_>)) -> Self { + Self(Inner::InspectContext { op }) + } - // The main_timer needs to be dropped before the Profiler is. - drop(main_timer); - Profiler::global().drop(); + /// Asserts that evaluating `source` returns the `true` value. + pub(crate) fn assert(source: impl Into>) -> Self { + Self(Inner::Assert { + source: source.into(), + }) + } - result -} + /// Asserts that the script returns `expected` when evaluating `source`. + pub(crate) fn assert_eq( + source: impl Into>, + expected: impl Into, + ) -> Self { + Self(Inner::AssertEq { + source: source.into(), + expected: expected.into(), + }) + } -/// Create a clean Context and execute the code -#[cfg(test)] -pub(crate) fn exec + ?Sized>(src: &T) -> String { - match Context::default().eval_script(Source::from_bytes(src)) { - Ok(value) => value.display().to_string(), - Err(error) => error.to_string(), + /// Asserts that calling `op` with the value obtained from evaluating `source` returns `true`. + /// + /// Useful to check properties of the obtained value that cannot be checked from JS code. + pub(crate) fn assert_with_op( + source: impl Into>, + op: fn(JsValue, &mut Context<'_>) -> bool, + ) -> Self { + Self(Inner::AssertWithOp { + source: source.into(), + op, + }) + } + + /// Asserts that evaluating `source` throws the opaque error `value`. + pub(crate) fn assert_opaque_error( + source: impl Into>, + value: impl Into, + ) -> Self { + Self(Inner::AssertOpaqueError { + source: source.into(), + expected: value.into(), + }) + } + + /// Asserts that evaluating `source` throws a native error of `kind` and `message`. + pub(crate) fn assert_native_error( + source: impl Into>, + kind: builtins::error::ErrorKind, + message: &'static str, + ) -> Self { + Self(Inner::AssertNativeError { + source: source.into(), + kind, + message, + }) + } + + /// Asserts that calling `op` with the currently executing context returns `true`. + pub(crate) fn assert_context(op: fn(&mut Context<'_>) -> bool) -> Self { + Self(Inner::AssertContext { op }) } } +/// Executes a list of test actions on a new, default context. #[cfg(test)] -pub(crate) enum TestAction { - Execute(&'static str), - TestEq(&'static str, &'static str), - TestStartsWith(&'static str, &'static str), +#[track_caller] +pub(crate) fn run_test(actions: impl IntoIterator) { + let context = &mut Context::default(); + run_test_with(actions, context); } -/// Create a clean Context, call "forward" for each action, and optionally -/// assert equality of the returned value or if returned value starts with -/// expected string. +/// Executes a list of test actions on the provided context. #[cfg(test)] #[track_caller] -pub(crate) fn check_output(actions: &[TestAction]) { - let mut context = Context::default(); +pub(crate) fn run_test_with( + actions: impl IntoIterator, + context: &mut Context<'_>, +) { + #[track_caller] + fn forward_val(context: &mut Context<'_>, source: &str) -> JsResult { + context.eval_script(Source::from_bytes(source)) + } + #[track_caller] + fn fmt_test(source: &str, test: usize) -> String { + format!( + "\n\nTest case {test}: \n```\n{}\n```", + textwrap::indent(source, " ") + ) + } + + // Some unwrapping patterns look weird because they're replaceable + // by simpler patterns like `unwrap_or_else` or `unwrap_err let mut i = 1; - for action in actions { + for action in actions.into_iter().map(|a| a.0) { match action { - TestAction::Execute(src) => { - forward(&mut context, src); + Inner::RunHarness => { + // add utility functions for testing + // TODO: extract to a file + forward_val( + context, + r#" + function equals(a, b) { + if (Array.isArray(a) && Array.isArray(b)) { + return arrayEquals(a, b); + } + return a === b; + } + function arrayEquals(a, b) { + return Array.isArray(a) && + Array.isArray(b) && + a.length === b.length && + a.every((val, index) => equals(val, b[index])); + } + "#, + ) + .expect("failed to evaluate test harness"); + } + Inner::Run { source } => { + if let Err(e) = forward_val(context, &source) { + panic!("{}\nUncaught {e}", fmt_test(&source, i)); + } + } + Inner::InspectContext { op } => { + op(context); + } + Inner::Assert { source } => { + let val = match forward_val(context, &source) { + Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)), + Ok(v) => v, + }; + let Some(val) = val.as_boolean() else { + panic!( + "{}\nTried to assert with the non-boolean value `{}`", + fmt_test(&source, i), + val.display() + ) + }; + assert!(val, "{}", fmt_test(&source, i)); + i += 1; + } + Inner::AssertEq { source, expected } => { + let val = match forward_val(context, &source) { + Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)), + Ok(v) => v, + }; + assert_eq!(val, expected, "{}", fmt_test(&source, i)); + i += 1; + } + Inner::AssertWithOp { source, op } => { + let val = match forward_val(context, &source) { + Err(e) => panic!("{}\nUncaught {e}", fmt_test(&source, i)), + Ok(v) => v, + }; + assert!(op(val, context), "{}", fmt_test(&source, i)); + i += 1; } - TestAction::TestEq(case, expected) => { - assert_eq!( - &forward(&mut context, case), - expected, - "Test case {i} ('{case}')" - ); + Inner::AssertOpaqueError { source, expected } => { + let err = match forward_val(context, &source) { + Ok(v) => panic!( + "{}\nExpected error, got value `{}`", + fmt_test(&source, i), + v.display() + ), + Err(e) => e, + }; + let Some(err) = err.as_opaque() else { + panic!("{}\nExpected opaque error, got native error `{}`", fmt_test(&source, i), err) + }; + + assert_eq!(err, &expected, "{}", fmt_test(&source, i)); + i += 1; + } + Inner::AssertNativeError { + source, + kind, + message, + } => { + let err = match forward_val(context, &source) { + Ok(v) => panic!( + "{}\nExpected error, got value `{}`", + fmt_test(&source, i), + v.display() + ), + Err(e) => e, + }; + let native = match err.try_native(context) { + Ok(err) => err, + Err(e) => panic!( + "{}\nCouldn't obtain a native error: {e}", + fmt_test(&source, i) + ), + }; + + assert_eq!(&native.kind, &kind, "{}", fmt_test(&source, i)); + assert_eq!(native.message(), message, "{}", fmt_test(&source, i)); i += 1; } - TestAction::TestStartsWith(case, expected) => { - assert!( - &forward(&mut context, case).starts_with(expected), - "Test case {i} ('{case}')", - ); + Inner::AssertContext { op } => { + assert!(op(context), "Test case {i}"); i += 1; } } diff --git a/boa_engine/src/object/tests.rs b/boa_engine/src/object/tests.rs index 9bf24ecf592..01a39dca4cb 100644 --- a/boa_engine/src/object/tests.rs +++ b/boa_engine/src/object/tests.rs @@ -1,46 +1,40 @@ -use crate::{check_output, exec, TestAction}; +use crate::{builtins::error::ErrorKind, run_test, TestAction}; +use indoc::indoc; #[test] fn ordinary_has_instance_nonobject_prototype() { - let scenario = r#" - try { - function C() {} - C.prototype = 1 - String instanceof C - } catch (err) { - err.toString() - } - "#; - - assert_eq!( - &exec(scenario), - "\"TypeError: function has non-object prototype in instanceof check\"" - ); + run_test([TestAction::assert_native_error( + indoc! {r#" + function C() {} + C.prototype = 1 + String instanceof C + "#}, + ErrorKind::Type, + "function has non-object prototype in instanceof check", + )]); } #[test] fn object_properties_return_order() { - let scenario = r#" - var o = { - p1: 'v1', - p2: 'v2', - p3: 'v3', - }; - o.p4 = 'v4'; - o[2] = 'iv2'; - o[0] = 'iv0'; - o[1] = 'iv1'; - delete o.p1; - delete o.p3; - o.p1 = 'v1'; - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("Object.keys(o)", r#"[ "0", "1", "2", "p2", "p4", "p1" ]"#), - TestAction::TestEq( - "Object.values(o)", - r#"[ "iv0", "iv1", "iv2", "v2", "v4", "v1" ]"#, + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var o = { + p1: 'v1', + p2: 'v2', + p3: 'v3', + }; + o.p4 = 'v4'; + o[2] = 'iv2'; + o[0] = 'iv0'; + o[1] = 'iv1'; + delete o.p1; + delete o.p3; + o.p1 = 'v1'; + "#}), + TestAction::assert(r#"arrayEquals(Object.keys(o), [ "0", "1", "2", "p2", "p4", "p1" ])"#), + TestAction::assert( + r#"arrayEquals(Object.values(o), [ "iv0", "iv1", "iv2", "v2", "v4", "v1" ])"#, ), ]); } diff --git a/boa_engine/src/tests.rs b/boa_engine/src/tests.rs deleted file mode 100644 index 57498fae5ba..00000000000 --- a/boa_engine/src/tests.rs +++ /dev/null @@ -1,3172 +0,0 @@ -use boa_parser::Source; - -use crate::{ - builtins::Number, check_output, exec, forward, forward_val, string::utf16, - value::IntegerOrInfinity, Context, JsValue, TestAction, -}; - -#[test] -fn function_declaration_returns_undefined() { - let scenario = r#" - function abc() {} - "#; - - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -fn empty_function_returns_undefined() { - let scenario = "(function () {}) ()"; - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -fn property_accessor_member_expression_dot_notation_on_string_literal() { - let scenario = r#" - typeof 'asd'.matchAll; - "#; - - assert_eq!(&exec(scenario), "\"function\""); -} - -#[test] -fn property_accessor_member_expression_bracket_notation_on_string_literal() { - let scenario = r#" - typeof 'asd'['matchAll']; - "#; - - assert_eq!(&exec(scenario), "\"function\""); -} - -#[test] -fn length_correct_value_on_string_literal() { - let scenario = r#" - 'hello'.length; - "#; - - assert_eq!(&exec(scenario), "5"); -} - -#[test] -fn property_accessor_member_expression_dot_notation_on_function() { - let scenario = r#" - function asd () {}; - asd.name; - "#; - - assert_eq!(&exec(scenario), "\"asd\""); -} - -#[test] -fn property_accessor_member_expression_bracket_notation_on_function() { - let scenario = r#" - function asd () {}; - asd['name']; - "#; - - assert_eq!(&exec(scenario), "\"asd\""); -} - -#[test] -fn empty_let_decl_undefined() { - let scenario = r#" - let a; - a === undefined; - "#; - - assert_eq!(&exec(scenario), "true"); -} - -#[test] -fn semicolon_expression_stop() { - let scenario = r#" - var a = 1; - + 1; - a - "#; - - assert_eq!(&exec(scenario), "1"); -} - -#[test] -fn empty_var_decl_undefined() { - let scenario = r#" - let b; - b === undefined; - "#; - - assert_eq!(&exec(scenario), "true"); -} - -#[test] -fn identifier_on_global_object_undefined() { - let scenario = r#" - try { - bar; - } catch (err) { - err.message - } - "#; - - assert_eq!(&exec(scenario), "\"bar is not defined\""); -} - -#[test] -fn object_field_set() { - let scenario = r#" - let m = {}; - m['key'] = 22; - m['key'] - "#; - assert_eq!(&exec(scenario), "22"); -} - -#[test] -fn object_spread() { - let scenario = r#" - var b = {x: -1, z: -3} - var a = {x: 1, y: 2, ...b}; - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("a.x", "-1"), - TestAction::TestEq("a.y", "2"), - TestAction::TestEq("a.z", "-3"), - ]); -} - -#[test] -fn spread_with_arguments() { - let scenario = r#" - const a = [1, "test", 3, 4]; - function foo(...a) { - return arguments; - } - - var result = foo(...a); - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result[0]", "1"), - TestAction::TestEq("result[1]", "\"test\""), - TestAction::TestEq("result[2]", "3"), - TestAction::TestEq("result[3]", "4"), - ]); -} - -#[test] -fn array_rest_with_arguments() { - let scenario = r#" - var b = [4, 5, 6] - var a = [1, 2, 3, ...b]; - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("a", "[ 1, 2, 3, 4, 5, 6 ]"), - ]); -} - -#[test] -fn array_field_set() { - let element_changes = r#" - let m = [1, 2, 3]; - m[1] = 5; - m[1] - "#; - assert_eq!(&exec(element_changes), "5"); - - let length_changes = r#" - let m = [1, 2, 3]; - m[10] = 52; - m.length - "#; - assert_eq!(&exec(length_changes), "11"); - - let negative_index_wont_affect_length = r#" - let m = [1, 2, 3]; - m[-11] = 5; - m.length - "#; - assert_eq!(&exec(negative_index_wont_affect_length), "3"); - - let non_num_key_wont_affect_length = r#" - let m = [1, 2, 3]; - m["magic"] = 5; - m.length - "#; - assert_eq!(&exec(non_num_key_wont_affect_length), "3"); -} - -#[test] -fn tilde_operator() { - let float = r#" - let f = -1.2; - ~f - "#; - assert_eq!(&exec(float), "0"); - - let numeric = r#" - let f = 1789; - ~f - "#; - assert_eq!(&exec(numeric), "-1790"); - - let nan = r#" - var m = NaN; - ~m - "#; - assert_eq!(&exec(nan), "-1"); - - let object = r#" - let m = {}; - ~m - "#; - assert_eq!(&exec(object), "-1"); - - let boolean_true = r#" - ~true - "#; - assert_eq!(&exec(boolean_true), "-2"); - - let boolean_false = r#" - ~false - "#; - assert_eq!(&exec(boolean_false), "-1"); -} - -#[test] -fn early_return() { - let early_return = r#" - function early_return() { - if (true) { - return true; - } - return false; - } - early_return() - "#; - assert_eq!(&exec(early_return), "true"); - - let early_return = r#" - function nested_fnct() { - return "nested"; - } - function outer_fnct() { - nested_fnct(); - return "outer"; - } - outer_fnct() - "#; - assert_eq!(&exec(early_return), "\"outer\""); -} - -#[test] -fn short_circuit_evaluation() { - // OR operation - assert_eq!(&exec("true || true"), "true"); - assert_eq!(&exec("true || false"), "true"); - assert_eq!(&exec("false || true"), "true"); - assert_eq!(&exec("false || false"), "false"); - - // the second operand must NOT be evaluated if the first one resolve to `true`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return true; - } - let counter = { value: 0 }; - let _ = add_one(counter) || add_one(counter); - counter.value - "#; - assert_eq!(&exec(short_circuit_eval), "1"); - - // the second operand must be evaluated if the first one resolve to `false`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return false; - } - let counter = { value: 0 }; - let _ = add_one(counter) || add_one(counter); - counter.value - "#; - assert_eq!(&exec(short_circuit_eval), "2"); - - // AND operation - assert_eq!(&exec("true && true"), "true"); - assert_eq!(&exec("true && false"), "false"); - assert_eq!(&exec("false && true"), "false"); - assert_eq!(&exec("false && false"), "false"); - - // the second operand must be evaluated if the first one resolve to `true`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return true; - } - let counter = { value: 0 }; - let _ = add_one(counter) && add_one(counter); - counter.value - "#; - assert_eq!(&exec(short_circuit_eval), "2"); - - // the second operand must NOT be evaluated if the first one resolve to `false`. - let short_circuit_eval = r#" - function add_one(counter) { - counter.value += 1; - return false; - } - let counter = { value: 0 }; - let _ = add_one(counter) && add_one(counter); - counter.value - "#; - assert_eq!(&exec(short_circuit_eval), "1"); -} - -#[test] -fn assign_operator_precedence() { - let src = r#" - let a = 1; - a = a + 1; - a - "#; - assert_eq!(&exec(src), "2"); -} - -#[test] -fn do_while_loop() { - let simple_one = r#" - a = 0; - do { - a += 1; - } while (a < 10); - - a - "#; - assert_eq!(&exec(simple_one), "10"); - - let multiline_statement = r#" - pow = 0; - b = 1; - do { - pow += 1; - b *= 2; - } while (pow < 8); - b - "#; - assert_eq!(&exec(multiline_statement), "256"); -} - -#[test] -fn do_while_loop_at_least_once() { - let body_is_executed_at_least_once = r#" - a = 0; - do - { - a += 1; - } - while (false); - a - "#; - assert_eq!(&exec(body_is_executed_at_least_once), "1"); -} - -#[test] -fn do_while_post_inc() { - let with_post_incrementors = r#" - var i = 0; - do {} while(i++ < 10) i; - "#; - assert_eq!(&exec(with_post_incrementors), "11"); -} - -#[test] -fn do_while_in_block() { - let in_block = r#" - { - var i = 0; - do { - i += 1; - } - while(false); - i; - } - "#; - assert_eq!(&exec(in_block), "1"); -} - -#[test] -fn for_loop() { - let simple = r#" - const a = ['h', 'e', 'l', 'l', 'o']; - let b = ''; - for (let i = 0; i < a.length; i++) { - b = b + a[i]; - } - b - "#; - assert_eq!(&exec(simple), "\"hello\""); - - let without_init_and_inc_step = r#" - let a = 0; - let i = 0; - for (;i < 10;) { - a = a + i; - i++; - } - - a - "#; - assert_eq!(&exec(without_init_and_inc_step), "45"); - - let body_should_not_execute_on_false_condition = r#" - let a = 0 - for (;false;) { - a++; - } - - a - "#; - assert_eq!(&exec(body_should_not_execute_on_false_condition), "0"); -} - -#[test] -fn for_loop_iteration_variable_does_not_leak() { - let inner_scope = r#" - for (let i = 0;false;) {} - - try { - i - } catch (err) { - err.message - } - "#; - - assert_eq!(&exec(inner_scope), "\"i is not defined\""); -} - -#[test] -fn test_invalid_break_target() { - let src = r#" - while (false) { - break nonexistent; - } - "#; - - assert!(Context::default() - .eval_script(Source::from_bytes(src)) - .is_err()); -} - -#[test] -fn test_invalid_break() { - let mut context = Context::default(); - let src = r#" - break; - "#; - - let string = forward(&mut context, src); - assert_eq!( - string, - "Uncaught SyntaxError: Syntax Error: illegal break statement at position: 1:1" - ); -} - -#[test] -fn test_invalid_continue_target() { - let mut context = Context::default(); - let src = r#" - while (false) { - continue nonexistent; - } - "#; - let string = forward(&mut context, src); - assert_eq!( - string, - "Uncaught SyntaxError: Syntax Error: undefined continue target: nonexistent at position: 1:1" - ); -} - -#[test] -fn test_invalid_continue() { - let mut context = Context::default(); - let string = forward(&mut context, r"continue;"); - assert_eq!( - string, - "Uncaught SyntaxError: Syntax Error: illegal continue statement at position: 1:1" - ); -} - -#[test] -fn unary_pre() { - let unary_inc = r#" - let a = 5; - ++a; - a; - "#; - assert_eq!(&exec(unary_inc), "6"); - - let unary_dec = r#" - let a = 5; - --a; - a; - "#; - assert_eq!(&exec(unary_dec), "4"); - - let inc_obj_prop = r#" - const a = { b: 5 }; - ++a.b; - a['b']; - "#; - assert_eq!(&exec(inc_obj_prop), "6"); - - let inc_obj_field = r#" - const a = { b: 5 }; - ++a['b']; - a.b; - "#; - assert_eq!(&exec(inc_obj_field), "6"); - - let execs_before_inc = r#" - let a = 5; - ++a === 6; - "#; - assert_eq!(&exec(execs_before_inc), "true"); - - let execs_before_dec = r#" - let a = 5; - --a === 4; - "#; - assert_eq!(&exec(execs_before_dec), "true"); - - let i32_limit_inc = r#" - let a = 2147483647; - ++a; - a; - "#; - assert_eq!(&exec(i32_limit_inc), "2147483648"); - - let i32_limit_dec = r#" - let a = -2147483648; - --a; - a; - "#; - assert_eq!(&exec(i32_limit_dec), "-2147483649"); -} - -#[test] -fn invalid_unary_access() { - check_output(&[ - TestAction::TestStartsWith("++[];", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("[]++;", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("--[];", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("[]--;", "Uncaught SyntaxError: "), - ]); -} - -#[test] -fn typeof_string() { - let typeof_string = r#" - const a = String(); - typeof a; - "#; - assert_eq!(&exec(typeof_string), "\"string\""); -} - -#[test] -fn typeof_int() { - let typeof_int = r#" - let a = 5; - typeof a; - "#; - assert_eq!(&exec(typeof_int), "\"number\""); -} - -#[test] -fn typeof_rational() { - let typeof_rational = r#" - let a = 0.5; - typeof a; - "#; - assert_eq!(&exec(typeof_rational), "\"number\""); -} - -#[test] -fn typeof_undefined() { - let typeof_undefined = r#" - let a = undefined; - typeof a; - "#; - assert_eq!(&exec(typeof_undefined), "\"undefined\""); -} - -#[test] -fn typeof_undefined_directly() { - let typeof_undefined = r#" - typeof undefined; - "#; - assert_eq!(&exec(typeof_undefined), "\"undefined\""); -} - -#[test] -fn typeof_boolean() { - let typeof_boolean = r#" - let a = true; - typeof a; - "#; - assert_eq!(&exec(typeof_boolean), "\"boolean\""); -} - -#[test] -fn typeof_null() { - let typeof_null = r#" - let a = null; - typeof a; - "#; - assert_eq!(&exec(typeof_null), "\"object\""); -} - -#[test] -fn typeof_object() { - let typeof_object = r#" - let a = {}; - typeof a; - "#; - assert_eq!(&exec(typeof_object), "\"object\""); -} - -#[test] -fn typeof_symbol() { - let typeof_symbol = r#" - let a = Symbol(); - typeof a; - "#; - assert_eq!(&exec(typeof_symbol), "\"symbol\""); -} - -#[test] -fn typeof_function() { - let typeof_function = r#" - let a = function(){}; - typeof a; - "#; - assert_eq!(&exec(typeof_function), "\"function\""); -} - -#[test] -fn unary_post() { - let unary_inc = r#" - let a = 5; - a++; - a; - "#; - assert_eq!(&exec(unary_inc), "6"); - - let unary_dec = r#" - let a = 5; - a--; - a; - "#; - assert_eq!(&exec(unary_dec), "4"); - - let inc_obj_prop = r#" - const a = { b: 5 }; - a.b++; - a['b']; - "#; - assert_eq!(&exec(inc_obj_prop), "6"); - - let inc_obj_field = r#" - const a = { b: 5 }; - a['b']++; - a.b; - "#; - assert_eq!(&exec(inc_obj_field), "6"); - - let execs_after_inc = r#" - let a = 5; - a++ === 5; - "#; - assert_eq!(&exec(execs_after_inc), "true"); - - let execs_after_dec = r#" - let a = 5; - a-- === 5; - "#; - assert_eq!(&exec(execs_after_dec), "true"); - - let i32_limit_inc = r#" - let a = 2147483647; - a++; - a; - "#; - assert_eq!(&exec(i32_limit_inc), "2147483648"); - - let i32_limit_dec = r#" - let a = -2147483648; - a--; - a; - "#; - assert_eq!(&exec(i32_limit_dec), "-2147483649"); - - let to_numeric_inc = r#" - let a = {[Symbol.toPrimitive]() { return 123; }}; - a++ - "#; - assert_eq!(&exec(to_numeric_inc), "123"); - - let to_numeric_dec = r#" - let a = {[Symbol.toPrimitive]() { return 123; }}; - a-- - "#; - assert_eq!(&exec(to_numeric_dec), "123"); -} - -#[test] -fn unary_void() { - let void_should_return_undefined = r#" - const a = 0; - void a; - "#; - assert_eq!(&exec(void_should_return_undefined), "undefined"); - - let void_invocation = r#" - let a = 0; - const test = () => a = 42; - const b = void test() + ''; - a + b - "#; - assert_eq!(&exec(void_invocation), "\"42undefined\""); -} - -#[test] -fn unary_delete() { - let delete_var = r#" - let a = 5; - const b = delete a + ''; - a + b - "#; - assert_eq!(&exec(delete_var), "\"5false\""); - - let delete_prop = r#" - const a = { b: 5 }; - const c = delete a.b + ''; - a.b + c - "#; - assert_eq!(&exec(delete_prop), "\"undefinedtrue\""); - - let delete_not_existing_prop = r#" - const a = { b: 5 }; - const c = delete a.c + ''; - a.b + c - "#; - assert_eq!(&exec(delete_not_existing_prop), "\"5true\""); - - let delete_field = r#" - const a = { b: 5 }; - const c = delete a['b'] + ''; - a.b + c - "#; - assert_eq!(&exec(delete_field), "\"undefinedtrue\""); - - let delete_object = r#" - const a = { b: 5 }; - delete a - "#; - assert_eq!(&exec(delete_object), "false"); - - let delete_array = r#" - delete []; - "#; - assert_eq!(&exec(delete_array), "true"); - - let delete_func = r#" - delete function() {}; - "#; - assert_eq!(&exec(delete_func), "true"); - - let delete_recursive = r#" - delete delete delete 1; - "#; - assert_eq!(&exec(delete_recursive), "true"); -} - -#[cfg(test)] -mod in_operator { - use super::*; - use crate::forward_val; - - #[test] - fn propery_in_object() { - let p_in_o = r#" - var o = {a: 'a'}; - var p = 'a'; - p in o - "#; - assert_eq!(&exec(p_in_o), "true"); - } - - #[test] - fn property_in_property_chain() { - let p_in_o = r#" - var o = {}; - var p = 'toString'; - p in o - "#; - assert_eq!(&exec(p_in_o), "true"); - } - - #[test] - fn property_not_in_object() { - let p_not_in_o = r#" - var o = {a: 'a'}; - var p = 'b'; - p in o - "#; - assert_eq!(&exec(p_not_in_o), "false"); - } - - #[test] - fn number_in_array() { - // Note: this is valid because the LHS is converted to a prop key with ToPropertyKey - // and arrays are just fancy objects like {'0': 'a'} - let num_in_array = r#" - var n = 0; - var a = ['a']; - n in a - "#; - assert_eq!(&exec(num_in_array), "true"); - } - - #[test] - fn symbol_in_object() { - let sym_in_object = r#" - var sym = Symbol('hi'); - var o = {}; - o[sym] = 'hello'; - sym in o - "#; - assert_eq!(&exec(sym_in_object), "true"); - } - - #[test] - fn should_type_error_when_rhs_not_object() { - let scenario = r#" - var x = false; - try { - 'fail' in undefined - } catch(e) { - x = true; - } - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("x", "true"), - ]); - } - - #[test] - fn should_set_this_value() { - let scenario = r#" - function Foo() { - this.a = "a"; - this.b = "b"; - } - - var bar = new Foo(); - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("bar.a", "\"a\""), - TestAction::TestEq("bar.b", "\"b\""), - ]); - } - - #[test] - fn should_type_error_when_new_is_not_constructor() { - let scenario = r#" - const a = ""; - new a(); - "#; - - check_output(&[TestAction::TestEq( - scenario, - "Uncaught TypeError: not a constructor", - )]); - } - - #[test] - fn new_instance_should_point_to_prototype() { - // A new instance should point to a prototype object created with the constructor function - let mut context = Context::default(); - - let scenario = r#" - function Foo() {} - var bar = new Foo(); - "#; - forward(&mut context, scenario); - let bar_val = forward_val(&mut context, "bar").unwrap(); - let bar_obj = bar_val.as_object().unwrap(); - let foo_val = forward_val(&mut context, "Foo").unwrap(); - assert_eq!( - *bar_obj.prototype(), - foo_val.as_object().and_then(|obj| obj - .get("prototype", &mut context) - .unwrap() - .as_object() - .cloned()) - ); - } -} - -#[test] -fn var_decl_hoisting_simple() { - let scenario = r#" - x = 5; - - var x; - x; - "#; - assert_eq!(&exec(scenario), "5"); -} - -#[test] -fn var_decl_hoisting_with_initialization() { - let scenario = r#" - x = 5; - - var x = 10; - x; - "#; - assert_eq!(&exec(scenario), "10"); -} - -#[test] -#[ignore] -fn var_decl_hoisting_2_variables_hoisting() { - let scenario = r#" - x = y; - - var x = 10; - var y = 5; - - x; - "#; - assert_eq!(&exec(scenario), "10"); -} - -#[test] -#[ignore] -fn var_decl_hoisting_2_variables_hoisting_2() { - let scenario = r#" - var x = y; - - var y = 5; - x; - "#; - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -#[ignore] -fn var_decl_hoisting_2_variables_hoisting_3() { - let scenario = r#" - let y = x; - x = 5; - - var x = 10; - y; - "#; - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -fn function_decl_hoisting() { - let scenario = r#" - let a = hello(); - function hello() { return 5 } - - a; - "#; - assert_eq!(&exec(scenario), "5"); - - let scenario = r#" - x = hello(); - - function hello() {return 5} - var x; - x; - "#; - assert_eq!(&exec(scenario), "5"); - - let scenario = r#" - hello = function() { return 5 } - x = hello(); - - x; - "#; - assert_eq!(&exec(scenario), "5"); - - let scenario = r#" - let x = b(); - - function a() {return 5} - function b() {return a()} - - x; - "#; - assert_eq!(&exec(scenario), "5"); - - let scenario = r#" - let x = b(); - - function b() {return a()} - function a() {return 5} - - x; - "#; - assert_eq!(&exec(scenario), "5"); -} - -#[test] -fn to_bigint() { - let mut context = Context::default(); - - assert!(JsValue::null().to_bigint(&mut context).is_err()); - assert!(JsValue::undefined().to_bigint(&mut context).is_err()); - assert!(JsValue::new(55).to_bigint(&mut context).is_err()); - assert!(JsValue::new(10.0).to_bigint(&mut context).is_err()); - assert!(JsValue::new("100").to_bigint(&mut context).is_ok()); -} - -#[test] -fn to_index() { - let mut context = Context::default(); - - assert_eq!(JsValue::undefined().to_index(&mut context).unwrap(), 0); - assert!(JsValue::new(-1).to_index(&mut context).is_err()); -} - -#[test] -fn to_integer_or_infinity() { - let mut context = Context::default(); - - assert_eq!( - JsValue::nan().to_integer_or_infinity(&mut context).unwrap(), - 0 - ); - assert_eq!( - JsValue::new(f64::NEG_INFINITY) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::NegativeInfinity - ); - assert_eq!( - JsValue::new(f64::INFINITY) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::PositiveInfinity - ); - assert_eq!( - JsValue::new(0.0) - .to_integer_or_infinity(&mut context) - .unwrap(), - 0 - ); - assert_eq!( - JsValue::new(-0.0) - .to_integer_or_infinity(&mut context) - .unwrap(), - 0 - ); - assert_eq!( - JsValue::new(20.9) - .to_integer_or_infinity(&mut context) - .unwrap(), - 20 - ); - assert_eq!( - JsValue::new(-20.9) - .to_integer_or_infinity(&mut context) - .unwrap(), - -20 - ); -} - -#[test] -fn to_length() { - let mut context = Context::default(); - - assert_eq!(JsValue::new(f64::NAN).to_length(&mut context).unwrap(), 0); - assert_eq!( - JsValue::new(f64::NEG_INFINITY) - .to_length(&mut context) - .unwrap(), - 0 - ); - assert_eq!( - JsValue::new(f64::INFINITY).to_length(&mut context).unwrap(), - Number::MAX_SAFE_INTEGER as u64 - ); - assert_eq!(JsValue::new(0.0).to_length(&mut context).unwrap(), 0); - assert_eq!(JsValue::new(-0.0).to_length(&mut context).unwrap(), 0); - assert_eq!(JsValue::new(20.9).to_length(&mut context).unwrap(), 20); - assert_eq!(JsValue::new(-20.9).to_length(&mut context).unwrap(), 0); - assert_eq!( - JsValue::new(100_000_000_000.0) - .to_length(&mut context) - .unwrap(), - 100_000_000_000 - ); - assert_eq!( - JsValue::new(4_010_101_101.0) - .to_length(&mut context) - .unwrap(), - 4_010_101_101 - ); -} - -#[test] -fn to_int32() { - let mut context = Context::default(); - - macro_rules! check_to_int32 { - ($from:expr => $to:expr) => { - assert_eq!(JsValue::new($from).to_i32(&mut context).unwrap(), $to); - }; - } - - check_to_int32!(f64::NAN => 0); - check_to_int32!(f64::NEG_INFINITY => 0); - check_to_int32!(f64::INFINITY => 0); - check_to_int32!(0 => 0); - check_to_int32!(-0.0 => 0); - - check_to_int32!(20.9 => 20); - check_to_int32!(-20.9 => -20); - - check_to_int32!(Number::MIN_VALUE => 0); - check_to_int32!(-Number::MIN_VALUE => 0); - check_to_int32!(0.1 => 0); - check_to_int32!(-0.1 => 0); - check_to_int32!(1 => 1); - check_to_int32!(1.1 => 1); - check_to_int32!(-1 => -1); - check_to_int32!(0.6 => 0); - check_to_int32!(1.6 => 1); - check_to_int32!(-0.6 => 0); - check_to_int32!(-1.6 => -1); - - check_to_int32!(2_147_483_647.0 => 2_147_483_647); - check_to_int32!(2_147_483_648.0 => -2_147_483_648); - check_to_int32!(2_147_483_649.0 => -2_147_483_647); - - check_to_int32!(4_294_967_295.0 => -1); - check_to_int32!(4_294_967_296.0 => 0); - check_to_int32!(4_294_967_297.0 => 1); - - check_to_int32!(-2_147_483_647.0 => -2_147_483_647); - check_to_int32!(-2_147_483_648.0 => -2_147_483_648); - check_to_int32!(-2_147_483_649.0 => 2_147_483_647); - - check_to_int32!(-4_294_967_295.0 => 1); - check_to_int32!(-4_294_967_296.0 => 0); - check_to_int32!(-4_294_967_297.0 => -1); - - check_to_int32!(2_147_483_648.25 => -2_147_483_648); - check_to_int32!(2_147_483_648.5 => -2_147_483_648); - check_to_int32!(2_147_483_648.75 => -2_147_483_648); - check_to_int32!(4_294_967_295.25 => -1); - check_to_int32!(4_294_967_295.5 => -1); - check_to_int32!(4_294_967_295.75 => -1); - check_to_int32!(3_000_000_000.25 => -1_294_967_296); - check_to_int32!(3_000_000_000.5 => -1_294_967_296); - check_to_int32!(3_000_000_000.75 => -1_294_967_296); - - check_to_int32!(-2_147_483_648.25 => -2_147_483_648); - check_to_int32!(-2_147_483_648.5 => -2_147_483_648); - check_to_int32!(-2_147_483_648.75 => -2_147_483_648); - check_to_int32!(-4_294_967_295.25 => 1); - check_to_int32!(-4_294_967_295.5 => 1); - check_to_int32!(-4_294_967_295.75 => 1); - check_to_int32!(-3_000_000_000.25 => 1_294_967_296); - check_to_int32!(-3_000_000_000.5 => 1_294_967_296); - check_to_int32!(-3_000_000_000.75 => 1_294_967_296); - - let base = 2f64.powi(64); - check_to_int32!(base + 0.0 => 0); - check_to_int32!(base + 1117.0 => 0); - check_to_int32!(base + 2234.0 => 4096); - check_to_int32!(base + 3351.0 => 4096); - check_to_int32!(base + 4468.0 => 4096); - check_to_int32!(base + 5585.0 => 4096); - check_to_int32!(base + 6702.0 => 8192); - check_to_int32!(base + 7819.0 => 8192); - check_to_int32!(base + 8936.0 => 8192); - check_to_int32!(base + 10053.0 => 8192); - check_to_int32!(base + 11170.0 => 12288); - check_to_int32!(base + 12287.0 => 12288); - check_to_int32!(base + 13404.0 => 12288); - check_to_int32!(base + 14521.0 => 16384); - check_to_int32!(base + 15638.0 => 16384); - check_to_int32!(base + 16755.0 => 16384); - check_to_int32!(base + 17872.0 => 16384); - check_to_int32!(base + 18989.0 => 20480); - check_to_int32!(base + 20106.0 => 20480); - check_to_int32!(base + 21223.0 => 20480); - check_to_int32!(base + 22340.0 => 20480); - check_to_int32!(base + 23457.0 => 24576); - check_to_int32!(base + 24574.0 => 24576); - check_to_int32!(base + 25691.0 => 24576); - check_to_int32!(base + 26808.0 => 28672); - check_to_int32!(base + 27925.0 => 28672); - check_to_int32!(base + 29042.0 => 28672); - check_to_int32!(base + 30159.0 => 28672); - check_to_int32!(base + 31276.0 => 32768); - - // bignum is (2^53 - 1) * 2^31 - highest number with bit 31 set. - let bignum = 2f64.powi(84) - 2f64.powi(31); - check_to_int32!(bignum => -2_147_483_648); - check_to_int32!(-bignum => -2_147_483_648); - check_to_int32!(2.0 * bignum => 0); - check_to_int32!(-(2.0 * bignum) => 0); - check_to_int32!(bignum - 2f64.powi(31) => 0); - check_to_int32!(-(bignum - 2f64.powi(31)) => 0); - - // max_fraction is largest number below 1. - let max_fraction = 1.0 - 2f64.powi(-53); - check_to_int32!(max_fraction => 0); - check_to_int32!(-max_fraction => 0); -} - -#[test] -fn to_string() { - let mut context = Context::default(); - - assert_eq!( - &JsValue::null().to_string(&mut context).unwrap(), - utf16!("null") - ); - assert_eq!( - &JsValue::undefined().to_string(&mut context).unwrap(), - utf16!("undefined") - ); - assert_eq!( - &JsValue::new(55).to_string(&mut context).unwrap(), - utf16!("55") - ); - assert_eq!( - &JsValue::new(55.0).to_string(&mut context).unwrap(), - utf16!("55") - ); - assert_eq!( - &JsValue::new("hello").to_string(&mut context).unwrap(), - utf16!("hello") - ); -} - -#[test] -fn calling_function_with_unspecified_arguments() { - let mut context = Context::default(); - let scenario = r#" - function test(a, b) { - return b; - } - - test(10) - "#; - - assert_eq!(forward(&mut context, scenario), "undefined"); -} - -#[test] -fn check_this_binding_in_object_literal() { - let mut context = Context::default(); - let init = r#" - var foo = { - a: 3, - bar: function () { return this.a + 5 } - }; - - foo.bar() - "#; - - assert_eq!(forward(&mut context, init), "8"); -} - -#[test] -fn array_creation_benchmark() { - let mut context = Context::default(); - let init = r#" - (function(){ - let testArr = []; - for (let a = 0; a <= 500; a++) { - testArr[a] = ('p' + a); - } - - return testArr; - })(); - "#; - - assert_eq!(forward(&mut context, init), "[ \"p0\", \"p1\", \"p2\", \"p3\", \"p4\", \"p5\", \"p6\", \"p7\", \"p8\", \"p9\", \"p10\", \"p11\", \"p12\", \"p13\", \"p14\", \"p15\", \"p16\", \"p17\", \"p18\", \"p19\", \"p20\", \"p21\", \"p22\", \"p23\", \"p24\", \"p25\", \"p26\", \"p27\", \"p28\", \"p29\", \"p30\", \"p31\", \"p32\", \"p33\", \"p34\", \"p35\", \"p36\", \"p37\", \"p38\", \"p39\", \"p40\", \"p41\", \"p42\", \"p43\", \"p44\", \"p45\", \"p46\", \"p47\", \"p48\", \"p49\", \"p50\", \"p51\", \"p52\", \"p53\", \"p54\", \"p55\", \"p56\", \"p57\", \"p58\", \"p59\", \"p60\", \"p61\", \"p62\", \"p63\", \"p64\", \"p65\", \"p66\", \"p67\", \"p68\", \"p69\", \"p70\", \"p71\", \"p72\", \"p73\", \"p74\", \"p75\", \"p76\", \"p77\", \"p78\", \"p79\", \"p80\", \"p81\", \"p82\", \"p83\", \"p84\", \"p85\", \"p86\", \"p87\", \"p88\", \"p89\", \"p90\", \"p91\", \"p92\", \"p93\", \"p94\", \"p95\", \"p96\", \"p97\", \"p98\", \"p99\", \"p100\", \"p101\", \"p102\", \"p103\", \"p104\", \"p105\", \"p106\", \"p107\", \"p108\", \"p109\", \"p110\", \"p111\", \"p112\", \"p113\", \"p114\", \"p115\", \"p116\", \"p117\", \"p118\", \"p119\", \"p120\", \"p121\", \"p122\", \"p123\", \"p124\", \"p125\", \"p126\", \"p127\", \"p128\", \"p129\", \"p130\", \"p131\", \"p132\", \"p133\", \"p134\", \"p135\", \"p136\", \"p137\", \"p138\", \"p139\", \"p140\", \"p141\", \"p142\", \"p143\", \"p144\", \"p145\", \"p146\", \"p147\", \"p148\", \"p149\", \"p150\", \"p151\", \"p152\", \"p153\", \"p154\", \"p155\", \"p156\", \"p157\", \"p158\", \"p159\", \"p160\", \"p161\", \"p162\", \"p163\", \"p164\", \"p165\", \"p166\", \"p167\", \"p168\", \"p169\", \"p170\", \"p171\", \"p172\", \"p173\", \"p174\", \"p175\", \"p176\", \"p177\", \"p178\", \"p179\", \"p180\", \"p181\", \"p182\", \"p183\", \"p184\", \"p185\", \"p186\", \"p187\", \"p188\", \"p189\", \"p190\", \"p191\", \"p192\", \"p193\", \"p194\", \"p195\", \"p196\", \"p197\", \"p198\", \"p199\", \"p200\", \"p201\", \"p202\", \"p203\", \"p204\", \"p205\", \"p206\", \"p207\", \"p208\", \"p209\", \"p210\", \"p211\", \"p212\", \"p213\", \"p214\", \"p215\", \"p216\", \"p217\", \"p218\", \"p219\", \"p220\", \"p221\", \"p222\", \"p223\", \"p224\", \"p225\", \"p226\", \"p227\", \"p228\", \"p229\", \"p230\", \"p231\", \"p232\", \"p233\", \"p234\", \"p235\", \"p236\", \"p237\", \"p238\", \"p239\", \"p240\", \"p241\", \"p242\", \"p243\", \"p244\", \"p245\", \"p246\", \"p247\", \"p248\", \"p249\", \"p250\", \"p251\", \"p252\", \"p253\", \"p254\", \"p255\", \"p256\", \"p257\", \"p258\", \"p259\", \"p260\", \"p261\", \"p262\", \"p263\", \"p264\", \"p265\", \"p266\", \"p267\", \"p268\", \"p269\", \"p270\", \"p271\", \"p272\", \"p273\", \"p274\", \"p275\", \"p276\", \"p277\", \"p278\", \"p279\", \"p280\", \"p281\", \"p282\", \"p283\", \"p284\", \"p285\", \"p286\", \"p287\", \"p288\", \"p289\", \"p290\", \"p291\", \"p292\", \"p293\", \"p294\", \"p295\", \"p296\", \"p297\", \"p298\", \"p299\", \"p300\", \"p301\", \"p302\", \"p303\", \"p304\", \"p305\", \"p306\", \"p307\", \"p308\", \"p309\", \"p310\", \"p311\", \"p312\", \"p313\", \"p314\", \"p315\", \"p316\", \"p317\", \"p318\", \"p319\", \"p320\", \"p321\", \"p322\", \"p323\", \"p324\", \"p325\", \"p326\", \"p327\", \"p328\", \"p329\", \"p330\", \"p331\", \"p332\", \"p333\", \"p334\", \"p335\", \"p336\", \"p337\", \"p338\", \"p339\", \"p340\", \"p341\", \"p342\", \"p343\", \"p344\", \"p345\", \"p346\", \"p347\", \"p348\", \"p349\", \"p350\", \"p351\", \"p352\", \"p353\", \"p354\", \"p355\", \"p356\", \"p357\", \"p358\", \"p359\", \"p360\", \"p361\", \"p362\", \"p363\", \"p364\", \"p365\", \"p366\", \"p367\", \"p368\", \"p369\", \"p370\", \"p371\", \"p372\", \"p373\", \"p374\", \"p375\", \"p376\", \"p377\", \"p378\", \"p379\", \"p380\", \"p381\", \"p382\", \"p383\", \"p384\", \"p385\", \"p386\", \"p387\", \"p388\", \"p389\", \"p390\", \"p391\", \"p392\", \"p393\", \"p394\", \"p395\", \"p396\", \"p397\", \"p398\", \"p399\", \"p400\", \"p401\", \"p402\", \"p403\", \"p404\", \"p405\", \"p406\", \"p407\", \"p408\", \"p409\", \"p410\", \"p411\", \"p412\", \"p413\", \"p414\", \"p415\", \"p416\", \"p417\", \"p418\", \"p419\", \"p420\", \"p421\", \"p422\", \"p423\", \"p424\", \"p425\", \"p426\", \"p427\", \"p428\", \"p429\", \"p430\", \"p431\", \"p432\", \"p433\", \"p434\", \"p435\", \"p436\", \"p437\", \"p438\", \"p439\", \"p440\", \"p441\", \"p442\", \"p443\", \"p444\", \"p445\", \"p446\", \"p447\", \"p448\", \"p449\", \"p450\", \"p451\", \"p452\", \"p453\", \"p454\", \"p455\", \"p456\", \"p457\", \"p458\", \"p459\", \"p460\", \"p461\", \"p462\", \"p463\", \"p464\", \"p465\", \"p466\", \"p467\", \"p468\", \"p469\", \"p470\", \"p471\", \"p472\", \"p473\", \"p474\", \"p475\", \"p476\", \"p477\", \"p478\", \"p479\", \"p480\", \"p481\", \"p482\", \"p483\", \"p484\", \"p485\", \"p486\", \"p487\", \"p488\", \"p489\", \"p490\", \"p491\", \"p492\", \"p493\", \"p494\", \"p495\", \"p496\", \"p497\", \"p498\", \"p499\", \"p500\" ]"); -} - -#[test] -fn array_pop_benchmark() { - let mut context = Context::default(); - let init = r#" - (function(){ - let testArray = [83, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, - 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, - 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, - 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, - 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, - 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, - 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, - 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, - 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, - 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, - 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, - 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, - 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, - 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, - 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, - 45, 93, 17, 28, 83, 62, 99, 36, 28]; - - while (testArray.length > 0) { - testArray.pop(); - } - - return testArray; - })(); - "#; - - assert_eq!(forward(&mut context, init), "[]"); -} - -#[test] -fn number_object_access_benchmark() { - let mut context = Context::default(); - let init = r#" - new Number( - new Number( - new Number( - new Number(100).valueOf() - 10.5 - ).valueOf() + 100 - ).valueOf() * 1.6 - ) - "#; - - assert!(forward_val(&mut context, init).is_ok()); -} - -#[test] -fn not_a_function() { - let init = r#" - let a = {}; - let b = true; - "#; - let scenario1 = r#" - try { - a(); - } catch(e) { - e.toString() - } - "#; - let scenario2 = r#" - try { - a.a(); - } catch(e) { - e.toString() - } - "#; - let scenario3 = r#" - try { - b(); - } catch(e) { - e.toString() - } - "#; - - check_output(&[ - TestAction::Execute(init), - TestAction::TestEq(scenario1, "\"TypeError: not a callable function\""), - TestAction::TestEq(scenario2, "\"TypeError: not a callable function\""), - TestAction::TestEq(scenario3, "\"TypeError: not a callable function\""), - ]); -} - -#[test] -fn comma_operator() { - let scenario = r#" - var a, b; - b = 10; - a = (b++, b); - a - "#; - assert_eq!(&exec(scenario), "11"); - - let scenario = r#" - var a, b; - b = 10; - a = (b += 5, b /= 3, b - 3); - a - "#; - assert_eq!(&exec(scenario), "2"); -} - -#[test] -fn assignment_to_non_assignable() { - // Relates to the behaviour described at - // https://tc39.es/ecma262/#sec-assignment-operators-static-semantics-early-errors - let mut context = Context::default(); - - // Tests all assignment operators as per [spec] and [mdn] - // - // [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment - // [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator - let test_cases = [ - "3 -= 5", "3 *= 5", "3 /= 5", "3 %= 5", "3 &= 5", "3 ^= 5", "3 |= 5", "3 += 5", "3 = 5", - ]; - - for case in &test_cases { - let string = forward(&mut context, case); - - assert!(string.starts_with("Uncaught SyntaxError: ")); - assert!(string.contains("1:3")); - } -} - -#[test] -fn assignment_to_non_assignable_ctd() { - check_output(&[ - TestAction::TestStartsWith("(()=>{})() -= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() *= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() /= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() %= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() &= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() ^= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() |= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() += 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() = 5", "Uncaught SyntaxError: "), - ]); -} - -#[test] -fn multicharacter_assignment_to_non_assignable() { - // Relates to the behaviour described at - // https://tc39.es/ecma262/#sec-assignment-operators-static-semantics-early-errors - let mut context = Context::default(); - - let test_cases = ["3 **= 5", "3 <<= 5", "3 >>= 5"]; - - for case in &test_cases { - let string = forward(&mut context, case); - - assert!(string.starts_with("Uncaught SyntaxError: ")); - assert!(string.contains("1:3")); - } -} - -#[test] -fn multicharacter_assignment_to_non_assignable_ctd() { - check_output(&[ - TestAction::TestStartsWith("(()=>{})() **= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() <<= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() >>= 5", "Uncaught SyntaxError: "), - ]); -} - -#[test] -fn multicharacter_bitwise_assignment_to_non_assignable() { - let mut context = Context::default(); - - // Disabled - awaiting implementation. - let test_cases = ["3 >>>= 5", "3 &&= 5", "3 ||= 5", "3 ??= 5"]; - - for case in &test_cases { - let string = forward(&mut context, case); - - assert!(string.starts_with("Uncaught SyntaxError: ")); - assert!(string.contains("1:3")); - } -} - -#[test] -fn multicharacter_bitwise_assignment_to_non_assignable_ctd() { - check_output(&[ - TestAction::TestStartsWith("(()=>{})() >>>= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() &&= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() ||= 5", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("(()=>{})() ??= 5", "Uncaught SyntaxError: "), - ]); -} - -#[test] -fn assign_to_array_decl() { - check_output(&[ - TestAction::TestStartsWith("[1] = [2]", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("[3, 5] = [7, 8]", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("[6, 8] = [2]", "Uncaught SyntaxError: "), - TestAction::TestStartsWith("[6] = [2, 9]", "Uncaught SyntaxError: "), - ]); -} - -#[test] -fn assign_to_object_decl() { - const ERR_MSG: &str = - "Uncaught SyntaxError: unexpected token '=', primary expression at line 1, col 8"; - - let mut context = Context::default(); - - assert_eq!(forward(&mut context, "{a: 3} = {a: 5};"), ERR_MSG); -} - -#[test] -fn multiline_str_concat() { - let scenario = r#" - let a = 'hello ' + - 'world'; - - a"#; - assert_eq!(&exec(scenario), "\"hello world\""); -} - -#[test] -fn test_result_of_empty_block() { - let scenario = "{}"; - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -fn test_undefined_constant() { - let scenario = "undefined"; - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -fn test_undefined_type() { - let scenario = "typeof undefined"; - assert_eq!(&exec(scenario), "\"undefined\""); -} - -#[test] -fn test_conditional_op() { - let scenario = "1 === 2 ? 'a' : 'b'"; - assert_eq!(&exec(scenario), "\"b\""); -} - -#[test] -fn test_identifier_op() { - let scenario = "break = 1"; - assert_eq!( - &exec(scenario), - "SyntaxError: expected token \'identifier\', got \'=\' in identifier parsing at line 1, col 7" - ); -} - -#[test] -fn test_strict_mode_octal() { - // Checks as per https://tc39.es/ecma262/#sec-literals-numeric-literals that 0 prefix - // octal number literal syntax is a syntax error in strict mode. - - let scenario = r#" - 'use strict'; - var n = 023; - "#; - - check_output(&[TestAction::TestStartsWith( - scenario, - "Uncaught SyntaxError: ", - )]); -} - -#[test] -fn test_strict_mode_with() { - // Checks as per https://tc39.es/ecma262/#sec-with-statement-static-semantics-early-errors - // that a with statement is an error in strict mode code. - - let scenario = r#" - 'use strict'; - function f(x, o) { - with (o) { - console.log(x); - } - } - "#; - - check_output(&[TestAction::TestStartsWith( - scenario, - "Uncaught SyntaxError: ", - )]); -} - -#[test] -fn test_strict_mode_delete() { - // Checks as per https://tc39.es/ecma262/#sec-delete-operator-static-semantics-early-errors - // that delete on a variable name is an error in strict mode code. - - let scenario = r#" - 'use strict'; - let x = 10; - delete x; - "#; - - check_output(&[TestAction::TestStartsWith( - scenario, - "Uncaught SyntaxError: ", - )]); -} - -#[test] -fn test_strict_mode_reserved_name() { - // Checks that usage of a reserved keyword for an identifier name is - // an error in strict mode code as per https://tc39.es/ecma262/#sec-strict-mode-of-ecmascript. - - let test_cases = [ - "var implements = 10;", - "var interface = 10;", - "var package = 10;", - "var private = 10;", - "var protected = 10;", - "var public = 10;", - "var static = 10;", - "var eval = 10;", - "var arguments = 10;", - "var let = 10;", - "var yield = 10;", - ]; - - for case in &test_cases { - let mut context = Context::default(); - let scenario = format!("'use strict'; \n {case}"); - - let string = forward(&mut context, &scenario); - - assert!(string.starts_with("Uncaught SyntaxError: ")); - } -} - -#[test] -fn test_strict_mode_dup_func_parameters() { - // Checks that a function cannot contain duplicate parameter - // names in strict mode code as per https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors. - - let scenario = r#" - 'use strict'; - function f(a, b, b) {} - "#; - - check_output(&[TestAction::TestStartsWith( - scenario, - "Uncaught SyntaxError: ", - )]); -} - -#[test] -fn strict_mode_global() { - let scenario = r#" - 'use strict'; - let throws = false; - try { - delete Boolean.prototype; - } catch (e) { - throws = true; - } - throws - "#; - - check_output(&[TestAction::TestEq(scenario, "true")]); -} - -#[test] -fn strict_mode_function() { - let scenario = r#" - let throws = false; - function t() { - 'use strict'; - try { - delete Boolean.prototype; - } catch (e) { - throws = true; - } - } - t() - throws - "#; - - check_output(&[TestAction::TestEq(scenario, "true")]); -} - -#[test] -fn strict_mode_function_after() { - let scenario = r#" - function t() { - 'use strict'; - } - t() - let throws = false; - try { - delete Boolean.prototype; - } catch (e) { - throws = true; - } - throws - "#; - - check_output(&[TestAction::TestEq(scenario, "false")]); -} - -#[test] -fn strict_mode_global_active_in_function() { - let scenario = r#" - 'use strict' - let throws = false; - function a(){ - try { - delete Boolean.prototype; - } catch (e) { - throws = true; - } - } - a(); - throws - "#; - - check_output(&[TestAction::TestEq(scenario, "true")]); -} - -#[test] -fn strict_mode_function_in_function() { - let scenario = r#" - let throws = false; - function a(){ - try { - delete Boolean.prototype; - } catch (e) { - throws = true; - } - } - function b(){ - 'use strict'; - a(); - } - b(); - throws - "#; - - check_output(&[TestAction::TestEq(scenario, "false")]); -} - -#[test] -fn strict_mode_function_return() { - let scenario = r#" - let throws = false; - function a() { - 'use strict'; - - return function () { - try { - delete Boolean.prototype; - } catch (e) { - throws = true; - } - } - } - a()(); - throws - "#; - - check_output(&[TestAction::TestEq(scenario, "true")]); -} - -#[test] -fn test_empty_statement() { - let src = r#" - ;;;let a = 10;; - if(a) ; - a - "#; - assert_eq!(&exec(src), "10"); -} - -#[test] -fn test_labelled_block() { - let src = r#" - let result = true; - { - let x = 2; - L: { - let x = 3; - result &&= (x === 3); - break L; - result &&= (false); - } - result &&= (x === 2); - } - result; - "#; - assert_eq!(&exec(src), "true"); -} - -#[test] -fn simple_try() { - let scenario = r#" - let a = 10; - try { - a = 20; - } catch { - a = 30; - } - - a; - "#; - assert_eq!(&exec(scenario), "20"); -} - -#[test] -fn finally() { - let scenario = r#" - let a = 10; - try { - a = 20; - } finally { - a = 30; - } - - a; - "#; - assert_eq!(&exec(scenario), "30"); -} - -#[test] -fn catch_finally() { - let scenario = r#" - let a = 10; - try { - a = 20; - } catch { - a = 40; - } finally { - a = 30; - } - - a; - "#; - assert_eq!(&exec(scenario), "30"); -} - -#[test] -fn catch() { - let scenario = r#" - let a = 10; - try { - throw "error"; - } catch { - a = 20; - } - - a; - "#; - assert_eq!(&exec(scenario), "20"); -} - -#[test] -fn catch_binding() { - let scenario = r#" - let a = 10; - try { - throw 20; - } catch(err) { - a = err; - } - - a; - "#; - assert_eq!(&exec(scenario), "20"); -} - -#[test] -fn catch_binding_pattern_object() { - let scenario = r#" - let a = 10; - try { - throw { - n: 30, - }; - } catch ({ n }) { - a = n; - } - - a; - "#; - assert_eq!(&exec(scenario), "30"); -} - -#[test] -fn catch_binding_pattern_array() { - let scenario = r#" - let a = 10; - try { - throw [20, 30]; - } catch ([, n]) { - a = n; - } - - a; - "#; - assert_eq!(&exec(scenario), "30"); -} - -#[test] -fn catch_binding_finally() { - let scenario = r#" - let a = 10; - try { - throw 20; - } catch(err) { - a = err; - } finally { - a = 30; - } - - a; - "#; - assert_eq!(&exec(scenario), "30"); -} - -#[test] -fn single_case_switch() { - let scenario = r#" - let a = 10; - switch (a) { - case 10: - a = 20; - break; - } - - a; - "#; - assert_eq!(&exec(scenario), "20"); -} - -#[test] -fn no_cases_switch() { - let scenario = r#" - let a = 10; - switch (a) { - } - - a; - "#; - assert_eq!(&exec(scenario), "10"); -} - -#[test] -fn no_true_case_switch() { - let scenario = r#" - let a = 10; - switch (a) { - case 5: - a = 15; - break; - } - - a; - "#; - assert_eq!(&exec(scenario), "10"); -} - -#[test] -fn two_case_switch() { - let scenario = r#" - let a = 10; - switch (a) { - case 5: - a = 15; - break; - case 10: - a = 20; - break; - } - - a; - "#; - assert_eq!(&exec(scenario), "20"); -} - -#[test] -fn two_case_no_break_switch() { - let scenario = r#" - let a = 10; - let b = 10; - - switch (a) { - case 10: - a = 150; - case 20: - b = 150; - break; - } - - a + b; - "#; - assert_eq!(&exec(scenario), "300"); -} - -#[test] -fn three_case_partial_fallthrough() { - let scenario = r#" - let a = 10; - let b = 10; - - switch (a) { - case 10: - a = 150; - case 20: - b = 150; - break; - case 15: - b = 1000; - break; - } - - a + b; - "#; - assert_eq!(&exec(scenario), "300"); -} - -#[test] -fn default_taken_switch() { - let scenario = r#" - let a = 10; - - switch (a) { - case 5: - a = 150; - break; - default: - a = 70; - } - - a; - "#; - assert_eq!(&exec(scenario), "70"); -} - -#[test] -fn default_not_taken_switch() { - let scenario = r#" - let a = 5; - - switch (a) { - case 5: - a = 150; - break; - default: - a = 70; - } - - a; - "#; - assert_eq!(&exec(scenario), "150"); -} - -#[test] -fn string_switch() { - let scenario = r#" - let a = "hello"; - - switch (a) { - case "hello": - a = "world"; - break; - default: - a = "hi"; - } - - a; - "#; - assert_eq!(&exec(scenario), "\"world\""); -} - -#[test] -fn bigger_switch_example() { - let expected = [ - "\"Mon\"", - "\"Tue\"", - "\"Wed\"", - "\"Thurs\"", - "\"Fri\"", - "\"Sat\"", - "\"Sun\"", - ]; - - for (i, val) in expected.iter().enumerate() { - let scenario = format!( - r#" - let a = {i}; - let b = "unknown"; - - switch (a) {{ - case 0: - b = "Mon"; - break; - case 1: - b = "Tue"; - break; - case 2: - b = "Wed"; - break; - case 3: - b = "Thurs"; - break; - case 4: - b = "Fri"; - break; - case 5: - b = "Sat"; - break; - case 6: - b = "Sun"; - break; - }} - - b; - - "#, - ); - - assert_eq!(&exec(&scenario), val); - } -} - -#[test] -fn break_environment_gauntlet() { - // test that break handles popping environments correctly. - let scenario = r#" - let a = 0; - - while(true) { - break; - } - - while(true) break - - do {break} while(true); - - while (a < 3) { - let a = 1; - if (a == 1) { - break; - } - - let b = 2; - } - - { - b = 0; - do { - b = 2 - if (b == 2) { - break; - } - b++ - } while( i < 3); - - let c = 1; - } - - { - for (let r = 0; r< 3; r++) { - if (r == 2) { - break; - } - } - } - - basic: for (let a = 0; a < 2; a++) { - break; - } - - { - let result = true; - { - let x = 2; - L: { - let x = 3; - result &&= (x === 3); - break L; - result &&= (false); - } - result &&= (x === 2); - } - result; - } - - { - var str = ""; - - far_outer: { - outer: for (let i = 0; i < 5; i++) { - inner: for (let b = 5; b < 10; b++) { - if (b === 7) { - break far_outer; - } - str = str + b; - } - str = str + i; - } - } - str - } - - { - for (let r = 0; r < 2; r++) { - str = str + r - } - } - - { - let result = ""; - lab_block: { - try { - result = "try_block"; - break lab_block; - result = "did not break" - } catch (err) {} - } - str = str + result - str - } - "#; - - assert_eq!(&exec(scenario), "\"5601try_block\""); -} - -#[test] -fn break_labelled_if_statement() { - let scenario = r#" - let result = ""; - bar: if(true) { - result = "foo"; - break bar; - result = 'this will not be executed'; - } - result - "#; - - assert_eq!(&exec(scenario), "\"foo\""); -} - -#[test] -fn break_labelled_try_statement() { - let scenario = r#" - let result = "" - one: try { - result = "foo"; - break one; - result = "did not break" - } catch (err) { - console.log(err) - } - result - "#; - - assert_eq!(&exec(scenario), "\"foo\""); -} - -#[test] -fn while_loop_late_break() { - // Ordering with statement before the break. - let scenario = r#" - let a = 1; - while (a < 5) { - a++; - if (a == 3) { - break; - } - } - a; - "#; - - assert_eq!(&exec(scenario), "3"); -} - -#[test] -fn while_loop_early_break() { - // Ordering with statements after the break. - let scenario = r#" - let a = 1; - while (a < 5) { - if (a == 3) { - break; - } - a++; - } - a; - "#; - - assert_eq!(&exec(scenario), "3"); -} - -#[test] -fn for_loop_break() { - let scenario = r#" - let a = 1; - for (; a < 5; a++) { - if (a == 3) { - break; - } - } - a; - "#; - - assert_eq!(&exec(scenario), "3"); -} - -#[test] -fn for_loop_return() { - let scenario = r#" - function foo() { - for (let a = 1; a < 5; a++) { - if (a == 3) { - return a; - } - } - } - - foo(); - "#; - - assert_eq!(&exec(scenario), "3"); -} - -#[test] -fn do_loop_late_break() { - // Ordering with statement before the break. - let scenario = r#" - let a = 1; - do { - a++; - if (a == 3) { - break; - } - } while (a < 5); - a; - "#; - - assert_eq!(&exec(scenario), "3"); -} - -#[test] -fn do_loop_early_break() { - // Ordering with statements after the break. - let scenario = r#" - let a = 1; - do { - if (a == 3) { - break; - } - a++; - } while (a < 5); - a; - "#; - - assert_eq!(&exec(scenario), "3"); -} - -#[test] -fn break_out_of_inner_loop() { - let scenario = r#" - var a = 0, b = 0; - for (let i = 0; i < 2; i++) { - a++; - for (let j = 0; j < 10; j++) { - b++; - if (j == 3) - break; - } - a++; - } - [a, b] - "#; - assert_eq!(&exec(scenario), "[ 4, 8 ]"); -} - -#[test] -fn continue_inner_loop() { - let scenario = r#" - var a = 0, b = 0; - for (let i = 0; i < 2; i++) { - a++; - for (let j = 0; j < 10; j++) { - if (j < 3) - continue; - b++; - } - a++; - } - [a, b] - "#; - assert_eq!(&exec(scenario), "[ 4, 14 ]"); -} - -#[test] -fn for_loop_continue_out_of_switch() { - let scenario = r#" - var a = 0, b = 0, c = 0; - for (let i = 0; i < 3; i++) { - a++; - switch (i) { - case 0: - continue; - c++; - case 1: - continue; - case 5: - c++; - } - b++; - } - [a, b, c] - "#; - assert_eq!(&exec(scenario), "[ 3, 1, 0 ]"); -} - -#[test] -fn while_loop_continue() { - let scenario = r#" - var i = 0, a = 0, b = 0; - while (i < 3) { - i++; - if (i < 2) { - a++; - continue; - } - b++; - } - [a, b] - "#; - assert_eq!(&exec(scenario), "[ 1, 2 ]"); -} - -#[test] -fn do_while_loop_continue() { - let scenario = r#" - var i = 0, a = 0, b = 0; - do { - i++; - if (i < 2) { - a++; - continue; - } - b++; - } while (i < 3) - [a, b] - "#; - assert_eq!(&exec(scenario), "[ 1, 2 ]"); -} - -#[test] -fn for_of_loop_declaration() { - let scenario = r#" - var result = 0; - for (i of [1, 2, 3]) { - result = i; - } - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result", "3"), - TestAction::TestEq("i", "3"), - ]); -} - -#[test] -fn for_of_loop_var() { - let scenario = r#" - var result = 0; - for (var i of [1, 2, 3]) { - result = i; - } - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result", "3"), - TestAction::TestEq("i", "3"), - ]); -} - -#[test] -fn for_of_loop_let() { - let scenario = r#" - var result = 0; - for (let i of [1, 2, 3]) { - result = i; - } - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result", "3"), - TestAction::TestEq( - r#" - try { - i - } catch(e) { - e.toString() - } - "#, - "\"ReferenceError: i is not defined\"", - ), - ]); -} - -#[test] -fn for_of_loop_const() { - let scenario = r#" - var result = 0; - for (let i of [1, 2, 3]) { - result = i; - } - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result", "3"), - TestAction::TestEq( - r#" - try { - i - } catch(e) { - e.toString() - } - "#, - "\"ReferenceError: i is not defined\"", - ), - ]); -} - -#[test] -fn for_of_loop_break() { - let scenario = r#" - var result = 0; - for (var i of [1, 2, 3]) { - if (i > 1) - break; - result = i - } - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result", "1"), - TestAction::TestEq("i", "2"), - ]); -} - -#[test] -fn for_of_loop_continue() { - let scenario = r#" - var result = 0; - for (var i of [1, 2, 3]) { - if (i == 3) - continue; - result = i - } - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result", "2"), - TestAction::TestEq("i", "3"), - ]); -} - -#[test] -fn for_of_loop_return() { - let scenario = r#" - function foo() { - for (i of [1, 2, 3]) { - if (i > 1) - return i; - } - } - "#; - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("foo()", "2"), - ]); -} - -#[test] -fn for_loop_break_label() { - let scenario = r#" - var str = ""; - - outer: for (let i = 0; i < 5; i++) { - inner: for (let b = 0; b < 5; b++) { - if (b === 2) { - break outer; - } - str = str + b; - } - str = str + i; - } - str - "#; - assert_eq!(&exec(scenario), "\"01\""); -} - -#[test] -fn for_loop_continue_label() { - let scenario = r#" - var count = 0; - label: for (let x = 0; x < 10;) { - while (true) { - x++; - count++; - continue label; - } - } - count - "#; - assert_eq!(&exec(scenario), "10"); -} - -#[test] -fn for_in_declaration() { - let init = r#" - let result = []; - let obj = { a: "a", b: 2}; - var i; - for (i in obj) { - result = result.concat([i]); - } - "#; - check_output(&[ - TestAction::Execute(init), - TestAction::TestEq( - "result.length === 2 && result.includes('a') && result.includes('b')", - "true", - ), - ]); -} - -#[test] -fn for_in_var_object() { - let init = r#" - let result = []; - let obj = { a: "a", b: 2}; - for (var i in obj) { - result = result.concat([i]); - } - "#; - check_output(&[ - TestAction::Execute(init), - TestAction::TestEq( - "result.length === 2 && result.includes('a') && result.includes('b')", - "true", - ), - ]); -} - -#[test] -fn for_in_var_array() { - let init = r#" - let result = []; - let arr = ["a", "b"]; - for (var i in arr) { - result = result.concat([i]); - } - "#; - check_output(&[ - TestAction::Execute(init), - TestAction::TestEq( - "result.length === 2 && result.includes('0') && result.includes('1')", - "true", - ), - ]); -} - -#[test] -fn for_in_let_object() { - let init = r#" - let result = []; - let obj = { a: "a", b: 2}; - for (let i in obj) { - result = result.concat([i]); - } - "#; - check_output(&[ - TestAction::Execute(init), - TestAction::TestEq( - "result.length === 2 && result.includes('a') && result.includes('b')", - "true", - ), - ]); -} - -#[test] -fn for_in_const_array() { - let init = r#" - let result = []; - let arr = ["a", "b"]; - for (const i in arr) { - result = result.concat([i]); - } - "#; - check_output(&[ - TestAction::Execute(init), - TestAction::TestEq( - "result.length === 2 && result.includes('0') && result.includes('1')", - "true", - ), - ]); -} - -#[test] -fn for_in_break_label() { - let scenario = r#" - var str = ""; - - outer: for (let i in [1, 2]) { - inner: for (let b in [2, 3, 4]) { - if (b === "1") { - break outer; - } - str = str + b; - } - str = str + i; - } - str - "#; - assert_eq!(&exec(scenario), "\"0\""); -} - -#[test] -fn for_in_continue_label() { - let scenario = r#" - var str = ""; - - outer: for (let i in [1, 2]) { - inner: for (let b in [2, 3, 4]) { - if (b === "1") { - continue outer; - } - str = str + b; - } - str = str + i; - } - str - "#; - assert_eq!(&exec(scenario), "\"00\""); -} - -#[test] -fn tagged_template() { - let scenario = r#" - function tag(t, ...args) { - let a = [] - a = a.concat([t[0], t[1], t[2]]); - a = a.concat([t.raw[0], t.raw[1], t.raw[2]]); - a = a.concat([args[0], args[1]]); - return a - } - let a = 10; - tag`result: ${a} \x26 ${a+10}`; - "#; - - assert_eq!( - &exec(scenario), - r#"[ "result: ", " & ", "", "result: ", " \x26 ", "", 10, 20 ]"# - ); -} - -#[test] -fn spread_shallow_clone() { - let scenario = r#" - var a = { x: {} }; - var aClone = { ...a }; - - a.x === aClone.x - "#; - assert_eq!(&exec(scenario), "true"); -} - -#[test] -fn spread_merge() { - let scenario = r#" - var a = { x: 1, y: 2 }; - var b = { x: -1, z: -3, ...a }; - - (b.x === 1) && (b.y === 2) && (b.z === -3) - "#; - assert_eq!(&exec(scenario), "true"); -} - -#[test] -fn spread_overriding_properties() { - let scenario = r#" - var a = { x: 0, y: 0 }; - var aWithOverrides = { ...a, ...{ x: 1, y: 2 } }; - - (aWithOverrides.x === 1) && (aWithOverrides.y === 2) - "#; - assert_eq!(&exec(scenario), "true"); -} - -#[test] -fn spread_getters_in_initializer() { - let scenario = r#" - var a = { x: 42 }; - var aWithXGetter = { ...a, get x() { throw new Error('not thrown yet') } }; - "#; - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -fn spread_getters_in_object() { - let scenario = r#" - var a = { x: 42 }; - var aWithXGetter = { ...a, ... { get x() { throw new Error('not thrown yet') } } }; - "#; - assert_eq!(&exec(scenario), "Error: not thrown yet"); -} - -#[test] -fn spread_setters() { - let scenario = r#" - var z = { set x(nexX) { throw new Error() }, ... { x: 1 } }; - "#; - assert_eq!(&exec(scenario), "undefined"); -} - -#[test] -fn spread_null_and_undefined_ignored() { - let scenario = r#" - var a = { ...null, ...undefined }; - var count = 0; - - for (key in a) { count++; } - - count === 0 - "#; - - assert_eq!(&exec(scenario), "true"); -} - -#[test] -fn template_literal() { - let scenario = r#" - let a = 10; - `result: ${a} and ${a+10}`; - "#; - - assert_eq!(&exec(scenario), "\"result: 10 and 20\""); -} - -#[test] -fn assignmentoperator_lhs_not_defined() { - let scenario = r#" - try { - a += 5 - } catch (err) { - err.toString() - } - "#; - - assert_eq!(&exec(scenario), "\"ReferenceError: a is not defined\""); -} - -#[test] -fn assignmentoperator_rhs_throws_error() { - let scenario = r#" - try { - let a; - a += b - } catch (err) { - err.toString() - } - "#; - - assert_eq!(&exec(scenario), "\"ReferenceError: b is not defined\""); -} - -#[test] -fn instanceofoperator_rhs_not_object() { - let scenario = r#" - try { - let s = new String(); - s instanceof 1 - } catch (err) { - err.toString() - } - "#; - - assert_eq!( - &exec(scenario), - "\"TypeError: right-hand side of 'instanceof' should be an object, got `number`\"" - ); -} - -#[test] -fn instanceofoperator_rhs_not_callable() { - let scenario = r#" - try { - let s = new String(); - s instanceof {} - } catch (err) { - err.toString() - } - "#; - - assert_eq!( - &exec(scenario), - "\"TypeError: right-hand side of 'instanceof' is not callable\"" - ); -} - -#[test] -fn logical_nullish_assignment() { - let scenario = r#" - let a = undefined; - a ??= 10; - a; - "#; - - assert_eq!(&exec(scenario), "10"); - - let scenario = r#" - let a = 20; - a ??= 10; - a; - "#; - - assert_eq!(&exec(scenario), "20"); -} - -#[test] -fn logical_assignment() { - let scenario = r#" - let a = false; - a &&= 10; - a; - "#; - - assert_eq!(&exec(scenario), "false"); - - let scenario = r#" - let a = 20; - a &&= 10; - a; - "#; - - assert_eq!(&exec(scenario), "10"); - - let scenario = r#" - let a = null; - a ||= 10; - a; - "#; - - assert_eq!(&exec(scenario), "10"); - let scenario = r#" - let a = 20; - a ||= 10; - a; - "#; - - assert_eq!(&exec(scenario), "20"); -} - -#[test] -fn duplicate_function_name() { - let scenario = r#" - function f () {} - function f () {return 12;} - f() - "#; - - assert_eq!(&exec(scenario), "12"); -} - -#[test] -fn spread_with_new() { - let scenario = r#" - function F(m) { - this.m = m; - } - function f(...args) { - return new F(...args); - } - let a = f('message'); - a.m; - "#; - assert_eq!(&exec(scenario), r#""message""#); -} - -#[test] -fn spread_with_call() { - let scenario = r#" - function f(m) { - return m; - } - function g(...args) { - return f(...args); - } - let a = g('message'); - a; - "#; - assert_eq!(&exec(scenario), r#""message""#); -} - -#[test] -fn unary_operations_on_this() { - // https://tc39.es/ecma262/#sec-assignment-operators-static-semantics-early-errors - let mut context = Context::default(); - let test_cases = [ - ("++this", "1:1"), - ("--this", "1:1"), - ("this++", "1:5"), - ("this--", "1:5"), - ]; - for (case, pos) in &test_cases { - let string = forward(&mut context, case); - assert!(string.starts_with("Uncaught SyntaxError: ")); - assert!(string.contains(pos)); - } -} - -#[test] -fn try_break_finally_edge_cases() { - let scenario = r#" - var a; - var b; - { - while (true) { - try { - try { - break; - } catch(a) { - } finally { - } - } finally { - } - } - } - - { - while (true) { - try { - try { - throw "b"; - } catch (b) { - break; - } finally { - a = "foo" - } - } finally { - } - } - } - - { - while (true) { - try { - try { - } catch (c) { - } finally { - b = "bar" - break; - } - } finally { - } - } - } - a + b - "#; - - assert_eq!(&exec(scenario), "\"foobar\""); -} - -#[test] -fn try_break_labels() { - let scenario = r#" - { - var str = ''; - - outer: { - foo: { - bar: { - while (true) { - try { - try { - break; - } catch(f) { - } finally { - str = "fin"; - break foo; - str += "This won't execute"; - } - } finally { - str = str + "ally!" - break bar; - } - } - str += " oh no"; - } - str += " :)"; - } - } - str - } - "#; - - assert_eq!(&exec(scenario), "\"finally! :)\""); -} - -#[test] -fn break_nested_labels_loops_and_try() { - let scenario = r#" - { - let nestedLabels = (x) => { - let str = ""; - foo: { - spacer: { - bar: { - while(true) { - try { - try { - break spacer; - } finally { - str = "foo"; - } - } catch(h) {} finally { - str += "bar" - if (x === true) { - break foo; - } else { - break bar; - } - } - } - str += " broke-while" - } - str += " broke-bar" - } - str += " broke-spacer" - } - str += " broke-foo"; - return str - } - nestedLabels(true) + " != " + nestedLabels(false) - } - "#; - - assert_eq!( - &exec(scenario), - "\"foobar broke-foo != foobar broke-bar broke-spacer broke-foo\"" - ); -} diff --git a/boa_engine/src/tests/control_flow/loops.rs b/boa_engine/src/tests/control_flow/loops.rs new file mode 100644 index 00000000000..3dc3fcc31ad --- /dev/null +++ b/boa_engine/src/tests/control_flow/loops.rs @@ -0,0 +1,848 @@ +use crate::{builtins::error::ErrorKind, run_test, TestAction}; +use indoc::indoc; + +#[test] +fn do_while_loop() { + run_test([ + TestAction::assert_eq( + indoc! {r#" + a = 0; + do { + a += 1; + } while (a < 10); + a + "#}, + 10, + ), + TestAction::assert_eq( + indoc! {r#" + pow = 0; + b = 1; + do { + pow += 1; + b *= 2; + } while (pow < 8); + b + "#}, + 256, + ), + ]); +} + +#[test] +fn do_while_loop_at_least_once() { + run_test([TestAction::assert_eq( + indoc! {r#" + a = 0; + do + { + a += 1; + } + while (false); + a + "#}, + 1, + )]); +} + +#[test] +fn do_while_post_inc() { + run_test([TestAction::assert_eq( + indoc! {r#" + var i = 0; + do {} while(i++ < 10) i; + "#}, + 11, + )]); +} + +#[test] +fn do_while_in_block() { + run_test([TestAction::assert_eq( + indoc! {r#" + { + var i = 0; + do { + i += 1; + } + while(false); + i; + } + "#}, + 1, + )]); +} + +#[test] +fn for_loop() { + run_test([ + TestAction::assert_eq( + indoc! {r#" + { + const a = ['h', 'e', 'l', 'l', 'o']; + let b = ''; + for (let i = 0; i < a.length; i++) { + b = b + a[i]; + } + b + } + "#}, + "hello", + ), + TestAction::assert_eq( + indoc! {r#" + { + let a = 0; + let i = 0; + for (;i < 10;) { + a = a + i; + i++; + } + a + } + "#}, + 45, + ), + TestAction::assert_eq( + indoc! {r#" + { + let a = 0 + for (;false;) { + a++; + } + a + } + "#}, + 0, + ), + ]); +} + +#[test] +fn for_loop_iteration_variable_does_not_leak() { + run_test([TestAction::assert_native_error( + indoc! {r#" + for (let i = 0;false;) {} + i + "#}, + ErrorKind::Reference, + "i is not defined", + )]); +} + +#[test] +fn test_invalid_break_target() { + run_test([TestAction::assert_native_error( + indoc! {r#" + while (false) { + break nonexistent; + } + "#}, + ErrorKind::Syntax, + "undefined break target: nonexistent at position: 1:1", + )]); +} + +#[test] +fn try_break_finally_edge_cases() { + let scenario = r#" + var a; + var b; + { + while (true) { + try { + try { + break; + } catch(a) { + } finally { + } + } finally { + } + } + } + + { + while (true) { + try { + try { + throw "b"; + } catch (b) { + break; + } finally { + a = "foo" + } + } finally { + } + } + } + + { + while (true) { + try { + try { + } catch (c) { + } finally { + b = "bar" + break; + } + } finally { + } + } + } + a + b + "#; + + run_test([TestAction::assert_eq(scenario, "foobar")]); +} + +#[test] +fn try_break_labels() { + let scenario = r#" + { + var str = ''; + + outer: { + foo: { + bar: { + while (true) { + try { + try { + break; + } catch(f) { + } finally { + str = "fin"; + break foo; + str += "This won't execute"; + } + } finally { + str = str + "ally!" + break bar; + } + } + str += " oh no"; + } + str += " :)"; + } + } + str + } + "#; + + run_test([TestAction::assert_eq(scenario, "finally! :)")]); +} + +#[test] +fn break_nested_labels_loops_and_try() { + let scenario = r#" + function nestedLabels(x) { + let str = ""; + foo: { + spacer: { + bar: { + while(true) { + try { + try { + break spacer; + } finally { + str = "foo"; + } + } catch(h) {} finally { + str += "bar" + if (x === true) { + break foo; + } else { + break bar; + } + } + } + str += " broke-while" + } + str += " broke-bar" + } + str += " broke-spacer" + } + str += " broke-foo"; + return str + } + "#; + + run_test([ + TestAction::run(scenario), + TestAction::assert_eq("nestedLabels(true)", "foobar broke-foo"), + TestAction::assert_eq( + "nestedLabels(false)", + "foobar broke-bar broke-spacer broke-foo", + ), + ]); +} + +#[test] +fn break_environment_gauntlet() { + // test that break handles popping environments correctly. + let scenario = r#" + let a = 0; + + while(true) { + break; + } + + while(true) break + + do {break} while(true); + + while (a < 3) { + let a = 1; + if (a == 1) { + break; + } + + let b = 2; + } + + { + b = 0; + do { + b = 2 + if (b == 2) { + break; + } + b++ + } while( i < 3); + + let c = 1; + } + + { + for (let r = 0; r< 3; r++) { + if (r == 2) { + break; + } + } + } + + basic: for (let a = 0; a < 2; a++) { + break; + } + + { + let result = true; + { + let x = 2; + L: { + let x = 3; + result &&= (x === 3); + break L; + result &&= (false); + } + result &&= (x === 2); + } + result; + } + + { + var str = ""; + + far_outer: { + outer: for (let i = 0; i < 5; i++) { + inner: for (let b = 5; b < 10; b++) { + if (b === 7) { + break far_outer; + } + str = str + b; + } + str = str + i; + } + } + str + } + + { + for (let r = 0; r < 2; r++) { + str = str + r + } + } + + { + let result = ""; + lab_block: { + try { + result = "try_block"; + break lab_block; + result = "did not break" + } catch (err) {} + } + str = str + result + str + } + "#; + + run_test([TestAction::assert_eq(scenario, "5601try_block")]); +} + +#[test] +fn while_loop_late_break() { + // Ordering with statement before the break. + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 1; + while (a < 5) { + a++; + if (a == 3) { + break; + } + } + a; + "#}, + 3, + )]); +} + +#[test] +fn while_loop_early_break() { + // Ordering with statements after the break. + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 1; + while (a < 5) { + if (a == 3) { + break; + } + a++; + } + a; + "#}, + 3, + )]); +} + +#[test] +fn for_loop_break() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 1; + for (; a < 5; a++) { + if (a == 3) { + break; + } + } + a; + "#}, + 3, + )]); +} + +#[test] +fn for_loop_return() { + run_test([TestAction::assert_eq( + indoc! {r#" + function foo() { + for (let a = 1; a < 5; a++) { + if (a == 3) { + return a; + } + } + } + foo(); + "#}, + 3, + )]); +} + +#[test] +fn do_loop_late_break() { + // Ordering with statement before the break. + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 1; + do { + a++; + if (a == 3) { + break; + } + } while (a < 5); + a; + "#}, + 3, + )]); +} + +#[test] +fn do_loop_early_break() { + // Ordering with statements after the break. + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 1; + do { + if (a == 3) { + break; + } + a++; + } while (a < 5); + a; + "#}, + 3, + )]); +} + +#[test] +fn break_out_of_inner_loop() { + run_test([ + TestAction::run(indoc! {r#" + var a = 0, b = 0; + for (let i = 0; i < 2; i++) { + a++; + for (let j = 0; j < 10; j++) { + b++; + if (j == 3) + break; + } + a++; + } + "#}), + TestAction::assert_eq("a", 4), + TestAction::assert_eq("b", 8), + ]); +} + +#[test] +fn continue_inner_loop() { + run_test([ + TestAction::run(indoc! {r#" + var a = 0, b = 0; + for (let i = 0; i < 2; i++) { + a++; + for (let j = 0; j < 10; j++) { + if (j < 3) + continue; + b++; + } + a++; + } + "#}), + TestAction::assert_eq("a", 4), + TestAction::assert_eq("b", 14), + ]); +} + +#[test] +fn for_loop_continue_out_of_switch() { + run_test([ + TestAction::run(indoc! {r#" + var a = 0, b = 0, c = 0; + for (let i = 0; i < 3; i++) { + a++; + switch (i) { + case 0: + continue; + c++; + case 1: + continue; + case 5: + c++; + } + b++; + } + "#}), + TestAction::assert_eq("a", 3), + TestAction::assert_eq("b", 1), + TestAction::assert_eq("c", 0), + ]); +} + +#[test] +fn while_loop_continue() { + run_test([ + TestAction::run(indoc! {r#" + var i = 0, a = 0, b = 0; + while (i < 3) { + i++; + if (i < 2) { + a++; + continue; + } + b++; + } + "#}), + TestAction::assert_eq("a", 1), + TestAction::assert_eq("b", 2), + ]); +} + +#[test] +fn do_while_loop_continue() { + run_test([ + TestAction::run(indoc! {r#" + var i = 0, a = 0, b = 0; + do { + i++; + if (i < 2) { + a++; + continue; + } + b++; + } while (i < 3) + "#}), + TestAction::assert_eq("a", 1), + TestAction::assert_eq("b", 2), + ]); +} + +#[test] +fn for_of_loop_declaration() { + run_test([ + TestAction::run(indoc! {r#" + var result = 0; + for (i of [1, 2, 3]) { + result = i; + } + "#}), + TestAction::assert_eq("result", 3), + TestAction::assert_eq("i", 3), + ]); +} + +#[test] +fn for_of_loop_var() { + run_test([ + TestAction::run(indoc! {r#" + var result = 0; + for (var i of [1, 2, 3]) { + result = i; + } + "#}), + TestAction::assert_eq("result", 3), + TestAction::assert_eq("i", 3), + ]); +} + +#[test] +fn for_of_loop_let() { + run_test([ + TestAction::run(indoc! {r#" + var result = 0; + for (let i of [1, 2, 3]) { + result = i; + } + "#}), + TestAction::assert_eq("result", 3), + TestAction::assert_native_error("i", ErrorKind::Reference, "i is not defined"), + ]); +} + +#[test] +fn for_of_loop_const() { + run_test([ + TestAction::run(indoc! {r#" + var result = 0; + for (let i of [1, 2, 3]) { + result = i; + } + "#}), + TestAction::assert_eq("result", 3), + TestAction::assert_native_error("i", ErrorKind::Reference, "i is not defined"), + ]); +} + +#[test] +fn for_of_loop_break() { + run_test([ + TestAction::run(indoc! {r#" + var result = 0; + for (var i of [1, 2, 3]) { + if (i > 1) + break; + result = i + } + "#}), + TestAction::assert_eq("result", 1), + TestAction::assert_eq("i", 2), + ]); +} + +#[test] +fn for_of_loop_continue() { + run_test([ + TestAction::run(indoc! {r#" + var result = 0; + for (var i of [1, 2, 3]) { + if (i == 3) + continue; + result = i + } + "#}), + TestAction::assert_eq("result", 2), + TestAction::assert_eq("i", 3), + ]); +} + +#[test] +fn for_of_loop_return() { + run_test([ + TestAction::run(indoc! {r#" + function foo() { + for (i of [1, 2, 3]) { + if (i > 1) + return i; + } + } + "#}), + TestAction::assert_eq("foo()", 2), + ]); +} + +#[test] +fn for_loop_break_label() { + run_test([TestAction::assert_eq( + indoc! {r#" + var str = ""; + + outer: for (let i = 0; i < 5; i++) { + inner: for (let b = 0; b < 5; b++) { + if (b === 2) { + break outer; + } + str = str + b; + } + str = str + i; + } + str + "#}, + "01", + )]); +} + +#[test] +fn for_loop_continue_label() { + run_test([TestAction::assert_eq( + indoc! {r#" + var count = 0; + label: for (let x = 0; x < 10;) { + while (true) { + x++; + count++; + continue label; + } + } + count + "#}, + 10, + )]); +} + +#[test] +fn for_in_declaration() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let result = []; + let obj = { a: "a", b: 2}; + var i; + for (i in obj) { + result = result.concat([i]); + } + "#}), + TestAction::assert("arrayEquals(result, ['a', 'b'])"), + ]); +} + +#[test] +fn for_in_var_object() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let result = []; + let obj = { a: "a", b: 2}; + for (var i in obj) { + result = result.concat([i]); + } + "#}), + TestAction::assert("arrayEquals(result, ['a', 'b'])"), + ]); +} + +#[test] +fn for_in_var_array() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let result = []; + let arr = ["a", "b"]; + for (var i in arr) { + result = result.concat([i]); + } + "#}), + TestAction::assert("arrayEquals(result, ['0', '1'])"), + ]); +} + +#[test] +fn for_in_let_object() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let result = []; + let obj = { a: "a", b: 2}; + for (let i in obj) { + result = result.concat([i]); + } + "#}), + TestAction::assert("arrayEquals(result, ['a', 'b'])"), + ]); +} + +#[test] +fn for_in_const_array() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + let result = []; + let arr = ["a", "b"]; + for (const i in arr) { + result = result.concat([i]); + } + "#}), + TestAction::assert("arrayEquals(result, ['0', '1'])"), + ]); +} + +#[test] +fn for_in_break_label() { + run_test([TestAction::assert_eq( + indoc! {r#" + var str = ""; + + outer: for (let i in [1, 2]) { + inner: for (let b in [2, 3, 4]) { + if (b === "1") { + break outer; + } + str = str + b; + } + str = str + i; + } + str + "#}, + "0", + )]); +} + +#[test] +fn for_in_continue_label() { + run_test([TestAction::assert_eq( + indoc! {r#" + var str = ""; + + outer: for (let i in [1, 2]) { + inner: for (let b in [2, 3, 4]) { + if (b === "1") { + continue outer; + } + str = str + b; + } + str = str + i; + } + str + "#}, + "00", + )]); +} diff --git a/boa_engine/src/tests/control_flow/mod.rs b/boa_engine/src/tests/control_flow/mod.rs new file mode 100644 index 00000000000..e8b4e824383 --- /dev/null +++ b/boa_engine/src/tests/control_flow/mod.rs @@ -0,0 +1,445 @@ +use indoc::indoc; +mod loops; + +use crate::{builtins::error::ErrorKind, run_test, TestAction}; + +#[test] +fn test_invalid_break() { + run_test([TestAction::assert_native_error( + "break;", + ErrorKind::Syntax, + "illegal break statement at position: 1:1", + )]); +} + +#[test] +fn test_invalid_continue_target() { + run_test([TestAction::assert_native_error( + indoc! {r#" + while (false) { + continue nonexistent; + } + "#}, + ErrorKind::Syntax, + "undefined continue target: nonexistent at position: 1:1", + )]); +} + +#[test] +fn test_invalid_continue() { + run_test([TestAction::assert_native_error( + "continue;", + ErrorKind::Syntax, + "illegal continue statement at position: 1:1", + )]); +} + +#[test] +fn test_labelled_block() { + run_test([TestAction::assert(indoc! {r#" + let result = true; + { + let x = 2; + L: { + let x = 3; + result &&= (x === 3); + break L; + result &&= (false); + } + result &&= (x === 2); + } + result; + "#})]); +} + +#[test] +fn simple_try() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + a = 20; + } catch { + a = 30; + } + + a; + "#}, + 20, + )]); +} + +#[test] +fn finally() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + a = 20; + } finally { + a = 30; + } + + a; + "#}, + 30, + )]); +} + +#[test] +fn catch_finally() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + a = 20; + } catch { + a = 40; + } finally { + a = 30; + } + + a; + "#}, + 30, + )]); +} + +#[test] +fn catch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + throw "error"; + } catch { + a = 20; + } + + a; + "#}, + 20, + )]); +} + +#[test] +fn catch_binding() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + throw 20; + } catch(err) { + a = err; + } + + a; + "#}, + 20, + )]); +} + +#[test] +fn catch_binding_pattern_object() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + throw { + n: 30, + }; + } catch ({ n }) { + a = n; + } + + a; + "#}, + 30, + )]); +} + +#[test] +fn catch_binding_pattern_array() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + throw [20, 30]; + } catch ([, n]) { + a = n; + } + + a; + "#}, + 30, + )]); +} + +#[test] +fn catch_binding_finally() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + try { + throw 20; + } catch(err) { + a = err; + } finally { + a = 30; + } + + a; + "#}, + 30, + )]); +} + +#[test] +fn single_case_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + switch (a) { + case 10: + a = 20; + break; + } + + a; + "#}, + 20, + )]); +} + +#[test] +fn no_cases_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + switch (a) { + } + + a; + "#}, + 10, + )]); +} + +#[test] +fn no_true_case_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + switch (a) { + case 5: + a = 15; + break; + } + + a; + "#}, + 10, + )]); +} + +#[test] +fn two_case_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + switch (a) { + case 5: + a = 15; + break; + case 10: + a = 20; + break; + } + + a; + "#}, + 20, + )]); +} + +#[test] +fn two_case_no_break_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + let b = 10; + + switch (a) { + case 10: + a = 150; + case 20: + b = 150; + break; + } + + a + b; + "#}, + 300, + )]); +} + +#[test] +fn three_case_partial_fallthrough() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + let b = 10; + + switch (a) { + case 10: + a = 150; + case 20: + b = 150; + break; + case 15: + b = 1000; + break; + } + + a + b; + "#}, + 300, + )]); +} + +#[test] +fn default_taken_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + + switch (a) { + case 5: + a = 150; + break; + default: + a = 70; + } + + a; + "#}, + 70, + )]); +} + +#[test] +fn default_not_taken_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 5; + + switch (a) { + case 5: + a = 150; + break; + default: + a = 70; + } + + a; + "#}, + 150, + )]); +} + +#[test] +fn string_switch() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = "hello"; + + switch (a) { + case "hello": + a = "world"; + break; + default: + a = "hi"; + } + + a; + "#}, + "world", + )]); +} + +#[test] +fn bigger_switch_example() { + run_test([ + TestAction::run(indoc! {r#" + function f(a) { + let b; + + switch (a) { + case 0: + b = "Mon"; + break; + case 1: + b = "Tue"; + break; + case 2: + b = "Wed"; + break; + case 3: + b = "Thurs"; + break; + case 4: + b = "Fri"; + break; + case 5: + b = "Sat"; + break; + case 6: + b = "Sun"; + break; + } + return b; + } + "#}), + TestAction::assert_eq("f(0)", "Mon"), + TestAction::assert_eq("f(1)", "Tue"), + TestAction::assert_eq("f(2)", "Wed"), + TestAction::assert_eq("f(3)", "Thurs"), + TestAction::assert_eq("f(4)", "Fri"), + TestAction::assert_eq("f(5)", "Sat"), + TestAction::assert_eq("f(6)", "Sun"), + ]); +} + +#[test] +fn break_labelled_if_statement() { + run_test([TestAction::assert_eq( + indoc! {r#" + let result = ""; + bar: if(true) { + result = "foo"; + break bar; + result = 'this will not be executed'; + } + result + "#}, + "foo", + )]); +} + +#[test] +fn break_labelled_try_statement() { + run_test([TestAction::assert_eq( + indoc! {r#" + let result = "" + one: try { + result = "foo"; + break one; + result = "did not break" + } catch (err) { + console.log(err) + } + result + "#}, + "foo", + )]); +} diff --git a/boa_engine/src/tests/function.rs b/boa_engine/src/tests/function.rs new file mode 100644 index 00000000000..3364448006b --- /dev/null +++ b/boa_engine/src/tests/function.rs @@ -0,0 +1,158 @@ +use crate::{builtins::error::ErrorKind, run_test, JsValue, TestAction}; +use indoc::indoc; + +#[test] +fn function_declaration_returns_undefined() { + run_test([TestAction::assert_eq( + "function abc() {}", + JsValue::undefined(), + )]); +} + +#[test] +fn empty_function_returns_undefined() { + run_test([TestAction::assert_eq( + "(function () {}) ()", + JsValue::undefined(), + )]); +} + +#[test] +fn property_accessor_member_expression_dot_notation_on_function() { + run_test([TestAction::assert_eq( + indoc! {r#" + function asd () {}; + asd.name; + "#}, + "asd", + )]); +} + +#[test] +fn property_accessor_member_expression_bracket_notation_on_function() { + run_test([TestAction::assert_eq( + indoc! {r#" + function asd () {}; + asd['name']; + "#}, + "asd", + )]); +} + +#[test] +fn early_return() { + run_test([ + TestAction::assert(indoc! {r#" + function early_return() { + if (true) { + return true; + } + return false; + } + early_return() + "#}), + TestAction::assert_eq( + indoc! {r#" + function nested_fnct() { + return "nested"; + } + function outer_fnct() { + nested_fnct(); + return "outer"; + } + outer_fnct() + "#}, + "outer", + ), + ]); +} + +#[test] +fn should_set_this_value() { + run_test([ + TestAction::run(indoc! {r#" + function Foo() { + this.a = "a"; + this.b = "b"; + } + + var bar = new Foo(); + "#}), + TestAction::assert_eq("bar.a", "a"), + TestAction::assert_eq("bar.b", "b"), + ]); +} + +#[test] +fn should_type_error_when_new_is_not_constructor() { + run_test([TestAction::assert_native_error( + "new ''()", + ErrorKind::Type, + "not a constructor", + )]); +} + +#[test] +fn new_instance_should_point_to_prototype() { + // A new instance should point to a prototype object created with the constructor function + run_test([ + TestAction::run(indoc! {r#" + function Foo() {} + var bar = new Foo(); + "#}), + TestAction::assert("Object.getPrototypeOf(bar) == Foo.prototype "), + ]); +} + +#[test] +fn calling_function_with_unspecified_arguments() { + run_test([TestAction::assert_eq( + indoc! {r#" + function test(a, b) { + return b; + } + + test(10) + "#}, + JsValue::undefined(), + )]); +} + +#[test] +fn not_a_function() { + run_test([ + TestAction::run(indoc! {r#" + let a = {}; + let b = true; + "#}), + TestAction::assert_native_error("a()", ErrorKind::Type, "not a callable function"), + TestAction::assert_native_error("a.a()", ErrorKind::Type, "not a callable function"), + TestAction::assert_native_error("b()", ErrorKind::Type, "not a callable function"), + ]); +} + +#[test] +fn strict_mode_dup_func_parameters() { + // Checks that a function cannot contain duplicate parameter + // names in strict mode code as per https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors. + run_test([TestAction::assert_native_error( + indoc! {r#" + 'use strict'; + function f(a, b, b) {} + "#}, + ErrorKind::Syntax, + "Duplicate parameter name not allowed in this context at position: 2:12", + )]); +} + +#[test] +fn duplicate_function_name() { + run_test([TestAction::assert_eq( + indoc! {r#" + function f () {} + function f () {return 12;} + f() + "#}, + 12, + )]); +} diff --git a/boa_engine/src/tests/mod.rs b/boa_engine/src/tests/mod.rs new file mode 100644 index 00000000000..211bacadf95 --- /dev/null +++ b/boa_engine/src/tests/mod.rs @@ -0,0 +1,477 @@ +use indoc::indoc; + +mod control_flow; +mod function; +mod operators; +mod spread; + +use crate::{builtins::error::ErrorKind, run_test, JsValue, TestAction}; + +#[test] +fn length_correct_value_on_string_literal() { + run_test([TestAction::assert_eq("'hello'.length", 5)]); +} + +#[test] +fn empty_let_decl_undefined() { + run_test([TestAction::assert_eq("let a; a", JsValue::undefined())]); +} + +#[test] +fn semicolon_expression_stop() { + run_test([TestAction::assert_eq( + indoc! {r#" + var a = 1; + + 1; + a + "#}, + 1, + )]); +} + +#[test] +fn empty_var_decl_undefined() { + run_test([TestAction::assert_eq("var a; a", JsValue::undefined())]); +} + +#[test] +fn identifier_on_global_object_undefined() { + run_test([TestAction::assert_native_error( + "bar;", + ErrorKind::Reference, + "bar is not defined", + )]); +} + +#[test] +fn object_field_set() { + run_test([TestAction::assert_eq( + indoc! {r#" + let m = {}; + m['key'] = 22; + m['key'] + "#}, + 22, + )]); +} + +#[test] +fn array_field_set() { + run_test([ + TestAction::run("let m;"), + // element_changes + TestAction::assert_eq( + indoc! {r#" + m = [1, 2, 3]; + m[1] = 5; + m[1] + "#}, + 5, + ), + // length changes + TestAction::assert_eq( + indoc! {r#" + m = [1, 2, 3]; + m[10] = 52; + m.length + "#}, + 11, + ), + // negative_index_wont_affect_length + TestAction::assert_eq( + indoc! {r#" + m = [1, 2, 3]; + m[-11] = 5; + m.length + "#}, + 3, + ), + // non_num_key_wont_affect_length + TestAction::assert_eq( + indoc! {r#" + m = [1, 2, 3]; + m["magic"] = 5; + m.length + "#}, + 3, + ), + ]); +} + +#[test] +fn var_decl_hoisting_simple() { + run_test([TestAction::assert_eq("x = 5; var x; x", 5)]); +} + +#[test] +fn var_decl_hoisting_with_initialization() { + run_test([TestAction::assert_eq("x = 5; var x = 10; x", 10)]); +} + +#[test] +fn var_decl_hoisting_2_variables_hoisting() { + run_test([TestAction::assert_eq( + indoc! {r#" + x = y; + + var x = 10; + var y = 5; + + x; + "#}, + 10, + )]); +} + +#[test] +fn var_decl_hoisting_2_variables_hoisting_2() { + run_test([TestAction::assert_eq( + indoc! {r#" + var x = y; + + var y = 5; + x; + "#}, + JsValue::undefined(), + )]); +} + +#[test] +fn var_decl_hoisting_2_variables_hoisting_3() { + run_test([TestAction::assert_eq( + indoc! {r#" + let y = x; + x = 5; + + var x = 10; + y; + "#}, + JsValue::undefined(), + )]); +} + +#[test] +fn function_decl_hoisting() { + run_test([ + TestAction::assert_eq( + indoc! {r#" + { + let a = hello(); + function hello() { return 5 } + + a; + } + "#}, + 5, + ), + TestAction::assert_eq( + indoc! {r#" + { + x = hello(); + + function hello() { return 5 } + var x; + x; + } + "#}, + 5, + ), + TestAction::assert_eq( + indoc! {r#" + { + hello = function() { return 5 } + x = hello(); + + x; + } + "#}, + 5, + ), + TestAction::assert_eq( + indoc! {r#" + { + let x = b(); + + function a() {return 5} + function b() {return a()} + + x; + } + "#}, + 5, + ), + TestAction::assert_eq( + indoc! {r#" + { + let x = b(); + + function b() {return a()} + function a() {return 5} + + x; + } + "#}, + 5, + ), + ]); +} + +#[test] +fn check_this_binding_in_object_literal() { + run_test([TestAction::assert_eq( + indoc! {r#" + var foo = { + a: 3, + bar: function () { return this.a + 5 } + }; + + foo.bar() + "#}, + 8, + )]); +} + +#[test] +fn array_creation_benchmark() { + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + const finalArr = [ "p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9", "p10", + "p11", "p12", "p13", "p14", "p15", "p16", "p17", "p18", "p19", "p20", + "p21", "p22", "p23", "p24", "p25", "p26", "p27", "p28", "p29", "p30", + "p31", "p32", "p33", "p34", "p35", "p36", "p37", "p38", "p39", "p40", + "p41", "p42", "p43", "p44", "p45", "p46", "p47", "p48", "p49", "p50", + "p51", "p52", "p53", "p54", "p55", "p56", "p57", "p58", "p59", "p60", + "p61", "p62", "p63", "p64", "p65", "p66", "p67", "p68", "p69", "p70", + "p71", "p72", "p73", "p74", "p75", "p76", "p77", "p78", "p79", "p80", + "p81", "p82", "p83", "p84", "p85", "p86", "p87", "p88", "p89", "p90", + "p91", "p92", "p93", "p94", "p95", "p96", "p97", "p98", "p99", "p100", + "p101", "p102", "p103", "p104", "p105", "p106", "p107", "p108", "p109", "p110", + "p111", "p112", "p113", "p114", "p115", "p116", "p117", "p118", "p119", "p120", + "p121", "p122", "p123", "p124", "p125", "p126", "p127", "p128", "p129", "p130", + "p131", "p132", "p133", "p134", "p135", "p136", "p137", "p138", "p139", "p140", + "p141", "p142", "p143", "p144", "p145", "p146", "p147", "p148", "p149", "p150", + "p151", "p152", "p153", "p154", "p155", "p156", "p157", "p158", "p159", "p160", + "p161", "p162", "p163", "p164", "p165", "p166", "p167", "p168", "p169", "p170", + "p171", "p172", "p173", "p174", "p175", "p176", "p177", "p178", "p179", "p180", + "p181", "p182", "p183", "p184", "p185", "p186", "p187", "p188", "p189", "p190", + "p191", "p192", "p193", "p194", "p195", "p196", "p197", "p198", "p199", "p200", + "p201", "p202", "p203", "p204", "p205", "p206", "p207", "p208", "p209", "p210", + "p211", "p212", "p213", "p214", "p215", "p216", "p217", "p218", "p219", "p220", + "p221", "p222", "p223", "p224", "p225", "p226", "p227", "p228", "p229", "p230", + "p231", "p232", "p233", "p234", "p235", "p236", "p237", "p238", "p239", "p240", + "p241", "p242", "p243", "p244", "p245", "p246", "p247", "p248", "p249", "p250", + "p251", "p252", "p253", "p254", "p255", "p256", "p257", "p258", "p259", "p260", + "p261", "p262", "p263", "p264", "p265", "p266", "p267", "p268", "p269", "p270", + "p271", "p272", "p273", "p274", "p275", "p276", "p277", "p278", "p279", "p280", + "p281", "p282", "p283", "p284", "p285", "p286", "p287", "p288", "p289", "p290", + "p291", "p292", "p293", "p294", "p295", "p296", "p297", "p298", "p299", "p300", + "p301", "p302", "p303", "p304", "p305", "p306", "p307", "p308", "p309", "p310", + "p311", "p312", "p313", "p314", "p315", "p316", "p317", "p318", "p319", "p320", + "p321", "p322", "p323", "p324", "p325", "p326", "p327", "p328", "p329", "p330", + "p331", "p332", "p333", "p334", "p335", "p336", "p337", "p338", "p339", "p340", + "p341", "p342", "p343", "p344", "p345", "p346", "p347", "p348", "p349", "p350", + "p351", "p352", "p353", "p354", "p355", "p356", "p357", "p358", "p359", "p360", + "p361", "p362", "p363", "p364", "p365", "p366", "p367", "p368", "p369", "p370", + "p371", "p372", "p373", "p374", "p375", "p376", "p377", "p378", "p379", "p380", + "p381", "p382", "p383", "p384", "p385", "p386", "p387", "p388", "p389", "p390", + "p391", "p392", "p393", "p394", "p395", "p396", "p397", "p398", "p399", "p400", + "p401", "p402", "p403", "p404", "p405", "p406", "p407", "p408", "p409", "p410", + "p411", "p412", "p413", "p414", "p415", "p416", "p417", "p418", "p419", "p420", + "p421", "p422", "p423", "p424", "p425", "p426", "p427", "p428", "p429", "p430", + "p431", "p432", "p433", "p434", "p435", "p436", "p437", "p438", "p439", "p440", + "p441", "p442", "p443", "p444", "p445", "p446", "p447", "p448", "p449", "p450", + "p451", "p452", "p453", "p454", "p455", "p456", "p457", "p458", "p459", "p460", + "p461", "p462", "p463", "p464", "p465", "p466", "p467", "p468", "p469", "p470", + "p471", "p472", "p473", "p474", "p475", "p476", "p477", "p478", "p479", "p480", + "p481", "p482", "p483", "p484", "p485", "p486", "p487", "p488", "p489", "p490", + "p491", "p492", "p493", "p494", "p495", "p496", "p497", "p498", "p499", "p500" ]; + + let testArr = []; + for (let a = 0; a <= 500; a++) { + testArr[a] = ('p' + a); + } + + arrayEquals(testArr, finalArr) + "#}), + ]); +} + +#[test] +fn array_pop_benchmark() { + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + let testArray = [83, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, + 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, + 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, + 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, + 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, + 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, + 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, + 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, + 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, 45, 93, + 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, 23, + 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, 36, + 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, + 45, 93, 17, 28, 83, 62, 99, 36, 28, 93, 27, 29, 2828, 234, + 23, 56, 32, 56, 67, 77, 32, 45, 93, 17, 28, 83, 62, 99, + 36, 28, 93, 27, 29, 2828, 234, 23, 56, 32, 56, 67, 77, 32, + 45, 93, 17, 28, 83, 62, 99, 36, 28]; + + while (testArray.length > 0) { + testArray.pop(); + } + + arrayEquals(testArray, []) + "#}), + ]); +} + +#[test] +fn number_object_access_benchmark() { + run_test([TestAction::assert_eq( + indoc! {r#" + new Number( + new Number( + new Number( + new Number(100).valueOf() - 10.5 + ).valueOf() + 100 + ).valueOf() * 1.6 + ).valueOf() + "#}, + 303.2, + )]); +} + +#[test] +fn multiline_str_concat() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 'hello ' + + 'world'; + a + "#}, + "hello world", + )]); +} + +#[test] +fn result_of_empty_block() { + run_test([TestAction::assert_eq("{}", JsValue::undefined())]); +} + +#[test] +fn undefined_constant() { + run_test([TestAction::assert_eq("undefined", JsValue::undefined())]); +} + +#[test] +fn identifier_op() { + run_test([TestAction::assert_native_error( + "break = 1", + ErrorKind::Syntax, + r#"expected token 'identifier', got '=' in identifier parsing at line 1, col 7"#, + )]); +} + +#[test] +fn strict_mode_octal() { + // Checks as per https://tc39.es/ecma262/#sec-literals-numeric-literals that 0 prefix + // octal number literal syntax is a syntax error in strict mode. + run_test([TestAction::assert_native_error( + indoc! {r#" + 'use strict'; + var n = 023; + "#}, + ErrorKind::Syntax, + "implicit octal literals are not allowed in strict mode at position: 2:9", + )]); +} + +#[test] +fn strict_mode_with() { + // Checks as per https://tc39.es/ecma262/#sec-with-statement-static-semantics-early-errors + // that a with statement is an error in strict mode code. + run_test([TestAction::assert_native_error( + indoc! {r#" + 'use strict'; + function f(x, o) { + with (o) { + console.log(x); + } + } + "#}, + ErrorKind::Syntax, + "unexpected token 'with', primary expression at line 3, col 5", + )]); +} + +#[test] +fn strict_mode_reserved_name() { + // Checks that usage of a reserved keyword for an identifier name is + // an error in strict mode code as per https://tc39.es/ecma262/#sec-strict-mode-of-ecmascript. + + let cases = [ + ("var implements = 10;", "unexpected token 'implements', strict reserved word cannot be an identifier at line 1, col 19"), + ("var interface = 10;", "unexpected token 'interface', strict reserved word cannot be an identifier at line 1, col 19"), + ("var package = 10;", "unexpected token 'package', strict reserved word cannot be an identifier at line 1, col 19"), + ("var private = 10;", "unexpected token 'private', strict reserved word cannot be an identifier at line 1, col 19"), + ("var protected = 10;", "unexpected token 'protected', strict reserved word cannot be an identifier at line 1, col 19"), + ("var public = 10;", "unexpected token 'public', strict reserved word cannot be an identifier at line 1, col 19"), + ("var static = 10;", "unexpected token 'static', strict reserved word cannot be an identifier at line 1, col 19"), + ("var eval = 10;", "unexpected token 'eval', binding identifier `eval` not allowed in strict mode at line 1, col 19"), + ("var arguments = 10;", "unexpected token 'arguments', binding identifier `arguments` not allowed in strict mode at line 1, col 19"), + ("var let = 10;", "unexpected token 'let', strict reserved word cannot be an identifier at line 1, col 19"), + ("var yield = 10;", "unexpected token 'yield', strict reserved word cannot be an identifier at line 1, col 19"), + ]; + + run_test(cases.into_iter().map(|(case, msg)| { + TestAction::assert_native_error(format!("'use strict'; {case}"), ErrorKind::Syntax, msg) + })); +} + +#[test] +fn empty_statement() { + run_test([TestAction::assert_eq( + indoc! {r#" + ;;;let a = 10;; + if(a) ; + a + "#}, + 10, + )]); +} + +#[test] +fn tagged_template() { + run_test([ + TestAction::run_harness(), + TestAction::assert(indoc! {r#" + function tag(t, ...args) { + let a = [] + a = a.concat([t[0], t[1], t[2]]); + a = a.concat([t.raw[0], t.raw[1], t.raw[2]]); + a = a.concat([args[0], args[1]]); + return a + } + let a = 10; + + arrayEquals( + tag`result: ${a} \x26 ${a+10}`, + [ "result: ", " & ", "", "result: ", " \\x26 ", "", 10, 20 ] + ) + "#}), + ]); +} + +#[test] +fn template_literal() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 10; + `result: ${a} and ${a+10}`; + "#}, + "result: 10 and 20", + )]); +} diff --git a/boa_engine/src/tests/operators.rs b/boa_engine/src/tests/operators.rs new file mode 100644 index 00000000000..bc90ffecbaf --- /dev/null +++ b/boa_engine/src/tests/operators.rs @@ -0,0 +1,640 @@ +use crate::{builtins::error::ErrorKind, run_test, JsValue, TestAction}; +use indoc::indoc; + +#[test] +fn property_accessor_member_expression_dot_notation_on_string_literal() { + run_test([TestAction::assert_eq("typeof 'asd'.matchAll", "function")]); +} + +#[test] +fn property_accessor_member_expression_bracket_notation_on_string_literal() { + run_test([TestAction::assert_eq( + "typeof 'asd'['matchAll']", + "function", + )]); +} + +#[test] +fn short_circuit_evaluation() { + run_test([ + // OR operation + TestAction::assert("true || true"), + TestAction::assert("true || false"), + TestAction::assert("false || true"), + TestAction::assert("!(false || false)"), + // short circuiting OR. + TestAction::assert_eq( + indoc! {r#" + function add_one(counter) { + counter.value += 1; + return true; + } + let counter = { value: 0 }; + let _ = add_one(counter) || add_one(counter); + counter.value + "#}, + 1, + ), + TestAction::assert_eq( + indoc! {r#" + function add_one(counter) { + counter.value += 1; + return false; + } + let counter = { value: 0 }; + let _ = add_one(counter) || add_one(counter); + counter.value + "#}, + 2, + ), + // AND operation + TestAction::assert("true && true"), + TestAction::assert("!(true && false)"), + TestAction::assert("!(false && true)"), + TestAction::assert("!(false && false)"), + // short circuiting AND + TestAction::assert_eq( + indoc! {r#" + function add_one(counter) { + counter.value += 1; + return true; + } + let counter = { value: 0 }; + let _ = add_one(counter) && add_one(counter); + counter.value + "#}, + 2, + ), + TestAction::assert_eq( + indoc! {r#" + function add_one(counter) { + counter.value += 1; + return false; + } + let counter = { value: 0 }; + let _ = add_one(counter) && add_one(counter); + counter.value + "#}, + 1, + ), + ]); +} + +#[test] +fn tilde_operator() { + run_test([ + // float + TestAction::assert_eq("~(-1.2)", 0), + // numeric + TestAction::assert_eq("~1789", -1790), + // nan + TestAction::assert_eq("~NaN", -1), + // object + TestAction::assert_eq("~{}", -1), + // boolean true + TestAction::assert_eq("~true", -2), + // boolean false + TestAction::assert_eq("~false", -1), + ]); +} + +#[test] +fn assign_operator_precedence() { + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 1; + a = a + 1; + a + "#}, + 2, + )]); +} + +#[test] +fn unary_pre() { + run_test([ + TestAction::assert_eq("{ let a = 5; ++a; a }", 6), + TestAction::assert_eq("{ let a = 5; --a; a }", 4), + TestAction::assert_eq("{ const a = { b: 5 }; ++a.b; a['b'] }", 6), + TestAction::assert_eq("{ const a = { b: 5 }; --a['b']; a.b }", 4), + TestAction::assert_eq("{ let a = 5; ++a }", 6), + TestAction::assert_eq("{ let a = 5; --a }", 4), + TestAction::assert_eq("{ let a = 2147483647; ++a }", 2147483648i64), + TestAction::assert_eq("{ let a = -2147483648; --a }", -2147483649i64), + TestAction::assert_eq( + indoc! {r#" + let a = {[Symbol.toPrimitive]() { return 123; }}; + ++a + "#}, + 124, + ), + TestAction::assert_eq( + indoc! {r#" + let a = {[Symbol.toPrimitive]() { return 123; }}; + ++a + "#}, + 124, + ), + ]); +} + +#[test] +fn invalid_unary_access() { + run_test([ + TestAction::assert_native_error( + "++[]", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:1", + ), + TestAction::assert_native_error( + "[]++", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:3", + ), + TestAction::assert_native_error( + "--[]", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:1", + ), + TestAction::assert_native_error( + "[]--", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:3", + ), + ]); +} + +#[test] +fn unary_operations_on_this() { + // https://tc39.es/ecma262/#sec-assignment-operators-static-semantics-early-errors + run_test([ + TestAction::assert_native_error( + "++this", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:1", + ), + TestAction::assert_native_error( + "--this", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:1", + ), + TestAction::assert_native_error( + "this++", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:5", + ), + TestAction::assert_native_error( + "this--", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:5", + ), + ]); +} + +#[test] +fn typeofs() { + run_test([ + TestAction::assert_eq("typeof String()", "string"), + TestAction::assert_eq("typeof 5", "number"), + TestAction::assert_eq("typeof 0.5", "number"), + TestAction::assert_eq("typeof undefined", "undefined"), + TestAction::assert_eq("typeof true", "boolean"), + TestAction::assert_eq("typeof null", "object"), + TestAction::assert_eq("typeof {}", "object"), + TestAction::assert_eq("typeof Symbol()", "symbol"), + TestAction::assert_eq("typeof function(){}", "function"), + ]); +} + +#[test] +fn unary_post() { + run_test([ + TestAction::assert_eq("{ let a = 5; a++; a }", 6), + TestAction::assert_eq("{ let a = 5; a--; a }", 4), + TestAction::assert_eq("{ const a = { b: 5 }; a.b++; a['b'] }", 6), + TestAction::assert_eq("{ const a = { b: 5 }; a['b']--; a.b }", 4), + TestAction::assert_eq("{ let a = 5; a++ }", 5), + TestAction::assert_eq("{ let a = 5; a-- }", 5), + TestAction::assert_eq("{ let a = 2147483647; a++; a }", 2147483648i64), + TestAction::assert_eq("{ let a = -2147483648; a--; a }", -2147483649i64), + TestAction::assert_eq( + indoc! {r#" + let a = {[Symbol.toPrimitive]() { return 123; }}; + a++ + "#}, + 123, + ), + TestAction::assert_eq( + indoc! {r#" + let a = {[Symbol.toPrimitive]() { return 123; }}; + a-- + "#}, + 123, + ), + ]); +} + +#[test] +fn unary_void() { + run_test([ + TestAction::assert_eq("{ const a = 0; void a }", JsValue::undefined()), + TestAction::assert_eq( + indoc! {r#" + let a = 0; + const test = () => a = 42; + const b = void test() + ''; + a + b + "#}, + "42undefined", + ), + ]); +} + +#[test] +fn unary_delete() { + run_test([ + TestAction::assert("{ var a = 5; !(delete a) && a === 5 }"), + TestAction::assert("{ const a = { b: 5 }; delete a.b && a.b === undefined }"), + TestAction::assert("{ const a = { b: 5 }; delete a.c && a.b === 5 }"), + TestAction::assert("{ const a = { b: 5 }; delete a['b'] && a.b === undefined }"), + TestAction::assert("{ const a = { b: 5 }; !(delete a) }"), + TestAction::assert("delete []"), + TestAction::assert("delete function(){}"), + TestAction::assert("delete delete delete 1"), + ]); +} + +#[test] +fn comma_operator() { + run_test([ + TestAction::assert_eq( + indoc! {r#" + var a, b; + b = 10; + a = (b++, b); + a + "#}, + 11, + ), + TestAction::assert_eq( + indoc! {r#" + var a, b; + b = 10; + a = (b += 5, b /= 3, b - 3); + a + "#}, + 2, + ), + ]); +} + +#[test] +fn assignment_to_non_assignable() { + // Relates to the behaviour described at + // https://tc39.es/ecma262/#sec-assignment-operators-static-semantics-early-errors + // Tests all assignment operators as per [spec] and [mdn] + // + // [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Assignment + // [spec]: https://tc39.es/ecma262/#prod-AssignmentOperator + + run_test( + [ + "3 -= 5", "3 *= 5", "3 /= 5", "3 %= 5", "3 &= 5", "3 ^= 5", "3 |= 5", "3 += 5", "3 = 5", + ] + .into_iter() + .map(|src| { + TestAction::assert_native_error( + src, + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:3", + ) + }), + ); +} + +#[test] +fn assignment_to_non_assignable_ctd() { + run_test( + [ + "(()=>{})() -= 5", + "(()=>{})() *= 5", + "(()=>{})() /= 5", + "(()=>{})() %= 5", + "(()=>{})() &= 5", + "(()=>{})() ^= 5", + "(()=>{})() |= 5", + "(()=>{})() += 5", + "(()=>{})() = 5", + ] + .into_iter() + .map(|src| { + TestAction::assert_native_error( + src, + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:13", + ) + }), + ); +} + +#[test] +fn multicharacter_assignment_to_non_assignable() { + // Relates to the behaviour described at + // https://tc39.es/ecma262/#sec-assignment-operators-static-semantics-early-errors + run_test(["3 **= 5", "3 <<= 5", "3 >>= 5"].into_iter().map(|src| { + TestAction::assert_native_error( + src, + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:3", + ) + })); +} + +#[test] +fn multicharacter_assignment_to_non_assignable_ctd() { + run_test( + ["(()=>{})() **= 5", "(()=>{})() <<= 5", "(()=>{})() >>= 5"] + .into_iter() + .map(|src| { + TestAction::assert_native_error( + src, + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:13", + ) + }), + ); +} + +#[test] +fn multicharacter_bitwise_assignment_to_non_assignable() { + run_test( + ["3 >>>= 5", "3 &&= 5", "3 ||= 5", "3 ??= 5"] + .into_iter() + .map(|src| { + TestAction::assert_native_error( + src, + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:3", + ) + }), + ); +} + +#[test] +fn multicharacter_bitwise_assignment_to_non_assignable_ctd() { + run_test( + [ + "(()=>{})() >>>= 5", + "(()=>{})() &&= 5", + "(()=>{})() ||= 5", + "(()=>{})() ??= 5", + ] + .into_iter() + .map(|src| { + TestAction::assert_native_error( + src, + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:13", + ) + }), + ); +} + +#[test] +fn assign_to_array_decl() { + run_test([ + TestAction::assert_native_error( + "[1] = [2]", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:5", + ), + TestAction::assert_native_error( + "[3, 5] = [7, 8]", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:8", + ), + TestAction::assert_native_error( + "[6, 8] = [2]", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:8", + ), + TestAction::assert_native_error( + "[6] = [2, 9]", + ErrorKind::Syntax, + "Invalid left-hand side in assignment at position: 1:5", + ), + ]); +} + +#[test] +fn assign_to_object_decl() { + run_test([TestAction::assert_native_error( + "{a: 3} = {a: 5};", + ErrorKind::Syntax, + "unexpected token '=', primary expression at line 1, col 8", + )]); +} + +#[test] +fn assignmentoperator_lhs_not_defined() { + run_test([TestAction::assert_native_error( + "a += 5", + ErrorKind::Reference, + "a is not defined", + )]); +} + +#[test] +fn assignmentoperator_rhs_throws_error() { + run_test([TestAction::assert_native_error( + "let a; a += b", + ErrorKind::Reference, + "b is not defined", + )]); +} + +#[test] +fn instanceofoperator_rhs_not_object() { + run_test([TestAction::assert_native_error( + "let s = new String(); s instanceof 1", + ErrorKind::Type, + "right-hand side of 'instanceof' should be an object, got `number`", + )]); +} + +#[test] +fn instanceofoperator_rhs_not_callable() { + run_test([TestAction::assert_native_error( + "let s = new String(); s instanceof {}", + ErrorKind::Type, + "right-hand side of 'instanceof' is not callable", + )]); +} + +#[test] +fn logical_nullish_assignment() { + run_test([ + TestAction::assert_eq("{ let a = undefined; a ??= 10; a }", 10), + TestAction::assert_eq("{ let a = 20; a ??= 10; a }", 20), + ]); +} + +#[test] +fn logical_assignment() { + run_test([ + TestAction::assert("{ let a = false; a &&= 10; !a }"), + TestAction::assert_eq("{ let a = 20; a &&= 10; a }", 10), + TestAction::assert_eq("{ let a = null; a ||= 10; a }", 10), + TestAction::assert_eq("{ let a = 20; a ||= 10; a }", 20), + ]); +} + +#[test] +fn conditional_op() { + run_test([TestAction::assert_eq("1 === 2 ? 'a' : 'b'", "b")]); +} + +#[test] +fn delete_variable_in_strict() { + // Checks as per https://tc39.es/ecma262/#sec-delete-operator-static-semantics-early-errors + // that delete on a variable name is an error in strict mode code. + run_test([TestAction::assert_native_error( + indoc! {r#" + 'use strict'; + let x = 10; + delete x; + "#}, + ErrorKind::Syntax, + "cannot delete variables in strict mode at position: 3:1", + )]); +} + +#[test] +fn delete_non_configurable() { + run_test([TestAction::assert_native_error( + "'use strict'; delete Boolean.prototype", + ErrorKind::Type, + "Cannot delete property", + )]); +} + +#[test] +fn delete_non_configurable_in_function() { + run_test([TestAction::assert_native_error( + indoc! {r#" + function t() { + 'use strict'; + delete Boolean.prototype; + } + t() + "#}, + ErrorKind::Type, + "Cannot delete property", + )]); +} + +#[test] +fn delete_after_strict_function() { + run_test([TestAction::assert_eq( + indoc! {r#" + function t() { + 'use strict'; + } + t() + delete Boolean.prototype; + "#}, + false, + )]); +} + +#[test] +fn delete_in_function_global_strict() { + run_test([TestAction::assert_native_error( + indoc! {r#" + 'use strict' + function a(){ + delete Boolean.prototype; + } + a(); + "#}, + ErrorKind::Type, + "Cannot delete property", + )]); +} + +#[test] +fn delete_in_function_in_strict_function() { + run_test([TestAction::assert_eq( + indoc! {r#" + function a(){ + return delete Boolean.prototype; + } + function b(){ + 'use strict'; + return a(); + } + b(); + "#}, + false, + )]); +} + +#[test] +fn delete_in_strict_function_returned() { + run_test([TestAction::assert_native_error( + indoc! {r#" + function a() { + 'use strict'; + return function () { + delete Boolean.prototype; + } + } + a()(); + "#}, + ErrorKind::Type, + "Cannot delete property", + )]); +} + +mod in_operator { + use super::*; + + #[test] + fn propery_in_object() { + run_test([TestAction::assert("'a' in {a: 'x'}")]); + } + + #[test] + fn property_in_property_chain() { + run_test([TestAction::assert("'toString' in {}")]); + } + + #[test] + fn property_not_in_object() { + run_test([TestAction::assert("!('b' in {a: 'a'})")]); + } + + #[test] + fn number_in_array() { + // Note: this is valid because the LHS is converted to a prop key with ToPropertyKey + // and arrays are just fancy objects like {'0': 'a'} + run_test([TestAction::assert("0 in ['a']")]); + } + + #[test] + fn symbol_in_object() { + run_test([TestAction::assert(indoc! {r#" + var sym = Symbol('hi'); + sym in { [sym]: 'hello' } + "#})]); + } + + #[test] + fn should_type_error_when_rhs_not_object() { + run_test([TestAction::assert_native_error( + "'fail' in undefined", + ErrorKind::Type, + "right-hand side of 'in' should be an object, got `undefined`", + )]); + } +} diff --git a/boa_engine/src/tests/spread.rs b/boa_engine/src/tests/spread.rs new file mode 100644 index 00000000000..495188dacfc --- /dev/null +++ b/boa_engine/src/tests/spread.rs @@ -0,0 +1,152 @@ +use crate::{builtins::error::ErrorKind, run_test, JsValue, TestAction}; +use indoc::indoc; + +#[test] +fn object_spread() { + run_test([ + TestAction::run(indoc! {r#" + var b = {x: -1, z: -3} + var a = {x: 1, y: 2, ...b}; + "#}), + TestAction::assert_eq("a.x", -1), + TestAction::assert_eq("a.y", 2), + TestAction::assert_eq("a.z", -3), + ]); +} + +#[test] +fn spread_with_arguments() { + run_test([ + TestAction::run(indoc! {r#" + const a = [1, "test", 3, 4]; + function foo(...a) { + return arguments; + } + + var result = foo(...a); + "#}), + TestAction::assert_eq("result[0]", 1), + TestAction::assert_eq("result[1]", "test"), + TestAction::assert_eq("result[2]", 3), + TestAction::assert_eq("result[3]", 4), + ]); +} + +#[test] +fn array_rest_with_arguments() { + run_test([ + TestAction::run_harness(), + TestAction::run(indoc! {r#" + var b = [4, 5, 6] + var a = [1, 2, 3, ...b]; + "#}), + TestAction::assert("arrayEquals(a, [ 1, 2, 3, 4, 5, 6 ])"), + ]); +} + +#[test] +fn spread_shallow_clone() { + run_test([TestAction::assert(indoc! {r#" + var a = { x: {} }; + var aClone = { ...a }; + + a.x === aClone.x + "#})]); +} + +#[test] +fn spread_merge() { + run_test([ + TestAction::run(indoc! {r#" + var a = { x: 1, y: 2 }; + var b = { x: -1, z: -3, ...a }; + "#}), + TestAction::assert_eq("b.x", 1), + TestAction::assert_eq("b.y", 2), + TestAction::assert_eq("b.z", -3), + ]); +} + +#[test] +fn spread_overriding_properties() { + run_test([ + TestAction::run(indoc! {r#" + var a = { x: 0, y: 0 }; + var aWithOverrides = { ...a, ...{ x: 1, y: 2 } }; + "#}), + TestAction::assert_eq("aWithOverrides.x", 1), + TestAction::assert_eq("aWithOverrides.y", 2), + ]); +} + +#[test] +fn spread_getters_in_initializer() { + run_test([TestAction::assert_eq( + indoc! {r#" + var a = { x: 42 }; + var aWithXGetter = { ...a, get x() { throw new Error('not thrown yet') } }; + "#}, + JsValue::undefined(), + )]); +} + +#[test] +fn spread_getters_in_object() { + run_test([TestAction::assert_native_error( + indoc! {r#" + var a = { x: 42 }; + var aWithXGetter = { ...a, ... { get x() { throw new Error('not thrown yet') } } }; + "#}, + ErrorKind::Error, + "not thrown yet", + )]); +} + +#[test] +fn spread_setters() { + run_test([TestAction::assert_eq( + "var z = { set x(nexX) { throw new Error() }, ... { x: 1 } }", + JsValue::undefined(), + )]); +} + +#[test] +fn spread_null_and_undefined_ignored() { + run_test([ + TestAction::run("var a = { ...null, ...undefined };"), + TestAction::assert("!(undefined in a)"), + TestAction::assert("!(null in a)"), + ]); +} + +#[test] +fn spread_with_new() { + run_test([TestAction::assert_eq( + indoc! {r#" + function F(m) { + this.m = m; + } + function f(...args) { + return new F(...args); + } + f('message').m; + "#}, + "message", + )]); +} + +#[test] +fn spread_with_call() { + run_test([TestAction::assert_eq( + indoc! {r#" + function f(m) { + return m; + } + function g(...args) { + return f(...args); + } + g('message'); + "#}, + "message", + )]); +} diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index 96a527a0b7f..fb7a53b70af 100644 --- a/boa_engine/src/value/tests.rs +++ b/boa_engine/src/value/tests.rs @@ -1,7 +1,7 @@ -#![allow(clippy::float_cmp)] +use indoc::indoc; use super::*; -use crate::{check_output, forward, forward_val, string::utf16, Context, TestAction}; +use crate::{run_test, TestAction}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -23,15 +23,13 @@ fn undefined() { #[test] fn get_set_field() { - let mut context = Context::default(); - let obj = &JsObject::with_object_proto(&mut context); - // Create string and convert it to a Value - let s = JsValue::new("bar"); - obj.set("foo", s, false, &mut context).unwrap(); - assert_eq!( - obj.get("foo", &mut context).unwrap().display().to_string(), - "\"bar\"" - ); + run_test([TestAction::assert_context(|ctx| { + let obj = &JsObject::with_object_proto(ctx); + // Create string and convert it to a Value + let s = JsValue::new("bar"); + obj.set("foo", s, false, ctx).unwrap(); + obj.get("foo", ctx).unwrap() == JsValue::new("bar") + })]); } #[test] @@ -54,44 +52,38 @@ fn number_is_true() { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness #[test] fn abstract_equality_comparison() { - check_output(&[ - TestAction::TestEq("undefined == undefined", "true"), - TestAction::TestEq("null == null", "true"), - TestAction::TestEq("true == true", "true"), - TestAction::TestEq("false == false", "true"), - TestAction::TestEq("'foo' == 'foo'", "true"), - TestAction::TestEq("0 == 0", "true"), - TestAction::TestEq("+0 == -0", "true"), - TestAction::TestEq("+0 == 0", "true"), - TestAction::TestEq("-0 == 0", "true"), - TestAction::TestEq("0 == false", "true"), - TestAction::TestEq("'' == false", "true"), - TestAction::TestEq("'' == 0", "true"), - TestAction::TestEq("'17' == 17", "true"), - TestAction::TestEq("[1,2] == '1,2'", "true"), - TestAction::TestEq("new String('foo') == 'foo'", "true"), - TestAction::TestEq("null == undefined", "true"), - TestAction::TestEq("undefined == null", "true"), - TestAction::TestEq("null == false", "false"), - TestAction::TestEq("[] == ![]", "true"), - TestAction::TestEq("a = { foo: 'bar' }; b = { foo: 'bar'}; a == b", "false"), - TestAction::TestEq("new String('foo') == new String('foo')", "false"), - TestAction::TestEq("0 == null", "false"), - TestAction::TestEq("0 == '-0'", "true"), - TestAction::TestEq("0 == '+0'", "true"), - TestAction::TestEq("'+0' == 0", "true"), - TestAction::TestEq("'-0' == 0", "true"), - TestAction::TestEq("0 == NaN", "false"), - TestAction::TestEq("'foo' == NaN", "false"), - TestAction::TestEq("NaN == NaN", "false"), - TestAction::TestEq( - "Number.POSITIVE_INFINITY === Number.POSITIVE_INFINITY", - "true", - ), - TestAction::TestEq( - "Number.NEGATIVE_INFINITY === Number.NEGATIVE_INFINITY", - "true", - ), + run_test([ + TestAction::assert("undefined == undefined"), + TestAction::assert("null == null"), + TestAction::assert("true == true"), + TestAction::assert("false == false"), + TestAction::assert("'foo' == 'foo'"), + TestAction::assert("0 == 0"), + TestAction::assert("+0 == -0"), + TestAction::assert("+0 == 0"), + TestAction::assert("-0 == 0"), + TestAction::assert("0 == false"), + TestAction::assert("'' == false"), + TestAction::assert("'' == 0"), + TestAction::assert("'17' == 17"), + TestAction::assert("[1,2] == '1,2'"), + TestAction::assert("new String('foo') == 'foo'"), + TestAction::assert("null == undefined"), + TestAction::assert("undefined == null"), + TestAction::assert("null != false"), + TestAction::assert("[] == ![]"), + TestAction::assert("a = { foo: 'bar' }; b = { foo: 'bar'}; a != b"), + TestAction::assert("new String('foo') != new String('foo')"), + TestAction::assert("0 != null"), + TestAction::assert("0 == '-0'"), + TestAction::assert("0 == '+0'"), + TestAction::assert("'+0' == 0"), + TestAction::assert("'-0' == 0"), + TestAction::assert("0 != NaN"), + TestAction::assert("'foo' != NaN"), + TestAction::assert("NaN != NaN"), + TestAction::assert("Number.POSITIVE_INFINITY === Number.POSITIVE_INFINITY"), + TestAction::assert("Number.NEGATIVE_INFINITY === Number.NEGATIVE_INFINITY"), ]); } @@ -145,62 +137,29 @@ fn hash_object() { #[test] fn get_types() { - let mut context = Context::default(); - - assert_eq!( - forward_val(&mut context, "undefined").unwrap().get_type(), - Type::Undefined - ); - assert_eq!( - forward_val(&mut context, "1").unwrap().get_type(), - Type::Number - ); - assert_eq!( - forward_val(&mut context, "1.5").unwrap().get_type(), - Type::Number - ); - assert_eq!( - forward_val(&mut context, "BigInt(\"123442424242424424242424242\")") - .unwrap() - .get_type(), - Type::BigInt - ); - assert_eq!( - forward_val(&mut context, "true").unwrap().get_type(), - Type::Boolean - ); - assert_eq!( - forward_val(&mut context, "false").unwrap().get_type(), - Type::Boolean - ); - assert_eq!( - forward_val(&mut context, "function foo() {console.log(\"foo\");}") - .unwrap() - .get_type(), - Type::Undefined - ); - assert_eq!( - forward_val(&mut context, "null").unwrap().get_type(), - Type::Null - ); - assert_eq!( - forward_val(&mut context, "var x = {arg: \"hi\", foo: \"hello\"}; x") - .unwrap() - .get_type(), - Type::Object - ); - assert_eq!( - forward_val(&mut context, "\"Hi\"").unwrap().get_type(), - Type::String - ); - assert_eq!( - forward_val(&mut context, "Symbol()").unwrap().get_type(), - Type::Symbol - ); + run_test([ + TestAction::assert_with_op("undefined", |value, _| value.get_type() == Type::Undefined), + TestAction::assert_with_op("1", |value, _| value.get_type() == Type::Number), + TestAction::assert_with_op("1.5", |value, _| value.get_type() == Type::Number), + TestAction::assert_with_op("BigInt(\"123442424242424424242424242\")", |value, _| { + value.get_type() == Type::BigInt + }), + TestAction::assert_with_op("true", |value, _| value.get_type() == Type::Boolean), + TestAction::assert_with_op("false", |value, _| value.get_type() == Type::Boolean), + TestAction::assert_with_op("function foo() {console.log(\"foo\");}", |value, _| { + value.get_type() == Type::Undefined + }), + TestAction::assert_with_op("null", |value, _| value.get_type() == Type::Null), + TestAction::assert_with_op("var x = {arg: \"hi\", foo: \"hello\"}; x", |value, _| { + value.get_type() == Type::Object + }), + TestAction::assert_with_op("\"Hi\"", |value, _| value.get_type() == Type::String), + TestAction::assert_with_op("Symbol()", |value, _| value.get_type() == Type::Symbol), + ]); } #[test] -fn to_string() { +fn float_display() { let f64_to_str = |f| JsValue::new(f).display().to_string(); assert_eq!(f64_to_str(f64::NAN), "NaN"); @@ -238,186 +197,128 @@ fn to_string() { #[test] fn string_length_is_not_enumerable() { - let mut context = Context::default(); - - let object = JsValue::new("foo").to_object(&mut context).unwrap(); - let length_desc = object - .__get_own_property__(&PropertyKey::from("length"), &mut context) - .unwrap() - .unwrap(); - assert!(!length_desc.expect_enumerable()); + run_test([TestAction::assert_context(|ctx| { + let object = JsValue::new("foo").to_object(ctx).unwrap(); + let length_desc = object + .__get_own_property__(&PropertyKey::from("length"), ctx) + .unwrap() + .unwrap(); + !length_desc.expect_enumerable() + })]); } #[test] fn string_length_is_in_utf16_codeunits() { - let mut context = Context::default(); - - // 😀 is one Unicode code point, but 2 UTF-16 code units - let object = JsValue::new("😀").to_object(&mut context).unwrap(); - let length_desc = object - .__get_own_property__(&PropertyKey::from("length"), &mut context) - .unwrap() - .unwrap(); - assert_eq!( + run_test([TestAction::assert_context(|ctx| { + // 😀 is one Unicode code point, but 2 UTF-16 code units + let object = JsValue::new("😀").to_object(ctx).unwrap(); + let length_desc = object + .__get_own_property__(&PropertyKey::from("length"), ctx) + .unwrap() + .unwrap(); length_desc .expect_value() - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(2) - ); + .to_integer_or_infinity(ctx) + .unwrap() + == IntegerOrInfinity::Integer(2) + })]); } #[test] fn add_number_and_number() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "1 + 2").unwrap(); - let value = value.to_i32(&mut context).unwrap(); - assert_eq!(value, 3); + run_test([TestAction::assert_eq("1 + 2", 3)]); } #[test] fn add_number_and_string() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "1 + \" + 2 = 3\"").unwrap(); - let value = value.to_string(&mut context).unwrap(); - assert_eq!(&value, utf16!("1 + 2 = 3")); + run_test([TestAction::assert_eq("1 + \" + 2 = 3\"", "1 + 2 = 3")]); } #[test] fn add_string_and_string() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "\"Hello\" + \", world\"").unwrap(); - let value = value.to_string(&mut context).unwrap(); - assert_eq!(&value, utf16!("Hello, world")); + run_test([TestAction::assert_eq( + "\"Hello\" + \", world\"", + "Hello, world", + )]); } #[test] fn add_number_object_and_number() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "new Number(10) + 6").unwrap(); - let value = value.to_i32(&mut context).unwrap(); - assert_eq!(value, 16); + run_test([TestAction::assert_eq("new Number(10) + 6", 16)]); } #[test] fn add_number_object_and_string_object() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "new Number(10) + new String(\"0\")").unwrap(); - let value = value.to_string(&mut context).unwrap(); - assert_eq!(&value, utf16!("100")); + run_test([TestAction::assert_eq( + "new Number(10) + new String(\"0\")", + "100", + )]); } #[test] fn sub_number_and_number() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "1 - 999").unwrap(); - let value = value.to_i32(&mut context).unwrap(); - assert_eq!(value, -998); + run_test([TestAction::assert_eq("1 - 999", -998)]); } #[test] fn sub_number_object_and_number_object() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "new Number(1) - new Number(999)").unwrap(); - let value = value.to_i32(&mut context).unwrap(); - assert_eq!(value, -998); + run_test([TestAction::assert_eq( + "new Number(1) - new Number(999)", + -998, + )]); } #[test] fn sub_string_and_number_object() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "'Hello' - new Number(999)").unwrap(); - let value = value.to_number(&mut context).unwrap(); - assert!(value.is_nan()); + run_test([TestAction::assert_eq("'Hello' - new Number(999)", f64::NAN)]); } #[test] fn div_by_zero() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "1 / 0").unwrap(); - let value = value.to_number(&mut context).unwrap(); - assert!(value.is_infinite()); + run_test([TestAction::assert_eq("1 / 0", f64::INFINITY)]); } #[test] fn rem_by_zero() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "1 % 0").unwrap(); - let value = value.to_number(&mut context).unwrap(); - assert!(value.is_nan()); + run_test([TestAction::assert_eq("1 % 0", f64::NAN)]); } #[test] fn bitand_integer_and_integer() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "0xFFFF & 0xFF").unwrap(); - let value = value.to_i32(&mut context).unwrap(); - assert_eq!(value, 255); + run_test([TestAction::assert_eq("0xFFFF & 0xFF", 255)]); } #[test] fn bitand_integer_and_rational() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "0xFFFF & 255.5").unwrap(); - let value = value.to_i32(&mut context).unwrap(); - assert_eq!(value, 255); + run_test([TestAction::assert_eq("0xFFFF & 255.5", 255)]); } #[test] fn bitand_rational_and_rational() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "255.772 & 255.5").unwrap(); - let value = value.to_i32(&mut context).unwrap(); - assert_eq!(value, 255); + run_test([TestAction::assert_eq("255.772 & 255.5", 255)]); } #[test] #[allow(clippy::float_cmp)] fn pow_number_and_number() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "3 ** 3").unwrap(); - let value = value.to_number(&mut context).unwrap(); - assert_eq!(value, 27.0); + run_test([TestAction::assert_eq("3 ** 3", 27.0)]); } #[test] fn pow_number_and_string() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "3 ** 'Hello'").unwrap(); - let value = value.to_number(&mut context).unwrap(); - assert!(value.is_nan()); + run_test([TestAction::assert_eq("3 ** 'Hello'", f64::NAN)]); } #[test] fn assign_pow_number_and_string() { - let mut context = Context::default(); - - let value = forward_val( - &mut context, - r" - let a = 3; - a **= 'Hello' - a - ", - ) - .unwrap(); - let value = value.to_number(&mut context).unwrap(); - assert!(value.is_nan()); + run_test([TestAction::assert_eq( + indoc! {r" + let a = 3; + a **= 'Hello' + a + "}, + f64::NAN, + )]); } #[test] @@ -429,293 +330,402 @@ fn display_string() { #[test] fn display_array_string() { - let mut context = Context::default(); - - let value = forward_val(&mut context, "[\"Hello\"]").unwrap(); - assert_eq!(value.display().to_string(), "[ \"Hello\" ]"); + run_test([TestAction::assert_with_op("[\"Hello\"]", |v, _| { + v.display().to_string() == "[ \"Hello\" ]" + })]); } #[test] fn display_boolean_object() { - let mut context = Context::default(); - let d_obj = r#" - let bool = new Boolean(0); - bool - "#; - let value = forward_val(&mut context, d_obj).unwrap(); - assert_eq!(value.display().to_string(), "Boolean { false }"); + run_test([TestAction::assert_with_op( + indoc! {r#" + let bool = new Boolean(0); + bool + "#}, + |v, _| v.display().to_string() == "Boolean { false }", + )]); } #[test] fn display_number_object() { - let mut context = Context::default(); - let d_obj = r#" - let num = new Number(3.14); - num - "#; - let value = forward_val(&mut context, d_obj).unwrap(); - assert_eq!(value.display().to_string(), "Number { 3.14 }"); + run_test([TestAction::assert_with_op( + indoc! {r#" + let num = new Number(3.14); + num + "#}, + |v, _| v.display().to_string() == "Number { 3.14 }", + )]); } #[test] fn display_negative_zero_object() { - let mut context = Context::default(); - let d_obj = r#" - let num = new Number(-0); - num - "#; - let value = forward_val(&mut context, d_obj).unwrap(); - assert_eq!(value.display().to_string(), "Number { -0 }"); + run_test([TestAction::assert_with_op( + indoc! {r#" + let num = new Number(-0); + num + "#}, + |v, _| v.display().to_string() == "Number { -0 }", + )]); } #[test] fn debug_object() { - let mut context = Context::default(); - let value = forward_val(&mut context, "new Array([new Date()])").unwrap(); - // We don't care about the contents of the debug display (it is *debug* after all). In the // commit that this test was added, this would cause a stack overflow, so executing // `Debug::fmt` is the assertion. // // However, we want to make sure that no data is being left in the internal hashset, so // executing the formatting twice should result in the same output. - - assert_eq!(format!("{value:?}"), format!("{value:?}")); + run_test([TestAction::assert_with_op( + "new Array([new Date()])", + |v, _| format!("{v:?}") == format!("{v:?}"), + )]); } #[test] -#[ignore] // TODO: Once objects are printed in a simpler way this test can be simplified and used fn display_object() { - let mut context = Context::default(); - let d_obj = r#" - let o = {a: 'a'}; - o - "#; - let value = forward_val(&mut context, d_obj).unwrap(); - assert_eq!( - value.display().to_string(), - r#"{ - a: "a", -__proto__: { -constructor: { -setPrototypeOf: { - length: 2 - }, - prototype: [Cycle], - name: "Object", - length: 1, -defineProperty: { - length: 3 - }, -getPrototypeOf: { - length: 1 - }, - is: { - length: 2 - }, - __proto__: { - constructor: { - name: "Function", - prototype: [Cycle], - length: 1, - __proto__: undefined - }, - __proto__: undefined - } - }, -hasOwnProperty: { - length: 0 - }, -propertyIsEnumerable: { - length: 0 - }, -toString: { - length: 0 - } - } -}"# - ); + const DISPLAY: &str = indoc! {r#" + { + a: "a" + }"# + }; + run_test([TestAction::assert_with_op("({a: 'a'})", |v, _| { + v.display().to_string() == DISPLAY + })]); } #[test] fn to_integer_or_infinity() { - let mut context = Context::default(); - - assert_eq!( - JsValue::undefined() - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(0) - ); - assert_eq!( - JsValue::nan().to_integer_or_infinity(&mut context).unwrap(), - IntegerOrInfinity::Integer(0) - ); - assert_eq!( - JsValue::new(0.0) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(0) - ); - assert_eq!( - JsValue::new(-0.0) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(0) - ); - - assert_eq!( - JsValue::new(f64::INFINITY) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::PositiveInfinity - ); - assert_eq!( - JsValue::new(f64::NEG_INFINITY) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::NegativeInfinity - ); - - assert_eq!( - JsValue::new(10) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(10) - ); - assert_eq!( - JsValue::new(11.0) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(11) - ); - assert_eq!( - JsValue::new("12") - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(12) - ); - assert_eq!( - JsValue::new(true) - .to_integer_or_infinity(&mut context) - .unwrap(), - IntegerOrInfinity::Integer(1) - ); + run_test([TestAction::inspect_context(|ctx| { + assert_eq!( + JsValue::undefined().to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(0) + ); + assert_eq!( + JsValue::nan().to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(0) + ); + assert_eq!( + JsValue::new(0.0).to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(0) + ); + assert_eq!( + JsValue::new(-0.0).to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(0) + ); + assert_eq!( + JsValue::new(f64::INFINITY) + .to_integer_or_infinity(ctx) + .unwrap(), + IntegerOrInfinity::PositiveInfinity + ); + assert_eq!( + JsValue::new(f64::NEG_INFINITY) + .to_integer_or_infinity(ctx) + .unwrap(), + IntegerOrInfinity::NegativeInfinity + ); + assert_eq!( + JsValue::new(10).to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(10) + ); + assert_eq!( + JsValue::new(11.0).to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(11) + ); + assert_eq!( + JsValue::new("12").to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(12) + ); + assert_eq!( + JsValue::new(true).to_integer_or_infinity(ctx).unwrap(), + IntegerOrInfinity::Integer(1) + ); + })]); } #[test] fn test_accessors() { - let src = r#" - let arr = []; - let a = { get b() { return "c" }, set b(value) { arr = arr.concat([value]) }} ; - a.b = "a"; - "#; - check_output(&[ - TestAction::Execute(src), - TestAction::TestEq("a.b", r#""c""#), - TestAction::TestEq("arr", r#"[ "a" ]"#), + run_test([ + TestAction::run(indoc! {r#" + let arr = []; + let a = { get b() { return "c" }, set b(value) { arr = arr.concat([value]) }} ; + a.b = "a"; + "#}), + TestAction::assert_eq("a.b", "c"), + TestAction::assert_eq("arr[0]", "a"), ]); } #[test] fn to_primitive() { - let src = r#" - let a = {}; - a[Symbol.toPrimitive] = function() { - return 42; - }; - let primitive = a + 0; - "#; - check_output(&[ - TestAction::Execute(src), - TestAction::TestEq("primitive", "42"), + run_test([ + TestAction::run(indoc! {r#" + let a = {}; + a[Symbol.toPrimitive] = function() { + return 42; + }; + let primitive = a + 0; + "#}), + TestAction::assert_eq("primitive", 42), ]); } #[test] fn object_to_property_key() { - let src = r#" - let obj = {}; + let source = r#" + let obj = {}; - let to_primitive_42 = { - [Symbol.toPrimitive]() { - return 42; - } - }; - obj[to_primitive_42] = 1; + let to_primitive_42 = { + [Symbol.toPrimitive]() { + return 42; + } + }; + obj[to_primitive_42] = 1; - let to_primitive_true = { - [Symbol.toPrimitive]() { - return true; - } - }; - obj[to_primitive_true] = 2; + let to_primitive_true = { + [Symbol.toPrimitive]() { + return true; + } + }; + obj[to_primitive_true] = 2; - let to_primitive_str = { - [Symbol.toPrimitive]() { - return "str1"; - } - }; - obj[to_primitive_str] = 3; + let to_primitive_str = { + [Symbol.toPrimitive]() { + return "str1"; + } + }; + obj[to_primitive_str] = 3; - let mysymbol = Symbol("test"); - let to_primitive_symbol = { - [Symbol.toPrimitive]() { - return mysymbol; - } - }; - obj[to_primitive_symbol] = 4; + let mysymbol = Symbol("test"); + let to_primitive_symbol = { + [Symbol.toPrimitive]() { + return mysymbol; + } + }; + obj[to_primitive_symbol] = 4; - let to_str = { - toString: function() { - return "str2"; - } - }; - obj[to_str] = 5; + let to_str = { + toString: function() { + return "str2"; + } + }; + obj[to_str] = 5; "#; - check_output(&[ - TestAction::Execute(src), - TestAction::TestEq("obj[42]", "1"), - TestAction::TestEq("obj[true]", "2"), - TestAction::TestEq("obj['str1']", "3"), - TestAction::TestEq("obj[mysymbol]", "4"), - TestAction::TestEq("obj['str2']", "5"), + run_test([ + TestAction::run(source), + TestAction::assert_eq("obj[42]", 1), + TestAction::assert_eq("obj[true]", 2), + TestAction::assert_eq("obj['str1']", 3), + TestAction::assert_eq("obj[mysymbol]", 4), + TestAction::assert_eq("obj['str2']", 5), ]); } +#[test] +fn to_index() { + run_test([TestAction::inspect_context(|ctx| { + assert_eq!(JsValue::undefined().to_index(ctx).unwrap(), 0); + assert!(JsValue::new(-1).to_index(ctx).is_err()); + })]); +} + +#[test] +fn to_length() { + run_test([TestAction::inspect_context(|ctx| { + assert_eq!(JsValue::new(f64::NAN).to_length(ctx).unwrap(), 0); + assert_eq!(JsValue::new(f64::NEG_INFINITY).to_length(ctx).unwrap(), 0); + assert_eq!( + JsValue::new(f64::INFINITY).to_length(ctx).unwrap(), + Number::MAX_SAFE_INTEGER as u64 + ); + assert_eq!(JsValue::new(0.0).to_length(ctx).unwrap(), 0); + assert_eq!(JsValue::new(-0.0).to_length(ctx).unwrap(), 0); + assert_eq!(JsValue::new(20.9).to_length(ctx).unwrap(), 20); + assert_eq!(JsValue::new(-20.9).to_length(ctx).unwrap(), 0); + assert_eq!( + JsValue::new(100_000_000_000.0).to_length(ctx).unwrap(), + 100_000_000_000 + ); + assert_eq!( + JsValue::new(4_010_101_101.0).to_length(ctx).unwrap(), + 4_010_101_101 + ); + })]); +} + +#[test] +fn to_int32() { + run_test([TestAction::inspect_context(|ctx| { + macro_rules! check_to_int32 { + ($from:expr => $to:expr) => { + assert_eq!(JsValue::new($from).to_i32(ctx).unwrap(), $to); + }; + } + + check_to_int32!(f64::NAN => 0); + check_to_int32!(f64::NEG_INFINITY => 0); + check_to_int32!(f64::INFINITY => 0); + check_to_int32!(0 => 0); + check_to_int32!(-0.0 => 0); + + check_to_int32!(20.9 => 20); + check_to_int32!(-20.9 => -20); + + check_to_int32!(Number::MIN_VALUE => 0); + check_to_int32!(-Number::MIN_VALUE => 0); + check_to_int32!(0.1 => 0); + check_to_int32!(-0.1 => 0); + check_to_int32!(1 => 1); + check_to_int32!(1.1 => 1); + check_to_int32!(-1 => -1); + check_to_int32!(0.6 => 0); + check_to_int32!(1.6 => 1); + check_to_int32!(-0.6 => 0); + check_to_int32!(-1.6 => -1); + + check_to_int32!(2_147_483_647.0 => 2_147_483_647); + check_to_int32!(2_147_483_648.0 => -2_147_483_648); + check_to_int32!(2_147_483_649.0 => -2_147_483_647); + + check_to_int32!(4_294_967_295.0 => -1); + check_to_int32!(4_294_967_296.0 => 0); + check_to_int32!(4_294_967_297.0 => 1); + + check_to_int32!(-2_147_483_647.0 => -2_147_483_647); + check_to_int32!(-2_147_483_648.0 => -2_147_483_648); + check_to_int32!(-2_147_483_649.0 => 2_147_483_647); + + check_to_int32!(-4_294_967_295.0 => 1); + check_to_int32!(-4_294_967_296.0 => 0); + check_to_int32!(-4_294_967_297.0 => -1); + + check_to_int32!(2_147_483_648.25 => -2_147_483_648); + check_to_int32!(2_147_483_648.5 => -2_147_483_648); + check_to_int32!(2_147_483_648.75 => -2_147_483_648); + check_to_int32!(4_294_967_295.25 => -1); + check_to_int32!(4_294_967_295.5 => -1); + check_to_int32!(4_294_967_295.75 => -1); + check_to_int32!(3_000_000_000.25 => -1_294_967_296); + check_to_int32!(3_000_000_000.5 => -1_294_967_296); + check_to_int32!(3_000_000_000.75 => -1_294_967_296); + + check_to_int32!(-2_147_483_648.25 => -2_147_483_648); + check_to_int32!(-2_147_483_648.5 => -2_147_483_648); + check_to_int32!(-2_147_483_648.75 => -2_147_483_648); + check_to_int32!(-4_294_967_295.25 => 1); + check_to_int32!(-4_294_967_295.5 => 1); + check_to_int32!(-4_294_967_295.75 => 1); + check_to_int32!(-3_000_000_000.25 => 1_294_967_296); + check_to_int32!(-3_000_000_000.5 => 1_294_967_296); + check_to_int32!(-3_000_000_000.75 => 1_294_967_296); + + let base = 2f64.powi(64); + check_to_int32!(base + 0.0 => 0); + check_to_int32!(base + 1117.0 => 0); + check_to_int32!(base + 2234.0 => 4096); + check_to_int32!(base + 3351.0 => 4096); + check_to_int32!(base + 4468.0 => 4096); + check_to_int32!(base + 5585.0 => 4096); + check_to_int32!(base + 6702.0 => 8192); + check_to_int32!(base + 7819.0 => 8192); + check_to_int32!(base + 8936.0 => 8192); + check_to_int32!(base + 10053.0 => 8192); + check_to_int32!(base + 11170.0 => 12288); + check_to_int32!(base + 12287.0 => 12288); + check_to_int32!(base + 13404.0 => 12288); + check_to_int32!(base + 14521.0 => 16384); + check_to_int32!(base + 15638.0 => 16384); + check_to_int32!(base + 16755.0 => 16384); + check_to_int32!(base + 17872.0 => 16384); + check_to_int32!(base + 18989.0 => 20480); + check_to_int32!(base + 20106.0 => 20480); + check_to_int32!(base + 21223.0 => 20480); + check_to_int32!(base + 22340.0 => 20480); + check_to_int32!(base + 23457.0 => 24576); + check_to_int32!(base + 24574.0 => 24576); + check_to_int32!(base + 25691.0 => 24576); + check_to_int32!(base + 26808.0 => 28672); + check_to_int32!(base + 27925.0 => 28672); + check_to_int32!(base + 29042.0 => 28672); + check_to_int32!(base + 30159.0 => 28672); + check_to_int32!(base + 31276.0 => 32768); + + // bignum is (2^53 - 1) * 2^31 - highest number with bit 31 set. + let bignum = 2f64.powi(84) - 2f64.powi(31); + check_to_int32!(bignum => -2_147_483_648); + check_to_int32!(-bignum => -2_147_483_648); + check_to_int32!(2.0 * bignum => 0); + check_to_int32!(-(2.0 * bignum) => 0); + check_to_int32!(bignum - 2f64.powi(31) => 0); + check_to_int32!(-(bignum - 2f64.powi(31)) => 0); + + // max_fraction is largest number below 1. + let max_fraction = 1.0 - 2f64.powi(-53); + check_to_int32!(max_fraction => 0); + check_to_int32!(-max_fraction => 0); + })]); +} + +#[test] +fn to_string() { + run_test([TestAction::inspect_context(|ctx| { + assert_eq!(&JsValue::null().to_string(ctx).unwrap(), utf16!("null")); + assert_eq!( + &JsValue::undefined().to_string(ctx).unwrap(), + utf16!("undefined") + ); + assert_eq!(&JsValue::new(55).to_string(ctx).unwrap(), utf16!("55")); + assert_eq!(&JsValue::new(55.0).to_string(ctx).unwrap(), utf16!("55")); + assert_eq!( + &JsValue::new("hello").to_string(ctx).unwrap(), + utf16!("hello") + ); + })]); +} + +#[test] +fn to_bigint() { + run_test([TestAction::inspect_context(|ctx| { + assert!(JsValue::null().to_bigint(ctx).is_err()); + assert!(JsValue::undefined().to_bigint(ctx).is_err()); + assert!(JsValue::new(55).to_bigint(ctx).is_err()); + assert!(JsValue::new(10.0).to_bigint(ctx).is_err()); + assert!(JsValue::new("100").to_bigint(ctx).is_ok()); + })]); +} + /// Test cyclic conversions that previously caused stack overflows /// Relevant mitigation for these are in `JsObject::ordinary_to_primitive` and /// `JsObject::to_json` mod cyclic_conversions { + use crate::builtins::error::ErrorKind; + use super::*; #[test] fn to_json_cyclic() { - let mut context = Context::default(); - let src = r#" - let a = []; - a[0] = a; - JSON.stringify(a) - "#; - - assert_eq!( - forward(&mut context, src), - "Uncaught TypeError: cyclic object value", - ); + run_test([TestAction::assert_native_error( + indoc! {r#" + let a = []; + a[0] = a; + JSON.stringify(a) + "#}, + ErrorKind::Type, + "cyclic object value", + )]); } #[test] fn to_json_noncyclic() { - let mut context = Context::default(); - let src = r#" - let b = []; - let a = [b, b]; - JSON.stringify(a) - "#; - - let value = forward_val(&mut context, src).unwrap(); - let result = value.as_string().unwrap(); - assert_eq!(result, utf16!("[[],[]]")); + run_test([TestAction::assert_eq( + indoc! {r#" + let b = []; + let a = [b, b]; + JSON.stringify(a) + "#}, + "[[],[]]", + )]); } // These tests don't throw errors. Instead we mirror Chrome / Firefox behavior for these @@ -723,85 +733,69 @@ mod cyclic_conversions { #[test] fn to_string_cyclic() { - let mut context = Context::default(); - let src = r#" - let a = []; - a[0] = a; - a.toString() - "#; - - let value = forward_val(&mut context, src).unwrap(); - let result = value.as_string().unwrap(); - assert_eq!(result, utf16!("")); + run_test([TestAction::assert_eq( + indoc! {r#" + let a = []; + a[0] = a; + a.toString() + "#}, + "", + )]); } #[test] fn to_number_cyclic() { - let mut context = Context::default(); - let src = r#" - let a = []; - a[0] = a; - +a - "#; - - let value = forward_val(&mut context, src).unwrap(); - let result = value.as_number().unwrap(); - assert_eq!(result, 0.0); + run_test([TestAction::assert_eq( + indoc! {r#" + let a = []; + a[0] = a; + +a + "#}, + 0.0, + )]); } #[test] fn to_boolean_cyclic() { // this already worked before the mitigation, but we don't want to cause a regression - let mut context = Context::default(); - let src = r#" - let a = []; - a[0] = a; - !!a - "#; - - let value = forward_val(&mut context, src).unwrap(); - // There isn't an as_boolean function for some reason? - assert_eq!(value, JsValue::new(true)); + run_test([TestAction::assert(indoc! {r#" + let a = []; + a[0] = a; + !!a + "#})]); } #[test] fn to_bigint_cyclic() { - let mut context = Context::default(); - let src = r#" - let a = []; - a[0] = a; - BigInt(a) - "#; - - let value = forward_val(&mut context, src).unwrap(); - let result = value.as_bigint().unwrap().to_f64(); - assert_eq!(result, 0.); + run_test([TestAction::assert_eq( + indoc! {r#" + let a = []; + a[0] = a; + BigInt(a) + "#}, + JsBigInt::new(0), + )]); } #[test] fn to_u32_cyclic() { - let mut context = Context::default(); - let src = r#" - let a = []; - a[0] = a; - a | 0 - "#; - - let value = forward_val(&mut context, src).unwrap(); - let result = value.as_number().unwrap(); - assert_eq!(result, 0.); + run_test([TestAction::assert_eq( + indoc! {r#" + let a = []; + a[0] = a; + a | 0 + "#}, + 0.0, + )]); } #[test] fn console_log_cyclic() { - let mut context = Context::default(); - let src = r#" - let a = [1]; - a[1] = a; - console.log(a); - "#; - - let _res = forward(&mut context, src); + run_test([TestAction::run(indoc! {r#" + let a = [1]; + a[1] = a; + console.log(a); + "#})]); // Should not stack overflow } } @@ -810,600 +804,656 @@ mod abstract_relational_comparison { #![allow(clippy::bool_assert_comparison)] use super::*; - macro_rules! check_comparison { - ($context:ident, $string:expr => $expect:expr) => { - assert_eq!( - forward_val(&mut $context, $string).unwrap().to_boolean(), - $expect - ); - }; - } #[test] fn number_less_than_number() { - let mut context = Context::default(); - check_comparison!(context, "1 < 2" => true); - check_comparison!(context, "2 < 2" => false); - check_comparison!(context, "3 < 2" => false); - check_comparison!(context, "2 < 2.5" => true); - check_comparison!(context, "2.5 < 2" => false); + run_test([ + TestAction::assert("1 < 2"), + TestAction::assert("!(2 < 2)"), + TestAction::assert("!(3 < 2)"), + TestAction::assert("2 < 2.5"), + TestAction::assert("!(2.5 < 2)"), + ]); } #[test] fn string_less_than_number() { - let mut context = Context::default(); - check_comparison!(context, "'1' < 2" => true); - check_comparison!(context, "'2' < 2" => false); - check_comparison!(context, "'3' < 2" => false); - check_comparison!(context, "'2' < 2.5" => true); - check_comparison!(context, "'2.5' < 2" => false); + run_test([ + TestAction::assert("'1' < 2"), + TestAction::assert("!('2' < 2)"), + TestAction::assert("!('3' < 2)"), + TestAction::assert("'2' < 2.5"), + TestAction::assert("!('2.5' < 2.5)"), + ]); } #[test] fn number_less_than_string() { - let mut context = Context::default(); - check_comparison!(context, "1 < '2'" => true); - check_comparison!(context, "2 < '2'" => false); - check_comparison!(context, "3 < '2'" => false); - check_comparison!(context, "2 < '2.5'" => true); - check_comparison!(context, "2.5 < '2'" => false); + run_test([ + TestAction::assert("1 < '2'"), + TestAction::assert("!(2 < '2')"), + TestAction::assert("!(3 < '2')"), + TestAction::assert("2 < '2.5'"), + TestAction::assert("!(2.5 < '2')"), + ]); } #[test] fn number_object_less_than_number() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) < '2'" => true); - check_comparison!(context, "new Number(2) < '2'" => false); - check_comparison!(context, "new Number(3) < '2'" => false); - check_comparison!(context, "new Number(2) < '2.5'" => true); - check_comparison!(context, "new Number(2.5) < '2'" => false); + run_test([ + TestAction::assert("new Number(1) < 2"), + TestAction::assert("!(new Number(2) < 2)"), + TestAction::assert("!(new Number(3) < 2)"), + TestAction::assert("new Number(2) < 2.5"), + TestAction::assert("!(new Number(2.5) < 2)"), + ]); } #[test] fn number_object_less_than_number_object() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) < new Number(2)" => true); - check_comparison!(context, "new Number(2) < new Number(2)" => false); - check_comparison!(context, "new Number(3) < new Number(2)" => false); - check_comparison!(context, "new Number(2) < new Number(2.5)" => true); - check_comparison!(context, "new Number(2.5) < new Number(2)" => false); + run_test([ + TestAction::assert("new Number(1) < new Number(2)"), + TestAction::assert("!(new Number(2) < new Number(2))"), + TestAction::assert("!(new Number(3) < new Number(2))"), + TestAction::assert("new Number(2) < new Number(2.5)"), + TestAction::assert("!(new Number(2.5) < new Number(2))"), + ]); } #[test] fn string_less_than_string() { - let mut context = Context::default(); - check_comparison!(context, "'hello' < 'hello'" => false); - check_comparison!(context, "'hell' < 'hello'" => true); - check_comparison!(context, "'hello, world' < 'world'" => true); - check_comparison!(context, "'aa' < 'ab'" => true); + run_test([ + TestAction::assert("!('hello' < 'hello')"), + TestAction::assert("'hell' < 'hello'"), + TestAction::assert("'hello, world' < 'world'"), + TestAction::assert("'aa' < 'ab'"), + ]); } #[test] fn string_object_less_than_string() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') < 'hello'" => false); - check_comparison!(context, "new String('hell') < 'hello'" => true); - check_comparison!(context, "new String('hello, world') < 'world'" => true); - check_comparison!(context, "new String('aa') < 'ab'" => true); + run_test([ + TestAction::assert("!(new String('hello') < 'hello')"), + TestAction::assert("new String('hell') < 'hello'"), + TestAction::assert("new String('hello, world') < 'world'"), + TestAction::assert("new String('aa') < 'ab'"), + ]); } #[test] fn string_object_less_than_string_object() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') < new String('hello')" => false); - check_comparison!(context, "new String('hell') < new String('hello')" => true); - check_comparison!(context, "new String('hello, world') < new String('world')" => true); - check_comparison!(context, "new String('aa') < new String('ab')" => true); + run_test([ + TestAction::assert("!(new String('hello') < new String('hello'))"), + TestAction::assert("new String('hell') < new String('hello')"), + TestAction::assert("new String('hello, world') < new String('world')"), + TestAction::assert("new String('aa') < new String('ab')"), + ]); } #[test] fn bigint_less_than_number() { - let mut context = Context::default(); - check_comparison!(context, "1n < 10" => true); - check_comparison!(context, "10n < 10" => false); - check_comparison!(context, "100n < 10" => false); - check_comparison!(context, "10n < 10.9" => true); + run_test([ + TestAction::assert("1n < 10"), + TestAction::assert("!(10n < 10)"), + TestAction::assert("!(100n < 10)"), + TestAction::assert("10n < 10.9"), + ]); } #[test] fn number_less_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "10 < 1n" => false); - check_comparison!(context, "1 < 1n" => false); - check_comparison!(context, "-1 < -1n" => false); - check_comparison!(context, "-1.9 < -1n" => true); + run_test([ + TestAction::assert("!(10 < 1n)"), + TestAction::assert("!(1 < 1n)"), + TestAction::assert("!(-1 < -1n)"), + TestAction::assert("-1.9 < -1n"), + ]); } #[test] fn negative_infinity_less_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "-Infinity < -10000000000n" => true); - check_comparison!(context, "-Infinity < (-1n << 100n)" => true); + run_test([ + TestAction::assert("-Infinity < -10000000000n"), + TestAction::assert("-Infinity < (-1n << 100n)"), + ]); } #[test] fn bigint_less_than_infinity() { - let mut context = Context::default(); - check_comparison!(context, "1000n < NaN" => false); - check_comparison!(context, "(1n << 100n) < NaN" => false); + run_test([ + TestAction::assert("!(1000n < NaN)"), + TestAction::assert("!((1n << 100n) < NaN)"), + ]); } #[test] fn nan_less_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "NaN < -10000000000n" => false); - check_comparison!(context, "NaN < (-1n << 100n)" => false); + run_test([ + TestAction::assert("!(NaN < -10000000000n)"), + TestAction::assert("!(NaN < (-1n << 100n))"), + ]); } #[test] fn bigint_less_than_nan() { - let mut context = Context::default(); - check_comparison!(context, "1000n < Infinity" => true); - check_comparison!(context, "(1n << 100n) < Infinity" => true); + run_test([ + TestAction::assert("1000n < Infinity"), + TestAction::assert("(1n << 100n) < Infinity"), + ]); } #[test] fn bigint_less_than_string() { - let mut context = Context::default(); - check_comparison!(context, "1000n < '1000'" => false); - check_comparison!(context, "1000n < '2000'" => true); - check_comparison!(context, "1n < '-1'" => false); - check_comparison!(context, "2n < '-1'" => false); - check_comparison!(context, "-100n < 'InvalidBigInt'" => false); + run_test([ + TestAction::assert("!(1000n < '1000')"), + TestAction::assert("1000n < '2000'"), + TestAction::assert("!(1n < '-1')"), + TestAction::assert("!(2n < '-1')"), + TestAction::assert("!(-100n < 'InvalidBigInt')"), + ]); } #[test] fn string_less_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "'1000' < 1000n" => false); - check_comparison!(context, "'2000' < 1000n" => false); - check_comparison!(context, "'500' < 1000n" => true); - check_comparison!(context, "'-1' < 1n" => true); - check_comparison!(context, "'-1' < 2n" => true); - check_comparison!(context, "'InvalidBigInt' < -100n" => false); + run_test([ + TestAction::assert("!('1000' < 1000n)"), + TestAction::assert("!('2000' < 1000n)"), + TestAction::assert("'500' < 1000n"), + TestAction::assert("'-1' < 1n"), + TestAction::assert("'-1' < 2n"), + TestAction::assert("!('InvalidBigInt' < -100n)"), + ]); } // ------------------------------------------- #[test] fn number_less_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "1 <= 2" => true); - check_comparison!(context, "2 <= 2" => true); - check_comparison!(context, "3 <= 2" => false); - check_comparison!(context, "2 <= 2.5" => true); - check_comparison!(context, "2.5 <= 2" => false); + run_test([ + TestAction::assert("1 <= 2"), + TestAction::assert("2 <= 2"), + TestAction::assert("!(3 <= 2)"), + TestAction::assert("2 <= 2.5"), + TestAction::assert("!(2.5 <= 2)"), + ]); } #[test] fn string_less_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "'1' <= 2" => true); - check_comparison!(context, "'2' <= 2" => true); - check_comparison!(context, "'3' <= 2" => false); - check_comparison!(context, "'2' <= 2.5" => true); - check_comparison!(context, "'2.5' < 2" => false); + run_test([ + TestAction::assert("'1' <= 2"), + TestAction::assert("'2' <= 2"), + TestAction::assert("!('3' <= 2)"), + TestAction::assert("'2' <= 2.5"), + TestAction::assert("!('2.5' <= 2)"), + ]); } #[test] fn number_less_than_or_equal_string() { - let mut context = Context::default(); - check_comparison!(context, "1 <= '2'" => true); - check_comparison!(context, "2 <= '2'" => true); - check_comparison!(context, "3 <= '2'" => false); - check_comparison!(context, "2 <= '2.5'" => true); - check_comparison!(context, "2.5 <= '2'" => false); + run_test([ + TestAction::assert("1 <= '2'"), + TestAction::assert("2 <= '2'"), + TestAction::assert("!(3 <= '2')"), + TestAction::assert("2 <= '2.5'"), + TestAction::assert("!(2.5 <= '2')"), + ]); } #[test] fn number_object_less_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) <= '2'" => true); - check_comparison!(context, "new Number(2) <= '2'" => true); - check_comparison!(context, "new Number(3) <= '2'" => false); - check_comparison!(context, "new Number(2) <= '2.5'" => true); - check_comparison!(context, "new Number(2.5) <= '2'" => false); + run_test([ + TestAction::assert("new Number(1) <= '2'"), + TestAction::assert("new Number(2) <= '2'"), + TestAction::assert("!(new Number(3) <= '2')"), + TestAction::assert("new Number(2) <= '2.5'"), + TestAction::assert("!(new Number(2.5) <= '2')"), + ]); } #[test] fn number_object_less_than_number_or_equal_object() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) <= new Number(2)" => true); - check_comparison!(context, "new Number(2) <= new Number(2)" => true); - check_comparison!(context, "new Number(3) <= new Number(2)" => false); - check_comparison!(context, "new Number(2) <= new Number(2.5)" => true); - check_comparison!(context, "new Number(2.5) <= new Number(2)" => false); + run_test([ + TestAction::assert("new Number(1) <= new Number(2)"), + TestAction::assert("new Number(2) <= new Number(2)"), + TestAction::assert("!(new Number(3) <= new Number(2))"), + TestAction::assert("new Number(2) <= new Number(2.5)"), + TestAction::assert("!(new Number(2.5) <= new Number(2))"), + ]); } #[test] fn string_less_than_or_equal_string() { - let mut context = Context::default(); - check_comparison!(context, "'hello' <= 'hello'" => true); - check_comparison!(context, "'hell' <= 'hello'" => true); - check_comparison!(context, "'hello, world' <= 'world'" => true); - check_comparison!(context, "'aa' <= 'ab'" => true); + run_test([ + TestAction::assert("'hello' <= 'hello'"), + TestAction::assert("'hell' <= 'hello'"), + TestAction::assert("'hello, world' <= 'world'"), + TestAction::assert("'aa' <= 'ab'"), + ]); } #[test] fn string_object_less_than_or_equal_string() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') <= 'hello'" => true); - check_comparison!(context, "new String('hell') <= 'hello'" => true); - check_comparison!(context, "new String('hello, world') <= 'world'" => true); - check_comparison!(context, "new String('aa') <= 'ab'" => true); + run_test([ + TestAction::assert("new String('hello') <= 'hello'"), + TestAction::assert("new String('hell') <= 'hello'"), + TestAction::assert("new String('hello, world') <= 'world'"), + TestAction::assert("new String('aa') <= 'ab'"), + ]); } #[test] fn string_object_less_than_string_or_equal_object() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') <= new String('hello')" => true); - check_comparison!(context, "new String('hell') <= new String('hello')" => true); - check_comparison!(context, "new String('hello, world') <= new String('world')" => true); - check_comparison!(context, "new String('aa') <= new String('ab')" => true); + run_test([ + TestAction::assert("new String('hello') <= new String('hello')"), + TestAction::assert("new String('hell') <= new String('hello')"), + TestAction::assert("new String('hello, world') <= new String('world')"), + TestAction::assert("new String('aa') <= new String('ab')"), + ]); } #[test] fn bigint_less_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "1n <= 10" => true); - check_comparison!(context, "10n <= 10" => true); - check_comparison!(context, "100n <= 10" => false); - check_comparison!(context, "10n <= 10.9" => true); + run_test([ + TestAction::assert("1n <= 10"), + TestAction::assert("10n <= 10"), + TestAction::assert("!(100n <= 10)"), + TestAction::assert("10n <= 10.9"), + ]); } #[test] fn number_less_than_or_equal_bigint() { - let mut context = Context::default(); - check_comparison!(context, "10 <= 1n" => false); - check_comparison!(context, "1 <= 1n" => true); - check_comparison!(context, "-1 <= -1n" => true); - check_comparison!(context, "-1.9 <= -1n" => true); + run_test([ + TestAction::assert("!(10 <= 1n)"), + TestAction::assert("1 <= 1n"), + TestAction::assert("-1 <= -1n"), + TestAction::assert("-1.9 <= -1n"), + ]); } #[test] fn negative_infinity_less_than_or_equal_bigint() { - let mut context = Context::default(); - check_comparison!(context, "-Infinity <= -10000000000n" => true); - check_comparison!(context, "-Infinity <= (-1n << 100n)" => true); + run_test([ + TestAction::assert("-Infinity <= -10000000000n"), + TestAction::assert("-Infinity <= (-1n << 100n)"), + ]); } #[test] fn bigint_less_than_or_equal_infinity() { - let mut context = Context::default(); - check_comparison!(context, "1000n <= NaN" => false); - check_comparison!(context, "(1n << 100n) <= NaN" => false); + run_test([ + TestAction::assert("!(1000n <= NaN)"), + TestAction::assert("!((1n << 100n) <= NaN)"), + ]); } #[test] fn nan_less_than_or_equal_bigint() { - let mut context = Context::default(); - check_comparison!(context, "NaN <= -10000000000n" => false); - check_comparison!(context, "NaN <= (-1n << 100n)" => false); + run_test([ + TestAction::assert("!(NaN <= -10000000000n)"), + TestAction::assert("!(NaN <= (-1n << 100n))"), + ]); } #[test] fn bigint_less_than_or_equal_nan() { - let mut context = Context::default(); - check_comparison!(context, "1000n <= Infinity" => true); - check_comparison!(context, "(1n << 100n) <= Infinity" => true); + run_test([ + TestAction::assert("1000n <= Infinity"), + TestAction::assert("(1n << 100n) <= Infinity"), + ]); } #[test] fn bigint_less_than_or_equal_string() { - let mut context = Context::default(); - check_comparison!(context, "1000n <= '1000'" => true); - check_comparison!(context, "1000n <= '2000'" => true); - check_comparison!(context, "1n <= '-1'" => false); - check_comparison!(context, "2n <= '-1'" => false); - check_comparison!(context, "-100n <= 'InvalidBigInt'" => false); + run_test([ + TestAction::assert("1000n <= '1000'"), + TestAction::assert("1000n <= '2000'"), + TestAction::assert("!(1n <= '-1')"), + TestAction::assert("!(2n <= '-1')"), + TestAction::assert("!(-100n <= 'InvalidBigInt')"), + ]); } #[test] fn string_less_than_or_equal_bigint() { - let mut context = Context::default(); - check_comparison!(context, "'1000' <= 1000n" => true); - check_comparison!(context, "'2000' <= 1000n" => false); - check_comparison!(context, "'500' <= 1000n" => true); - check_comparison!(context, "'-1' <= 1n" => true); - check_comparison!(context, "'-1' <= 2n" => true); - check_comparison!(context, "'InvalidBigInt' <= -100n" => false); + run_test([ + TestAction::assert("'1000' <= 1000n"), + TestAction::assert("!('2000' <= 1000n)"), + TestAction::assert("'500' <= 1000n"), + TestAction::assert("'-1' <= 1n"), + TestAction::assert("'-1' <= 2n"), + TestAction::assert("!('InvalidBigInt' <= -100n)"), + ]); } // ------------------------------------------- #[test] fn number_greater_than_number() { - let mut context = Context::default(); - check_comparison!(context, "1 > 2" => false); - check_comparison!(context, "2 > 2" => false); - check_comparison!(context, "3 > 2" => true); - check_comparison!(context, "2 > 2.5" => false); - check_comparison!(context, "2.5 > 2" => true); + run_test([ + TestAction::assert("!(1 > 2)"), + TestAction::assert("!(2 > 2)"), + TestAction::assert("3 > 2"), + TestAction::assert("!(2 > 2.5)"), + TestAction::assert("2.5 > 2"), + ]); } #[test] fn string_greater_than_number() { - let mut context = Context::default(); - check_comparison!(context, "'1' > 2" => false); - check_comparison!(context, "'2' > 2" => false); - check_comparison!(context, "'3' > 2" => true); - check_comparison!(context, "'2' > 2.5" => false); - check_comparison!(context, "'2.5' > 2" => true); + run_test([ + TestAction::assert("!('1' > 2)"), + TestAction::assert("!('2' > 2)"), + TestAction::assert("'3' > 2"), + TestAction::assert("!('2' > 2.5)"), + TestAction::assert("'2.5' > 2"), + ]); } #[test] fn number_less_greater_string() { - let mut context = Context::default(); - check_comparison!(context, "1 > '2'" => false); - check_comparison!(context, "2 > '2'" => false); - check_comparison!(context, "3 > '2'" => true); - check_comparison!(context, "2 > '2.5'" => false); - check_comparison!(context, "2.5 > '2'" => true); + run_test([ + TestAction::assert("!(1 > '2')"), + TestAction::assert("!(2 > '2')"), + TestAction::assert("3 > '2'"), + TestAction::assert("!(2 > '2.5')"), + TestAction::assert("2.5 > '2'"), + ]); } #[test] fn number_object_greater_than_number() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) > '2'" => false); - check_comparison!(context, "new Number(2) > '2'" => false); - check_comparison!(context, "new Number(3) > '2'" => true); - check_comparison!(context, "new Number(2) > '2.5'" => false); - check_comparison!(context, "new Number(2.5) > '2'" => true); + run_test([ + TestAction::assert("!(new Number(1) > '2')"), + TestAction::assert("!(new Number(2) > '2')"), + TestAction::assert("new Number(3) > '2'"), + TestAction::assert("!(new Number(2) > '2.5')"), + TestAction::assert("new Number(2.5) > '2'"), + ]); } #[test] fn number_object_greater_than_number_object() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) > new Number(2)" => false); - check_comparison!(context, "new Number(2) > new Number(2)" => false); - check_comparison!(context, "new Number(3) > new Number(2)" => true); - check_comparison!(context, "new Number(2) > new Number(2.5)" => false); - check_comparison!(context, "new Number(2.5) > new Number(2)" => true); + run_test([ + TestAction::assert("!(new Number(1) > new Number(2))"), + TestAction::assert("!(new Number(2) > new Number(2))"), + TestAction::assert("3 > new Number(2)"), + TestAction::assert("!(new Number(2) > new Number(2.5))"), + TestAction::assert("new Number(2.5) > new Number(2)"), + ]); } #[test] fn string_greater_than_string() { - let mut context = Context::default(); - check_comparison!(context, "'hello' > 'hello'" => false); - check_comparison!(context, "'hell' > 'hello'" => false); - check_comparison!(context, "'hello, world' > 'world'" => false); - check_comparison!(context, "'aa' > 'ab'" => false); - check_comparison!(context, "'ab' > 'aa'" => true); + run_test([ + TestAction::assert("!('hello' > 'hello')"), + TestAction::assert("!('hell' > 'hello')"), + TestAction::assert("!('hello, world' > 'world')"), + TestAction::assert("!('aa' > 'ab')"), + TestAction::assert("'ab' > 'aa'"), + ]); } #[test] fn string_object_greater_than_string() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') > 'hello'" => false); - check_comparison!(context, "new String('hell') > 'hello'" => false); - check_comparison!(context, "new String('hello, world') > 'world'" => false); - check_comparison!(context, "new String('aa') > 'ab'" => false); - check_comparison!(context, "new String('ab') > 'aa'" => true); + run_test([ + TestAction::assert("!(new String('hello') > 'hello')"), + TestAction::assert("!(new String('hell') > 'hello')"), + TestAction::assert("!(new String('hello, world') > 'world')"), + TestAction::assert("!(new String('aa') > 'ab')"), + TestAction::assert("new String('ab') > 'aa'"), + ]); } #[test] fn string_object_greater_than_string_object() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') > new String('hello')" => false); - check_comparison!(context, "new String('hell') > new String('hello')" => false); - check_comparison!(context, "new String('hello, world') > new String('world')" => false); - check_comparison!(context, "new String('aa') > new String('ab')" => false); - check_comparison!(context, "new String('ab') > new String('aa')" => true); + run_test([ + TestAction::assert("!(new String('hello') > new String('hello'))"), + TestAction::assert("!(new String('hell') > new String('hello'))"), + TestAction::assert("!(new String('hello, world') > new String('world'))"), + TestAction::assert("!(new String('aa') > new String('ab'))"), + TestAction::assert("new String('ab') > new String('aa')"), + ]); } #[test] fn bigint_greater_than_number() { - let mut context = Context::default(); - check_comparison!(context, "1n > 10" => false); - check_comparison!(context, "10n > 10" => false); - check_comparison!(context, "100n > 10" => true); - check_comparison!(context, "10n > 10.9" => false); + run_test([ + TestAction::assert("!(1n > 10)"), + TestAction::assert("!(10n > 10)"), + TestAction::assert("100n > 10"), + TestAction::assert("!(10n > 10.9)"), + ]); } #[test] fn number_greater_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "10 > 1n" => true); - check_comparison!(context, "1 > 1n" => false); - check_comparison!(context, "-1 > -1n" => false); - check_comparison!(context, "-1.9 > -1n" => false); + run_test([ + TestAction::assert("10 > 1n"), + TestAction::assert("!(1 > 1n)"), + TestAction::assert("!(-1 > -1n)"), + TestAction::assert("!(-1.9 > -1n)"), + ]); } #[test] fn negative_infinity_greater_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "-Infinity > -10000000000n" => false); - check_comparison!(context, "-Infinity > (-1n << 100n)" => false); + run_test([ + TestAction::assert("!(-Infinity > -10000000000n)"), + TestAction::assert("!(-Infinity > (-1n << 100n))"), + ]); } #[test] fn bigint_greater_than_infinity() { - let mut context = Context::default(); - check_comparison!(context, "1000n > NaN" => false); - check_comparison!(context, "(1n << 100n) > NaN" => false); + run_test([ + TestAction::assert("!(1000n > NaN)"), + TestAction::assert("!((1n << 100n) > NaN)"), + ]); } #[test] fn nan_greater_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "NaN > -10000000000n" => false); - check_comparison!(context, "NaN > (-1n << 100n)" => false); + run_test([ + TestAction::assert("!(NaN > -10000000000n)"), + TestAction::assert("!(NaN > (-1n << 100n))"), + ]); } #[test] fn bigint_greater_than_nan() { - let mut context = Context::default(); - check_comparison!(context, "1000n > Infinity" => false); - check_comparison!(context, "(1n << 100n) > Infinity" => false); + run_test([ + TestAction::assert("!(1000n > Infinity)"), + TestAction::assert("!((1n << 100n) > Infinity)"), + ]); } #[test] fn bigint_greater_than_string() { - let mut context = Context::default(); - check_comparison!(context, "1000n > '1000'" => false); - check_comparison!(context, "1000n > '2000'" => false); - check_comparison!(context, "1n > '-1'" => true); - check_comparison!(context, "2n > '-1'" => true); - check_comparison!(context, "-100n > 'InvalidBigInt'" => false); + run_test([ + TestAction::assert("!(1000n > '1000')"), + TestAction::assert("!(1000n > '2000')"), + TestAction::assert("1n > '-1'"), + TestAction::assert("2n > '-1'"), + TestAction::assert("!(-100n > 'InvalidBigInt')"), + ]); } #[test] fn string_greater_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "'1000' > 1000n" => false); - check_comparison!(context, "'2000' > 1000n" => true); - check_comparison!(context, "'500' > 1000n" => false); - check_comparison!(context, "'-1' > 1n" => false); - check_comparison!(context, "'-1' > 2n" => false); - check_comparison!(context, "'InvalidBigInt' > -100n" => false); + run_test([ + TestAction::assert("!('1000' > 1000n)"), + TestAction::assert("'2000' > 1000n"), + TestAction::assert("!('500' > 1000n)"), + TestAction::assert("!('-1' > 1n)"), + TestAction::assert("!('-1' > 2n)"), + TestAction::assert("!('InvalidBigInt' > -100n)"), + ]); } // ---------------------------------------------- #[test] fn number_greater_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "1 >= 2" => false); - check_comparison!(context, "2 >= 2" => true); - check_comparison!(context, "3 >= 2" => true); - check_comparison!(context, "2 >= 2.5" => false); - check_comparison!(context, "2.5 >= 2" => true); + run_test([ + TestAction::assert("!(1 >= 2)"), + TestAction::assert("2 >= 2"), + TestAction::assert("3 >= 2"), + TestAction::assert("!(2 >= 2.5)"), + TestAction::assert("2.5 >= 2"), + ]); } #[test] fn string_greater_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "'1' >= 2" => false); - check_comparison!(context, "'2' >= 2" => true); - check_comparison!(context, "'3' >= 2" => true); - check_comparison!(context, "'2' >= 2.5" => false); - check_comparison!(context, "'2.5' >= 2" => true); + run_test([ + TestAction::assert("!('1' >= 2)"), + TestAction::assert("'2' >= 2"), + TestAction::assert("'3' >= 2"), + TestAction::assert("!('2' >= 2.5)"), + TestAction::assert("'2.5' >= 2"), + ]); } #[test] fn number_less_greater_or_equal_string() { - let mut context = Context::default(); - check_comparison!(context, "1 >= '2'" => false); - check_comparison!(context, "2 >= '2'" => true); - check_comparison!(context, "3 >= '2'" => true); - check_comparison!(context, "2 >= '2.5'" => false); - check_comparison!(context, "2.5 >= '2'" => true); + run_test([ + TestAction::assert("!(1 >= '2')"), + TestAction::assert("2 >= '2'"), + TestAction::assert("3 >= '2'"), + TestAction::assert("!(2 >= '2.5')"), + TestAction::assert("2.5 >= '2'"), + ]); } #[test] fn number_object_greater_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) >= '2'" => false); - check_comparison!(context, "new Number(2) >= '2'" => true); - check_comparison!(context, "new Number(3) >= '2'" => true); - check_comparison!(context, "new Number(2) >= '2.5'" => false); - check_comparison!(context, "new Number(2.5) >= '2'" => true); + run_test([ + TestAction::assert("!(new Number(1) >= '2')"), + TestAction::assert("new Number(2) >= '2'"), + TestAction::assert("new Number(3) >= '2'"), + TestAction::assert("!(new Number(2) >= '2.5')"), + TestAction::assert("new Number(2.5) >= '2'"), + ]); } #[test] fn number_object_greater_than_or_equal_number_object() { - let mut context = Context::default(); - check_comparison!(context, "new Number(1) >= new Number(2)" => false); - check_comparison!(context, "new Number(2) >= new Number(2)" => true); - check_comparison!(context, "new Number(3) >= new Number(2)" => true); - check_comparison!(context, "new Number(2) >= new Number(2.5)" => false); - check_comparison!(context, "new Number(2.5) >= new Number(2)" => true); + run_test([ + TestAction::assert("!(new Number(1) >= new Number(2))"), + TestAction::assert("new Number(2) >= new Number(2)"), + TestAction::assert("new Number(3) >= new Number(2)"), + TestAction::assert("!(new Number(2) >= new Number(2.5))"), + TestAction::assert("new Number(2.5) >= new Number(2)"), + ]); } #[test] fn string_greater_than_or_equal_string() { - let mut context = Context::default(); - check_comparison!(context, "'hello' >= 'hello'" => true); - check_comparison!(context, "'hell' >= 'hello'" => false); - check_comparison!(context, "'hello, world' >= 'world'" => false); - check_comparison!(context, "'aa' >= 'ab'" => false); - check_comparison!(context, "'ab' >= 'aa'" => true); + run_test([ + TestAction::assert("'hello' >= 'hello'"), + TestAction::assert("!('hell' >= 'hello')"), + TestAction::assert("!('hello, world' >= 'world')"), + TestAction::assert("!('aa' >= 'ab')"), + TestAction::assert("'ab' >= 'aa'"), + ]); } #[test] fn string_object_greater_or_equal_than_string() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') >= 'hello'" => true); - check_comparison!(context, "new String('hell') >= 'hello'" => false); - check_comparison!(context, "new String('hello, world') >= 'world'" => false); - check_comparison!(context, "new String('aa') >= 'ab'" => false); - check_comparison!(context, "new String('ab') >= 'aa'" => true); + run_test([ + TestAction::assert("new String('hello') >= 'hello'"), + TestAction::assert("!(new String('hell') >= 'hello')"), + TestAction::assert("!(new String('hello, world') >= 'world')"), + TestAction::assert("!(new String('aa') >= 'ab')"), + TestAction::assert("new String('ab') >= 'aa'"), + ]); } #[test] fn string_object_greater_than_or_equal_string_object() { - let mut context = Context::default(); - check_comparison!(context, "new String('hello') >= new String('hello')" => true); - check_comparison!(context, "new String('hell') >= new String('hello')" => false); - check_comparison!(context, "new String('hello, world') >= new String('world')" => false); - check_comparison!(context, "new String('aa') >= new String('ab')" => false); - check_comparison!(context, "new String('ab') >= new String('aa')" => true); + run_test([ + TestAction::assert("new String('hello') >= new String('hello')"), + TestAction::assert("!(new String('hell') >= new String('hello'))"), + TestAction::assert("!(new String('hello, world') >= new String('world'))"), + TestAction::assert("!(new String('aa') >= new String('ab'))"), + TestAction::assert("new String('ab') >= new String('aa')"), + ]); } #[test] fn bigint_greater_than_or_equal_number() { - let mut context = Context::default(); - check_comparison!(context, "1n >= 10" => false); - check_comparison!(context, "10n >= 10" => true); - check_comparison!(context, "100n >= 10" => true); - check_comparison!(context, "10n >= 10.9" => false); + run_test([ + TestAction::assert("!(1n >= 10)"), + TestAction::assert("10n >= 10"), + TestAction::assert("100n >= 10"), + TestAction::assert("!(10n >= 10.9)"), + ]); } #[test] fn number_greater_than_or_equal_bigint() { - let mut context = Context::default(); - check_comparison!(context, "10 >= 1n" => true); - check_comparison!(context, "1 >= 1n" => true); - check_comparison!(context, "-1 >= -1n" => true); - check_comparison!(context, "-1.9 >= -1n" => false); + run_test([ + TestAction::assert("10 >= 1n"), + TestAction::assert("1 >= 1n"), + TestAction::assert("-1 >= -1n"), + TestAction::assert("!(-1.9 >= -1n)"), + ]); } #[test] fn negative_infinity_greater_or_equal_than_bigint() { - let mut context = Context::default(); - check_comparison!(context, "-Infinity >= -10000000000n" => false); - check_comparison!(context, "-Infinity >= (-1n << 100n)" => false); + run_test([ + TestAction::assert("!(-Infinity >= -10000000000n)"), + TestAction::assert("!(-Infinity >= (-1n << 100n))"), + ]); } #[test] fn bigint_greater_than_or_equal_infinity() { - let mut context = Context::default(); - check_comparison!(context, "1000n >= NaN" => false); - check_comparison!(context, "(1n << 100n) >= NaN" => false); + run_test([ + TestAction::assert("!(1000n >= NaN)"), + TestAction::assert("!((1n << 100n) >= NaN)"), + ]); } #[test] fn nan_greater_than_or_equal_bigint() { - let mut context = Context::default(); - check_comparison!(context, "NaN >= -10000000000n" => false); - check_comparison!(context, "NaN >= (-1n << 100n)" => false); + run_test([ + TestAction::assert("!(NaN >= -10000000000n)"), + TestAction::assert("!(NaN >= (-1n << 100n))"), + ]); } #[test] fn bigint_greater_than_or_equal_nan() { - let mut context = Context::default(); - check_comparison!(context, "1000n >= Infinity" => false); - check_comparison!(context, "(1n << 100n) >= Infinity" => false); + run_test([ + TestAction::assert("!(1000n >= Infinity)"), + TestAction::assert("!((1n << 100n) >= Infinity)"), + ]); } #[test] fn bigint_greater_than_or_equal_string() { - let mut context = Context::default(); - check_comparison!(context, "1000n >= '1000'" => true); - check_comparison!(context, "1000n >= '2000'" => false); - check_comparison!(context, "1n >= '-1'" => true); - check_comparison!(context, "2n >= '-1'" => true); - check_comparison!(context, "-100n >= 'InvalidBigInt'" => false); + run_test([ + TestAction::assert("1000n >= '1000'"), + TestAction::assert("!(1000n >= '2000')"), + TestAction::assert("1n >= '-1'"), + TestAction::assert("2n >= '-1'"), + TestAction::assert("!(-100n >= 'InvalidBigInt')"), + ]); } #[test] fn string_greater_than_or_equal_bigint() { - let mut context = Context::default(); - check_comparison!(context, "'1000' >= 1000n" => true); - check_comparison!(context, "'2000' >= 1000n" => true); - check_comparison!(context, "'500' >= 1000n" => false); - check_comparison!(context, "'-1' >= 1n" => false); - check_comparison!(context, "'-1' >= 2n" => false); - check_comparison!(context, "'InvalidBigInt' >= -100n" => false); + run_test([ + TestAction::assert("'1000' >= 1000n"), + TestAction::assert("'2000' >= 1000n"), + TestAction::assert("!('500' >= 1000n)"), + TestAction::assert("!('-1' >= 1n)"), + TestAction::assert("!('-1' >= 2n)"), + TestAction::assert("!('InvalidBigInt' >= -100n)"), + ]); } } diff --git a/boa_engine/src/vm/tests.rs b/boa_engine/src/vm/tests.rs index 45f1c5546d5..d5f47d84ccd 100644 --- a/boa_engine/src/vm/tests.rs +++ b/boa_engine/src/vm/tests.rs @@ -1,33 +1,38 @@ -use boa_parser::Source; - -use crate::{check_output, exec, Context, JsValue, TestAction}; +use crate::{run_test, JsValue, TestAction}; +use indoc::indoc; #[test] fn typeof_string() { - let typeof_object = r#" - const a = "hello"; - typeof a; - "#; - assert_eq!(&exec(typeof_object), "\"string\""); + run_test([TestAction::assert_eq( + indoc! {r#" + const a = "hello"; + typeof a; + "#}, + "string", + )]); } #[test] fn typeof_number() { - let typeof_number = r#" - let a = 1234; - typeof a; - "#; - assert_eq!(&exec(typeof_number), "\"number\""); + run_test([TestAction::assert_eq( + indoc! {r#" + let a = 1234; + typeof a; + "#}, + "number", + )]); } #[test] fn basic_op() { - let basic_op = r#" - const a = 1; - const b = 2; - a + b - "#; - assert_eq!(&exec(basic_op), "3"); + run_test([TestAction::assert_eq( + indoc! {r#" + const a = 1; + const b = 2; + a + b + "#}, + 3, + )]); } #[test] @@ -36,207 +41,170 @@ fn try_catch_finally_from_init() { // // here we test that the stack is not popped more than intended due to multiple catches in the // same function, which could lead to VM stack corruption - let source = r#" - try { - [(() => {throw "h";})()]; - } catch (x) { - throw "h"; - } finally { - } - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap_err() - .as_opaque() - .unwrap(), - &"h".into() - ); + run_test([TestAction::assert_opaque_error( + indoc! {r#" + try { + [(() => {throw "h";})()]; + } catch (x) { + throw "h"; + } finally { + } + "#}, + "h", + )]); } #[test] fn multiple_catches() { // see explanation on `try_catch_finally_from_init` - let source = r#" - try { + run_test([TestAction::assert_eq( + indoc! {r#" try { - [(() => {throw "h";})()]; - } catch (x) { - throw "h"; + try { + [(() => {throw "h";})()]; + } catch (x) { + throw "h"; + } + } catch (y) { } - } catch (y) { - } - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap(), - JsValue::Undefined - ); + "#}, + JsValue::undefined(), + )]); } #[test] fn use_last_expr_try_block() { - let source = r#" - try { - 19; - 7.5; - "Hello!"; - } catch (y) { - 14; - "Bye!" - } - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap(), - JsValue::from("Hello!") - ); + run_test([TestAction::assert_eq( + indoc! {r#" + try { + 19; + 7.5; + "Hello!"; + } catch (y) { + 14; + "Bye!" + } + "#}, + "Hello!", + )]); } + #[test] fn use_last_expr_catch_block() { - let source = r#" - try { - throw Error("generic error"); - 19; - 7.5; - } catch (y) { - 14; - "Hello!"; - } - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap(), - JsValue::from("Hello!") - ); + run_test([TestAction::assert_eq( + indoc! {r#" + try { + throw Error("generic error"); + 19; + 7.5; + } catch (y) { + 14; + "Hello!"; + } + "#}, + "Hello!", + )]); } #[test] fn no_use_last_expr_finally_block() { - let source = r#" - try { - } catch (y) { - } finally { - "Unused"; - } - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap(), - JsValue::undefined() - ); + run_test([TestAction::assert_eq( + indoc! {r#" + try { + } catch (y) { + } finally { + "Unused"; + } + "#}, + JsValue::undefined(), + )]); } #[test] fn finally_block_binding_env() { - let source = r#" - let buf = "Hey hey" - try { - } catch (y) { - } finally { - let x = " people"; - buf += x; - } - buf - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap(), - JsValue::from("Hey hey people") - ); + run_test([TestAction::assert_eq( + indoc! {r#" + let buf = "Hey hey"; + try { + } catch (y) { + } finally { + let x = " people"; + buf += x; + } + buf + "#}, + "Hey hey people", + )]); } #[test] fn run_super_method_in_object() { - let source = r#" - let proto = { - m() { return "super"; } - }; - let obj = { - v() { return super.m(); } - }; - Object.setPrototypeOf(obj, proto); - obj.v(); - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap(), - JsValue::from("super") - ); + run_test([TestAction::assert_eq( + indoc! {r#" + let proto = { + m() { return "super"; } + }; + let obj = { + v() { return super.m(); } + }; + Object.setPrototypeOf(obj, proto); + obj.v(); + "#}, + "super", + )]); } #[test] fn get_reference_by_super() { - let source = r#" - var fromA, fromB; - var A = { fromA: 'a', fromB: 'a' }; - var B = { fromB: 'b' }; - Object.setPrototypeOf(B, A); - var obj = { - fromA: 'c', - fromB: 'c', - method() { - fromA = (() => { return super.fromA; })(); - fromB = (() => { return super.fromB; })(); - } - }; - Object.setPrototypeOf(obj, B); - obj.method(); - fromA + fromB - "#; - - assert_eq!( - Context::default() - .eval_script(Source::from_bytes(source)) - .unwrap(), - JsValue::from("ab") - ); + run_test([TestAction::assert_eq( + indoc! {r#" + var fromA, fromB; + var A = { fromA: 'a', fromB: 'a' }; + var B = { fromB: 'b' }; + Object.setPrototypeOf(B, A); + var obj = { + fromA: 'c', + fromB: 'c', + method() { + fromA = (() => { return super.fromA; })(); + fromB = (() => { return super.fromB; })(); + } + }; + Object.setPrototypeOf(obj, B); + obj.method(); + fromA + fromB + "#}, + "ab", + )]); } #[test] fn order_of_execution_in_assigment() { - let scenario = r#" - let i = 0; - let array = [[]]; - - array[i++][i++] = i++; - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("i", "3"), - TestAction::TestEq("array.length", "1"), - TestAction::TestEq("array[0].length", "2"), + run_test([ + TestAction::run(indoc! {r#" + let i = 0; + let array = [[]]; + + array[i++][i++] = i++; + "#}), + TestAction::assert_eq("i", 3), + TestAction::assert_eq("array.length", 1), + TestAction::assert_eq("array[0].length", 2), ]); } #[test] fn order_of_execution_in_assigment_with_comma_expressions() { - let scenario = r#" - let result = ""; - function f(i) { - result += i; - } - let a = [[]]; - - (f(1), a)[(f(2), 0)][(f(3), 0)] = (f(4), 123); - "#; - - check_output(&[ - TestAction::Execute(scenario), - TestAction::TestEq("result", "\"1234\""), - ]); + run_test([TestAction::assert_eq( + indoc! {r#" + let result = ""; + function f(i) { + result += i; + } + let a = [[]]; + (f(1), a)[(f(2), 0)][(f(3), 0)] = (f(4), 123); + result + "#}, + "1234", + )]); } diff --git a/boa_parser/src/error.rs b/boa_parser/src/error.rs index 26a26056c17..1179d8fcc43 100644 --- a/boa_parser/src/error.rs +++ b/boa_parser/src/error.rs @@ -205,7 +205,7 @@ impl fmt::Display for Error { position.line_number(), position.column_number() ), - Self::Lex { err } => fmt::Display::fmt(err, f), + Self::Lex { err } => write!(f, "{err}"), } } } diff --git a/boa_parser/src/lexer/error.rs b/boa_parser/src/lexer/error.rs index e3547793fb6..aebcde5f2fe 100644 --- a/boa_parser/src/lexer/error.rs +++ b/boa_parser/src/lexer/error.rs @@ -47,8 +47,8 @@ impl fmt::Display for Error { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::IO(e) => write!(f, "I/O error: {e}"), - Self::Syntax(e, pos) => write!(f, "Syntax Error: {e} at position: {pos}"), + Self::IO(e) => write!(f, "{e}"), + Self::Syntax(e, pos) => write!(f, "{e} at position: {pos}"), } } }