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