diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index b9ebee9e6e..333c224bc4 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -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 { + // 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) +} diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 2c2472122c..08c7264edc 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -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 { + // 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, + context: &mut Context, +) -> JsResult> { + // 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, + context: &mut Context, +) -> JsResult> { + // 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())) +} diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index b0a3d8de36..7a125b108a 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -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,14 +16,14 @@ fn best_avail_loc() { let no_extensions_locale = JsString::new("en-US"); let available_locales = Vec::::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) ); @@ -22,7 +31,7 @@ fn best_avail_loc() { 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,23 +51,23 @@ fn lookup_match() { let available_locales = Vec::::new(); let requested_locales = Vec::::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::::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"); @@ -66,7 +75,7 @@ fn lookup_match() { 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,21 +85,21 @@ 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") ); } @@ -98,7 +107,7 @@ fn insert_unicode_ext() { #[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,12 +152,12 @@ fn locale_resolution() { let requested_locales = Vec::::new(); let relevant_extension_keys = Vec::::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, @@ -156,14 +165,8 @@ fn locale_resolution() { &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 @@ -171,12 +174,12 @@ fn locale_resolution() { let requested_locales = Vec::::new(); let relevant_extension_keys = Vec::::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, @@ -184,14 +187,8 @@ fn locale_resolution() { &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] @@ -199,12 +196,12 @@ fn locale_resolution() { let requested_locales = vec![JsString::new("es-ES")]; let relevant_extension_keys = Vec::::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,12 +218,12 @@ fn locale_resolution() { let requested_locales = Vec::::new(); let relevant_extension_keys = Vec::::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, @@ -234,13 +231,284 @@ fn locale_resolution() { &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::::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::::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::::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); }