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/builtins/builtins_number_format.h"
17
18 #include "ecmascript/builtins/builtins_array.h"
19 #include "ecmascript/global_env.h"
20 #include "ecmascript/js_number_format.h"
21 #include "ecmascript/tests/test_helper.h"
22
23 using namespace panda::ecmascript;
24 using namespace panda::ecmascript::builtins;
25
26 namespace panda::test {
27 class BuiltinsNumberFormatTest : public testing::Test {
28 public:
SetUpTestCase()29 static void SetUpTestCase()
30 {
31 GTEST_LOG_(INFO) << "SetUpTestCase";
32 }
33
TearDownTestCase()34 static void TearDownTestCase()
35 {
36 GTEST_LOG_(INFO) << "TearDownCase";
37 }
38
SetUp()39 void SetUp() override
40 {
41 JSRuntimeOptions options;
42 #if PANDA_TARGET_LINUX
43 // for consistency requirement, use ohos_icu4j/data as icu-data-path
44 options.SetIcuDataPath(ICU_PATH);
45 #endif
46 options.SetEnableForceGC(true);
47 instance = JSNApi::CreateEcmaVM(options);
48 instance->SetEnableForceGC(true);
49 ASSERT_TRUE(instance != nullptr) << "Cannot create EcmaVM";
50 thread = instance->GetJSThread();
51 scope = new EcmaHandleScope(thread);
52 }
53
TearDown()54 void TearDown() override
55 {
56 TestHelper::DestroyEcmaVMWithScope(instance, scope);
57 }
58
59 EcmaVM *instance {nullptr};
60 EcmaHandleScope *scope {nullptr};
61 JSThread *thread {nullptr};
62 };
63
64 // new DateTimeFormat(newTarget is undefined)
HWTEST_F_L0(BuiltinsNumberFormatTest,NumberFormatConstructor)65 HWTEST_F_L0(BuiltinsNumberFormatTest, NumberFormatConstructor)
66 {
67 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
68 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
69 JSHandle<JSFunction> newTarget(env->GetNumberFormatFunction());
70
71 JSHandle<JSTaggedValue> localesString(factory->NewFromASCII("en-US"));
72 auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 8);
73 ecmaRuntimeCallInfo->SetFunction(newTarget.GetTaggedValue());
74 ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
75 ecmaRuntimeCallInfo->SetCallArg(0, localesString.GetTaggedValue());
76 // option tag is default value
77 ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue::Undefined());
78
79 [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
80 JSTaggedValue result = BuiltinsNumberFormat::NumberFormatConstructor(ecmaRuntimeCallInfo);
81 TestHelper::TearDownFrame(thread, prev);
82
83 EXPECT_TRUE(result.IsJSNumberFormat());
84 }
85
BuiltinsFormatTest(JSThread * thread,JSHandle<JSObject> & options,JSHandle<JSTaggedValue> & number,JSHandle<JSTaggedValue> & locale)86 static JSTaggedValue BuiltinsFormatTest(JSThread *thread, JSHandle<JSObject> &options,
87 JSHandle<JSTaggedValue> &number, JSHandle<JSTaggedValue> &locale)
88 {
89 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
90 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
91 JSHandle<JSFunction> newTarget(env->GetNumberFormatFunction());
92
93 auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 8);
94 ecmaRuntimeCallInfo1->SetFunction(newTarget.GetTaggedValue());
95 ecmaRuntimeCallInfo1->SetThis(JSTaggedValue::Undefined());
96 ecmaRuntimeCallInfo1->SetCallArg(0, locale.GetTaggedValue());
97 ecmaRuntimeCallInfo1->SetCallArg(1, options.GetTaggedValue());
98 // construct numberformat
99 [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
100 JSTaggedValue numberFormat = BuiltinsNumberFormat::NumberFormatConstructor(ecmaRuntimeCallInfo1);
101 JSHandle<JSTaggedValue> numberFormatVal(thread, numberFormat);
102 TestHelper::TearDownFrame(thread, prev);
103 // get function by calling Format function
104 auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 4);
105 ecmaRuntimeCallInfo2->SetFunction(JSTaggedValue::Undefined());
106 ecmaRuntimeCallInfo2->SetThis(numberFormatVal.GetTaggedValue());
107
108 prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo2);
109 JSTaggedValue resultFunc = BuiltinsNumberFormat::Format(ecmaRuntimeCallInfo2);
110 JSHandle<JSFunction> jsFunction(thread, resultFunc);
111 TestHelper::TearDownFrame(thread, prev);
112 JSArray *jsArray =
113 JSArray::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject());
114 JSHandle<JSObject> jsObject(thread, jsArray);
115 PropertyDescriptor desc(thread, JSHandle<JSTaggedValue>(jsFunction), true, true, true);
116 JSHandle<JSTaggedValue> joinKey(factory->NewFromASCII("join"));
117 JSArray::DefineOwnProperty(thread, jsObject, joinKey, desc);
118
119 auto ecmaRuntimeCallInfo3 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
120 ecmaRuntimeCallInfo3->SetFunction(JSTaggedValue::Undefined());
121 ecmaRuntimeCallInfo3->SetThis(jsObject.GetTaggedValue());
122 ecmaRuntimeCallInfo3->SetCallArg(0, number.GetTaggedValue());
123
124 prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo3);
125 JSTaggedValue result = BuiltinsArray::ToString(ecmaRuntimeCallInfo3);
126 TestHelper::TearDownFrame(thread, prev);
127 return result;
128 }
129
130 // format decimal
HWTEST_F_L0(BuiltinsNumberFormatTest,Format_001)131 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_001)
132 {
133 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
134 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
135 auto globalConst = thread->GlobalConstants();
136 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
137
138 JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
139 JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("decimal"));
140 JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
141 JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(3500));
142
143 JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
144 JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
145 JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
146
147 JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
148 EXPECT_STREQ("3,500", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
149 }
150
151 // format currency
HWTEST_F_L0(BuiltinsNumberFormatTest,Format_002)152 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_002)
153 {
154 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
155 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
156 auto globalConst = thread->GlobalConstants();
157 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
158
159 JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
160 JSHandle<JSTaggedValue> currencyKey = globalConst->GetHandledCurrencyString();
161 JSHandle<JSTaggedValue> currencyDisplayKey = globalConst->GetHandledCurrencyDisplayString();
162 JSHandle<JSTaggedValue> currencySignDisplayKey = globalConst->GetHandledCurrencySignString();
163
164 JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("currency"));
165 JSHandle<JSTaggedValue> currencyValue(factory->NewFromASCII("USD"));
166 JSHandle<JSTaggedValue> currencyDisplayValue(factory->NewFromASCII("name"));
167 JSHandle<JSTaggedValue> currencySignDisplayValue(factory->NewFromASCII("accounting"));
168 JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
169 JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(static_cast<int32_t>(-3500)));
170
171 JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
172 JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
173 JSObject::SetProperty(thread, optionsObj, currencyKey, currencyValue);
174 JSObject::SetProperty(thread, optionsObj, currencyDisplayKey, currencyDisplayValue);
175 JSObject::SetProperty(thread, optionsObj, currencySignDisplayKey, currencySignDisplayValue);
176 JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
177
178 JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
179 EXPECT_STREQ("($3,500.00)", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
180 }
181
182 // format percent
HWTEST_F_L0(BuiltinsNumberFormatTest,Format_003)183 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_003)
184 {
185 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
186 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
187 auto globalConst = thread->GlobalConstants();
188 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
189
190 JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
191 JSHandle<JSTaggedValue> signDisplayKey = globalConst->GetHandledSignDisplayString();
192
193 JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("percent"));
194 JSHandle<JSTaggedValue> signDisplayValue(factory->NewFromASCII("exceptZero"));
195 JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
196 JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(static_cast<double>(0.55)));
197
198 JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
199 JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
200 JSObject::SetProperty(thread, optionsObj, signDisplayKey, signDisplayValue);
201 JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
202
203 JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
204 EXPECT_STREQ("+55%", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
205 }
206
207 // format unit
HWTEST_F_L0(BuiltinsNumberFormatTest,Format_004)208 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_004)
209 {
210 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
211 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
212 auto globalConst = thread->GlobalConstants();
213 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
214
215 JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
216 JSHandle<JSTaggedValue> unitKey = globalConst->GetHandledUnitString();
217
218 JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("unit"));
219 JSHandle<JSTaggedValue> unitValue(factory->NewFromASCII("liter"));
220 JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
221 JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(3500));
222
223 JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
224 JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
225 JSObject::SetProperty(thread, optionsObj, unitKey, unitValue);
226 JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
227
228 JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
229 EXPECT_STREQ("3,500 L", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
230 }
231
232 // format notation
HWTEST_F_L0(BuiltinsNumberFormatTest,Format_005)233 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_005)
234 {
235 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
236 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
237 auto globalConst = thread->GlobalConstants();
238 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
239
240 JSHandle<JSTaggedValue> notationKey = globalConst->GetHandledNotationString();
241 JSHandle<JSTaggedValue> notationValue(factory->NewFromASCII("compact"));
242 JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("zh-CN"));
243 JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(987654321));
244
245 JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
246 JSObject::SetProperty(thread, optionsObj, notationKey, notationValue);
247 JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
248
249 JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
250 EXPECT_STREQ("9.9亿", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
251 }
252
NumberFormatCreateTest(JSThread * thread,JSHandle<JSObject> & options,JSHandle<JSTaggedValue> & locale)253 static JSTaggedValue NumberFormatCreateTest(JSThread *thread, JSHandle<JSObject> &options,
254 JSHandle<JSTaggedValue> &locale)
255 {
256 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
257 JSHandle<JSFunction> newTarget(env->GetNumberFormatFunction());
258
259 auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 8);
260 ecmaRuntimeCallInfo->SetFunction(newTarget.GetTaggedValue());
261 ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
262 ecmaRuntimeCallInfo->SetCallArg(0, locale.GetTaggedValue());
263 ecmaRuntimeCallInfo->SetCallArg(1, options.GetTaggedValue());
264 // construct numberformat
265 [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
266 JSTaggedValue numberFormat = BuiltinsNumberFormat::NumberFormatConstructor(ecmaRuntimeCallInfo);
267 TestHelper::TearDownFrame(thread, prev);
268 return numberFormat;
269 }
270
HWTEST_F_L0(BuiltinsNumberFormatTest,FormatToParts)271 HWTEST_F_L0(BuiltinsNumberFormatTest, FormatToParts)
272 {
273 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
274 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
275 auto globalConst = thread->GlobalConstants();
276 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
277
278 JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
279 JSHandle<JSTaggedValue> currencyKey = globalConst->GetHandledCurrencyString();
280
281 JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("currency"));
282 JSHandle<JSTaggedValue> currencyValue(factory->NewFromASCII("EUR"));
283 JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("de-DE"));
284 JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(3500));
285
286 JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
287 JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
288 JSObject::SetProperty(thread, optionsObj, currencyKey, currencyValue);
289 JSTaggedValue numberFormat = NumberFormatCreateTest(thread, optionsObj, localeString);
290 JSHandle<JSTaggedValue> numberFormatVal(thread, numberFormat);
291 // format currency
292 JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
293 JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
294 EXPECT_STREQ("3.500,00 €", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
295
296 auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
297 ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
298 ecmaRuntimeCallInfo->SetThis(numberFormatVal.GetTaggedValue());
299 ecmaRuntimeCallInfo->SetCallArg(0, numberVal.GetTaggedValue());
300 // format currency to part
301 [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
302 JSTaggedValue result = BuiltinsNumberFormat::FormatToParts(ecmaRuntimeCallInfo);
303 TestHelper::TearDownFrame(thread, prev);
304
305 JSHandle<JSArray> resultHandle(thread, result);
306 JSHandle<TaggedArray> elements(thread, resultHandle->GetElements());
307 EXPECT_EQ(elements->GetLength(), 10U); // "3","." ,"5" ,"0" ,"0" ,"," ,"0" ,0" ," " ,"€"
308 }
309
HWTEST_F_L0(BuiltinsNumberFormatTest,ResolvedOptions)310 HWTEST_F_L0(BuiltinsNumberFormatTest, ResolvedOptions)
311 {
312 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
313 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
314 auto globalConst = thread->GlobalConstants();
315 JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
316
317 JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
318 JSHandle<JSTaggedValue> currencyKey = globalConst->GetHandledCurrencyString();
319
320 JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("currency"));
321 JSHandle<JSTaggedValue> currencyValue(factory->NewFromASCII("USD"));
322 JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
323
324 JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
325 JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
326 JSObject::SetProperty(thread, optionsObj, currencyKey, currencyValue);
327 JSTaggedValue numberFormat = NumberFormatCreateTest(thread, optionsObj, localeString);
328 JSHandle<JSTaggedValue> numberFormatVal(thread, numberFormat);
329
330 auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 4);
331 ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
332 ecmaRuntimeCallInfo->SetThis(numberFormatVal.GetTaggedValue());
333
334 [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
335 JSTaggedValue result = BuiltinsNumberFormat::ResolvedOptions(ecmaRuntimeCallInfo);
336 TestHelper::TearDownFrame(thread, prev);
337
338 JSHandle<JSTaggedValue> resultObj =
339 JSHandle<JSTaggedValue>(thread, JSTaggedValue(static_cast<JSTaggedType>(result.GetRawData())));
340 // judge whether the properties of the object are the same as those of jsnumberformat tag
341 EXPECT_EQ(JSTaggedValue::SameValue(
342 JSObject::GetProperty(thread, resultObj, styleKey).GetValue(), styleValue), true);
343 EXPECT_EQ(JSTaggedValue::SameValue(
344 JSObject::GetProperty(thread, resultObj, currencyKey).GetValue(), currencyValue), true);
345 }
346 }
347