text: // Copyright 2018 PDFium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "fxjs/cfx_v8.h"
  
  #include <math.h>
  
  #include <memory>
  
  #include "testing/fxv8_unittest.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "v8/include/v8-container.h"
  #include "v8/include/v8-context.h"
  #include "v8/include/v8-date.h"
  #include "v8/include/v8-isolate.h"
  
  namespace {
  bool getter_sentinel = false;
  bool setter_sentinel = false;
  }  // namespace
  
  class CFXV8UnitTest : public FXV8UnitTest {
   public:
    CFXV8UnitTest() = default;
    ~CFXV8UnitTest() override = default;
  
    // FXV8UnitTest:
    void SetUp() override {
      FXV8UnitTest::SetUp();
      cfx_v8_ = std::make_unique<CFX_V8>(isolate());
    }
  
    CFX_V8* cfx_v8() const { return cfx_v8_.get(); }
  
   protected:
    std::unique_ptr<CFX_V8> cfx_v8_;
  };
  
  TEST_F(CFXV8UnitTest, EmptyLocal) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    v8::Local<v8::Value> empty;
    EXPECT_FALSE(cfx_v8()->ToBoolean(empty));
    EXPECT_EQ(0, cfx_v8()->ToInt32(empty));
    EXPECT_EQ(0.0, cfx_v8()->ToDouble(empty));
    EXPECT_EQ("", cfx_v8()->ToByteString(empty));
    EXPECT_EQ(L"", cfx_v8()->ToWideString(empty));
    EXPECT_TRUE(cfx_v8()->ToObject(empty).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(empty).IsEmpty());
  
    // Can't set properties on empty objects, but does not fault.
    v8::Local<v8::Value> marker = cfx_v8()->NewNumber(2);
    v8::Local<v8::Object> empty_object;
    cfx_v8()->PutObjectProperty(empty_object, "clams", marker);
    EXPECT_TRUE(cfx_v8()->GetObjectProperty(empty_object, "clams").IsEmpty());
    EXPECT_EQ(0u, cfx_v8()->GetObjectPropertyNames(empty_object).size());
  
    // Can't set elements in empty arrays, but does not fault.
    v8::Local<v8::Array> empty_array;
    cfx_v8()->PutArrayElement(empty_array, 0, marker);
    EXPECT_TRUE(cfx_v8()->GetArrayElement(empty_array, 0).IsEmpty());
    EXPECT_EQ(0u, cfx_v8()->GetArrayLength(empty_array));
  }
  
  TEST_F(CFXV8UnitTest, NewNull) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto nullz = cfx_v8()->NewNull();
    EXPECT_FALSE(cfx_v8()->ToBoolean(nullz));
    EXPECT_EQ(0, cfx_v8()->ToInt32(nullz));
    EXPECT_EQ(0.0, cfx_v8()->ToDouble(nullz));
    EXPECT_EQ("null", cfx_v8()->ToByteString(nullz));
    EXPECT_EQ(L"null", cfx_v8()->ToWideString(nullz));
    EXPECT_TRUE(cfx_v8()->ToObject(nullz).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(nullz).IsEmpty());
  }
  
  TEST_F(CFXV8UnitTest, NewUndefined) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto undef = cfx_v8()->NewUndefined();
    EXPECT_FALSE(cfx_v8()->ToBoolean(undef));
    EXPECT_EQ(0, cfx_v8()->ToInt32(undef));
    EXPECT_TRUE(isnan(cfx_v8()->ToDouble(undef)));
    EXPECT_EQ("undefined", cfx_v8()->ToByteString(undef));
    EXPECT_EQ(L"undefined", cfx_v8()->ToWideString(undef));
    EXPECT_TRUE(cfx_v8()->ToObject(undef).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(undef).IsEmpty());
  }
  
  TEST_F(CFXV8UnitTest, NewBoolean) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto boolz = cfx_v8()->NewBoolean(true);
    EXPECT_TRUE(cfx_v8()->ToBoolean(boolz));
    EXPECT_EQ(1, cfx_v8()->ToInt32(boolz));
    EXPECT_EQ(1.0, cfx_v8()->ToDouble(boolz));
    EXPECT_EQ("true", cfx_v8()->ToByteString(boolz));
    EXPECT_EQ(L"true", cfx_v8()->ToWideString(boolz));
    EXPECT_TRUE(cfx_v8()->ToObject(boolz).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(boolz).IsEmpty());
  
    boolz = cfx_v8()->NewBoolean(false);
    EXPECT_FALSE(cfx_v8()->ToBoolean(boolz));
    EXPECT_EQ(0, cfx_v8()->ToInt32(boolz));
    EXPECT_EQ(0.0, cfx_v8()->ToDouble(boolz));
    EXPECT_EQ("false", cfx_v8()->ToByteString(boolz));
    EXPECT_EQ(L"false", cfx_v8()->ToWideString(boolz));
    EXPECT_TRUE(cfx_v8()->ToObject(boolz).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(boolz).IsEmpty());
  }
  
  TEST_F(CFXV8UnitTest, NewNumber) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto num = cfx_v8()->NewNumber(42.1);
    EXPECT_TRUE(cfx_v8()->ToBoolean(num));
    EXPECT_EQ(42, cfx_v8()->ToInt32(num));
    EXPECT_EQ(42.1, cfx_v8()->ToDouble(num));
    EXPECT_EQ("42.1", cfx_v8()->ToByteString(num));
    EXPECT_EQ(L"42.1", cfx_v8()->ToWideString(num));
    EXPECT_TRUE(cfx_v8()->ToObject(num).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(num).IsEmpty());
  }
  
  TEST_F(CFXV8UnitTest, NewString) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto str = cfx_v8()->NewString("123");
    EXPECT_TRUE(cfx_v8()->ToBoolean(str));
    EXPECT_EQ(123, cfx_v8()->ToInt32(str));
    EXPECT_EQ(123, cfx_v8()->ToDouble(str));
    EXPECT_EQ("123", cfx_v8()->ToByteString(str));
    EXPECT_EQ(L"123", cfx_v8()->ToWideString(str));
    EXPECT_TRUE(cfx_v8()->ToObject(str).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(str).IsEmpty());
  
    auto str2 = cfx_v8()->NewString(L"123");
    EXPECT_TRUE(cfx_v8()->ToBoolean(str2));
    EXPECT_EQ(123, cfx_v8()->ToInt32(str2));
    EXPECT_EQ(123, cfx_v8()->ToDouble(str2));
    EXPECT_EQ("123", cfx_v8()->ToByteString(str2));
    EXPECT_EQ(L"123", cfx_v8()->ToWideString(str2));
    EXPECT_TRUE(cfx_v8()->ToObject(str2).IsEmpty());
    EXPECT_TRUE(cfx_v8()->ToArray(str2).IsEmpty());
  }
  
  TEST_F(CFXV8UnitTest, NewDate) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto date = cfx_v8()->NewDate(1111111111);
    EXPECT_TRUE(cfx_v8()->ToBoolean(date));
    EXPECT_EQ(1111111111, cfx_v8()->ToInt32(date));
    EXPECT_EQ(1111111111.0, cfx_v8()->ToDouble(date));
    EXPECT_NE("", cfx_v8()->ToByteString(date));   // exact format varies.
    EXPECT_NE(L"", cfx_v8()->ToWideString(date));  // exact format varies.
    EXPECT_TRUE(cfx_v8()->ToObject(date)->IsObject());
    EXPECT_TRUE(cfx_v8()->ToArray(date).IsEmpty());
  }
  
  TEST_F(CFXV8UnitTest, NewArray) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto array = cfx_v8()->NewArray();
    EXPECT_EQ(0u, cfx_v8()->GetArrayLength(array));
    EXPECT_FALSE(cfx_v8()->GetArrayElement(array, 2).IsEmpty());
    EXPECT_TRUE(cfx_v8()->GetArrayElement(array, 2)->IsUndefined());
    EXPECT_EQ(0u, cfx_v8()->GetArrayLength(array));
  
    cfx_v8()->PutArrayElement(array, 3, cfx_v8()->NewNumber(12));
    EXPECT_FALSE(cfx_v8()->GetArrayElement(array, 2).IsEmpty());
    EXPECT_TRUE(cfx_v8()->GetArrayElement(array, 2)->IsUndefined());
    EXPECT_FALSE(cfx_v8()->GetArrayElement(array, 3).IsEmpty());
    EXPECT_TRUE(cfx_v8()->GetArrayElement(array, 3)->IsNumber());
    EXPECT_EQ(4u, cfx_v8()->GetArrayLength(array));
  
    EXPECT_TRUE(cfx_v8()->ToBoolean(array));
    EXPECT_EQ(0, cfx_v8()->ToInt32(array));
    double d = cfx_v8()->ToDouble(array);
    EXPECT_NE(d, d);  // i.e. NaN.
    EXPECT_EQ(L",,,12", cfx_v8()->ToWideString(array));
    EXPECT_TRUE(cfx_v8()->ToObject(array)->IsObject());
    EXPECT_TRUE(cfx_v8()->ToArray(array)->IsArray());
  }
  
  TEST_F(CFXV8UnitTest, NewObject) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Context::Scope context_scope(v8::Context::New(isolate()));
  
    auto object = cfx_v8()->NewObject();
    ASSERT_FALSE(object.IsEmpty());
    EXPECT_EQ(0u, cfx_v8()->GetObjectPropertyNames(object).size());
    EXPECT_FALSE(cfx_v8()->GetObjectProperty(object, "clams").IsEmpty());
    EXPECT_TRUE(cfx_v8()->GetObjectProperty(object, "clams")->IsUndefined());
    EXPECT_EQ(0u, cfx_v8()->GetObjectPropertyNames(object).size());
  
    cfx_v8()->PutObjectProperty(object, "clams", cfx_v8()->NewNumber(12));
    EXPECT_FALSE(cfx_v8()->GetObjectProperty(object, "clams").IsEmpty());
    EXPECT_TRUE(cfx_v8()->GetObjectProperty(object, "clams")->IsNumber());
    EXPECT_EQ(1u, cfx_v8()->GetObjectPropertyNames(object).size());
    EXPECT_EQ(L"clams", cfx_v8()->GetObjectPropertyNames(object)[0]);
  
    EXPECT_TRUE(cfx_v8()->ToBoolean(object));
    EXPECT_EQ(0, cfx_v8()->ToInt32(object));
    double d = cfx_v8()->ToDouble(object);
    EXPECT_NE(d, d);  // i.e. NaN.
    EXPECT_EQ(L"[object Object]", cfx_v8()->ToWideString(object));
    EXPECT_TRUE(cfx_v8()->ToObject(object)->IsObject());
    EXPECT_TRUE(cfx_v8()->ToArray(object).IsEmpty());
  }
  
  TEST_F(CFXV8UnitTest, ThrowFromGetter) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Local<v8::Context> context = v8::Context::New(isolate());
    v8::Context::Scope context_scope(context);
  
    v8::Local<v8::Object> object = cfx_v8()->NewObject();
    v8::Local<v8::String> name = cfx_v8()->NewString("clams");
    EXPECT_TRUE(
        object
            ->SetAccessor(context, name,
                          [](v8::Local<v8::Name> property,
                             const v8::PropertyCallbackInfo<v8::Value>& info) {
                            getter_sentinel = true;
                            info.GetIsolate()->ThrowException(property);
                          })
            .FromJust());
    getter_sentinel = false;
    EXPECT_TRUE(cfx_v8()->GetObjectProperty(object, "clams").IsEmpty());
    EXPECT_TRUE(getter_sentinel);
  }
  
  TEST_F(CFXV8UnitTest, ThrowFromSetter) {
    v8::Isolate::Scope isolate_scope(isolate());
    v8::HandleScope handle_scope(isolate());
    v8::Local<v8::Context> context = v8::Context::New(isolate());
    v8::Context::Scope context_scope(context);
  
    v8::Local<v8::Object> object = cfx_v8()->NewObject();
    v8::Local<v8::String> name = cfx_v8()->NewString("clams");
    EXPECT_TRUE(object
                    ->SetAccessor(context, name, nullptr,
                                  [](v8::Local<v8::Name> property,
                                     v8::Local<v8::Value> value,
                                     const v8::PropertyCallbackInfo<void>& info) {
                                    setter_sentinel = true;
                                    info.GetIsolate()->ThrowException(property);
                                  })
                    .FromJust());
    setter_sentinel = false;
    cfx_v8()->PutObjectProperty(object, "clams", name);
    EXPECT_TRUE(setter_sentinel);
  }
  