Add unit tests for CPDF_FormField::IsItemSelected()

Change CPDF_FormField to allow initialization with a null
CPDF_InteractiveForm.

Bug: pdfium:1505
Change-Id: I168783c333870fdedb6931daf18e7e6d9da3b96e
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/68810
Commit-Queue: Daniel Hosseinian <dhoss@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfdoc/BUILD.gn b/core/fpdfdoc/BUILD.gn
index f25ace9..caf53f9 100644
--- a/core/fpdfdoc/BUILD.gn
+++ b/core/fpdfdoc/BUILD.gn
@@ -106,7 +106,10 @@
   ]
   deps = [
     ":fpdfdoc",
+    "../../constants",
+    "../fpdfapi/page",
     "../fpdfapi/parser",
+    "../fpdfapi/render",
   ]
   pdfium_root_dir = "../../"
 }
diff --git a/core/fpdfdoc/cpdf_formfield_unittest.cpp b/core/fpdfdoc/cpdf_formfield_unittest.cpp
index ca75891..2174efb 100644
--- a/core/fpdfdoc/cpdf_formfield_unittest.cpp
+++ b/core/fpdfdoc/cpdf_formfield_unittest.cpp
@@ -4,13 +4,73 @@
 
 #include "core/fpdfdoc/cpdf_formfield.h"
 
+#include <vector>
+
+#include "constants/form_fields.h"
+#include "constants/form_flags.h"
+#include "core/fpdfapi/page/cpdf_docpagedata.h"
+#include "core/fpdfapi/page/cpdf_pagemodule.h"
+#include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
+#include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfapi/parser/cpdf_indirect_object_holder.h"
 #include "core/fpdfapi/parser/cpdf_name.h"
+#include "core/fpdfapi/parser/cpdf_number.h"
 #include "core/fpdfapi/parser/cpdf_reference.h"
+#include "core/fpdfapi/parser/cpdf_string.h"
+#include "core/fpdfapi/render/cpdf_docrenderdata.h"
+#include "core/fpdfdoc/cpdf_interactiveform.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/base/stl_util.h"
 
-TEST(cpdf_formfield, GetFullNameForDict) {
+namespace {
+
+// Create and destroys the page module that is necessary when instantiating a
+// CPDF_Document.
+class ScopedCPDF_PageModule {
+ public:
+  ScopedCPDF_PageModule() { CPDF_PageModule::Create(); }
+  ~ScopedCPDF_PageModule() { CPDF_PageModule::Destroy(); }
+};
+
+class CPDF_TestEmptyDocument final : public CPDF_Document {
+ public:
+  CPDF_TestEmptyDocument()
+      : CPDF_Document(pdfium::MakeUnique<CPDF_DocRenderData>(),
+                      pdfium::MakeUnique<CPDF_DocPageData>()) {}
+};
+
+void TestMultiselectFieldDict(RetainPtr<CPDF_Array> opt_array,
+                              RetainPtr<CPDF_Object> values,
+                              RetainPtr<CPDF_Object> selected_indices,
+                              const std::vector<int>& expected_indices,
+                              const std::vector<int>& excluded_indices) {
+  auto form_dict = pdfium::MakeRetain<CPDF_Dictionary>();
+  form_dict->SetNewFor<CPDF_Name>("Type", "Annot");
+  form_dict->SetNewFor<CPDF_Name>("Subtype", "Widget");
+  form_dict->SetNewFor<CPDF_Name>(pdfium::form_fields::kFT,
+                                  pdfium::form_fields::kCh);
+  constexpr int kMuliSelectFlag = pdfium::form_flags::kChoiceMultiSelect;
+  form_dict->SetNewFor<CPDF_Number>(pdfium::form_fields::kFf, kMuliSelectFlag);
+  form_dict->SetFor("Opt", opt_array);
+  form_dict->SetFor(pdfium::form_fields::kV, values);
+  form_dict->SetFor("I", selected_indices);
+
+  CPDF_TestEmptyDocument doc;
+  CPDF_InteractiveForm form(&doc);
+  CPDF_FormField form_field(&form, form_dict.Get());
+  for (int i = 0; i < form_field.CountOptions(); i++) {
+    const bool expected_selected = pdfium::ContainsValue(expected_indices, i);
+    EXPECT_EQ(expected_selected, form_field.IsItemSelected(i));
+  }
+  for (int i : excluded_indices) {
+    EXPECT_FALSE(form_field.IsItemSelected(i));
+  }
+}
+
+}  // namespace
+
+TEST(CPDF_FormFieldTest, GetFullNameForDict) {
   WideString name = CPDF_FormField::GetFullNameForDict(nullptr);
   EXPECT_TRUE(name.IsEmpty());
 
@@ -47,3 +107,220 @@
   name = CPDF_FormField::GetFullNameForDict(dict3);
   EXPECT_STREQ("bar.foo.qux", name.ToUTF8().c_str());
 }
+
+TEST(CPDF_FormFieldTest, IsItemSelected) {
+  ScopedCPDF_PageModule page_module;
+
+  auto opt_array = pdfium::MakeRetain<CPDF_Array>();
+  opt_array->AppendNew<CPDF_String>(L"Alpha");
+  opt_array->AppendNew<CPDF_String>(L"Beta");
+  opt_array->AppendNew<CPDF_String>(L"Gamma");
+  opt_array->AppendNew<CPDF_String>(L"Delta");
+  opt_array->AppendNew<CPDF_String>(L"Epsilon");
+
+  {
+    // No Values (/V) or Selected Indices (/I) objects.
+    std::vector<int> expected_indices;
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, /*values=*/nullptr,
+                             /*selected_indices=*/nullptr, expected_indices,
+                             excluded_indices);
+  }
+  {
+    // Values (/V) object is just a string.
+    auto values = pdfium::MakeRetain<CPDF_String>(/*pPool=*/nullptr, L"Gamma");
+    std::vector<int> expected_indices{2};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) object is just an invalid string.
+    auto values = pdfium::MakeRetain<CPDF_String>(/*pPool=*/nullptr, L"Omega");
+    std::vector<int> expected_indices;
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) object is an array with one object.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Beta");
+    // TODO(bug_1505): Should be selected at index 1.
+    std::vector<int> expected_indices;
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) object is an array with one invalid object.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Omega");
+    std::vector<int> expected_indices;
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) object is an array with multiple objects.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Beta");
+    values->AppendNew<CPDF_String>(L"Epsilon");
+    // TODO(bug_1505): Should be selected at index 1 and index 4.
+    std::vector<int> expected_indices;
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) object is an array with multiple objects with one invalid.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Beta");
+    values->AppendNew<CPDF_String>(L"Epsilon");
+    values->AppendNew<CPDF_String>(L"Omega");
+    // TODO(bug_1505): Should be selected at index 1 and index 4.
+    std::vector<int> expected_indices;
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, /*selected_indices=*/nullptr,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Selected indices (/I) object is just a number.
+    auto selected_indices = pdfium::MakeRetain<CPDF_Number>(3);
+    std::vector<int> expected_indices{3};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Selected indices (/I) object is just an invalid number.
+    auto selected_indices = pdfium::MakeRetain<CPDF_Number>(26);
+    std::vector<int> expected_indices;
+    std::vector<int> excluded_indices{-1, 5, 26};
+    TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Selected indices (/I) object is an array with one object.
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(0);
+    std::vector<int> expected_indices{0};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Selected indices (/I) object is an array with multiple objects.
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(0);
+    selected_indices->AppendNew<CPDF_Number>(2);
+    selected_indices->AppendNew<CPDF_Number>(3);
+    std::vector<int> expected_indices{0, 2, 3};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Selected indices (/I) object is an array with multiple objects and some
+    // are invalid.
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(0);
+    selected_indices->AppendNew<CPDF_Number>(2);
+    selected_indices->AppendNew<CPDF_Number>(3);
+    selected_indices->AppendNew<CPDF_Number>(-5);
+    selected_indices->AppendNew<CPDF_Number>(12);
+    selected_indices->AppendNew<CPDF_Number>(42);
+    std::vector<int> expected_indices{0, 2, 3};
+    std::vector<int> excluded_indices{-5, -1, 5, 12, 42};
+    TestMultiselectFieldDict(opt_array, /*values=*/nullptr, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) or Selected Indices (/I) objects conflict with different
+    // lengths.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Beta");
+    values->AppendNew<CPDF_String>(L"Epsilon");
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(0);
+    selected_indices->AppendNew<CPDF_Number>(2);
+    selected_indices->AppendNew<CPDF_Number>(3);
+    // TODO(bug_1505): Should be selected at index 1 and index 4.
+    std::vector<int> expected_indices{0, 2, 3};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) or Selected Indices (/I) objects conflict with same lengths.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Alpha");
+    values->AppendNew<CPDF_String>(L"Epsilon");
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(2);
+    selected_indices->AppendNew<CPDF_Number>(3);
+    // TODO(bug_1505): Should be selected at index 0 and index 4.
+    std::vector<int> expected_indices{2, 3};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) or Selected Indices (/I) objects conflict with values being
+    // invalid.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Beta");
+    values->AppendNew<CPDF_String>(L"Epsilon");
+    values->AppendNew<CPDF_String>(L"Omega");
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(1);
+    selected_indices->AppendNew<CPDF_Number>(4);
+    std::vector<int> expected_indices{1, 4};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) or Selected Indices (/I) objects conflict with selected
+    // indices being invalid.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Beta");
+    values->AppendNew<CPDF_String>(L"Epsilon");
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(1);
+    selected_indices->AppendNew<CPDF_Number>(4);
+    selected_indices->AppendNew<CPDF_Number>(26);
+    std::vector<int> expected_indices{1, 4};
+    std::vector<int> excluded_indices{-1, 5, 26};
+    TestMultiselectFieldDict(opt_array, values, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) or Selected Indices (/I) objects conflict with both being
+    // invalid.
+    auto values = pdfium::MakeRetain<CPDF_Array>();
+    values->AppendNew<CPDF_String>(L"Beta");
+    values->AppendNew<CPDF_String>(L"Epsilon");
+    values->AppendNew<CPDF_String>(L"Omega");
+    auto selected_indices = pdfium::MakeRetain<CPDF_Array>();
+    selected_indices->AppendNew<CPDF_Number>(0);
+    selected_indices->AppendNew<CPDF_Number>(2);
+    selected_indices->AppendNew<CPDF_Number>(3);
+    selected_indices->AppendNew<CPDF_Number>(26);
+    // TODO(bug_1505): Should be selected at index 1 and index 4.
+    std::vector<int> expected_indices{0, 2, 3};
+    std::vector<int> excluded_indices{-1, 5, 26};
+    TestMultiselectFieldDict(opt_array, values, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+  {
+    // Values (/V) or Selected Indices (/I) objects conflict with each not being
+    // an array.
+    auto values = pdfium::MakeRetain<CPDF_String>(/*pPool=*/nullptr, L"Gamma");
+    auto selected_indices = pdfium::MakeRetain<CPDF_Number>(4);
+    std::vector<int> expected_indices{2};
+    std::vector<int> excluded_indices{-1, 5};
+    TestMultiselectFieldDict(opt_array, values, selected_indices,
+                             expected_indices, excluded_indices);
+  }
+}