Skip to content

Commit

Permalink
DateTimeFormat helpers (#2064)
Browse files Browse the repository at this point in the history
This Pull Request adds several helpers to resolve #1562.

It adds the following subroutines:

- toDateTimeOptions
- GetOption
- GetNumberOption
- DefaultNumberOption
NorbertGarfield committed May 13, 2022
1 parent 9d89e12 commit 5a0ff91
Showing 3 changed files with 548 additions and 48 deletions.
120 changes: 120 additions & 0 deletions boa_engine/src/builtins/intl/date_time_format.rs
Original file line number Diff line number Diff line change
@@ -114,3 +114,123 @@ impl DateTimeFormat {
Ok(date_time_format.into())
}
}

/// Represents the `required` and `defaults` arguments in the abstract operation
/// `toDateTimeOptions`.
///
/// Since `required` and `defaults` differ only in the `any` and `all` variants,
/// we combine both in a single variant `AnyAll`.
#[allow(unused)]
#[derive(Debug, PartialEq)]
pub(crate) enum DateTimeReqs {
Date,
Time,
AnyAll,
}

/// The abstract operation `toDateTimeOptions` is called with arguments `options`, `required` and
/// `defaults`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-todatetimeoptions
#[allow(unused)]
pub(crate) fn to_date_time_options(
options: &JsValue,
required: &DateTimeReqs,
defaults: &DateTimeReqs,
context: &mut Context,
) -> JsResult<JsObject> {
// 1. If options is undefined, let options be null;
// otherwise let options be ? ToObject(options).
// 2. Let options be ! OrdinaryObjectCreate(options).
let options = if options.is_undefined() {
None
} else {
Some(options.to_object(context)?)
};
let options = JsObject::from_proto_and_data(options, ObjectData::ordinary());

// 3. Let needDefaults be true.
let mut need_defaults = true;

// 4. If required is "date" or "any", then
if [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(required) {
// a. For each property name prop of « "weekday", "year", "month", "day" », do
for property in ["weekday", "year", "month", "day"] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;

// ii. If value is not undefined, let needDefaults be false.
if !value.is_undefined() {
need_defaults = false;
}
}
}

// 5. If required is "time" or "any", then
if [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(required) {
// a. For each property name prop of « "dayPeriod", "hour", "minute", "second",
// "fractionalSecondDigits" », do
for property in [
"dayPeriod",
"hour",
"minute",
"second",
"fractionalSecondDigits",
] {
// i. Let value be ? Get(options, prop).
let value = options.get(property, context)?;

// ii. If value is not undefined, let needDefaults be false.
if !value.is_undefined() {
need_defaults = false;
}
}
}

// 6. Let dateStyle be ? Get(options, "dateStyle").
let date_style = options.get("dateStyle", context)?;

// 7. Let timeStyle be ? Get(options, "timeStyle").
let time_style = options.get("timeStyle", context)?;

// 8. If dateStyle is not undefined or timeStyle is not undefined, let needDefaults be false.
if !date_style.is_undefined() || !time_style.is_undefined() {
need_defaults = false;
}

// 9. If required is "date" and timeStyle is not undefined, then
if required == &DateTimeReqs::Date && !time_style.is_undefined() {
// a. Throw a TypeError exception.
return context.throw_type_error("'date' is required, but timeStyle was defined");
}

// 10. If required is "time" and dateStyle is not undefined, then
if required == &DateTimeReqs::Time && !date_style.is_undefined() {
// a. Throw a TypeError exception.
return context.throw_type_error("'time' is required, but dateStyle was defined");
}

// 11. If needDefaults is true and defaults is either "date" or "all", then
if need_defaults && [DateTimeReqs::Date, DateTimeReqs::AnyAll].contains(defaults) {
// a. For each property name prop of « "year", "month", "day" », do
for property in ["year", "month", "day"] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, "numeric", context)?;
}
}

// 12. If needDefaults is true and defaults is either "time" or "all", then
if need_defaults && [DateTimeReqs::Time, DateTimeReqs::AnyAll].contains(defaults) {
// a. For each property name prop of « "hour", "minute", "second" », do
for property in ["hour", "minute", "second"] {
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
options.create_data_property_or_throw(property, "numeric", context)?;
}
}

// 13. Return options.
Ok(options)
}
114 changes: 113 additions & 1 deletion boa_engine/src/builtins/intl/mod.rs
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
use crate::{
builtins::intl::date_time_format::DateTimeFormat,
builtins::{Array, BuiltIn, JsArgs},
object::ObjectInitializer,
object::{JsObject, ObjectInitializer},
property::Attribute,
symbol::WellKnownSymbols,
Context, JsResult, JsString, JsValue,
@@ -653,3 +653,115 @@ fn resolve_locale(
// 12. Return result.
result
}

#[allow(unused)]
pub(crate) enum GetOptionType {
String,
Boolean,
}

/// The abstract operation `GetOption` extracts the value of the property named `property` from the
/// provided `options` object, converts it to the required `type`, checks whether it is one of a
/// `List` of allowed `values`, and fills in a `fallback` value if necessary. If `values` is
/// undefined, there is no fixed set of values and any is permitted.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-getoption
#[allow(unused)]
pub(crate) fn get_option(
options: &JsObject,
property: &str,
r#type: &GetOptionType,
values: &[JsString],
fallback: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Assert: Type(options) is Object.
// 2. Let value be ? Get(options, property).
let mut value = options.get(property, context)?;

// 3. If value is undefined, return fallback.
if value.is_undefined() {
return Ok(fallback.clone());
}

// 4. Assert: type is "boolean" or "string".
// 5. If type is "boolean", then
// a. Set value to ! ToBoolean(value).
// 6. If type is "string", then
// a. Set value to ? ToString(value).
// 7. If values is not undefined and values does not contain an element equal to value,
// throw a RangeError exception.
value = match r#type {
GetOptionType::Boolean => JsValue::Boolean(value.to_boolean()),
GetOptionType::String => {
let string_value = value.to_string(context)?;
if !values.is_empty() && !values.contains(&string_value) {
return context.throw_range_error("GetOption: values array does not contain value");
}
JsValue::String(string_value)
}
};

// 8. Return value.
Ok(value)
}

/// The abstract operation `GetNumberOption` extracts the value of the property named `property`
/// from the provided `options` object, converts it to a `Number value`, checks whether it is in
/// the allowed range, and fills in a `fallback` value if necessary.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-getnumberoption
#[allow(unused)]
pub(crate) fn get_number_option(
options: &JsObject,
property: &str,
minimum: f64,
maximum: f64,
fallback: Option<f64>,
context: &mut Context,
) -> JsResult<Option<f64>> {
// 1. Assert: Type(options) is Object.
// 2. Let value be ? Get(options, property).
let value = options.get(property, context)?;

// 3. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
default_number_option(&value, minimum, maximum, fallback, context)
}

/// The abstract operation `DefaultNumberOption` converts `value` to a `Number value`, checks
/// whether it is in the allowed range, and fills in a `fallback` value if necessary.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma402/#sec-defaultnumberoption
#[allow(unused)]
pub(crate) fn default_number_option(
value: &JsValue,
minimum: f64,
maximum: f64,
fallback: Option<f64>,
context: &mut Context,
) -> JsResult<Option<f64>> {
// 1. If value is undefined, return fallback.
if value.is_undefined() {
return Ok(fallback);
}

// 2. Set value to ? ToNumber(value).
let value = value.to_number(context)?;

// 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
if value.is_nan() || value < minimum || value > maximum {
return context.throw_range_error("DefaultNumberOption: value is out of range.");
}

// 4. Return floor(value).
Ok(Some(value.floor()))
}
362 changes: 315 additions & 47 deletions boa_engine/src/builtins/intl/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
use crate::{Context, JsString};
use crate::{
builtins::intl::date_time_format::{to_date_time_options, DateTimeReqs},
builtins::intl::{
best_available_locale, best_fit_matcher, default_locale, default_number_option,
get_number_option, get_option, insert_unicode_extension_and_canonicalize, lookup_matcher,
resolve_locale, unicode_extension_components, DateTimeFormatRecord, GetOptionType,
},
object::JsObject,
Context, JsString, JsValue,
};

use rustc_hash::FxHashMap;

@@ -7,22 +16,22 @@ fn best_avail_loc() {
let no_extensions_locale = JsString::new("en-US");
let available_locales = Vec::<JsString>::new();
assert_eq!(
crate::builtins::intl::best_available_locale(&available_locales, &no_extensions_locale,),
best_available_locale(&available_locales, &no_extensions_locale,),
None
);

let no_extensions_locale = JsString::new("de-DE");
let available_locales = vec![no_extensions_locale.clone()];
assert_eq!(
crate::builtins::intl::best_available_locale(&available_locales, &no_extensions_locale,),
best_available_locale(&available_locales, &no_extensions_locale,),
Some(no_extensions_locale)
);

let locale_part = "fr".to_string();
let no_extensions_locale = JsString::new(locale_part.clone() + &"-CA".to_string());
let available_locales = vec![JsString::new(locale_part.clone())];
assert_eq!(
crate::builtins::intl::best_available_locale(&available_locales, &no_extensions_locale,),
best_available_locale(&available_locales, &no_extensions_locale,),
Some(JsString::new(locale_part))
);

@@ -31,7 +40,7 @@ fn best_avail_loc() {
let no_extensions_locale = JsString::new("ja-Kana-JP-t-it-latn-it");
let available_locales = vec![ja_kana_t.clone(), ja_kana.clone()];
assert_eq!(
crate::builtins::intl::best_available_locale(&available_locales, &no_extensions_locale,),
best_available_locale(&available_locales, &no_extensions_locale,),
Some(ja_kana)
);
}
@@ -42,31 +51,31 @@ fn lookup_match() {
let available_locales = Vec::<JsString>::new();
let requested_locales = Vec::<JsString>::new();

let matcher = crate::builtins::intl::lookup_matcher(&available_locales, &requested_locales);
assert_eq!(matcher.locale, crate::builtins::intl::default_locale());
let matcher = lookup_matcher(&available_locales, &requested_locales);
assert_eq!(matcher.locale, default_locale());
assert_eq!(matcher.extension, "");

// available: [de-DE], requested: []
let available_locales = vec![JsString::new("de-DE")];
let requested_locales = Vec::<JsString>::new();

let matcher = crate::builtins::intl::lookup_matcher(&available_locales, &requested_locales);
assert_eq!(matcher.locale, crate::builtins::intl::default_locale());
let matcher = lookup_matcher(&available_locales, &requested_locales);
assert_eq!(matcher.locale, default_locale());
assert_eq!(matcher.extension, "");

// available: [fr-FR], requested: [fr-FR-u-hc-h12]
let available_locales = vec![JsString::new("fr-FR")];
let requested_locales = vec![JsString::new("fr-FR-u-hc-h12")];

let matcher = crate::builtins::intl::lookup_matcher(&available_locales, &requested_locales);
let matcher = lookup_matcher(&available_locales, &requested_locales);
assert_eq!(matcher.locale, "fr-FR");
assert_eq!(matcher.extension, "-u-hc-h12");

// available: [es-ES], requested: [es-ES]
let available_locales = vec![JsString::new("es-ES")];
let requested_locales = vec![JsString::new("es-ES")];

let matcher = crate::builtins::intl::best_fit_matcher(&available_locales, &requested_locales);
let matcher = best_fit_matcher(&available_locales, &requested_locales);
assert_eq!(matcher.locale, "es-ES");
assert_eq!(matcher.extension, "");
}
@@ -76,29 +85,29 @@ fn insert_unicode_ext() {
let locale = JsString::new("hu-HU");
let ext = JsString::empty();
assert_eq!(
crate::builtins::intl::insert_unicode_extension_and_canonicalize(&locale, &ext),
insert_unicode_extension_and_canonicalize(&locale, &ext),
locale
);

let locale = JsString::new("hu-HU");
let ext = JsString::new("-u-hc-h12");
assert_eq!(
crate::builtins::intl::insert_unicode_extension_and_canonicalize(&locale, &ext),
insert_unicode_extension_and_canonicalize(&locale, &ext),
JsString::new("hu-HU-u-hc-h12")
);

let locale = JsString::new("hu-HU-x-PRIVATE");
let ext = JsString::new("-u-hc-h12");
assert_eq!(
crate::builtins::intl::insert_unicode_extension_and_canonicalize(&locale, &ext),
insert_unicode_extension_and_canonicalize(&locale, &ext),
JsString::new("hu-HU-u-hc-h12-x-PRIVATE")
);
}

#[test]
fn uni_ext_comp() {
let ext = JsString::new("-u-ca-japanese-hc-h12");
let components = crate::builtins::intl::unicode_extension_components(&ext);
let components = unicode_extension_components(&ext);
assert_eq!(components.attributes.is_empty(), true);
assert_eq!(components.keywords.len(), 2);
assert_eq!(components.keywords[0].key, "ca");
@@ -107,7 +116,7 @@ fn uni_ext_comp() {
assert_eq!(components.keywords[1].value, "h12");

let ext = JsString::new("-u-alias-co-phonebk-ka-shifted");
let components = crate::builtins::intl::unicode_extension_components(&ext);
let components = unicode_extension_components(&ext);
assert_eq!(components.attributes, vec![JsString::new("alias")]);
assert_eq!(components.keywords.len(), 2);
assert_eq!(components.keywords[0].key, "co");
@@ -116,7 +125,7 @@ fn uni_ext_comp() {
assert_eq!(components.keywords[1].value, "shifted");

let ext = JsString::new("-u-ca-buddhist-kk-nu-thai");
let components = crate::builtins::intl::unicode_extension_components(&ext);
let components = unicode_extension_components(&ext);
assert_eq!(components.attributes.is_empty(), true);
assert_eq!(components.keywords.len(), 3);
assert_eq!(components.keywords[0].key, "ca");
@@ -127,7 +136,7 @@ fn uni_ext_comp() {
assert_eq!(components.keywords[2].value, "thai");

let ext = JsString::new("-u-ca-islamic-civil");
let components = crate::builtins::intl::unicode_extension_components(&ext);
let components = unicode_extension_components(&ext);
assert_eq!(components.attributes.is_empty(), true);
assert_eq!(components.keywords.len(), 1);
assert_eq!(components.keywords[0].key, "ca");
@@ -143,68 +152,56 @@ fn locale_resolution() {
let requested_locales = Vec::<JsString>::new();
let relevant_extension_keys = Vec::<JsString>::new();
let locale_data = FxHashMap::default();
let options = crate::builtins::intl::DateTimeFormatRecord {
let options = DateTimeFormatRecord {
locale_matcher: JsString::new("lookup"),
properties: FxHashMap::default(),
};

let locale_record = crate::builtins::intl::resolve_locale(
let locale_record = resolve_locale(
&available_locales,
&requested_locales,
&options,
&relevant_extension_keys,
&locale_data,
&mut context,
);
assert_eq!(
locale_record.locale,
crate::builtins::intl::default_locale()
);
assert_eq!(
locale_record.data_locale,
crate::builtins::intl::default_locale()
);
assert_eq!(locale_record.locale, default_locale());
assert_eq!(locale_record.data_locale, default_locale());
assert_eq!(locale_record.properties.is_empty(), true);

// test best fit
let available_locales = Vec::<JsString>::new();
let requested_locales = Vec::<JsString>::new();
let relevant_extension_keys = Vec::<JsString>::new();
let locale_data = FxHashMap::default();
let options = crate::builtins::intl::DateTimeFormatRecord {
let options = DateTimeFormatRecord {
locale_matcher: JsString::new("best-fit"),
properties: FxHashMap::default(),
};

let locale_record = crate::builtins::intl::resolve_locale(
let locale_record = resolve_locale(
&available_locales,
&requested_locales,
&options,
&relevant_extension_keys,
&locale_data,
&mut context,
);
assert_eq!(
locale_record.locale,
crate::builtins::intl::default_locale()
);
assert_eq!(
locale_record.data_locale,
crate::builtins::intl::default_locale()
);
assert_eq!(locale_record.locale, default_locale());
assert_eq!(locale_record.data_locale, default_locale());
assert_eq!(locale_record.properties.is_empty(), true);

// available: [es-ES], requested: [es-ES]
let available_locales = vec![JsString::new("es-ES")];
let requested_locales = vec![JsString::new("es-ES")];
let relevant_extension_keys = Vec::<JsString>::new();
let locale_data = FxHashMap::default();
let options = crate::builtins::intl::DateTimeFormatRecord {
let options = DateTimeFormatRecord {
locale_matcher: JsString::new("lookup"),
properties: FxHashMap::default(),
};

let locale_record = crate::builtins::intl::resolve_locale(
let locale_record = resolve_locale(
&available_locales,
&requested_locales,
&options,
@@ -221,26 +218,297 @@ fn locale_resolution() {
let requested_locales = Vec::<JsString>::new();
let relevant_extension_keys = Vec::<JsString>::new();
let locale_data = FxHashMap::default();
let options = crate::builtins::intl::DateTimeFormatRecord {
let options = DateTimeFormatRecord {
locale_matcher: JsString::new("lookup"),
properties: FxHashMap::default(),
};

let locale_record = crate::builtins::intl::resolve_locale(
let locale_record = resolve_locale(
&available_locales,
&requested_locales,
&options,
&relevant_extension_keys,
&locale_data,
&mut context,
);
assert_eq!(locale_record.locale, default_locale());
assert_eq!(locale_record.data_locale, default_locale());
assert_eq!(locale_record.properties.is_empty(), true);
}

#[test]
fn get_opt() {
let mut context = Context::default();

let values = Vec::<JsString>::new();
let fallback = JsValue::String(JsString::new("fallback"));
let options_obj = JsObject::empty();
let option_type = GetOptionType::String;
let get_option_result = get_option(
&options_obj,
"",
&option_type,
&values,
&fallback,
&mut context,
)
.expect("GetOption should not fail on fallback test");
assert_eq!(get_option_result, fallback);

let values = Vec::<JsString>::new();
let fallback = JsValue::String(JsString::new("fallback"));
let options_obj = JsObject::empty();
let locale_value = JsValue::String(JsString::new("en-US"));
options_obj
.set("Locale", locale_value.clone(), true, &mut context)
.expect("Setting a property should not fail");
let option_type = GetOptionType::String;
let get_option_result = get_option(
&options_obj,
"Locale",
&option_type,
&values,
&fallback,
&mut context,
)
.expect("GetOption should not fail on string test");
assert_eq!(get_option_result, locale_value);

let fallback = JsValue::String(JsString::new("fallback"));
let options_obj = JsObject::empty();
let locale_string = JsString::new("en-US");
let locale_value = JsValue::String(locale_string.clone());
let values = vec![locale_string];
options_obj
.set("Locale", locale_value.clone(), true, &mut context)
.expect("Setting a property should not fail");
let option_type = GetOptionType::String;
let get_option_result = get_option(
&options_obj,
"Locale",
&option_type,
&values,
&fallback,
&mut context,
)
.expect("GetOption should not fail on values test");
assert_eq!(get_option_result, locale_value);

let fallback = JsValue::new(false);
let options_obj = JsObject::empty();
let boolean_value = JsValue::new(true);
let values = Vec::<JsString>::new();
options_obj
.set("boolean_val", boolean_value.clone(), true, &mut context)
.expect("Setting a property should not fail");
let option_type = GetOptionType::Boolean;
let get_option_result = get_option(
&options_obj,
"boolean_val",
&option_type,
&values,
&fallback,
&mut context,
)
.expect("GetOption should not fail on boolean test");
assert_eq!(get_option_result, boolean_value);

let fallback = JsValue::String(JsString::new("fallback"));
let options_obj = JsObject::empty();
let locale_value = JsValue::String(JsString::new("en-US"));
let other_locale_str = JsString::new("de-DE");
let values = vec![other_locale_str];
options_obj
.set("Locale", locale_value.clone(), true, &mut context)
.expect("Setting a property should not fail");
let option_type = GetOptionType::String;
let get_option_result = get_option(
&options_obj,
"Locale",
&option_type,
&values,
&fallback,
&mut context,
);
assert_eq!(get_option_result.is_err(), true);

let value = JsValue::undefined();
let minimum = 1.0;
let maximum = 10.0;
let fallback_val = 5.0;
let fallback = Some(fallback_val);
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
assert_eq!(get_option_result, Ok(fallback));

let value = JsValue::nan();
let minimum = 1.0;
let maximum = 10.0;
let fallback = Some(5.0);
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
assert_eq!(get_option_result.is_err(), true);

let value = JsValue::new(0);
let minimum = 1.0;
let maximum = 10.0;
let fallback = Some(5.0);
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
assert_eq!(get_option_result.is_err(), true);

let value = JsValue::new(11);
let minimum = 1.0;
let maximum = 10.0;
let fallback = Some(5.0);
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
assert_eq!(get_option_result.is_err(), true);

let value_f64 = 7.0;
let value = JsValue::new(value_f64);
let minimum = 1.0;
let maximum = 10.0;
let fallback = Some(5.0);
let get_option_result = default_number_option(&value, minimum, maximum, fallback, &mut context);
assert_eq!(get_option_result, Ok(Some(value_f64)));

let options = JsObject::empty();
let property = "fractionalSecondDigits";
let minimum = 1.0;
let maximum = 10.0;
let fallback_val = 5.0;
let fallback = Some(fallback_val);
let get_option_result = get_number_option(
&options,
&property,
minimum,
maximum,
fallback,
&mut context,
);
assert_eq!(get_option_result, Ok(fallback));

let options = JsObject::empty();
let value_f64 = 8.0;
let value = JsValue::new(value_f64);
let property = "fractionalSecondDigits";
options
.set(property, value.clone(), true, &mut context)
.expect("Setting a property should not fail");
let minimum = 1.0;
let maximum = 10.0;
let fallback = Some(5.0);
let get_option_result = get_number_option(
&options,
&property,
minimum,
maximum,
fallback,
&mut context,
);
assert_eq!(get_option_result, Ok(Some(value_f64)));
}

#[test]
fn to_date_time_opts() {
let mut context = Context::default();

let options_obj = JsObject::empty();
options_obj
.set("timeStyle", JsObject::empty(), true, &mut context)
.expect("Setting a property should not fail");
let date_time_opts = to_date_time_options(
&JsValue::new(options_obj),
&DateTimeReqs::Date,
&DateTimeReqs::Date,
&mut context,
);
assert_eq!(date_time_opts.is_err(), true);

let options_obj = JsObject::empty();
options_obj
.set("dateStyle", JsObject::empty(), true, &mut context)
.expect("Setting a property should not fail");
let date_time_opts = to_date_time_options(
&JsValue::new(options_obj),
&DateTimeReqs::Time,
&DateTimeReqs::Time,
&mut context,
);
assert_eq!(date_time_opts.is_err(), true);

let date_time_opts = to_date_time_options(
&JsValue::undefined(),
&DateTimeReqs::Date,
&DateTimeReqs::Date,
&mut context,
)
.expect("toDateTimeOptions should not fail in date test");

let numeric_jsstring = JsValue::String(JsString::new("numeric"));
assert_eq!(
locale_record.locale,
crate::builtins::intl::default_locale()
date_time_opts.get("year", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
locale_record.data_locale,
crate::builtins::intl::default_locale()
date_time_opts.get("month", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("day", &mut context),
Ok(numeric_jsstring.clone())
);

let date_time_opts = to_date_time_options(
&JsValue::undefined(),
&DateTimeReqs::Time,
&DateTimeReqs::Time,
&mut context,
)
.expect("toDateTimeOptions should not fail in time test");

let numeric_jsstring = JsValue::String(JsString::new("numeric"));
assert_eq!(
date_time_opts.get("hour", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("minute", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("second", &mut context),
Ok(numeric_jsstring.clone())
);

let date_time_opts = to_date_time_options(
&JsValue::undefined(),
&DateTimeReqs::AnyAll,
&DateTimeReqs::AnyAll,
&mut context,
)
.expect("toDateTimeOptions should not fail when testing required = 'any'");

let numeric_jsstring = JsValue::String(JsString::new("numeric"));
assert_eq!(
date_time_opts.get("year", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("month", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("day", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("hour", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("minute", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(
date_time_opts.get("second", &mut context),
Ok(numeric_jsstring.clone())
);
assert_eq!(locale_record.properties.is_empty(), true);
}

0 comments on commit 5a0ff91

Please sign in to comment.