blob: 3b715b8cd6f78fd37f36c547f9a315828c0f1b4b [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/navigation/text_fragment_utils.h"
#include <memory>
#include "base/test/scoped_feature_list.h"
#include "ios/web/common/features.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/test_web_state.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// These values correspond to the members that the JavaScript implementation is
// expecting.
const char kPrefixKey[] = "prefix";
const char kTextStartKey[] = "textStart";
const char kTextEndKey[] = "textEnd";
const char kSuffixKey[] = "suffix";
} // namespace
namespace web {
typedef PlatformTest TextFragmentUtilsTest;
TEST_F(TextFragmentUtilsTest, AreTextFragmentsAllowed) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kScrollToTextIOS);
std::unique_ptr<TestWebState> web_state = std::make_unique<TestWebState>();
TestWebState* web_state_ptr = web_state.get();
FakeNavigationContext context;
context.SetWebState(std::move(web_state));
// Working case: no opener, has user gesture, not same document
web_state_ptr->SetHasOpener(false);
context.SetHasUserGesture(true);
context.SetIsSameDocument(false);
EXPECT_TRUE(AreTextFragmentsAllowed(&context));
// Blocking case #1: WebState has an opener
web_state_ptr->SetHasOpener(true);
context.SetHasUserGesture(true);
context.SetIsSameDocument(false);
EXPECT_FALSE(AreTextFragmentsAllowed(&context));
// Blocking case #2: No user gesture
web_state_ptr->SetHasOpener(false);
context.SetHasUserGesture(false);
context.SetIsSameDocument(false);
EXPECT_FALSE(AreTextFragmentsAllowed(&context));
// Blocking case #3: Same-document navigation
web_state_ptr->SetHasOpener(false);
context.SetHasUserGesture(true);
context.SetIsSameDocument(true);
EXPECT_FALSE(AreTextFragmentsAllowed(&context));
}
TEST_F(TextFragmentUtilsTest, ParseTextFragments) {
GURL url_with_fragment(
"https://www.example.com/#idFrag:~:text=text%201&text=text%202");
std::vector<base::Value> result =
internal::ParseTextFragments(url_with_fragment);
ASSERT_EQ(2u, result.size());
EXPECT_EQ("text%201", result[0].FindKey(kTextStartKey)->GetString());
EXPECT_EQ("text%202", result[1].FindKey(kTextStartKey)->GetString());
GURL url_no_fragment("www.example.com");
std::vector<base::Value> empty_result =
internal::ParseTextFragments(url_no_fragment);
EXPECT_TRUE(empty_result.empty());
}
TEST_F(TextFragmentUtilsTest, ExtractTextFragments) {
std::vector<std::string> expected = {"test1", "test2", "test3"};
// Ensure presence/absence of a trailing & doesn't break anything
EXPECT_EQ(expected, internal::ExtractTextFragments(
"#id:~:text=test1&text=test2&text=test3"));
EXPECT_EQ(expected, internal::ExtractTextFragments(
"#id:~:text=test1&text=test2&text=test3&"));
// Test that empty tokens (&& or &text=&) are discarded
EXPECT_EQ(expected, internal::ExtractTextFragments(
"#id:~:text=test1&&text=test2&text=&text=test3"));
expected = {};
EXPECT_EQ(expected,
internal::ExtractTextFragments("#idButNoTextFragmentsHere"));
EXPECT_EQ(expected, internal::ExtractTextFragments(""));
}
TEST_F(TextFragmentUtilsTest, TextFragmentToValue) {
// Success cases
std::string fragment = "start";
base::Value result = internal::TextFragmentToValue(fragment);
EXPECT_FALSE(result.FindKey(kPrefixKey));
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_FALSE(result.FindKey(kTextEndKey));
EXPECT_FALSE(result.FindKey(kSuffixKey));
fragment = "start,end";
result = internal::TextFragmentToValue(fragment);
EXPECT_FALSE(result.FindKey(kPrefixKey));
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_EQ("end", result.FindKey(kTextEndKey)->GetString());
EXPECT_FALSE(result.FindKey(kSuffixKey));
fragment = "prefix-,start";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ("prefix", result.FindKey(kPrefixKey)->GetString());
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_FALSE(result.FindKey(kTextEndKey));
EXPECT_FALSE(result.FindKey(kSuffixKey));
fragment = "start,-suffix";
result = internal::TextFragmentToValue(fragment);
EXPECT_FALSE(result.FindKey(kPrefixKey));
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_FALSE(result.FindKey(kTextEndKey));
EXPECT_EQ("suffix", result.FindKey(kSuffixKey)->GetString());
fragment = "prefix-,start,end";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ("prefix", result.FindKey(kPrefixKey)->GetString());
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_EQ("end", result.FindKey(kTextEndKey)->GetString());
EXPECT_FALSE(result.FindKey(kSuffixKey));
fragment = "start,end,-suffix";
result = internal::TextFragmentToValue(fragment);
EXPECT_FALSE(result.FindKey(kPrefixKey));
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_EQ("end", result.FindKey(kTextEndKey)->GetString());
EXPECT_EQ("suffix", result.FindKey(kSuffixKey)->GetString());
fragment = "prefix-,start,end,-suffix";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ("prefix", result.FindKey(kPrefixKey)->GetString());
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_EQ("end", result.FindKey(kTextEndKey)->GetString());
EXPECT_EQ("suffix", result.FindKey(kSuffixKey)->GetString());
// Trailing comma doesn't break otherwise valid fragment
fragment = "start,";
result = internal::TextFragmentToValue(fragment);
EXPECT_FALSE(result.FindKey(kPrefixKey));
EXPECT_EQ("start", result.FindKey(kTextStartKey)->GetString());
EXPECT_FALSE(result.FindKey(kTextEndKey));
EXPECT_FALSE(result.FindKey(kSuffixKey));
// Failure Cases
fragment = "";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ(base::Value::Type::NONE, result.type());
fragment = "some,really-,malformed,-thing,with,too,many,commas";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ(base::Value::Type::NONE, result.type());
fragment = "prefix-,-suffix";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ(base::Value::Type::NONE, result.type());
fragment = "start,prefix-,-suffix";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ(base::Value::Type::NONE, result.type());
fragment = "prefix-,-suffix,start";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ(base::Value::Type::NONE, result.type());
fragment = "prefix-";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ(base::Value::Type::NONE, result.type());
fragment = "-suffix";
result = internal::TextFragmentToValue(fragment);
EXPECT_EQ(base::Value::Type::NONE, result.type());
}
} // namespace web