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