1 /*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/intl/locale_helper.h"
17 #include "ecmascript/global_env.h"
18 #include "ecmascript/js_array.h"
19 #include "ecmascript/js_list_format.h"
20 #include "ecmascript/js_iterator.h"
21 #include "ecmascript/tests/test_helper.h"
22
23 using namespace panda::ecmascript;
24
25 namespace panda::test {
26 class JSListFormatTest : public testing::Test {
27 public:
SetUpTestCase()28 static void SetUpTestCase()
29 {
30 GTEST_LOG_(INFO) << "SetUpTestCase";
31 }
32
TearDownTestCase()33 static void TearDownTestCase()
34 {
35 GTEST_LOG_(INFO) << "TearDownCase";
36 }
37
SetUp()38 void SetUp() override
39 {
40 JSRuntimeOptions options;
41 #if PANDA_TARGET_LINUX
42 // for consistency requirement, use ohos_icu4j/data/icudt67l.dat as icu-data-path
43 options.SetIcuDataPath(ICU_PATH);
44 #endif
45 options.SetEnableForceGC(true);
46 instance = JSNApi::CreateEcmaVM(options);
47 instance->SetEnableForceGC(true);
48 ASSERT_TRUE(instance != nullptr) << "Cannot create EcmaVM";
49 thread = instance->GetJSThread();
50 scope = new EcmaHandleScope(thread);
51 }
52
TearDown()53 void TearDown() override
54 {
55 TestHelper::DestroyEcmaVMWithScope(instance, scope);
56 }
57
58 EcmaVM *instance {nullptr};
59 ecmascript::EcmaHandleScope *scope {nullptr};
60 JSThread *thread {nullptr};
61 };
62
HWTEST_F_L0(JSListFormatTest,Set_Get_IcuListFormatter_001)63 HWTEST_F_L0(JSListFormatTest, Set_Get_IcuListFormatter_001)
64 {
65 auto vm = thread->GetEcmaVM();
66 auto factory = vm->GetFactory();
67 auto env = vm->GetGlobalEnv();
68 JSHandle<JSTaggedValue> ctor = env->GetListFormatFunction();
69 JSHandle<JSListFormat> jsFormatter =
70 JSHandle<JSListFormat>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor));
71 UErrorCode status = UErrorCode::U_ZERO_ERROR;
72 icu::Locale icuLocale("en", "Latn", "US");
73 icu::ListFormatter* icuFormatter = icu::ListFormatter::createInstance(icuLocale, status);
74 JSListFormat::SetIcuListFormatter(thread, jsFormatter, icuFormatter, JSListFormat::FreeIcuListFormatter);
75 icu::ListFormatter *resFormatter = jsFormatter->GetIcuListFormatter();
76 EXPECT_TRUE(resFormatter != nullptr);
77
78 const int32_t itemNum = 3;
79 const icu::UnicodeString items[itemNum] = { "One", "Two", "Three" };
80 icu::UnicodeString resStr = "";
81 resStr = resFormatter->format(items, itemNum, resStr, status);
82 const icu::UnicodeString expectResStr("One, Two, and Three");
83 EXPECT_TRUE(resStr.compare(expectResStr) == 0);
84 }
85
HWTEST_F_L0(JSListFormatTest,Set_Get_IcuListFormatter_002)86 HWTEST_F_L0(JSListFormatTest, Set_Get_IcuListFormatter_002)
87 {
88 auto vm = thread->GetEcmaVM();
89 auto factory = vm->GetFactory();
90 auto env = vm->GetGlobalEnv();
91 JSHandle<JSTaggedValue> ctor = env->GetListFormatFunction();
92 JSHandle<JSListFormat> jsFormatter =
93 JSHandle<JSListFormat>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor), ctor));
94 UErrorCode status = UErrorCode::U_ZERO_ERROR;
95 icu::Locale icuLocale("zh", "Hans", "Cn");
96 icu::ListFormatter* icuFormatter = icu::ListFormatter::createInstance(icuLocale, status);
97 JSListFormat::SetIcuListFormatter(thread, jsFormatter, icuFormatter, JSListFormat::FreeIcuListFormatter);
98 icu::ListFormatter *resFormatter = jsFormatter->GetIcuListFormatter();
99 EXPECT_TRUE(resFormatter != nullptr);
100
101 const int32_t itemNum = 3;
102 const icu::UnicodeString items[itemNum] = { "一", "二", "三" };
103 icu::UnicodeString resStr = "";
104 resStr = resFormatter->format(items, itemNum, resStr, status);
105 const icu::UnicodeString expectResStr("一、二和三");
106 EXPECT_TRUE(resStr.compare(expectResStr) == 0);
107 }
108
CreateJSListFormatterTest(JSThread * thread,icu::Locale icuLocale,JSHandle<JSObject> options)109 JSHandle<JSListFormat> CreateJSListFormatterTest(JSThread *thread, icu::Locale icuLocale, JSHandle<JSObject> options)
110 {
111 auto vm = thread->GetEcmaVM();
112 auto factory = vm->GetFactory();
113 auto env = vm->GetGlobalEnv();
114
115 JSHandle<JSTaggedValue> localeCtor = env->GetLocaleFunction();
116 JSHandle<JSTaggedValue> listCtor = env->GetListFormatFunction();
117 JSHandle<JSLocale> locales =
118 JSHandle<JSLocale>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(localeCtor), localeCtor));
119 JSHandle<JSListFormat> listFormatter =
120 JSHandle<JSListFormat>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(listCtor), listCtor));
121
122 JSHandle<JSTaggedValue> optionsVal = JSHandle<JSTaggedValue>::Cast(options);
123 factory->NewJSIntlIcuData(locales, icuLocale, JSLocale::FreeIcuLocale);
124 listFormatter = JSListFormat::InitializeListFormat(thread, listFormatter,
125 JSHandle<JSTaggedValue>::Cast(locales), optionsVal);
126 return listFormatter;
127 }
128
SetFormatterOptionsTest(JSThread * thread,JSHandle<JSObject> & optionsObj,std::map<std::string,std::string> & options)129 void SetFormatterOptionsTest(JSThread *thread, JSHandle<JSObject> &optionsObj,
130 std::map<std::string, std::string> &options)
131 {
132 auto vm = thread->GetEcmaVM();
133 auto factory = vm->GetFactory();
134 auto globalConst = thread->GlobalConstants();
135 JSHandle<JSTaggedValue> localeMatcherKey = globalConst->GetHandledLocaleMatcherString();
136 JSHandle<JSTaggedValue> typeKey = globalConst->GetHandledTypeString();
137 JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
138 JSHandle<JSTaggedValue> localeMatcherValue(factory->NewFromASCII(options["localeMatcher"].c_str()));
139 JSHandle<JSTaggedValue> typeValue(factory->NewFromASCII(options["type"].c_str()));
140 JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII(options["style"].c_str()));
141 JSObject::SetProperty(thread, optionsObj, localeMatcherKey, localeMatcherValue);
142 JSObject::SetProperty(thread, optionsObj, typeKey, typeValue);
143 JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
144 }
145
HWTEST_F_L0(JSListFormatTest,InitializeListFormat)146 HWTEST_F_L0(JSListFormatTest, InitializeListFormat)
147 {
148 auto vm = thread->GetEcmaVM();
149 auto factory = vm->GetFactory();
150 auto env = vm->GetGlobalEnv();
151 icu::Locale icuLocale("en", "Latn", "US");
152 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
153 JSHandle<JSObject> object = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
154 std::map<std::string, std::string> options {
155 { "localeMatcher", "best fit" },
156 { "type", "conjunction" },
157 { "style", "long" }
158 };
159 SetFormatterOptionsTest(thread, object, options);
160 JSHandle<JSTaggedValue> localeCtor = env->GetLocaleFunction();
161 JSHandle<JSTaggedValue> listCtor = env->GetListFormatFunction();
162 JSHandle<JSLocale> locales =
163 JSHandle<JSLocale>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(localeCtor), localeCtor));
164 JSHandle<JSListFormat> listFormatter =
165 JSHandle<JSListFormat>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(listCtor), listCtor));
166
167 JSHandle<JSTaggedValue> optionsVal = JSHandle<JSTaggedValue>::Cast(object);
168 factory->NewJSIntlIcuData(locales, icuLocale, JSLocale::FreeIcuLocale);
169 listFormatter = JSListFormat::InitializeListFormat(thread, listFormatter,
170 JSHandle<JSTaggedValue>::Cast(locales), optionsVal);
171 icu::ListFormatter *resFormatter = listFormatter->GetIcuListFormatter();
172 EXPECT_TRUE(resFormatter != nullptr);
173
174 const int32_t itemNum = 3;
175 UErrorCode status = UErrorCode::U_ZERO_ERROR;
176 const icu::UnicodeString items[itemNum] = { "Monday", "Tuesday", "Wednesday" };
177 icu::UnicodeString resStr = "";
178 resStr = resFormatter->format(items, itemNum, resStr, status);
179 const icu::UnicodeString expectResStr("Monday, Tuesday, and Wednesday");
180 EXPECT_TRUE(resStr.compare(expectResStr) == 0);
181 }
182
HWTEST_F_L0(JSListFormatTest,FormatList_001)183 HWTEST_F_L0(JSListFormatTest, FormatList_001)
184 {
185 auto vm = thread->GetEcmaVM();
186 auto factory = vm->GetFactory();
187 auto env = vm->GetGlobalEnv();
188 icu::Locale icuLocale("en", "Latn", "US");
189 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
190 JSHandle<JSObject> object = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
191 std::map<std::string, std::string> options {
192 { "localeMatcher", "best fit" },
193 { "type", "conjunction" },
194 { "style", "long" }
195 };
196 SetFormatterOptionsTest(thread, object, options);
197 JSHandle<JSListFormat> jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object);
198 JSHandle<JSObject> valueObj = JSHandle<JSObject>::Cast(factory->NewJSArray());
199 JSHandle<JSTaggedValue> key0(thread, JSTaggedValue(0));
200 JSHandle<JSTaggedValue> key1(thread, JSTaggedValue(1));
201 JSHandle<JSTaggedValue> key2(thread, JSTaggedValue(2));
202 JSHandle<JSTaggedValue> value0(factory->NewFromStdString("Zero"));
203 JSHandle<JSTaggedValue> value1(factory->NewFromStdString("One"));
204 JSHandle<JSTaggedValue> value2(factory->NewFromStdString("Two"));
205 JSObject::SetProperty(thread, valueObj, key0, value0);
206 JSObject::SetProperty(thread, valueObj, key1, value1);
207 JSObject::SetProperty(thread, valueObj, key2, value2);
208 JSHandle<JSArray> valueArr = JSHandle<JSArray>::Cast(valueObj);
209 JSHandle<EcmaString> valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr);
210 EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "Zero, One, and Two");
211 }
212
HWTEST_F_L0(JSListFormatTest,FormatList_002)213 HWTEST_F_L0(JSListFormatTest, FormatList_002)
214 {
215 auto vm = thread->GetEcmaVM();
216 auto factory = vm->GetFactory();
217 auto env = vm->GetGlobalEnv();
218 icu::Locale icuLocale("en", "Latn", "US");
219 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
220 JSHandle<JSObject> object = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
221 // when style is narrow, type can only be unit
222 std::map<std::string, std::string> options {
223 { "localeMatcher", "best fit" },
224 { "type", "unit" },
225 { "style", "narrow" }
226 };
227 SetFormatterOptionsTest(thread, object, options);
228 JSHandle<JSListFormat> jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object);
229 JSHandle<JSObject> valueObj = JSHandle<JSObject>::Cast(factory->NewJSArray());
230 JSHandle<JSTaggedValue> key0(thread, JSTaggedValue(0));
231 JSHandle<JSTaggedValue> key1(thread, JSTaggedValue(1));
232 JSHandle<JSTaggedValue> key2(thread, JSTaggedValue(2));
233 JSHandle<JSTaggedValue> value0(factory->NewFromStdString("Zero"));
234 JSHandle<JSTaggedValue> value1(factory->NewFromStdString("One"));
235 JSHandle<JSTaggedValue> value2(factory->NewFromStdString("Two"));
236 JSObject::SetProperty(thread, valueObj, key0, value0);
237 JSObject::SetProperty(thread, valueObj, key1, value1);
238 JSObject::SetProperty(thread, valueObj, key2, value2);
239 JSHandle<JSArray> valueArr = JSHandle<JSArray>::Cast(valueObj);
240 JSHandle<EcmaString> valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr);
241 EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "Zero One Two");
242 }
243
HWTEST_F_L0(JSListFormatTest,FormatList_003)244 HWTEST_F_L0(JSListFormatTest, FormatList_003)
245 {
246 auto vm = thread->GetEcmaVM();
247 auto factory = vm->GetFactory();
248 auto env = vm->GetGlobalEnv();
249 icu::Locale icuLocale("zh", "Hans", "Cn");
250 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
251 JSHandle<JSObject> object = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
252 std::map<std::string, std::string> options {
253 { "localeMatcher", "best fit" },
254 { "type", "disjunction" },
255 { "style", "long" }
256 };
257 SetFormatterOptionsTest(thread, object, options);
258 JSHandle<JSListFormat> jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object);
259 JSHandle<JSObject> valueObj = JSHandle<JSObject>::Cast(factory->NewJSArray());
260 JSHandle<JSTaggedValue> key0(thread, JSTaggedValue(0));
261 JSHandle<JSTaggedValue> key1(thread, JSTaggedValue(1));
262 JSHandle<JSTaggedValue> key2(thread, JSTaggedValue(2));
263 JSHandle<JSTaggedValue> value0(factory->NewFromStdString("苹果"));
264 JSHandle<JSTaggedValue> value1(factory->NewFromStdString("梨子"));
265 JSHandle<JSTaggedValue> value2(factory->NewFromStdString("桃"));
266 JSObject::SetProperty(thread, valueObj, key0, value0);
267 JSObject::SetProperty(thread, valueObj, key1, value1);
268 JSObject::SetProperty(thread, valueObj, key2, value2);
269 JSHandle<JSArray> valueArr = JSHandle<JSArray>::Cast(valueObj);
270 JSHandle<EcmaString> valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr);
271 EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "苹果、梨子或桃");
272 }
273
GetListPartStringTest(JSThread * thread,JSHandle<JSTaggedValue> key,JSHandle<JSTaggedValue> part)274 std::string GetListPartStringTest(JSThread *thread, JSHandle<JSTaggedValue> key, JSHandle<JSTaggedValue> part)
275 {
276 JSHandle<JSObject> partObj = JSHandle<JSObject>::Cast(part);
277 JSHandle<JSTaggedValue> partValue = JSObject::GetProperty(thread, partObj, key).GetValue();
278 JSHandle<EcmaString> partEcmaStr = JSHandle<EcmaString>::Cast(partValue);
279 std::string partStr = intl::LocaleHelper::ConvertToStdString(partEcmaStr);
280 return partStr;
281 }
282
HWTEST_F_L0(JSListFormatTest,FormatListToParts)283 HWTEST_F_L0(JSListFormatTest, FormatListToParts)
284 {
285 auto vm = thread->GetEcmaVM();
286 auto factory = vm->GetFactory();
287 auto env = vm->GetGlobalEnv();
288 auto globalConst = thread->GlobalConstants();
289 icu::Locale icuLocale("zh", "Hans", "Cn");
290 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
291 JSHandle<JSObject> object = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
292 std::map<std::string, std::string> options {
293 { "localeMatcher", "best fit" },
294 { "type", "conjunction" },
295 { "style", "long" }
296 };
297 SetFormatterOptionsTest(thread, object, options);
298 JSHandle<JSListFormat> jsFormatter = CreateJSListFormatterTest(thread, icuLocale, object);
299 JSHandle<JSObject> valueObj = JSHandle<JSObject>::Cast(factory->NewJSArray());
300 JSHandle<JSTaggedValue> key0(thread, JSTaggedValue(0));
301 JSHandle<JSTaggedValue> key1(thread, JSTaggedValue(1));
302 JSHandle<JSTaggedValue> key2(thread, JSTaggedValue(2));
303 JSHandle<JSTaggedValue> value0(factory->NewFromStdString("苹果"));
304 JSHandle<JSTaggedValue> value1(factory->NewFromStdString("梨子"));
305 JSHandle<JSTaggedValue> value2(factory->NewFromStdString("桃"));
306 JSObject::SetProperty(thread, valueObj, key0, value0);
307 JSObject::SetProperty(thread, valueObj, key1, value1);
308 JSObject::SetProperty(thread, valueObj, key2, value2);
309 JSHandle<JSArray> valueArr = JSHandle<JSArray>::Cast(valueObj);
310 JSHandle<EcmaString> valueStr = JSListFormat::FormatList(thread, jsFormatter, valueArr);
311 EXPECT_STREQ(EcmaStringAccessor(valueStr).ToCString().c_str(), "苹果、梨子和桃");
312
313 JSHandle<JSTaggedValue> typeKey = globalConst->GetHandledTypeString();
314 JSHandle<JSTaggedValue> valueKey = globalConst->GetHandledValueString();
315 JSHandle<JSArray> parts = JSListFormat::FormatListToParts(thread, jsFormatter, valueArr);
316 auto element1 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(parts), 0).GetValue();
317 auto literal1 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(parts), 1).GetValue();
318 auto element2 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(parts), 2).GetValue();
319 auto literal2 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(parts), 3).GetValue();
320 auto element3 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(parts), 4).GetValue();
321 EXPECT_STREQ(GetListPartStringTest(thread, typeKey, element1).c_str(), "element");
322 EXPECT_STREQ(GetListPartStringTest(thread, valueKey, element1).c_str(), "苹果");
323 EXPECT_STREQ(GetListPartStringTest(thread, typeKey, literal1).c_str(), "literal");
324 EXPECT_STREQ(GetListPartStringTest(thread, valueKey, literal1).c_str(), "、");
325 EXPECT_STREQ(GetListPartStringTest(thread, typeKey, element2).c_str(), "element");
326 EXPECT_STREQ(GetListPartStringTest(thread, valueKey, element2).c_str(), "梨子");
327 EXPECT_STREQ(GetListPartStringTest(thread, typeKey, literal2).c_str(), "literal");
328 EXPECT_STREQ(GetListPartStringTest(thread, valueKey, literal2).c_str(), "和");
329 EXPECT_STREQ(GetListPartStringTest(thread, typeKey, element3).c_str(), "element");
330 EXPECT_STREQ(GetListPartStringTest(thread, valueKey, element3).c_str(), "桃");
331 }
332
HWTEST_F_L0(JSListFormatTest,StringListFromIterable)333 HWTEST_F_L0(JSListFormatTest, StringListFromIterable)
334 {
335 auto vm = thread->GetEcmaVM();
336 auto factory = vm->GetFactory();
337 JSHandle<TaggedArray> data = factory->NewTaggedArray(3);
338 JSHandle<JSTaggedValue> value0(factory->NewFromStdString("one"));
339 JSHandle<JSTaggedValue> value1(factory->NewFromStdString("two"));
340 JSHandle<JSTaggedValue> value2(factory->NewFromStdString("three"));
341 data->Set(thread, 0, value0.GetTaggedValue());
342 data->Set(thread, 1, value1.GetTaggedValue());
343 data->Set(thread, 2, value2.GetTaggedValue());
344 JSHandle<JSTaggedValue> array(JSArray::CreateArrayFromList(thread, data));
345 JSHandle<JSArrayIterator> iter(JSIterator::GetIterator(thread, array));
346 JSHandle<JSTaggedValue> arrayString =
347 JSListFormat::StringListFromIterable(thread, JSHandle<JSTaggedValue>::Cast(iter));
348 JSHandle<JSArray> strValue = JSHandle<JSArray>::Cast(arrayString);
349 EXPECT_EQ(strValue->GetArrayLength(), 3U);
350
351 auto resValue0 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(strValue), 0).GetValue();
352 auto resValue1 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(strValue), 1).GetValue();
353 auto resValue2 = JSTaggedValue::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(strValue), 2).GetValue();
354 EXPECT_STREQ(EcmaStringAccessor(resValue0.GetTaggedValue()).ToCString().c_str(), "one");
355 EXPECT_STREQ(EcmaStringAccessor(resValue1.GetTaggedValue()).ToCString().c_str(), "two");
356 EXPECT_STREQ(EcmaStringAccessor(resValue2.GetTaggedValue()).ToCString().c_str(), "three");
357 }
358 } // namespace panda::test
359