|  | // 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 |