mirror of https://github.com/boa-dev/boa.git
Browse Source
* Exec do..while tests.
* Parser do..while test.
* Do..while loop parser and exec implementation
* rustfmt fixes
* Update boa/src/exec/mod.rs
Co-Authored-By: HalidOdat <halidodat@gmail.com>
* Use expect(), make expect() skip newlines
* rustmf fixes
* Revert "Use expect(), make expect() skip newlines"
This reverts commit 517c4d0e06
.
* Cargo Test Build task and Debug Test (Windows) launcher
* First attempt at console.assert implementation. Tests are just for
debugging. Run `cargo test console_assert -- --nocapture` to see stderror
messages.
* Refactoring - remove unnecessary map, variable rename.
* Update boa/src/builtins/console.rs
changes from HalidOdat
Co-Authored-By: HalidOdat <halidodat@gmail.com>
* Documentation fixes
* Remove space from documentation comment
* Update boa/src/builtins/console.rs
Simplify message printing.
Co-Authored-By: Iban Eguia <razican@protonmail.ch>
* Update boa/src/builtins/console.rs
Improve docs.
Co-Authored-By: Iban Eguia <razican@protonmail.ch>
* Update boa/src/builtins/console.rs
Improve getting of assertion result.
Co-Authored-By: Iban Eguia <razican@protonmail.ch>
* rustfmt
* console.count() and console.countReset() implementation
* Console state as native rust type, temporarily placed in Realm.
* console.time[,Log,End] methods implementation
* ConsoleState as internal state in console object.
* Fix merge mess
* Formatter function, get_arg_at_index moved out to function
* Fix merge mess, pt. 2
* console.group* functions
* Moved console code to its own subdirectory, formatter tests, fixed utf-8
handling.
* Most functions implemented.
* Basic logger and logLevel implementation
* console.group uses logger
* console.dir (and at the same time dirxml) implementation
* Make builtins::value::display_obj(...) public
* Update boa/src/builtins/console/mod.rs
Co-Authored-By: HalidOdat <halidodat@gmail.com>
* Update boa/src/builtins/console/mod.rs
Co-Authored-By: HalidOdat <halidodat@gmail.com>
* Update boa/src/builtins/value/mod.rs
Co-Authored-By: Iban Eguia <razican@protonmail.ch>
Co-authored-by: HalidOdat <halidodat@gmail.com>
Co-authored-by: Iban Eguia <razican@protonmail.ch>
pull/355/head
Radek Krahl
5 years ago
committed by
GitHub
6 changed files with 558 additions and 53 deletions
@ -1,42 +0,0 @@
|
||||
#![allow(clippy::print_stdout)] |
||||
|
||||
use crate::{ |
||||
builtins::{ |
||||
function::NativeFunctionData, |
||||
value::{from_value, log_string_from, to_value, ResultValue, Value, ValueData}, |
||||
}, |
||||
exec::Interpreter, |
||||
}; |
||||
use gc::Gc; |
||||
use std::{iter::FromIterator, ops::Deref}; |
||||
|
||||
/// Print a javascript value to the standard output stream
|
||||
/// <https://console.spec.whatwg.org/#logger>
|
||||
pub fn log(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
// Welcome to console.log! The output here is what the developer sees, so its best matching through value types and stringifying to the correct output
|
||||
// The input is a vector of Values, we generate a vector of strings then
|
||||
// pass them to println!
|
||||
let args: Vec<String> = |
||||
FromIterator::from_iter(args.iter().map(|x| log_string_from(x.deref(), false))); |
||||
|
||||
println!("{}", args.join(" ")); |
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
/// Print a javascript value to the standard error stream
|
||||
pub fn error(_: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let args: Vec<String> = FromIterator::from_iter( |
||||
args.iter() |
||||
.map(|x| from_value::<String>(x.clone()).expect("Could not convert value to String")), |
||||
); |
||||
eprintln!("{}", args.join(" ")); |
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// Create a new `console` object
|
||||
pub fn create_constructor(global: &Value) -> Value { |
||||
let console = ValueData::new_obj(Some(global)); |
||||
console.set_field_slice("log", to_value(log as NativeFunctionData)); |
||||
console.set_field_slice("error", to_value(error as NativeFunctionData)); |
||||
console.set_field_slice("exception", to_value(error as NativeFunctionData)); |
||||
console |
||||
} |
@ -0,0 +1,428 @@
|
||||
#![allow(clippy::print_stdout)] |
||||
|
||||
#[cfg(test)] |
||||
mod tests; |
||||
|
||||
use crate::{ |
||||
builtins::{ |
||||
function::NativeFunctionData, |
||||
object::InternalState, |
||||
value::{display_obj, from_value, to_value, FromValue, ResultValue, Value, ValueData}, |
||||
}, |
||||
exec::Interpreter, |
||||
}; |
||||
use gc::Gc; |
||||
use std::{collections::HashMap, time::SystemTime}; |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct ConsoleState { |
||||
count_map: HashMap<String, u32>, |
||||
timer_map: HashMap<String, u128>, |
||||
groups: Vec<String>, |
||||
} |
||||
|
||||
impl ConsoleState { |
||||
fn new() -> Self { |
||||
ConsoleState { |
||||
count_map: HashMap::new(), |
||||
timer_map: HashMap::new(), |
||||
groups: vec![], |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl InternalState for ConsoleState {} |
||||
|
||||
#[derive(Debug)] |
||||
pub enum LogMessage { |
||||
Log(String), |
||||
Info(String), |
||||
Warn(String), |
||||
Error(String), |
||||
} |
||||
|
||||
fn get_arg_at_index<T: FromValue + Default>(args: &[Value], index: usize) -> Option<T> { |
||||
args.get(index) |
||||
.cloned() |
||||
.map(|s| from_value::<T>(s).expect("Convert error")) |
||||
} |
||||
|
||||
pub fn logger(msg: LogMessage, console_state: &ConsoleState) { |
||||
let indent = 2 * console_state.groups.len(); |
||||
|
||||
match msg { |
||||
LogMessage::Error(msg) => { |
||||
eprintln!("{:>width$}", msg, width = indent); |
||||
} |
||||
LogMessage::Log(msg) | LogMessage::Info(msg) | LogMessage::Warn(msg) => { |
||||
println!("{:>width$}", msg, width = indent); |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn formatter(data: &[Value]) -> String { |
||||
let target = get_arg_at_index::<String>(data, 0).unwrap_or_default(); |
||||
match data.len() { |
||||
0 => String::new(), |
||||
1 => target, |
||||
_ => { |
||||
let mut formatted = String::new(); |
||||
let mut arg_index = 1; |
||||
let mut chars = target.chars(); |
||||
while let Some(c) = chars.next() { |
||||
if c == '%' { |
||||
let fmt = chars.next().unwrap_or('%'); |
||||
match fmt { |
||||
/* integer */ |
||||
'd' | 'i' => { |
||||
let arg = get_arg_at_index::<i32>(data, arg_index).unwrap_or_default(); |
||||
formatted.push_str(&format!("{}", arg)); |
||||
arg_index += 1; |
||||
} |
||||
/* float */ |
||||
'f' => { |
||||
let arg = get_arg_at_index::<f64>(data, arg_index).unwrap_or_default(); |
||||
formatted.push_str(&format!("{number:.prec$}", number = arg, prec = 6)); |
||||
arg_index += 1 |
||||
} |
||||
/* object, FIXME: how to render this properly? */ |
||||
'o' | 'O' => { |
||||
let arg = data.get(arg_index).cloned().unwrap_or_default(); |
||||
formatted.push_str(&format!("{}", arg)); |
||||
arg_index += 1 |
||||
} |
||||
/* string */ |
||||
's' => { |
||||
let arg = |
||||
get_arg_at_index::<String>(data, arg_index).unwrap_or_default(); |
||||
formatted.push_str(&arg); |
||||
arg_index += 1 |
||||
} |
||||
'%' => formatted.push('%'), |
||||
/* TODO: %c is not implemented */ |
||||
c => { |
||||
formatted.push('%'); |
||||
formatted.push(c); |
||||
} |
||||
} |
||||
} else { |
||||
formatted.push(c); |
||||
}; |
||||
} |
||||
|
||||
/* unformatted data */ |
||||
for rest in data.iter().skip(arg_index) { |
||||
formatted.push_str(&format!(" {}", rest)) |
||||
} |
||||
|
||||
formatted |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// `console.assert(condition, ...data)`
|
||||
///
|
||||
/// Prints a JavaScript value to the standard error if first argument evaluates to `false` or there
|
||||
/// were no arguments.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#assert>
|
||||
pub fn assert(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let assertion = get_arg_at_index::<bool>(args, 0).unwrap_or_default(); |
||||
|
||||
if !assertion { |
||||
let mut args: Vec<Value> = args.iter().skip(1).cloned().collect(); |
||||
let message = "Assertion failed".to_string(); |
||||
if args.is_empty() { |
||||
args.push(to_value::<String>(message)); |
||||
} else if !args[0].is_string() { |
||||
args.insert(0, to_value::<String>(message)); |
||||
} else { |
||||
let concat = format!("{}: {}", message, args[0]); |
||||
args[0] = to_value::<String>(concat); |
||||
} |
||||
|
||||
this.with_internal_state_ref(|state| { |
||||
logger(LogMessage::Error(formatter(&args[..])), state) |
||||
}); |
||||
} |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.clear()`
|
||||
///
|
||||
/// Removes all groups and clears console if possible.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#clear>
|
||||
pub fn clear(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
state.groups.clear(); |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.debug(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "debug" logLevel.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#debug>
|
||||
pub fn debug(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); |
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.error(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "error" logLevel.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#error>
|
||||
pub fn error(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_ref(|state| logger(LogMessage::Error(formatter(&args[..])), state)); |
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.info(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "info" logLevel.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#info>
|
||||
pub fn info(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_ref(|state| logger(LogMessage::Info(formatter(&args[..])), state)); |
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.log(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "log" logLevel.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#log>
|
||||
pub fn log(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); |
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.trace(...data)`
|
||||
///
|
||||
/// Prints a stack trace with "trace" logLevel, optionally labelled by data.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#trace>
|
||||
pub fn trace(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
if !args.is_empty() { |
||||
this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state)); |
||||
|
||||
/* TODO: get and print stack trace */ |
||||
this.with_internal_state_ref(|state| { |
||||
logger( |
||||
LogMessage::Log("Not implemented: <stack trace>".to_string()), |
||||
state, |
||||
) |
||||
}); |
||||
} |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.warn(...data)`
|
||||
///
|
||||
/// Prints a JavaScript values with "warn" logLevel.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#warn>
|
||||
pub fn warn(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_ref(|state| logger(LogMessage::Warn(formatter(&args[..])), state)); |
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.count(label)`
|
||||
///
|
||||
/// Prints number of times the function was called with that particular label.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#count>
|
||||
pub fn count(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string()); |
||||
|
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
let msg = format!("count {}:", &label); |
||||
let c = state.count_map.entry(label).or_insert(0); |
||||
*c += 1; |
||||
|
||||
logger(LogMessage::Info(format!("{} {}", msg, c)), state); |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.countReset(label)`
|
||||
///
|
||||
/// Resets the counter for label.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#countreset>
|
||||
pub fn count_reset(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string()); |
||||
|
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
state.count_map.remove(&label); |
||||
|
||||
logger(LogMessage::Warn(format!("countReset {}", label)), state); |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// Returns current system time in ms.
|
||||
fn system_time_in_ms() -> u128 { |
||||
let now = SystemTime::now(); |
||||
now.duration_since(SystemTime::UNIX_EPOCH) |
||||
.expect("negative duration") |
||||
.as_millis() |
||||
} |
||||
|
||||
/// `console.time(label)`
|
||||
///
|
||||
/// Starts the timer for given label.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#time>
|
||||
pub fn time(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string()); |
||||
|
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
if state.timer_map.get(&label).is_some() { |
||||
logger( |
||||
LogMessage::Warn(format!("Timer '{}' already exist", label)), |
||||
state, |
||||
); |
||||
} else { |
||||
let time = system_time_in_ms(); |
||||
state.timer_map.insert(label, time); |
||||
} |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.timeLog(label, ...data)`
|
||||
///
|
||||
/// Prints elapsed time for timer with given label.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#timelog>
|
||||
pub fn time_log(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string()); |
||||
|
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
if let Some(t) = state.timer_map.get(&label) { |
||||
let time = system_time_in_ms(); |
||||
let mut concat = format!("{}: {} ms", label, time - t); |
||||
for msg in args.iter().skip(1) { |
||||
concat = concat + " " + &msg.to_string(); |
||||
} |
||||
logger(LogMessage::Log(concat), state); |
||||
} else { |
||||
logger( |
||||
LogMessage::Warn(format!("Timer '{}' doesn't exist", label)), |
||||
state, |
||||
); |
||||
} |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.timeEnd(label)`
|
||||
///
|
||||
/// Removes the timer with given label.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#timeend>
|
||||
pub fn time_end(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string()); |
||||
|
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
if let Some(t) = state.timer_map.remove(&label) { |
||||
let time = system_time_in_ms(); |
||||
logger( |
||||
LogMessage::Info(format!("{}: {} ms - timer removed", label, time - t)), |
||||
state, |
||||
); |
||||
} else { |
||||
logger( |
||||
LogMessage::Warn(format!("Timer '{}' doesn't exist", label)), |
||||
state, |
||||
); |
||||
} |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.group(...data)`
|
||||
///
|
||||
/// Adds new group with name from formatted data to stack.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#group>
|
||||
pub fn group(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
let group_label = formatter(args); |
||||
|
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
logger(LogMessage::Info(format!("group: {}", &group_label)), state); |
||||
state.groups.push(group_label); |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.groupEnd(label)`
|
||||
///
|
||||
/// Removes the last group from the stack.
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#groupend>
|
||||
pub fn group_end(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
state.groups.pop(); |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// `console.dir(item, options)`
|
||||
///
|
||||
/// Prints info about item
|
||||
///
|
||||
/// More information: <https://console.spec.whatwg.org/#dir>
|
||||
pub fn dir(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { |
||||
this.with_internal_state_mut(|state: &mut ConsoleState| { |
||||
logger( |
||||
LogMessage::Info(display_obj( |
||||
args.get(0).unwrap_or(&Gc::new(ValueData::Undefined)), |
||||
true, |
||||
)), |
||||
state, |
||||
); |
||||
}); |
||||
|
||||
Ok(Gc::new(ValueData::Undefined)) |
||||
} |
||||
|
||||
/// Create a new `console` object
|
||||
pub fn create_constructor(global: &Value) -> Value { |
||||
let console = ValueData::new_obj(Some(global)); |
||||
console.set_field_slice("assert", to_value(assert as NativeFunctionData)); |
||||
console.set_field_slice("clear", to_value(clear as NativeFunctionData)); |
||||
console.set_field_slice("debug", to_value(debug as NativeFunctionData)); |
||||
console.set_field_slice("error", to_value(error as NativeFunctionData)); |
||||
console.set_field_slice("info", to_value(info as NativeFunctionData)); |
||||
console.set_field_slice("log", to_value(log as NativeFunctionData)); |
||||
console.set_field_slice("trace", to_value(trace as NativeFunctionData)); |
||||
console.set_field_slice("warn", to_value(warn as NativeFunctionData)); |
||||
console.set_field_slice("exception", to_value(error as NativeFunctionData)); |
||||
console.set_field_slice("count", to_value(count as NativeFunctionData)); |
||||
console.set_field_slice("countReset", to_value(count_reset as NativeFunctionData)); |
||||
console.set_field_slice("group", to_value(group as NativeFunctionData)); |
||||
console.set_field_slice("groupCollapsed", to_value(group as NativeFunctionData)); |
||||
console.set_field_slice("groupEnd", to_value(group_end as NativeFunctionData)); |
||||
console.set_field_slice("time", to_value(time as NativeFunctionData)); |
||||
console.set_field_slice("timeLog", to_value(time_log as NativeFunctionData)); |
||||
console.set_field_slice("timeEnd", to_value(time_end as NativeFunctionData)); |
||||
console.set_field_slice("dir", to_value(dir as NativeFunctionData)); |
||||
console.set_field_slice("dirxml", to_value(dir as NativeFunctionData)); |
||||
console.set_internal_state(ConsoleState::new()); |
||||
console |
||||
} |
@ -0,0 +1,66 @@
|
||||
use crate::builtins::{console::formatter, value::ValueData}; |
||||
use gc::Gc; |
||||
|
||||
#[test] |
||||
fn formatter_no_args_is_empty_string() { |
||||
assert_eq!(formatter(&[]), "") |
||||
} |
||||
|
||||
#[test] |
||||
fn formatter_empty_format_string_is_empty_string() { |
||||
let val = Gc::new(ValueData::String("".to_string())); |
||||
let res = formatter(&[val]); |
||||
assert_eq!(res, ""); |
||||
} |
||||
|
||||
#[test] |
||||
fn formatter_format_without_args_renders_verbatim() { |
||||
let val = [Gc::new(ValueData::String("%d %s %% %f".to_string()))]; |
||||
let res = formatter(&val); |
||||
assert_eq!(res, "%d %s %% %f"); |
||||
} |
||||
|
||||
#[test] |
||||
fn formatter_empty_format_string_concatenates_rest_of_args() { |
||||
let val = [ |
||||
Gc::new(ValueData::String("".to_string())), |
||||
Gc::new(ValueData::String("to powinno zostać".to_string())), |
||||
Gc::new(ValueData::String("połączone".to_string())), |
||||
]; |
||||
let res = formatter(&val); |
||||
assert_eq!(res, " to powinno zostać połączone"); |
||||
} |
||||
|
||||
#[test] |
||||
fn formatter_utf_8_checks() { |
||||
let val = [ |
||||
Gc::new(ValueData::String( |
||||
"Są takie chwile %dą %są tu%sów %привет%ź".to_string(), |
||||
)), |
||||
Gc::new(ValueData::Integer(123)), |
||||
Gc::new(ValueData::Number(1.23)), |
||||
Gc::new(ValueData::String("ł".to_string())), |
||||
]; |
||||
let res = formatter(&val); |
||||
assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź"); |
||||
} |
||||
|
||||
#[test] |
||||
fn formatter_trailing_format_leader_renders() { |
||||
let val = [ |
||||
Gc::new(ValueData::String("%%%%%".to_string())), |
||||
Gc::new(ValueData::String("|".to_string())), |
||||
]; |
||||
let res = formatter(&val); |
||||
assert_eq!(res, "%%% |") |
||||
} |
||||
|
||||
#[test] |
||||
fn formatter_float_format_works() { |
||||
let val = [ |
||||
Gc::new(ValueData::String("%f".to_string())), |
||||
Gc::new(ValueData::Number(3.1415)), |
||||
]; |
||||
let res = formatter(&val); |
||||
assert_eq!(res, "3.141500") |
||||
} |
Loading…
Reference in new issue