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 BaseTestWithScope<true> {
28 };
29 
30 // new DateTimeFormat(newTarget is undefined)
HWTEST_F_L0(BuiltinsNumberFormatTest, NumberFormatConstructor)31 HWTEST_F_L0(BuiltinsNumberFormatTest, NumberFormatConstructor)
32 {
33     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
34     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
35     JSHandle<JSFunction> newTarget(env->GetNumberFormatFunction());
36 
37     JSHandle<JSTaggedValue> localesString(factory->NewFromASCII("en-US"));
38     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 8);
39     ecmaRuntimeCallInfo->SetFunction(newTarget.GetTaggedValue());
40     ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
41     ecmaRuntimeCallInfo->SetCallArg(0, localesString.GetTaggedValue());
42     // option tag is default value
43     ecmaRuntimeCallInfo->SetCallArg(1, JSTaggedValue::Undefined());
44 
45     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
46     JSTaggedValue result = BuiltinsNumberFormat::NumberFormatConstructor(ecmaRuntimeCallInfo);
47     TestHelper::TearDownFrame(thread, prev);
48 
49     EXPECT_TRUE(result.IsJSNumberFormat());
50 }
51 
BuiltinsFormatTest(JSThread *thread, JSHandle<JSObject> &options, JSHandle<JSTaggedValue> &number, JSHandle<JSTaggedValue> &locale)52 static JSTaggedValue BuiltinsFormatTest(JSThread *thread, JSHandle<JSObject> &options,
53                                         JSHandle<JSTaggedValue> &number, JSHandle<JSTaggedValue> &locale)
54 {
55     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
56     JSHandle<JSFunction> newTarget(env->GetNumberFormatFunction());
57 
58     auto ecmaRuntimeCallInfo1 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 8);
59     ecmaRuntimeCallInfo1->SetFunction(newTarget.GetTaggedValue());
60     ecmaRuntimeCallInfo1->SetThis(JSTaggedValue::Undefined());
61     ecmaRuntimeCallInfo1->SetCallArg(0, locale.GetTaggedValue());
62     ecmaRuntimeCallInfo1->SetCallArg(1, options.GetTaggedValue());
63     // construct numberformat
64     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo1);
65     JSTaggedValue numberFormat = BuiltinsNumberFormat::NumberFormatConstructor(ecmaRuntimeCallInfo1);
66     JSHandle<JSTaggedValue> numberFormatVal(thread, numberFormat);
67     TestHelper::TearDownFrame(thread, prev);
68     // get function by calling Format function
69     auto ecmaRuntimeCallInfo2 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 4);
70     ecmaRuntimeCallInfo2->SetFunction(JSTaggedValue::Undefined());
71     ecmaRuntimeCallInfo2->SetThis(numberFormatVal.GetTaggedValue());
72 
73     prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo2);
74     JSTaggedValue resultFunc = BuiltinsNumberFormat::Format(ecmaRuntimeCallInfo2);
75     JSHandle<JSFunction> jsFunction(thread, resultFunc);
76     TestHelper::TearDownFrame(thread, prev);
77     auto ecmaRuntimeCallInfo3 = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
78     ecmaRuntimeCallInfo3->SetFunction(jsFunction.GetTaggedValue());
79     ecmaRuntimeCallInfo3->SetThis(jsFunction.GetTaggedValue());
80     ecmaRuntimeCallInfo3->SetCallArg(0, number.GetTaggedValue());
81 
82     prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo3);
83     JSTaggedValue result = JSFunction::Call(ecmaRuntimeCallInfo3);
84     TestHelper::TearDownFrame(thread, prev);
85     return result;
86 }
87 
88 // format decimal
HWTEST_F_L0(BuiltinsNumberFormatTest, Format_001)89 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_001)
90 {
91     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
92     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
93     auto globalConst = thread->GlobalConstants();
94     JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
95 
96     JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
97     JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("decimal"));
98     JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
99     JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(3500));
100 
101     JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
102     JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
103     JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
104 
105     JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
106     EXPECT_STREQ("3,500", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
107 }
108 
109 // format currency
HWTEST_F_L0(BuiltinsNumberFormatTest, Format_002)110 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_002)
111 {
112     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
113     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
114     auto globalConst = thread->GlobalConstants();
115     JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
116 
117     JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
118     JSHandle<JSTaggedValue> currencyKey = globalConst->GetHandledCurrencyString();
119     JSHandle<JSTaggedValue> currencyDisplayKey = globalConst->GetHandledCurrencyDisplayString();
120     JSHandle<JSTaggedValue> currencySignDisplayKey = globalConst->GetHandledCurrencySignString();
121 
122     JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("currency"));
123     JSHandle<JSTaggedValue> currencyValue(factory->NewFromASCII("USD"));
124     JSHandle<JSTaggedValue> currencyDisplayValue(factory->NewFromASCII("name"));
125     JSHandle<JSTaggedValue> currencySignDisplayValue(factory->NewFromASCII("accounting"));
126     JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
127     JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(static_cast<int32_t>(-3500)));
128 
129     JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
130     JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
131     JSObject::SetProperty(thread, optionsObj, currencyKey, currencyValue);
132     JSObject::SetProperty(thread, optionsObj, currencyDisplayKey, currencyDisplayValue);
133     JSObject::SetProperty(thread, optionsObj, currencySignDisplayKey, currencySignDisplayValue);
134     JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
135 
136     JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
137     EXPECT_STREQ("($3,500.00)", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
138 }
139 
140 // format percent
HWTEST_F_L0(BuiltinsNumberFormatTest, Format_003)141 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_003)
142 {
143     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
144     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
145     auto globalConst = thread->GlobalConstants();
146     JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
147 
148     JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
149     JSHandle<JSTaggedValue> signDisplayKey = globalConst->GetHandledSignDisplayString();
150 
151     JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("percent"));
152     JSHandle<JSTaggedValue> signDisplayValue(factory->NewFromASCII("exceptZero"));
153     JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
154     JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(static_cast<double>(0.55)));
155 
156     JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
157     JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
158     JSObject::SetProperty(thread, optionsObj, signDisplayKey, signDisplayValue);
159     JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
160 
161     JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
162     EXPECT_STREQ("+55%", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
163 }
164 
165 // format unit
HWTEST_F_L0(BuiltinsNumberFormatTest, Format_004)166 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_004)
167 {
168     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
169     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
170     auto globalConst = thread->GlobalConstants();
171     JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
172 
173     JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
174     JSHandle<JSTaggedValue> unitKey = globalConst->GetHandledUnitString();
175 
176     JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("unit"));
177     JSHandle<JSTaggedValue> unitValue(factory->NewFromASCII("liter"));
178     JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
179     JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(3500));
180 
181     JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
182     JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
183     JSObject::SetProperty(thread, optionsObj, unitKey, unitValue);
184     JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
185 
186     JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
187     EXPECT_STREQ("3,500 L", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
188 }
189 
190 // format notation
HWTEST_F_L0(BuiltinsNumberFormatTest, Format_005)191 HWTEST_F_L0(BuiltinsNumberFormatTest, Format_005)
192 {
193     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
194     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
195     auto globalConst = thread->GlobalConstants();
196     JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
197 
198     JSHandle<JSTaggedValue> notationKey = globalConst->GetHandledNotationString();
199     JSHandle<JSTaggedValue> notationValue(factory->NewFromASCII("compact"));
200     JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("zh-CN"));
201     JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(987654321));
202 
203     JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
204     JSObject::SetProperty(thread, optionsObj, notationKey, notationValue);
205     JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
206 
207     JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
208     EXPECT_STREQ("9.9亿", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
209 }
210 
NumberFormatCreateTest(JSThread *thread, JSHandle<JSObject> &options, JSHandle<JSTaggedValue> &locale)211 static JSTaggedValue NumberFormatCreateTest(JSThread *thread, JSHandle<JSObject> &options,
212                                             JSHandle<JSTaggedValue> &locale)
213 {
214     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
215     JSHandle<JSFunction> newTarget(env->GetNumberFormatFunction());
216 
217     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*newTarget), 8);
218     ecmaRuntimeCallInfo->SetFunction(newTarget.GetTaggedValue());
219     ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
220     ecmaRuntimeCallInfo->SetCallArg(0, locale.GetTaggedValue());
221     ecmaRuntimeCallInfo->SetCallArg(1, options.GetTaggedValue());
222     // construct numberformat
223     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
224     JSTaggedValue numberFormat = BuiltinsNumberFormat::NumberFormatConstructor(ecmaRuntimeCallInfo);
225     TestHelper::TearDownFrame(thread, prev);
226     return numberFormat;
227 }
228 
HWTEST_F_L0(BuiltinsNumberFormatTest, FormatToParts)229 HWTEST_F_L0(BuiltinsNumberFormatTest, FormatToParts)
230 {
231     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
232     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
233     auto globalConst = thread->GlobalConstants();
234     JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
235 
236     JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
237     JSHandle<JSTaggedValue> currencyKey = globalConst->GetHandledCurrencyString();
238 
239     JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("currency"));
240     JSHandle<JSTaggedValue> currencyValue(factory->NewFromASCII("EUR"));
241     JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("de-DE"));
242     JSHandle<JSTaggedValue> numberVal(thread, JSTaggedValue(3500));
243 
244     JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
245     JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
246     JSObject::SetProperty(thread, optionsObj, currencyKey, currencyValue);
247     JSTaggedValue numberFormat = NumberFormatCreateTest(thread, optionsObj, localeString);
248     JSHandle<JSTaggedValue> numberFormatVal(thread, numberFormat);
249     // format currency
250     JSTaggedValue formatResult = BuiltinsFormatTest(thread, optionsObj, numberVal, localeString);
251     JSHandle<EcmaString> resultEcmaStr(thread, formatResult);
252     EXPECT_STREQ("3.500,00 €", EcmaStringAccessor(resultEcmaStr).ToCString().c_str());
253 
254     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 6);
255     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
256     ecmaRuntimeCallInfo->SetThis(numberFormatVal.GetTaggedValue());
257     ecmaRuntimeCallInfo->SetCallArg(0, numberVal.GetTaggedValue());
258     // format currency to part
259     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
260     JSTaggedValue result = BuiltinsNumberFormat::FormatToParts(ecmaRuntimeCallInfo);
261     TestHelper::TearDownFrame(thread, prev);
262 
263     JSHandle<JSArray> resultHandle(thread, result);
264     JSHandle<TaggedArray> elements(thread, resultHandle->GetElements());
265     EXPECT_EQ(elements->GetLength(), 10U); // "3","." ,"5" ,"0" ,"0" ,"," ,"0" ,0" ," " ,"€"
266 }
267 
HWTEST_F_L0(BuiltinsNumberFormatTest, ResolvedOptions)268 HWTEST_F_L0(BuiltinsNumberFormatTest, ResolvedOptions)
269 {
270     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
271     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
272     auto globalConst = thread->GlobalConstants();
273     JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
274 
275     JSHandle<JSTaggedValue> styleKey = globalConst->GetHandledStyleString();
276     JSHandle<JSTaggedValue> currencyKey = globalConst->GetHandledCurrencyString();
277 
278     JSHandle<JSTaggedValue> styleValue(factory->NewFromASCII("currency"));
279     JSHandle<JSTaggedValue> currencyValue(factory->NewFromASCII("USD"));
280     JSHandle<JSTaggedValue> localeString(factory->NewFromASCII("en-US"));
281 
282     JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
283     JSObject::SetProperty(thread, optionsObj, styleKey, styleValue);
284     JSObject::SetProperty(thread, optionsObj, currencyKey, currencyValue);
285     JSTaggedValue numberFormat = NumberFormatCreateTest(thread, optionsObj, localeString);
286     JSHandle<JSTaggedValue> numberFormatVal(thread, numberFormat);
287 
288     auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), 4);
289     ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
290     ecmaRuntimeCallInfo->SetThis(numberFormatVal.GetTaggedValue());
291 
292     [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
293     JSTaggedValue result = BuiltinsNumberFormat::ResolvedOptions(ecmaRuntimeCallInfo);
294     TestHelper::TearDownFrame(thread, prev);
295 
296     JSHandle<JSTaggedValue> resultObj =
297         JSHandle<JSTaggedValue>(thread, JSTaggedValue(static_cast<JSTaggedType>(result.GetRawData())));
298     // judge whether the properties of the object are the same as those of jsnumberformat tag
299     EXPECT_EQ(JSTaggedValue::SameValue(
300         JSObject::GetProperty(thread, resultObj, styleKey).GetValue(), styleValue), true);
301     EXPECT_EQ(JSTaggedValue::SameValue(
302         JSObject::GetProperty(thread, resultObj, currencyKey).GetValue(), currencyValue), true);
303 }
304 }
305