// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/common/string_util.h"
#import <UIKit/UIKit.h>
#import "base/ios/ns_range.h"
#import "base/test/gtest_util.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/common/ui/util/text_view_util.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
namespace {
using StringUtilTest = PlatformTest;
TEST_F(StringUtilTest, AttributedStringFromStringWithLink) {
struct TestCase {
NSString* input;
NSDictionary* textAttributes;
NSDictionary* linkAttributes;
NSString* expectedString;
NSRange expectedTextRange;
NSRange expectedLinkRange;
};
const TestCase kAllTestCases[] = {
TestCase{@"Text with valid BEGIN_LINK link END_LINK and spaces.", @{},
@{NSLinkAttributeName : @"google.com"},
@"Text with valid link and spaces.", NSRange{0, 16},
NSRange{16, 4}},
TestCase{
@"Text with valid BEGIN_LINK link END_LINK and spaces.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{}, @"Text with valid link and spaces.", NSRange{0, 32},
NSRange{0, 32}},
TestCase{
@"Text with valid BEGIN_LINK link END_LINK and spaces.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{NSLinkAttributeName : @"google.com"},
@"Text with valid link and spaces.",
NSRange{0, 16},
NSRange{16, 4},
},
TestCase{
@"Text with valid BEGIN_LINKlinkEND_LINK and no spaces.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{NSLinkAttributeName : @"google.com"},
@"Text with valid link and no spaces.",
NSRange{0, 16},
NSRange{16, 4},
},
};
for (const TestCase& test_case : kAllTestCases) {
const NSAttributedString* result = AttributedStringFromStringWithLink(
test_case.input, test_case.textAttributes, test_case.linkAttributes);
EXPECT_NSEQ(result.string, test_case.expectedString);
// Text at index 0 has text attributes applied until the link location.
NSRange textRange;
NSDictionary* resultTextAttributes = [result attributesAtIndex:0
effectiveRange:&textRange];
EXPECT_TRUE(NSEqualRanges(test_case.expectedTextRange, textRange));
EXPECT_NSEQ(test_case.textAttributes, resultTextAttributes);
// Text at index `expectedRange.location` has link attributes applied.
NSRange linkRange;
NSDictionary* resultLinkAttributes =
[result attributesAtIndex:test_case.expectedLinkRange.location
effectiveRange:&linkRange];
EXPECT_TRUE(NSEqualRanges(test_case.expectedLinkRange, linkRange));
NSMutableDictionary* combinedAttributes =
[[NSMutableDictionary alloc] init];
[combinedAttributes addEntriesFromDictionary:test_case.textAttributes];
[combinedAttributes addEntriesFromDictionary:test_case.linkAttributes];
EXPECT_NSEQ(combinedAttributes, resultLinkAttributes);
}
}
TEST_F(StringUtilTest, AttributedStringFromStringWithLinkFailures) {
struct TestCase {
NSString* input;
NSDictionary* textAttributes;
NSDictionary* linkAttributes;
};
const TestCase kAllTestCases[] = {
TestCase{
@"Text without link.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{NSLinkAttributeName : @"google.com"},
},
TestCase{
@"Text with BEGIN_LINK and no end link.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{NSLinkAttributeName : @"google.com"},
},
TestCase{
@"Text with no begin link and END_LINK.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{NSLinkAttributeName : @"google.com"},
},
TestCase{
@"Text with END_LINK before BEGIN_LINK.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{NSLinkAttributeName : @"google.com"},
},
};
for (const TestCase& test_case : kAllTestCases) {
EXPECT_CHECK_DEATH(AttributedStringFromStringWithLink(
test_case.input, test_case.textAttributes, test_case.linkAttributes));
}
}
TEST_F(StringUtilTest, AttributedStringFromStringWithLinkWithEmptyLink) {
struct TestCase {
NSString* input;
NSDictionary* textAttributes;
NSDictionary* linkAttributes;
NSString* expectedString;
};
const TestCase test_case = TestCase {
@"Text with empty link BEGIN_LINK END_LINK.",
@{NSForegroundColorAttributeName : [UIColor colorNamed:kBlueColor]},
@{NSLinkAttributeName : @"google.com"}, @"Text with empty link .",
};
const NSAttributedString* result = AttributedStringFromStringWithLink(
test_case.input, test_case.textAttributes, test_case.linkAttributes);
EXPECT_NSEQ(result.string, test_case.expectedString);
// Text attributes apply to the full range of the result string.
NSRange textRange;
NSDictionary* resultTextAttributes = [result attributesAtIndex:0
effectiveRange:&textRange];
EXPECT_TRUE(NSEqualRanges(NSMakeRange(0, test_case.expectedString.length),
textRange));
EXPECT_NSEQ(test_case.textAttributes, resultTextAttributes);
}
TEST_F(StringUtilTest, ParseStringWithLinks) {
struct TestCase {
NSString* input;
StringWithTags expected;
};
const TestCase kAllTestCases[] = {
TestCase{
@"Text without link.",
StringWithTags{
@"Text without link.",
{},
},
},
TestCase{
@"Text with empty link BEGIN_LINK END_LINK.",
StringWithTags{
@"Text with empty link .",
{NSRange{21, 0}},
},
},
TestCase{
@"Text with BEGIN_LINK and no end link.",
StringWithTags{
@"Text with BEGIN_LINK and no end link.",
{},
},
},
TestCase{
@"Text with no begin link and END_LINK.",
StringWithTags{
@"Text with no begin link and END_LINK.",
{},
},
},
TestCase{@"Text with END_LINK before BEGIN_LINK.",
StringWithTags{
@"Text with END_LINK before BEGIN_LINK.",
{},
}},
TestCase{
@"Text with valid BEGIN_LINK link END_LINK and spaces.",
StringWithTags{
@"Text with valid link and spaces.",
{NSRange{16, 4}},
},
},
TestCase{
@"Text with valid BEGIN_LINKlinkEND_LINK and no spaces.",
StringWithTags{
@"Text with valid link and no spaces.",
{NSRange{16, 4}},
},
},
TestCase{
@"Text with multiple tags: BEGIN_LINKlink1END_LINK, "
@"BEGIN_LINKlink2END_LINK and BEGIN_LINKlink3END_LINK.",
StringWithTags{
@"Text with multiple tags: link1, link2 and link3.",
{NSRange{25, 5}, NSRange{32, 5}, NSRange{42, 5}},
},
},
};
for (const TestCase& test_case : kAllTestCases) {
const StringWithTags result = ParseStringWithLinks(test_case.input);
EXPECT_NSEQ(result.string, test_case.expected.string);
ASSERT_EQ(result.ranges.size(), test_case.expected.ranges.size());
for (size_t i = 0; i < test_case.expected.ranges.size(); i++) {
EXPECT_EQ(result.ranges[i], test_case.expected.ranges[i]);
}
}
}
TEST_F(StringUtilTest, ParseStringWithTag) {
struct TestCase {
NSString* input;
StringWithTag expected;
};
const TestCase kAllTestCases[] = {
TestCase{
@"Text without tag.",
StringWithTag{
@"Text without tag.",
NSRange{NSNotFound, 0},
},
},
TestCase{
@"Text with empty tag BEGIN_TAG END_TAG.",
StringWithTag{
@"Text with empty tag .",
NSRange{20, 1},
},
},
TestCase{
@"Text with BEGIN_TAG and no end tag.",
StringWithTag{
@"Text with BEGIN_TAG and no end tag.",
NSRange{NSNotFound, 0},
},
},
TestCase{
@"Text with no begin tag and END_TAG.",
StringWithTag{
@"Text with no begin tag and END_TAG.",
NSRange{NSNotFound, 0},
},
},
TestCase{@"Text with END_TAG before BEGIN_TAG.",
StringWithTag{
@"Text with END_TAG before BEGIN_TAG.",
NSRange{NSNotFound, 0},
}},
TestCase{
@"Text with valid BEGIN_TAG tag END_TAG and spaces.",
StringWithTag{
@"Text with valid tag and spaces.",
NSRange{16, 5},
},
},
TestCase{
@"Text with valid BEGIN_TAGtagEND_TAG and no spaces.",
StringWithTag{
@"Text with valid tag and no spaces.",
NSRange{16, 3},
},
},
};
for (const TestCase& test_case : kAllTestCases) {
const StringWithTag result =
ParseStringWithTag(test_case.input, @"BEGIN_TAG", @"END_TAG");
EXPECT_NSEQ(result.string, test_case.expected.string);
EXPECT_EQ(result.range, test_case.expected.range);
}
}
TEST_F(StringUtilTest, ParseStringWithTags) {
struct TestCase {
NSString* input;
StringWithTags expected;
};
const TestCase kAllTestCases[] = {
TestCase{
@"Text without tag.",
StringWithTags{
@"Text without tag.",
{},
},
},
TestCase{
@"Text with empty tag BEGIN_TAG END_TAG.",
StringWithTags{
@"Text with empty tag .",
{NSRange{20, 1}},
},
},
TestCase{
@"Text with BEGIN_TAG and no end tag.",
StringWithTags{
@"Text with BEGIN_TAG and no end tag.",
{},
},
},
TestCase{
@"Text with no begin tag and END_TAG.",
StringWithTags{
@"Text with no begin tag and END_TAG.",
{},
},
},
TestCase{@"Text with END_TAG before BEGIN_TAG.",
StringWithTags{
@"Text with END_TAG before BEGIN_TAG.",
{},
}},
TestCase{
@"Text with valid BEGIN_TAG tag END_TAG and spaces.",
StringWithTags{
@"Text with valid tag and spaces.",
{NSRange{16, 5}},
},
},
TestCase{
@"Text with valid BEGIN_TAGtagEND_TAG and no spaces.",
StringWithTags{
@"Text with valid tag and no spaces.",
{NSRange{16, 3}},
},
},
TestCase{
@"Text with multiple tags: BEGIN_TAGtag1END_TAG, "
@"BEGIN_TAGtag2END_TAG and BEGIN_TAGtag3END_TAG.",
StringWithTags{
@"Text with multiple tags: tag1, tag2 and tag3.",
{NSRange{25, 4}, NSRange{31, 4}, NSRange{40, 4}},
},
},
};
for (const TestCase& test_case : kAllTestCases) {
const StringWithTags result =
ParseStringWithTags(test_case.input, @"BEGIN_TAG", @"END_TAG");
EXPECT_NSEQ(result.string, test_case.expected.string);
ASSERT_EQ(result.ranges.size(), test_case.expected.ranges.size());
for (size_t i = 0; i < test_case.expected.ranges.size(); i++) {
EXPECT_EQ(result.ranges[i], test_case.expected.ranges[i]);
}
}
}
// Verifies when it should return CGRectNull and when it shouldn't.
TEST_F(StringUtilTest, TextViewLinkBound) {
UITextView* text_view = CreateUITextViewWithTextKit1();
text_view.text = @"Some text.";
// Returns CGRectNull for empty NSRange.
EXPECT_TRUE(CGRectEqualToRect(
TextViewLinkBound(text_view, NSMakeRange(-1, -1)), CGRectNull));
// Returns CGRectNull for a range out of bound.
EXPECT_TRUE(CGRectEqualToRect(
TextViewLinkBound(text_view, NSMakeRange(20, 5)), CGRectNull));
// Returns a CGRect diffent from CGRectNull when there is a range in bound.
EXPECT_FALSE(CGRectEqualToRect(
TextViewLinkBound(text_view, NSMakeRange(0, 5)), CGRectNull));
}
} // namespace