1/*
2 * Copyright (c) 2021 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_collator.h"
17
18#include "ecmascript/builtins/builtins_array.h"
19#include "ecmascript/global_env.h"
20#include "ecmascript/js_collator.h"
21#include "ecmascript/tests/test_helper.h"
22
23using namespace panda::ecmascript;
24using namespace panda::ecmascript::builtins;
25
26namespace panda::test {
27using BuiltinsArray = ecmascript::builtins::BuiltinsArray;
28class BuiltinsCollatorTest : public BaseTestWithScope<true> {
29};
30
31static JSTaggedValue CollatorConstructor(JSThread *thread, std::vector<JSTaggedValue>& args,
32    JSHandle<JSFunction>& target)
33{
34    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue(*target), 8);
35    ecmaRuntimeCallInfo->SetFunction(target.GetTaggedValue());
36    ecmaRuntimeCallInfo->SetThis(JSTaggedValue::Undefined());
37    for (size_t i = 0; i < args.size(); i++) {
38        ecmaRuntimeCallInfo->SetCallArg(i, args[i]);
39    }
40
41    auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
42    JSTaggedValue result = BuiltinsCollator::CollatorConstructor(ecmaRuntimeCallInfo);
43    TestHelper::TearDownFrame(thread, prev);
44    return result;
45}
46
47enum class AlgorithmType {
48    COLLATOR_SUPPORTED_LOCALES_OF,
49    COLLATOR_RESOLVED_OPTIONS,
50    COLLATOR_COMPARE,
51    ARRAY_SORT,
52    ARRAY_JOIN,
53    ARRAY_TOSTR,
54};
55
56static JSTaggedValue CollatorAlgorithm(JSThread *thread, std::vector<JSTaggedValue>& args, int32_t maxArgLen,
57    AlgorithmType type, JSTaggedValue thisValue = JSTaggedValue::Undefined())
58{
59    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread, JSTaggedValue::Undefined(), maxArgLen);
60    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
61    ecmaRuntimeCallInfo->SetThis(thisValue);
62    for (size_t i = 0; i < args.size(); i++) {
63        ecmaRuntimeCallInfo->SetCallArg(i, args[i]);
64    }
65    auto prev = TestHelper::SetupFrame(thread, ecmaRuntimeCallInfo);
66    JSTaggedValue result;
67    switch (type) {
68        case AlgorithmType::COLLATOR_SUPPORTED_LOCALES_OF:
69            result = BuiltinsCollator::SupportedLocalesOf(ecmaRuntimeCallInfo);
70            break;
71        case AlgorithmType::COLLATOR_RESOLVED_OPTIONS:
72            result = BuiltinsCollator::ResolvedOptions(ecmaRuntimeCallInfo);
73            break;
74        case AlgorithmType::COLLATOR_COMPARE:
75            result = BuiltinsCollator::Compare(ecmaRuntimeCallInfo);
76            break;
77        case AlgorithmType::ARRAY_SORT:
78            result = BuiltinsArray::Sort(ecmaRuntimeCallInfo);
79            break;
80        case AlgorithmType::ARRAY_JOIN:
81            result = BuiltinsArray::Join(ecmaRuntimeCallInfo);
82            break;
83        case AlgorithmType::ARRAY_TOSTR:
84            result = BuiltinsArray::ToString(ecmaRuntimeCallInfo);
85            break;
86        default:
87            break;
88    }
89    TestHelper::TearDownFrame(thread, prev);
90    return result;
91}
92
93HWTEST_F_L0(BuiltinsCollatorTest, CollatorConstructor)
94{
95    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
96    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
97    JSHandle<JSFunction> newTarget(env->GetCollatorFunction());
98    JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
99
100    JSHandle<JSTaggedValue> usageKey = thread->GlobalConstants()->GetHandledUsageString();
101    JSHandle<JSTaggedValue> localeMatcherKey = thread->GlobalConstants()->GetHandledLocaleMatcherString();
102    JSHandle<JSTaggedValue> numericKey = thread->GlobalConstants()->GetHandledNumericString();
103    JSHandle<JSTaggedValue> caseFirstKey = thread->GlobalConstants()->GetHandledCaseFirstString();
104    JSHandle<JSTaggedValue> sensitivityKey = thread->GlobalConstants()->GetHandledSensitivityString();
105    JSHandle<JSTaggedValue> ignorePunctuationKey = thread->GlobalConstants()->GetHandledIgnorePunctuationString();
106
107    JSHandle<JSTaggedValue> usageValue(factory->NewFromASCII("search"));
108    JSHandle<JSTaggedValue> localeMatcherValue(factory->NewFromASCII("lookup"));
109    JSHandle<JSTaggedValue> numericValue(factory->NewFromASCII("true"));
110    JSHandle<JSTaggedValue> caseFirstValue(factory->NewFromASCII("lower"));
111    JSHandle<JSTaggedValue> sensitivityValue(factory->NewFromASCII("variant"));
112    JSHandle<JSTaggedValue> ignorePunctuationValue(factory->NewFromASCII("true"));
113    JSHandle<JSTaggedValue> localesString(factory->NewFromASCII("en-Latn-US"));
114
115    JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
116    JSObject::SetProperty(thread, optionsObj, usageKey, usageValue);
117    JSObject::SetProperty(thread, optionsObj, localeMatcherKey, localeMatcherValue);
118    JSObject::SetProperty(thread, optionsObj, caseFirstKey, caseFirstValue);
119    JSObject::SetProperty(thread, optionsObj, sensitivityKey, sensitivityValue);
120    JSObject::SetProperty(thread, optionsObj, ignorePunctuationKey, ignorePunctuationValue);
121    JSObject::SetProperty(thread, optionsObj, numericKey, numericValue);
122
123    std::vector<JSTaggedValue> vals{localesString.GetTaggedValue(), optionsObj.GetTaggedValue()};
124    auto result = CollatorConstructor(thread, vals, newTarget);
125
126    EXPECT_TRUE(result.IsJSCollator());
127}
128
129static JSTaggedValue JSCollatorCreateWithLocaleTest(JSThread *thread, JSHandle<JSTaggedValue> &locale)
130{
131    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
132    JSHandle<JSFunction> newTarget(env->GetCollatorFunction());
133
134    JSHandle<JSTaggedValue> localesString = locale;
135    std::vector<JSTaggedValue> vals{localesString.GetTaggedValue(), JSTaggedValue::Undefined()};
136    auto result = CollatorConstructor(thread, vals, newTarget);
137
138    EXPECT_TRUE(result.IsJSCollator());
139    return result;
140}
141
142static JSTaggedValue JSCollatorCreateWithLocaleAndOptionsTest(JSThread *thread, JSHandle<JSTaggedValue> &locale)
143{
144    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
145    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
146    JSHandle<JSFunction> newTarget(env->GetCollatorFunction());
147    JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
148
149    JSHandle<JSTaggedValue> localesString = locale;
150    JSHandle<JSTaggedValue> usageKey = thread->GlobalConstants()->GetHandledUsageString();
151    JSHandle<JSTaggedValue> sensitivityKey = thread->GlobalConstants()->GetHandledSensitivityString();
152    JSHandle<JSTaggedValue> usageValue(factory->NewFromASCII("search"));
153    JSHandle<JSTaggedValue> sensitivityValue(factory->NewFromASCII("base"));
154
155    JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
156    JSObject::SetProperty(thread, optionsObj, usageKey, usageValue);
157    JSObject::SetProperty(thread, optionsObj, sensitivityKey, sensitivityValue);
158
159    std::vector<JSTaggedValue> vals{localesString.GetTaggedValue(), optionsObj.GetTaggedValue()};
160    auto result = CollatorConstructor(thread, vals, newTarget);
161
162    EXPECT_TRUE(result.IsJSCollator());
163    return result;
164}
165
166// compare with sort(de)
167HWTEST_F_L0(BuiltinsCollatorTest, Compare_001)
168{
169    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
170    JSHandle<JSTaggedValue> locale(factory->NewFromASCII("de"));
171    JSHandle<JSCollator> jsCollator = JSHandle<JSCollator>(thread, JSCollatorCreateWithLocaleTest(thread, locale));
172
173    std::vector<JSTaggedValue> vals{};
174    auto result1 = CollatorAlgorithm(thread, vals, 4, AlgorithmType::COLLATOR_COMPARE, jsCollator.GetTaggedValue());
175    JSHandle<JSFunction> jsFunction(thread, result1);
176
177    JSArray *jsArray =
178        JSArray::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject());
179    JSHandle<JSObject> jsObject(thread, jsArray);
180
181    JSHandle<JSTaggedValue> key0(thread, JSTaggedValue(0));
182    JSHandle<JSTaggedValue> value0(factory->NewFromASCII("Z"));
183    PropertyDescriptor desc0(thread, value0, true, true, true);
184    JSArray::DefineOwnProperty(thread, jsObject, key0, desc0);
185    JSHandle<JSTaggedValue> key1(thread, JSTaggedValue(1));
186    JSHandle<JSTaggedValue> value1(factory->NewFromASCII("a"));
187    PropertyDescriptor desc1(thread, value1, true, true, true);
188    JSArray::DefineOwnProperty(thread, jsObject, key1, desc1);
189    JSHandle<JSTaggedValue> key2(thread, JSTaggedValue(2));
190    JSHandle<JSTaggedValue> value2(factory->NewFromUtf8("ä"));
191    PropertyDescriptor desc2(thread, value2, true, true, true);
192    JSArray::DefineOwnProperty(thread, jsObject, key2, desc2);
193
194    std::vector<JSTaggedValue> arrayVals{jsFunction.GetTaggedValue()};
195    auto result2 = CollatorAlgorithm(thread, arrayVals, 6, AlgorithmType::ARRAY_SORT, jsObject.GetTaggedValue());
196
197    JSHandle<JSTaggedValue> resultArr =
198        JSHandle<JSTaggedValue>(thread, JSTaggedValue(static_cast<JSTaggedType>(result2.GetRawData())));
199    EXPECT_EQ(JSTaggedValue::SameValue(JSArray::GetProperty(thread, resultArr, key0).GetValue(), value1), true);
200    EXPECT_EQ(JSTaggedValue::SameValue(JSArray::GetProperty(thread, resultArr, key1).GetValue(), value2), true);
201    EXPECT_EQ(JSTaggedValue::SameValue(JSArray::GetProperty(thread, resultArr, key2).GetValue(), value0), true);
202}
203
204// // compare with sort(sv)
205HWTEST_F_L0(BuiltinsCollatorTest, Compare_002)
206{
207    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
208    JSHandle<JSTaggedValue> locale(factory->NewFromASCII("sv"));
209    JSHandle<JSCollator> jsCollator = JSHandle<JSCollator>(thread, JSCollatorCreateWithLocaleTest(thread, locale));
210
211    std::vector<JSTaggedValue> vals{};
212    auto result1 = CollatorAlgorithm(thread, vals, 4, AlgorithmType::COLLATOR_COMPARE, jsCollator.GetTaggedValue());
213
214    JSHandle<JSFunction> jsFunction(thread, result1);
215    JSArray *jsArray =
216        JSArray::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject());
217    JSHandle<JSObject> jsObject(thread, jsArray);
218
219    JSHandle<JSTaggedValue> key0(thread, JSTaggedValue(0));
220    JSHandle<JSTaggedValue> value0(factory->NewFromASCII("Z"));
221    PropertyDescriptor desc0(thread, value0, true, true, true);
222    JSArray::DefineOwnProperty(thread, jsObject, key0, desc0);
223    JSHandle<JSTaggedValue> key1(thread, JSTaggedValue(1));
224    JSHandle<JSTaggedValue> value1(factory->NewFromASCII("a"));
225    PropertyDescriptor desc1(thread, value1, true, true, true);
226    JSArray::DefineOwnProperty(thread, jsObject, key1, desc1);
227    JSHandle<JSTaggedValue> key2(thread, JSTaggedValue(2));
228    JSHandle<JSTaggedValue> value2(factory->NewFromUtf8("ä"));
229    PropertyDescriptor desc2(thread, value2, true, true, true);
230    JSArray::DefineOwnProperty(thread, jsObject, key2, desc2);
231
232    std::vector<JSTaggedValue> arrayVals{jsFunction.GetTaggedValue()};
233    auto result2 = CollatorAlgorithm(thread, arrayVals, 6, AlgorithmType::ARRAY_SORT, jsObject.GetTaggedValue());
234    JSHandle<JSObject> resultObj(thread, result2);
235
236    JSHandle<EcmaString> str = thread->GetEcmaVM()->GetFactory()->NewFromUtf8("a,Z,ä");
237    std::vector<JSTaggedValue> arrayVals2{};
238    auto result = CollatorAlgorithm(thread, arrayVals2, 4, AlgorithmType::ARRAY_JOIN, resultObj.GetTaggedValue());
239
240    JSHandle<EcmaString> resultHandle(thread, reinterpret_cast<EcmaString *>(result.GetRawData()));
241    EXPECT_EQ(EcmaStringAccessor::Compare(instance, resultHandle, str), 0);
242}
243
244// compare with options("search")
245HWTEST_F_L0(BuiltinsCollatorTest, Compare_003)
246{
247    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
248    JSHandle<JSTaggedValue> locale(factory->NewFromASCII("sv"));
249    JSHandle<JSCollator> jsCollator =
250        JSHandle<JSCollator>(thread, JSCollatorCreateWithLocaleAndOptionsTest(thread, locale));
251
252    std::vector<JSTaggedValue> vals{};
253    auto result1 = CollatorAlgorithm(thread, vals, 4, AlgorithmType::COLLATOR_COMPARE, jsCollator.GetTaggedValue());
254
255    JSHandle<JSFunction> jsFunction(thread, result1);
256    JSArray *jsArray =
257        JSArray::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue().GetTaggedObject());
258    JSHandle<JSObject> jsObject(thread, jsArray);
259
260    JSHandle<JSTaggedValue> value0(factory->NewFromUtf8("Congrès"));
261    JSHandle<JSTaggedValue> value1(factory->NewFromASCII("congres"));
262    PropertyDescriptor desc(thread, JSHandle<JSTaggedValue>(jsFunction), true, true, true);
263    JSHandle<JSTaggedValue> joinKey(factory->NewFromASCII("join"));
264    JSArray::DefineOwnProperty(thread, jsObject, joinKey, desc);
265
266    std::vector<JSTaggedValue> arrayVals{value0.GetTaggedValue(), value1.GetTaggedValue()};
267    auto result2 = CollatorAlgorithm(thread, arrayVals, 8, AlgorithmType::ARRAY_TOSTR, jsObject.GetTaggedValue());
268
269    JSHandle<JSTaggedValue> resultHandle(thread, result2);
270    EXPECT_EQ(resultHandle->GetInt(), 0); // Congrès and congres is matching
271}
272
273HWTEST_F_L0(BuiltinsCollatorTest, ResolvedOptions)
274{
275    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
276    auto globalConst = thread->GlobalConstants();
277    JSHandle<JSTaggedValue> locale(factory->NewFromASCII("de"));
278    JSHandle<JSCollator> jsCollator = JSHandle<JSCollator>(thread, JSCollatorCreateWithLocaleTest(thread, locale));
279
280    std::vector<JSTaggedValue> vals{};
281    auto result =
282        CollatorAlgorithm(thread, vals, 4, AlgorithmType::COLLATOR_RESOLVED_OPTIONS, jsCollator.GetTaggedValue());
283
284    JSHandle<JSTaggedValue> resultObj =
285        JSHandle<JSTaggedValue>(thread, JSTaggedValue(static_cast<JSTaggedType>(result.GetRawData())));
286    // judge whether the properties of the object are the same as those of jscollator tag
287    JSHandle<JSTaggedValue> localeKey = globalConst->GetHandledLocaleString();
288    EXPECT_EQ(JSTaggedValue::SameValue(JSObject::GetProperty(thread, resultObj, localeKey).GetValue(), locale), true);
289    JSHandle<JSTaggedValue> usageKey = globalConst->GetHandledUsageString();
290    JSHandle<JSTaggedValue> defaultUsageValue(factory->NewFromASCII("sort"));
291    EXPECT_EQ(
292        JSTaggedValue::SameValue(JSObject::GetProperty(thread, resultObj, usageKey).GetValue(), defaultUsageValue),
293        true);
294    JSHandle<JSTaggedValue> handledCaseFirstKey = globalConst->GetHandledCaseFirstString();
295    JSHandle<JSTaggedValue> handledCaseFirstValue(factory->NewFromASCII("upper"));
296    EXPECT_EQ(JSTaggedValue::SameValue(JSObject::GetProperty(thread, resultObj, handledCaseFirstKey).GetValue(),
297                                       handledCaseFirstValue),
298              true);
299}
300
301HWTEST_F_L0(BuiltinsCollatorTest, SupportedLocalesOf)
302{
303    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
304    JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
305    JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
306
307    JSHandle<JSTaggedValue> localeMatcherKey = thread->GlobalConstants()->GetHandledLocaleMatcherString();
308    JSHandle<JSTaggedValue> localeMatcherValue(factory->NewFromASCII("lookup"));
309    JSHandle<JSObject> optionsObj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(objFun), objFun);
310    JSObject::SetProperty(thread, optionsObj, localeMatcherKey, localeMatcherValue);
311    JSHandle<JSTaggedValue> locale(factory->NewFromASCII("id-u-co-pinyin-de-ID"));
312
313    std::vector<JSTaggedValue> vals{locale.GetTaggedValue(), optionsObj.GetTaggedValue()};
314    auto resultArr = CollatorAlgorithm(thread, vals, 8, AlgorithmType::COLLATOR_SUPPORTED_LOCALES_OF);
315
316    JSHandle<JSArray> resultHandle(thread, resultArr);
317    JSHandle<TaggedArray> elements(thread, resultHandle->GetElements());
318    EXPECT_EQ(elements->GetLength(), 1U);
319    JSHandle<EcmaString> handleEcmaStr(thread, elements->Get(0));
320    EXPECT_STREQ("id-u-co-pinyin-de-id", EcmaStringAccessor(handleEcmaStr).ToCString().c_str());
321}
322}  // namespace panda::test
323