mirror of https://github.com/boa-dev/boa.git
joshwd36
4 years ago
committed by
GitHub
22 changed files with 878 additions and 38 deletions
@ -0,0 +1,311 @@
|
||||
#![allow(clippy::mutable_key_type)] |
||||
|
||||
use super::function::{make_builtin_fn, make_constructor_fn}; |
||||
use crate::{ |
||||
builtins::{ |
||||
object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, |
||||
property::Property, |
||||
value::{ResultValue, Value}, |
||||
}, |
||||
exec::Interpreter, |
||||
BoaProfiler, |
||||
}; |
||||
use ordered_map::OrderedMap; |
||||
|
||||
pub mod ordered_map; |
||||
#[cfg(test)] |
||||
mod tests; |
||||
|
||||
#[derive(Debug, Clone)] |
||||
pub(crate) struct Map(OrderedMap<Value, Value>); |
||||
|
||||
impl Map { |
||||
pub(crate) const NAME: &'static str = "Map"; |
||||
|
||||
pub(crate) const LENGTH: usize = 1; |
||||
|
||||
/// Helper function to set the size property.
|
||||
fn set_size(this: &Value, size: usize) { |
||||
let size = Property::new() |
||||
.value(Value::from(size)) |
||||
.writable(false) |
||||
.configurable(false) |
||||
.enumerable(false); |
||||
|
||||
this.set_property("size".to_string(), size); |
||||
} |
||||
|
||||
/// `Map.prototype.set( key, value )`
|
||||
///
|
||||
/// This method associates the value with the key. Returns the map object.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.set
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set
|
||||
pub(crate) fn set(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { |
||||
let (key, value) = match args.len() { |
||||
0 => (Value::Undefined, Value::Undefined), |
||||
1 => (args[0].clone(), Value::Undefined), |
||||
_ => (args[0].clone(), args[1].clone()), |
||||
}; |
||||
|
||||
let size = if let Value::Object(ref object) = this { |
||||
let mut object = object.borrow_mut(); |
||||
if let Some(map) = object.as_map_mut() { |
||||
map.insert(key, value); |
||||
map.len() |
||||
} else { |
||||
return Err(ctx.construct_type_error("'this' is not a Map")); |
||||
} |
||||
} else { |
||||
return Err(ctx.construct_type_error("'this' is not a Map")); |
||||
}; |
||||
|
||||
Self::set_size(this, size); |
||||
Ok(this.clone()) |
||||
} |
||||
|
||||
/// `Map.prototype.delete( key )`
|
||||
///
|
||||
/// This method removes the element associated with the key, if it exists. Returns true if there was an element, false otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.delete
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete
|
||||
pub(crate) fn delete(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { |
||||
let undefined = Value::Undefined; |
||||
let key = match args.len() { |
||||
0 => &undefined, |
||||
_ => &args[0], |
||||
}; |
||||
|
||||
let (deleted, size) = if let Value::Object(ref object) = this { |
||||
let mut object = object.borrow_mut(); |
||||
if let Some(map) = object.as_map_mut() { |
||||
let deleted = map.remove(key).is_some(); |
||||
(deleted, map.len()) |
||||
} else { |
||||
return Err(ctx.construct_type_error("'this' is not a Map")); |
||||
} |
||||
} else { |
||||
return Err(ctx.construct_type_error("'this' is not a Map")); |
||||
}; |
||||
Self::set_size(this, size); |
||||
Ok(deleted.into()) |
||||
} |
||||
|
||||
/// `Map.prototype.get( key )`
|
||||
///
|
||||
/// This method returns the value associated with the key, or undefined if there is none.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.get
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
|
||||
pub(crate) fn get(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { |
||||
let undefined = Value::Undefined; |
||||
let key = match args.len() { |
||||
0 => &undefined, |
||||
_ => &args[0], |
||||
}; |
||||
|
||||
if let Value::Object(ref object) = this { |
||||
let object = object.borrow(); |
||||
if let Some(map) = object.as_map_ref() { |
||||
return Ok(if let Some(result) = map.get(key) { |
||||
result.clone() |
||||
} else { |
||||
Value::Undefined |
||||
}); |
||||
} |
||||
} |
||||
|
||||
Err(ctx.construct_type_error("'this' is not a Map")) |
||||
} |
||||
|
||||
/// `Map.prototype.clear( )`
|
||||
///
|
||||
/// This method removes all entries from the map.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.clear
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear
|
||||
pub(crate) fn clear(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.set_data(ObjectData::Map(OrderedMap::new())); |
||||
|
||||
Self::set_size(this, 0); |
||||
|
||||
Ok(Value::Undefined) |
||||
} |
||||
|
||||
/// `Map.prototype.has( key )`
|
||||
///
|
||||
/// This method checks if the map contains an entry with the given key.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
|
||||
pub(crate) fn has(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { |
||||
let undefined = Value::Undefined; |
||||
let key = match args.len() { |
||||
0 => &undefined, |
||||
_ => &args[0], |
||||
}; |
||||
|
||||
if let Value::Object(ref object) = this { |
||||
let object = object.borrow(); |
||||
if let Some(map) = object.as_map_ref() { |
||||
return Ok(map.contains_key(key).into()); |
||||
} |
||||
} |
||||
|
||||
Err(ctx.construct_type_error("'this' is not a Map")) |
||||
} |
||||
|
||||
/// `Map.prototype.forEach( callbackFn [ , thisArg ] )`
|
||||
///
|
||||
/// This method executes the provided callback function for each key-value pair in the map.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.foreach
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach
|
||||
pub(crate) fn for_each( |
||||
this: &Value, |
||||
args: &[Value], |
||||
interpreter: &mut Interpreter, |
||||
) -> ResultValue { |
||||
if args.is_empty() { |
||||
return Err(Value::from("Missing argument for Map.prototype.forEach")); |
||||
} |
||||
|
||||
let callback_arg = &args[0]; |
||||
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); |
||||
|
||||
if let Value::Object(ref object) = this { |
||||
let object = object.borrow(); |
||||
if let Some(map) = object.as_map_ref().cloned() { |
||||
for (key, value) in map { |
||||
let arguments = [value, key, this.clone()]; |
||||
|
||||
interpreter.call(callback_arg, &this_arg, &arguments)?; |
||||
} |
||||
} |
||||
} |
||||
|
||||
Ok(Value::Undefined) |
||||
} |
||||
|
||||
/// Helper function to get a key-value pair from an array.
|
||||
fn get_key_value(value: &Value) -> Option<(Value, Value)> { |
||||
if let Value::Object(object) = value { |
||||
if object.borrow().is_array() { |
||||
let (key, value) = match i32::from(&value.get_field("length")) { |
||||
0 => (Value::Undefined, Value::Undefined), |
||||
1 => (value.get_field("0"), Value::Undefined), |
||||
_ => (value.get_field("0"), value.get_field("1")), |
||||
}; |
||||
return Some((key, value)); |
||||
} |
||||
} |
||||
None |
||||
} |
||||
|
||||
/// Create a new map
|
||||
pub(crate) fn make_map(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { |
||||
// Make a new Object which will internally represent the Array (mapping
|
||||
// between indices and values): this creates an Object with no prototype
|
||||
|
||||
// Set Prototype
|
||||
let prototype = ctx.realm.global_obj.get_field("Map").get_field(PROTOTYPE); |
||||
|
||||
this.set_internal_slot(INSTANCE_PROTOTYPE, prototype); |
||||
// This value is used by console.log and other routines to match Object type
|
||||
// to its Javascript Identifier (global constructor method name)
|
||||
|
||||
// add our arguments in
|
||||
let data = match args.len() { |
||||
0 => OrderedMap::new(), |
||||
_ => match &args[0] { |
||||
Value::Object(object) => { |
||||
let object = object.borrow(); |
||||
if let Some(map) = object.as_map_ref().cloned() { |
||||
map |
||||
} else if object.is_array() { |
||||
let mut map = OrderedMap::new(); |
||||
let len = i32::from(&args[0].get_field("length")); |
||||
for i in 0..len { |
||||
let val = &args[0].get_field(i.to_string()); |
||||
let (key, value) = Self::get_key_value(val).ok_or_else(|| { |
||||
ctx.construct_type_error( |
||||
"iterable for Map should have array-like objects", |
||||
) |
||||
})?; |
||||
map.insert(key, value); |
||||
} |
||||
map |
||||
} else { |
||||
return Err(ctx.construct_type_error( |
||||
"iterable for Map should have array-like objects", |
||||
)); |
||||
} |
||||
} |
||||
_ => { |
||||
return Err( |
||||
ctx.construct_type_error("iterable for Map should have array-like objects") |
||||
) |
||||
} |
||||
}, |
||||
}; |
||||
|
||||
// finally create length property
|
||||
Self::set_size(this, data.len()); |
||||
|
||||
this.set_data(ObjectData::Map(data)); |
||||
|
||||
Ok(this.clone()) |
||||
} |
||||
|
||||
/// Initialise the `Map` object on the global object.
|
||||
pub(crate) fn init(global: &Value) -> (&str, Value) { |
||||
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
// Create prototype
|
||||
let prototype = Value::new_object(Some(global)); |
||||
|
||||
make_builtin_fn(Self::set, "set", &prototype, 2); |
||||
make_builtin_fn(Self::delete, "delete", &prototype, 1); |
||||
make_builtin_fn(Self::get, "get", &prototype, 1); |
||||
make_builtin_fn(Self::clear, "clear", &prototype, 0); |
||||
make_builtin_fn(Self::has, "has", &prototype, 1); |
||||
make_builtin_fn(Self::for_each, "forEach", &prototype, 1); |
||||
|
||||
let map_object = make_constructor_fn( |
||||
Self::NAME, |
||||
Self::LENGTH, |
||||
Self::make_map, |
||||
global, |
||||
prototype, |
||||
true, |
||||
false, |
||||
); |
||||
|
||||
(Self::NAME, map_object) |
||||
} |
||||
} |
@ -0,0 +1,144 @@
|
||||
use gc::{custom_trace, Finalize, Trace}; |
||||
use indexmap::{map::IntoIter, map::Iter, map::IterMut, IndexMap}; |
||||
use std::collections::hash_map::RandomState; |
||||
use std::fmt::Debug; |
||||
use std::hash::{BuildHasher, Hash}; |
||||
|
||||
/// A newtype wrapping indexmap::IndexMap
|
||||
#[derive(Clone)] |
||||
pub struct OrderedMap<K, V, S = RandomState>(IndexMap<K, V, S>) |
||||
where |
||||
K: Hash + Eq; |
||||
|
||||
impl<K: Eq + Hash + Trace, V: Trace, S: BuildHasher> Finalize for OrderedMap<K, V, S> {} |
||||
unsafe impl<K: Eq + Hash + Trace, V: Trace, S: BuildHasher> Trace for OrderedMap<K, V, S> { |
||||
custom_trace!(this, { |
||||
for (k, v) in this.0.iter() { |
||||
mark(k); |
||||
mark(v); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
impl<K: Hash + Eq + Debug, V: Debug> Debug for OrderedMap<K, V> { |
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
||||
self.0.fmt(formatter) |
||||
} |
||||
} |
||||
|
||||
impl<K: Hash + Eq, V> Default for OrderedMap<K, V> { |
||||
fn default() -> Self { |
||||
Self::new() |
||||
} |
||||
} |
||||
|
||||
impl<K, V> OrderedMap<K, V> |
||||
where |
||||
K: Hash + Eq, |
||||
{ |
||||
pub fn new() -> Self { |
||||
OrderedMap(IndexMap::new()) |
||||
} |
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self { |
||||
OrderedMap(IndexMap::with_capacity(capacity)) |
||||
} |
||||
|
||||
/// Return the number of key-value pairs in the map.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn len(&self) -> usize { |
||||
self.0.len() |
||||
} |
||||
|
||||
/// Returns true if the map contains no elements.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn is_empty(&self) -> bool { |
||||
self.0.len() == 0 |
||||
} |
||||
|
||||
/// Insert a key-value pair in the map.
|
||||
///
|
||||
/// If an equivalent key already exists in the map: the key remains and
|
||||
/// retains in its place in the order, its corresponding value is updated
|
||||
/// with `value` and the older value is returned inside `Some(_)`.
|
||||
///
|
||||
/// If no equivalent key existed in the map: the new key-value pair is
|
||||
/// inserted, last in order, and `None` is returned.
|
||||
///
|
||||
/// Computes in **O(1)** time (amortized average).
|
||||
pub fn insert(&mut self, key: K, value: V) -> Option<V> { |
||||
self.0.insert(key, value) |
||||
} |
||||
|
||||
/// Remove the key-value pair equivalent to `key` and return
|
||||
/// its value.
|
||||
///
|
||||
/// Like `Vec::remove`, the pair is removed by shifting all of the
|
||||
/// elements that follow it, preserving their relative order.
|
||||
/// **This perturbs the index of all of those elements!**
|
||||
///
|
||||
/// Return `None` if `key` is not in map.
|
||||
///
|
||||
/// Computes in **O(n)** time (average).
|
||||
pub fn remove(&mut self, key: &K) -> Option<V> { |
||||
self.0.shift_remove(key) |
||||
} |
||||
|
||||
/// Return a reference to the value stored for `key`, if it is present,
|
||||
/// else `None`.
|
||||
///
|
||||
/// Computes in **O(1)** time (average).
|
||||
pub fn get(&self, key: &K) -> Option<&V> { |
||||
self.0.get(key) |
||||
} |
||||
|
||||
/// Return an iterator over the key-value pairs of the map, in their order
|
||||
pub fn iter(&self) -> Iter<'_, K, V> { |
||||
self.0.iter() |
||||
} |
||||
|
||||
/// Return `true` if an equivalent to `key` exists in the map.
|
||||
///
|
||||
/// Computes in **O(1)** time (average).
|
||||
pub fn contains_key(&self, key: &K) -> bool { |
||||
self.0.contains_key(key) |
||||
} |
||||
} |
||||
|
||||
impl<'a, K, V, S> IntoIterator for &'a OrderedMap<K, V, S> |
||||
where |
||||
K: Hash + Eq, |
||||
S: BuildHasher, |
||||
{ |
||||
type Item = (&'a K, &'a V); |
||||
type IntoIter = Iter<'a, K, V>; |
||||
fn into_iter(self) -> Self::IntoIter { |
||||
self.0.iter() |
||||
} |
||||
} |
||||
|
||||
impl<'a, K, V, S> IntoIterator for &'a mut OrderedMap<K, V, S> |
||||
where |
||||
K: Hash + Eq, |
||||
S: BuildHasher, |
||||
{ |
||||
type Item = (&'a K, &'a mut V); |
||||
type IntoIter = IterMut<'a, K, V>; |
||||
fn into_iter(self) -> Self::IntoIter { |
||||
self.0.iter_mut() |
||||
} |
||||
} |
||||
|
||||
impl<K, V, S> IntoIterator for OrderedMap<K, V, S> |
||||
where |
||||
K: Hash + Eq, |
||||
S: BuildHasher, |
||||
{ |
||||
type Item = (K, V); |
||||
type IntoIter = IntoIter<K, V>; |
||||
fn into_iter(self) -> IntoIter<K, V> { |
||||
self.0.into_iter() |
||||
} |
||||
} |
@ -0,0 +1,231 @@
|
||||
use crate::{exec::Interpreter, forward, realm::Realm}; |
||||
|
||||
#[test] |
||||
fn construct_empty() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
var empty = new Map(); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "empty.size"); |
||||
assert_eq!(result, "0"); |
||||
} |
||||
|
||||
#[test] |
||||
fn construct_from_array() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map([["1", "one"], ["2", "two"]]); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map.size"); |
||||
assert_eq!(result, "2"); |
||||
} |
||||
|
||||
#[test] |
||||
fn clone() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let original = new Map([["1", "one"], ["2", "two"]]); |
||||
let clone = new Map(original); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "clone.size"); |
||||
assert_eq!(result, "2"); |
||||
let result = forward( |
||||
&mut engine, |
||||
r#" |
||||
original.set("3", "three"); |
||||
original.size"#, |
||||
); |
||||
assert_eq!(result, "3"); |
||||
let result = forward(&mut engine, "clone.size"); |
||||
assert_eq!(result, "2"); |
||||
} |
||||
|
||||
#[test] |
||||
fn merge() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
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 engine, init); |
||||
let result = forward(&mut engine, "merged1.size"); |
||||
assert_eq!(result, "3"); |
||||
let result = forward(&mut engine, "merged1.get('2')"); |
||||
assert_eq!(result, "second two"); |
||||
let result = forward(&mut engine, "merged2.size"); |
||||
assert_eq!(result, "4"); |
||||
} |
||||
|
||||
#[test] |
||||
fn get() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map([["1", "one"], ["2", "two"]]); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map.get('1')"); |
||||
assert_eq!(result, "one"); |
||||
let result = forward(&mut engine, "map.get('2')"); |
||||
assert_eq!(result, "two"); |
||||
let result = forward(&mut engine, "map.get('3')"); |
||||
assert_eq!(result, "undefined"); |
||||
let result = forward(&mut engine, "map.get()"); |
||||
assert_eq!(result, "undefined"); |
||||
} |
||||
|
||||
#[test] |
||||
fn set() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map(); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map.set()"); |
||||
assert_eq!(result, "Map { undefined → undefined }"); |
||||
let result = forward(&mut engine, "map.set('1', 'one')"); |
||||
assert_eq!(result, "Map { undefined → undefined, 1 → one }"); |
||||
let result = forward(&mut engine, "map.set('2')"); |
||||
assert_eq!( |
||||
result, |
||||
"Map { undefined → undefined, 1 → one, 2 → undefined }" |
||||
); |
||||
} |
||||
|
||||
#[test] |
||||
fn clear() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map([["1", "one"], ["2", "two"]]); |
||||
map.clear(); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map.size"); |
||||
assert_eq!(result, "0"); |
||||
} |
||||
|
||||
#[test] |
||||
fn delete() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map([["1", "one"], ["2", "two"]]); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map.delete('1')"); |
||||
assert_eq!(result, "true"); |
||||
let result = forward(&mut engine, "map.size"); |
||||
assert_eq!(result, "1"); |
||||
let result = forward(&mut engine, "map.delete('1')"); |
||||
assert_eq!(result, "false"); |
||||
} |
||||
|
||||
#[test] |
||||
fn has() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map([["1", "one"]]); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map.has()"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut engine, "map.has('1')"); |
||||
assert_eq!(result, "true"); |
||||
let result = forward(&mut engine, "map.has('2')"); |
||||
assert_eq!(result, "false"); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_each() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
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 engine, init); |
||||
assert_eq!(forward(&mut engine, "valueSum"), "30"); |
||||
assert_eq!(forward(&mut engine, "keySum"), "6"); |
||||
assert_eq!(forward(&mut engine, "sizeSum"), "9"); |
||||
} |
||||
|
||||
#[test] |
||||
fn modify_key() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let obj = new Object(); |
||||
let map = new Map([[obj, "one"]]); |
||||
obj.field = "Value"; |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map.get(obj)"); |
||||
assert_eq!(result, "one"); |
||||
} |
||||
|
||||
#[test] |
||||
fn order() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map([[1, "one"]]); |
||||
map.set(2, "two"); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map"); |
||||
assert_eq!(result, "Map { 1 → one, 2 → two }"); |
||||
let result = forward(&mut engine, "map.set(1, \"five\");map"); |
||||
assert_eq!(result, "Map { 1 → five, 2 → two }"); |
||||
let result = forward(&mut engine, "map.set();map"); |
||||
assert_eq!(result, "Map { 1 → five, 2 → two, undefined → undefined }"); |
||||
let result = forward(&mut engine, "map.delete(2);map"); |
||||
assert_eq!(result, "Map { 1 → five, undefined → undefined }"); |
||||
let result = forward(&mut engine, "map.set(2, \"two\");map"); |
||||
assert_eq!(result, "Map { 1 → five, undefined → undefined, 2 → two }"); |
||||
} |
||||
|
||||
#[test] |
||||
fn recursive_display() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = r#" |
||||
let map = new Map(); |
||||
let array = new Array([map]); |
||||
map.set("y", map); |
||||
"#; |
||||
forward(&mut engine, init); |
||||
let result = forward(&mut engine, "map"); |
||||
assert_eq!(result, "Map { y → Map(1) }"); |
||||
let result = forward(&mut engine, "map.set(\"z\", array)"); |
||||
assert_eq!(result, "Map { y → Map(2), z → Array(1) }"); |
||||
} |
||||
|
||||
#[test] |
||||
#[should_panic] |
||||
fn not_a_function() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Interpreter::new(realm); |
||||
let init = "let map = Map()"; |
||||
forward(&mut engine, init); |
||||
} |
Loading…
Reference in new issue