/* * Copyright (c) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/global_env.h" #include "ecmascript/js_array.h" #include "ecmascript/js_list_format.h" #include "ecmascript/js_iterator.h" #include "ecmascript/tests/test_helper.h" using namespace panda::ecmascript; namespace panda::test { class JSListFormatTest : public testing::Test { public: static void SetUpTestCase() { GTEST_LOG_(INFO) << "SetUpTestCase"; } static void TearDownTestCase() { GTEST_LOG_(INFO) << "TearDownCase"; } void SetUp() override { JSRuntimeOptions options; #if PANDA_TARGET_LINUX // for consistency requirement, use ohos_icu4j/data/icudt67l.dat as icu-data-path options.SetIcuDataPath(ICU_PATH); #endif options.SetEnableForceGC(true); instance = JSNApi::CreateEcmaVM(options); instance->SetEnableForceGC(true); ASSERT_TRUE(instance != nullptr) << "Cannot create EcmaVM"; thread = instance->GetJSThread(); scope = new EcmaHandleScope(thread); } void TearDown() override { TestHelper::DestroyEcmaVMWithScope(instance, scope); } EcmaVM *instance {nullptr}; ecmascript::EcmaHandleScope *scope {nullptr}; JSThread *thread {nullptr}; }; HWTEST_F_L0(JSListFormatTest, Set_Get_IcuListFormatter_001) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); JSHandle ctor = env->GetListFormatFunction(); JSHandle jsFormatter = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(ctor), ctor)); UErrorCode status = UErrorCode::U_ZERO_ERROR; icu::Locale icuLocale("en", "Latn", "US"); icu::ListFormatter* icuFormatter = icu::ListFormatter::createInstance(icuLocale, status); JSListFormat::SetIcuListFormatter(thread, jsFormatter, icuFormatter, JSListFormat::FreeIcuListFormatter); icu::ListFormatter *resFormatter = jsFormatter->GetIcuListFormatter(); EXPECT_TRUE(resFormatter != nullptr); const int32_t itemNum = 3; const icu::UnicodeString items[itemNum] = { "One", "Two", "Three" }; icu::UnicodeString resStr = ""; resStr = resFormatter->format(items, itemNum, resStr, status); const icu::UnicodeString expectResStr("One, Two, and Three"); EXPECT_TRUE(resStr.compare(expectResStr) == 0); } HWTEST_F_L0(JSListFormatTest, Set_Get_IcuListFormatter_002) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); JSHandle ctor = env->GetListFormatFunction(); JSHandle jsFormatter = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(ctor), ctor)); UErrorCode status = UErrorCode::U_ZERO_ERROR; icu::Locale icuLocale("zh", "Hans", "Cn"); icu::ListFormatter* icuFormatter = icu::ListFormatter::createInstance(icuLocale, status); JSListFormat::SetIcuListFormatter(thread, jsFormatter, icuFormatter, JSListFormat::FreeIcuListFormatter); icu::ListFormatter *resFormatter = jsFormatter->GetIcuListFormatter(); EXPECT_TRUE(resFormatter != nullptr); const int32_t itemNum = 3; const icu::UnicodeString items[itemNum] = { "一", "二", "三" }; icu::UnicodeString resStr = ""; resStr = resFormatter->format(items, itemNum, resStr, status); const icu::UnicodeString expectResStr("一、二和三"); EXPECT_TRUE(resStr.compare(expectResStr) == 0); } JSHandle CreateJSListFormatterTest(JSThread *thread, icu::Locale icuLocale, JSHandle options) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); JSHandle localeCtor = env->GetLocaleFunction(); JSHandle listCtor = env->GetListFormatFunction(); JSHandle locales = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(localeCtor), localeCtor)); JSHandle listFormatter = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(listCtor), listCtor)); JSHandle optionsVal = JSHandle::Cast(options); factory->NewJSIntlIcuData(locales, icuLocale, JSLocale::FreeIcuLocale); listFormatter = JSListFormat::InitializeListFormat(thread, listFormatter, JSHandle::Cast(locales), optionsVal); return listFormatter; } void SetFormatterOptionsTest(JSThread *thread, JSHandle &optionsObj, std::map &options) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto globalConst = thread->GlobalConstants(); JSHandle localeMatcherKey = globalConst->GetHandledLocaleMatcherString(); JSHandle typeKey = globalConst->GetHandledTypeString(); JSHandle styleKey = globalConst->GetHandledStyleString(); JSHandle localeMatcherValue(factory->NewFromASCII(options["localeMatcher"].c_str())); JSHandle typeValue(factory->NewFromASCII(options["type"].c_str())); JSHandle styleValue(factory->NewFromASCII(options["style"].c_str())); JSObject::SetProperty(thread, optionsObj, localeMatcherKey, localeMatcherValue); JSObject::SetProperty(thread, optionsObj, typeKey, typeValue); JSObject::SetProperty(thread, optionsObj, styleKey, styleValue); } HWTEST_F_L0(JSListFormatTest, InitializeListFormat) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); icu::Locale icuLocale("en", "Latn", "US"); JSHandle objFun = env->GetObjectFunction(); JSHandle object = factory->NewJSObjectByConstructor(JSHandle(objFun), objFun); std::map options { { "localeMatcher", "best fit" }, { "type", "conjunction" }, { "style", "long" } }; SetFormatterOptionsTest(thread, object, options); JSHandle localeCtor = env->GetLocaleFunction(); JSHandle listCtor = env->GetListFormatFunction(); JSHandle locales = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(localeCtor), localeCtor)); JSHandle listFormatter = JSHandle::Cast(factory->NewJSObjectByConstructor(JSHandle(listCtor), listCtor)); JSHandle optionsVal = JSHandle::Cast(object); factory->NewJSIntlIcuData(locales, icuLocale, JSLocale::FreeIcuLocale); listFormatter = JSListFormat::InitializeListFormat(thread, listFormatter, JSHandle::Cast(locales), optionsVal); icu::ListFormatter *resFormatter = listFormatter->GetIcuListFormatter(); EXPECT_TRUE(resFormatter != nullptr); const int32_t itemNum = 3; UErrorCode status = UErrorCode::U_ZERO_ERROR; const icu::UnicodeString items[itemNum] = { "Monday", "Tuesday", "Wednesday" }; icu::UnicodeString resStr = ""; resStr = resFormatter->format(items, itemNum, resStr, status); const icu::UnicodeString expectResStr("Monday, Tuesday, and Wednesday"); EXPECT_TRUE(resStr.compare(expectResStr) == 0); } HWTEST_F_L0(JSListFormatTest, FormatList_001) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); icu::Locale icuLocale("en", "Latn", "US"); JSHandle objFun = env->GetObjectFunction(); JSHandle object = factory->NewJSObjectByConstructor(JSHandle(objFun), objFun); std::map options { { "localeMatcher", "best fit" }, { "type", "conjunction" }, { "style", "long" } }; SetFormatterOptionsTest(thread, object, options); JSHandle jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object); JSHandle valueObj = JSHandle::Cast(factory->NewJSArray()); JSHandle key0(thread, JSTaggedValue(0)); JSHandle key1(thread, JSTaggedValue(1)); JSHandle key2(thread, JSTaggedValue(2)); JSHandle value0(factory->NewFromStdString("Zero")); JSHandle value1(factory->NewFromStdString("One")); JSHandle value2(factory->NewFromStdString("Two")); JSObject::SetProperty(thread, valueObj, key0, value0); JSObject::SetProperty(thread, valueObj, key1, value1); JSObject::SetProperty(thread, valueObj, key2, value2); JSHandle valueArr = JSHandle::Cast(valueObj); JSHandle valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr); EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "Zero, One, and Two"); } HWTEST_F_L0(JSListFormatTest, FormatList_002) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); icu::Locale icuLocale("en", "Latn", "US"); JSHandle objFun = env->GetObjectFunction(); JSHandle object = factory->NewJSObjectByConstructor(JSHandle(objFun), objFun); // when style is narrow, type can only be unit std::map options { { "localeMatcher", "best fit" }, { "type", "unit" }, { "style", "narrow" } }; SetFormatterOptionsTest(thread, object, options); JSHandle jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object); JSHandle valueObj = JSHandle::Cast(factory->NewJSArray()); JSHandle key0(thread, JSTaggedValue(0)); JSHandle key1(thread, JSTaggedValue(1)); JSHandle key2(thread, JSTaggedValue(2)); JSHandle value0(factory->NewFromStdString("Zero")); JSHandle value1(factory->NewFromStdString("One")); JSHandle value2(factory->NewFromStdString("Two")); JSObject::SetProperty(thread, valueObj, key0, value0); JSObject::SetProperty(thread, valueObj, key1, value1); JSObject::SetProperty(thread, valueObj, key2, value2); JSHandle valueArr = JSHandle::Cast(valueObj); JSHandle valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr); EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "Zero One Two"); } HWTEST_F_L0(JSListFormatTest, FormatList_003) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); icu::Locale icuLocale("zh", "Hans", "Cn"); JSHandle objFun = env->GetObjectFunction(); JSHandle object = factory->NewJSObjectByConstructor(JSHandle(objFun), objFun); std::map options { { "localeMatcher", "best fit" }, { "type", "disjunction" }, { "style", "long" } }; SetFormatterOptionsTest(thread, object, options); JSHandle jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object); JSHandle valueObj = JSHandle::Cast(factory->NewJSArray()); JSHandle key0(thread, JSTaggedValue(0)); JSHandle key1(thread, JSTaggedValue(1)); JSHandle key2(thread, JSTaggedValue(2)); JSHandle value0(factory->NewFromStdString("苹果")); JSHandle value1(factory->NewFromStdString("梨子")); JSHandle value2(factory->NewFromStdString("桃")); JSObject::SetProperty(thread, valueObj, key0, value0); JSObject::SetProperty(thread, valueObj, key1, value1); JSObject::SetProperty(thread, valueObj, key2, value2); JSHandle valueArr = JSHandle::Cast(valueObj); JSHandle valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr); EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "苹果、梨子或桃"); } std::string GetListPartStringTest(JSThread *thread, JSHandle key, JSHandle part) { JSHandle partObj = JSHandle::Cast(part); JSHandle partValue = JSObject::GetProperty(thread, partObj, key).GetValue(); JSHandle partEcmaStr = JSHandle::Cast(partValue); std::string partStr = JSLocale::ConvertToStdString(partEcmaStr); return partStr; } HWTEST_F_L0(JSListFormatTest, FormatListToParts) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); auto env = vm->GetGlobalEnv(); auto globalConst = thread->GlobalConstants(); icu::Locale icuLocale("zh", "Hans", "Cn"); JSHandle objFun = env->GetObjectFunction(); JSHandle object = factory->NewJSObjectByConstructor(JSHandle(objFun), objFun); std::map options { { "localeMatcher", "best fit" }, { "type", "conjunction" }, { "style", "long" } }; SetFormatterOptionsTest(thread, object, options); JSHandle jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object); JSHandle valueObj = JSHandle::Cast(factory->NewJSArray()); JSHandle key0(thread, JSTaggedValue(0)); JSHandle key1(thread, JSTaggedValue(1)); JSHandle key2(thread, JSTaggedValue(2)); JSHandle value0(factory->NewFromStdString("苹果")); JSHandle value1(factory->NewFromStdString("梨子")); JSHandle value2(factory->NewFromStdString("桃")); JSObject::SetProperty(thread, valueObj, key0, value0); JSObject::SetProperty(thread, valueObj, key1, value1); JSObject::SetProperty(thread, valueObj, key2, value2); JSHandle valueArr = JSHandle::Cast(valueObj); JSHandle valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr); EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "苹果、梨子和桃"); JSHandle typeKey = globalConst->GetHandledTypeString(); JSHandle valueKey = globalConst->GetHandledValueString(); JSHandle parts = JSListFormat::FormatListToParts(thread, jsFormatter, valueArr); auto element1 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(parts), 0).GetValue(); auto literal1 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(parts), 1).GetValue(); auto element2 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(parts), 2).GetValue(); auto literal2 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(parts), 3).GetValue(); auto element3 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(parts), 4).GetValue(); EXPECT_STREQ(GetListPartStringTest(thread, typeKey, element1).c_str(), "element"); EXPECT_STREQ(GetListPartStringTest(thread, valueKey, element1).c_str(), "苹果"); EXPECT_STREQ(GetListPartStringTest(thread, typeKey, literal1).c_str(), "literal"); EXPECT_STREQ(GetListPartStringTest(thread, valueKey, literal1).c_str(), "、"); EXPECT_STREQ(GetListPartStringTest(thread, typeKey, element2).c_str(), "element"); EXPECT_STREQ(GetListPartStringTest(thread, valueKey, element2).c_str(), "梨子"); EXPECT_STREQ(GetListPartStringTest(thread, typeKey, literal2).c_str(), "literal"); EXPECT_STREQ(GetListPartStringTest(thread, valueKey, literal2).c_str(), "和"); EXPECT_STREQ(GetListPartStringTest(thread, typeKey, element3).c_str(), "element"); EXPECT_STREQ(GetListPartStringTest(thread, valueKey, element3).c_str(), "桃"); } HWTEST_F_L0(JSListFormatTest, StringListFromIterable) { auto vm = thread->GetEcmaVM(); auto factory = vm->GetFactory(); JSHandle data = factory->NewTaggedArray(3); JSHandle value0(factory->NewFromStdString("one")); JSHandle value1(factory->NewFromStdString("two")); JSHandle value2(factory->NewFromStdString("three")); data->Set(thread, 0, value0.GetTaggedValue()); data->Set(thread, 1, value1.GetTaggedValue()); data->Set(thread, 2, value2.GetTaggedValue()); JSHandle array(JSArray::CreateArrayFromList(thread, data)); JSHandle iter(JSIterator::GetIterator(thread, array)); JSHandle arrayString = JSListFormat::StringListFromIterable(thread, JSHandle::Cast(iter)); JSHandle strValue = JSHandle::Cast(arrayString); EXPECT_EQ(strValue->GetArrayLength(), 3U); auto resValue0 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(strValue), 0).GetValue(); auto resValue1 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(strValue), 1).GetValue(); auto resValue2 = JSTaggedValue::GetProperty(thread, JSHandle::Cast(strValue), 2).GetValue(); EXPECT_STREQ(EcmaStringAccessor(resValue0.GetTaggedValue()).ToCString().c_str(), "one"); EXPECT_STREQ(EcmaStringAccessor(resValue1.GetTaggedValue()).ToCString().c_str(), "two"); EXPECT_STREQ(EcmaStringAccessor(resValue2.GetTaggedValue()).ToCString().c_str(), "three"); } } // namespace panda::test