twitter-text-kow 1.3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +40 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +4 -0
- data/LICENSE +188 -0
- data/README.md +193 -0
- data/Rakefile +52 -0
- data/config/README.md +142 -0
- data/config/v1.json +8 -0
- data/config/v2.json +29 -0
- data/config/v3.json +30 -0
- data/lib/assets/tld_lib.yml +1577 -0
- data/lib/twitter-text/autolink.rb +455 -0
- data/lib/twitter-text/configuration.rb +68 -0
- data/lib/twitter-text/deprecation.rb +21 -0
- data/lib/twitter-text/emoji_regex.rb +27 -0
- data/lib/twitter-text/extractor.rb +388 -0
- data/lib/twitter-text/hash_helper.rb +27 -0
- data/lib/twitter-text/hit_highlighter.rb +92 -0
- data/lib/twitter-text/regex.rb +381 -0
- data/lib/twitter-text/rewriter.rb +69 -0
- data/lib/twitter-text/unicode.rb +31 -0
- data/lib/twitter-text/validation.rb +251 -0
- data/lib/twitter-text/weighted_range.rb +24 -0
- data/lib/twitter-text.rb +29 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/autolinking_spec.rb +858 -0
- data/spec/configuration_spec.rb +136 -0
- data/spec/extractor_spec.rb +392 -0
- data/spec/hithighlighter_spec.rb +96 -0
- data/spec/regex_spec.rb +76 -0
- data/spec/rewriter_spec.rb +553 -0
- data/spec/spec_helper.rb +139 -0
- data/spec/test_urls.rb +90 -0
- data/spec/twitter_text_spec.rb +25 -0
- data/spec/unicode_spec.rb +35 -0
- data/spec/validation_spec.rb +87 -0
- data/test/conformance_test.rb +242 -0
- data/twitter-text.gemspec +35 -0
- metadata +228 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# Copyright 2018 Twitter, Inc.
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
3
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
$TESTING=true
|
6
|
+
|
7
|
+
# Ruby 1.8 encoding check
|
8
|
+
major, minor, patch = RUBY_VERSION.split('.')
|
9
|
+
if major.to_i == 1 && minor.to_i < 9
|
10
|
+
$KCODE='u'
|
11
|
+
end
|
12
|
+
|
13
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
14
|
+
|
15
|
+
require 'nokogiri'
|
16
|
+
require 'json'
|
17
|
+
require 'simplecov'
|
18
|
+
SimpleCov.start do
|
19
|
+
add_group 'Libraries', 'lib'
|
20
|
+
end
|
21
|
+
|
22
|
+
require File.expand_path('../../lib/twitter-text', __FILE__)
|
23
|
+
require File.expand_path('../test_urls', __FILE__)
|
24
|
+
|
25
|
+
RSpec.configure do |config|
|
26
|
+
config.include TestUrls
|
27
|
+
|
28
|
+
config.filter_run_excluding :ruby => lambda { |version|
|
29
|
+
case version.to_s
|
30
|
+
when /^> (.*)/
|
31
|
+
!(RUBY_VERSION.to_s > $1)
|
32
|
+
else
|
33
|
+
!(RUBY_VERSION.to_s =~ /^#{version.to_s}/)
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
RSpec::Matchers.define :match_autolink_expression do
|
39
|
+
match do |string|
|
40
|
+
!Twitter::TwitterText::Extractor.extract_urls(string).empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
RSpec::Matchers.define :match_autolink_expression_in do |text|
|
45
|
+
match do |url|
|
46
|
+
@match_data = Twitter::TwitterText::Regex[:valid_url].match(text)
|
47
|
+
@match_data && @match_data.to_s.strip == url
|
48
|
+
end
|
49
|
+
|
50
|
+
failure_message do |url|
|
51
|
+
"Expected to find url '#{url}' in text '#{text}', but the match was #{@match_data.captures}'"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
RSpec::Matchers.define :have_autolinked_url do |url, inner_text|
|
56
|
+
match do |text|
|
57
|
+
@link = Nokogiri::HTML(text).search("a[@href='#{url}']")
|
58
|
+
@link &&
|
59
|
+
@link.inner_text &&
|
60
|
+
(inner_text && @link.inner_text == inner_text) || (!inner_text && @link.inner_text == url)
|
61
|
+
end
|
62
|
+
|
63
|
+
failure_message do |text|
|
64
|
+
"Expected url '#{url}'#{", inner_text '#{inner_text}'" if inner_text} to be autolinked in '#{text}'"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
RSpec::Matchers.define :link_to_screen_name do |screen_name, inner_text|
|
69
|
+
expected = inner_text ? inner_text : screen_name
|
70
|
+
|
71
|
+
match do |text|
|
72
|
+
@link = Nokogiri::HTML(text).search("a.username")
|
73
|
+
return false unless @link && @link.inner_text == expected
|
74
|
+
expect("https://twitter.com/#{screen_name}").to eq(@link.first['href'])
|
75
|
+
end
|
76
|
+
|
77
|
+
failure_message do |text|
|
78
|
+
if @link.first
|
79
|
+
"Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' to match screen_name '#{expected}', but it does not."
|
80
|
+
else
|
81
|
+
"Expected screen name '#{screen_name}' to be autolinked in '#{text}', but no link was found."
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
failure_message_when_negated do |text|
|
86
|
+
"Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' not to match screen_name '#{expected}', but it does."
|
87
|
+
end
|
88
|
+
|
89
|
+
description do
|
90
|
+
"contain a link with the name and href pointing to the expected screen_name"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
RSpec::Matchers.define :link_to_list_path do |list_path, inner_text|
|
95
|
+
expected = inner_text ? inner_text : list_path
|
96
|
+
|
97
|
+
match do |text|
|
98
|
+
@link = Nokogiri::HTML(text).search("a.list-slug")
|
99
|
+
return false unless @link && @link.inner_text == expected
|
100
|
+
expect("https://twitter.com/#{list_path}".downcase).to eq(@link.first['href'])
|
101
|
+
end
|
102
|
+
|
103
|
+
failure_message do |text|
|
104
|
+
if @link.first
|
105
|
+
"Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' to match the list path '#{expected}', but it does not."
|
106
|
+
else
|
107
|
+
"Expected list path '#{list_path}' to be autolinked in '#{text}', but no link was found."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
failure_message_when_negated do |text|
|
112
|
+
"Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' not to match the list path '#{expected}', but it does."
|
113
|
+
end
|
114
|
+
|
115
|
+
description do
|
116
|
+
"contain a link with the list title and an href pointing to the list path"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
RSpec::Matchers.define :have_autolinked_hashtag do |hashtag|
|
121
|
+
match do |text|
|
122
|
+
@link = Nokogiri::HTML(text).search("a[@href='https://twitter.com/search?q=#{hashtag.sub(/^#/, '%23')}']")
|
123
|
+
@link &&
|
124
|
+
@link.inner_text &&
|
125
|
+
@link.inner_text == hashtag
|
126
|
+
end
|
127
|
+
|
128
|
+
failure_message do |text|
|
129
|
+
if @link.first
|
130
|
+
"Expected link text to be [#{hashtag}], but it was [#{@link.inner_text}] in #{text}"
|
131
|
+
else
|
132
|
+
"Expected hashtag #{hashtag} to be autolinked in '#{text}', but no link was found."
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
failure_message_when_negated do |text|
|
137
|
+
"Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' not to match the hashtag '#{hashtag}', but it does."
|
138
|
+
end
|
139
|
+
end
|
data/spec/test_urls.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright 2018 Twitter, Inc.
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
3
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
# encoding: utf-8
|
6
|
+
|
7
|
+
module TestUrls
|
8
|
+
VALID = [
|
9
|
+
"http://google.com",
|
10
|
+
"http://foobar.com/#",
|
11
|
+
"http://google.com/#foo",
|
12
|
+
"http://google.com/#search?q=iphone%20-filter%3Alinks",
|
13
|
+
"http://twitter.com/#search?q=iphone%20-filter%3Alinks",
|
14
|
+
"http://somedomain.com/index.php?path=/abc/def/",
|
15
|
+
"http://www.boingboing.net/2007/02/14/katamari_damacy_phon.html",
|
16
|
+
"http://somehost.com:3000",
|
17
|
+
"http://a_b.c-d.com",
|
18
|
+
"http://sub_domain-dash.twitter.com",
|
19
|
+
"http://xo.com/~matthew+%-x",
|
20
|
+
"http://en.wikipedia.org/wiki/Primer_(film)",
|
21
|
+
"http://www.ams.org/bookstore-getitem/item=mbk-59",
|
22
|
+
"http://chilp.it/?77e8fd",
|
23
|
+
"http://tell.me/why",
|
24
|
+
"http://longtlds.info",
|
25
|
+
"http://✪df.ws/ejp",
|
26
|
+
"http://日本.com",
|
27
|
+
"http://search.twitter.com/search?q=avro&lang=en",
|
28
|
+
"http://mrs.domain-dash.biz",
|
29
|
+
"http://x.com/has/one/char/domain",
|
30
|
+
"http://t.co/nwcLTFF",
|
31
|
+
"http://a.b.cd",
|
32
|
+
"http://a-b.b.com",
|
33
|
+
"http://twitter-dash.com",
|
34
|
+
"http://msdn.microsoft.com/ja-jp/library/system.net.httpwebrequest(v=VS.100).aspx",
|
35
|
+
"www.foobar.com",
|
36
|
+
"WWW.FOOBAR.COM",
|
37
|
+
"www.foobar.co.jp",
|
38
|
+
"http://t.co",
|
39
|
+
"t.co/nwcLTFF",
|
40
|
+
"http://foobar.みんな",
|
41
|
+
"http://foobar.中国",
|
42
|
+
"http://foobar.پاکستان",
|
43
|
+
"https://www.youtube.com/playlist?list=PL0ZPu8XSRTB7wZzn0mLHMvyzVFeRxbWn-",
|
44
|
+
"http://ああ.com"
|
45
|
+
] unless defined?(TestUrls::VALID)
|
46
|
+
|
47
|
+
INVALID = [
|
48
|
+
"http://no-tld",
|
49
|
+
"http://tld-too-short.x",
|
50
|
+
"http://-doman_dash.com",
|
51
|
+
"http://_leadingunderscore.twitter.com",
|
52
|
+
"http://trailingunderscore_.twitter.com",
|
53
|
+
"http://-leadingdash.twitter.com",
|
54
|
+
"http://trailingdash-.twitter.com",
|
55
|
+
"http://-leadingdash.com",
|
56
|
+
"http://trailingdash-.com",
|
57
|
+
"http://no_underscores.com",
|
58
|
+
"http://test.c_o_m",
|
59
|
+
"http://test.c-o-m",
|
60
|
+
"http://twitt#{[0x202A].pack('U')}.com",
|
61
|
+
"http://twitt#{[0x202B].pack('U')}.com",
|
62
|
+
"http://twitt#{[0x202C].pack('U')}.com",
|
63
|
+
"http://twitt#{[0x202D].pack('U')}.com",
|
64
|
+
"http://twitt#{[0x202E].pack('U')}.com",
|
65
|
+
"https://somesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurl.com/foo https://somesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurl.com/foo https://somesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurlsomesuperlongurl.com/foo"
|
66
|
+
] unless defined?(TestUrls::INVALID)
|
67
|
+
|
68
|
+
TCO = [
|
69
|
+
"http://t.co/P53cv5yO!",
|
70
|
+
"http://t.co/fQJmiPGg***",
|
71
|
+
"http://t.co/pbY2NfTZ's",
|
72
|
+
"http://t.co/2vYHpAc5;",
|
73
|
+
"http://t.co/ulYGBYSo:",
|
74
|
+
"http://t.co/GeT4bSiw=win",
|
75
|
+
"http://t.co/8MkmHU0k+fun",
|
76
|
+
"http://t.co/TKLp64dY.yes,",
|
77
|
+
"http://t.co/8vuO27cI$$",
|
78
|
+
"http://t.co/rPYTvdA8/",
|
79
|
+
"http://t.co/WvtMw5ku%",
|
80
|
+
"http://t.co/8t7G3ddS#",
|
81
|
+
"http://t.co/nfHNJDV2/#!",
|
82
|
+
"http://t.co/gK6NOXHs[good]",
|
83
|
+
"http://t.co/dMrT0o1Y]bad",
|
84
|
+
"http://t.co/FNkPfmii-",
|
85
|
+
"http://t.co/sMgS3pjI_oh",
|
86
|
+
"http://t.co/F8Dq3Plb~",
|
87
|
+
"http://t.co/ivvH58vC&help",
|
88
|
+
"http://t.co/iUBL15zD|NZ5KYLQ8"
|
89
|
+
] unless defined?(TestUrls::TCO)
|
90
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright 2018 Twitter, Inc.
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
3
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
# encoding: utf-8
|
6
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
7
|
+
|
8
|
+
major, minor, patch = RUBY_VERSION.split('.')
|
9
|
+
if major.to_i == 1 && minor.to_i < 9
|
10
|
+
describe "base" do
|
11
|
+
before do
|
12
|
+
$KCODE = 'NONE'
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
$KCODE = 'u'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should raise with invalid KCODE on Ruby < 1.9" do
|
20
|
+
expect(lambda do
|
21
|
+
require 'twitter-text'
|
22
|
+
end).to raise_error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Copyright 2018 Twitter, Inc.
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
3
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
# encoding: utf-8
|
6
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
7
|
+
|
8
|
+
describe Twitter::TwitterText::Unicode do
|
9
|
+
|
10
|
+
it "should lazy-init constants" do
|
11
|
+
expect(Twitter::TwitterText::Unicode.const_defined?(:UFEB6)).to eq(false)
|
12
|
+
expect(Twitter::TwitterText::Unicode::UFEB6).to_not be_nil
|
13
|
+
expect(Twitter::TwitterText::Unicode::UFEB6).to be_kind_of(String)
|
14
|
+
expect(Twitter::TwitterText::Unicode.const_defined?(:UFEB6)).to eq(true)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return corresponding character" do
|
18
|
+
expect(Twitter::TwitterText::Unicode::UFEB6).to be == [0xfeb6].pack('U')
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should allow lowercase notation" do
|
22
|
+
expect(Twitter::TwitterText::Unicode::Ufeb6).to be == Twitter::TwitterText::Unicode::UFEB6
|
23
|
+
expect(Twitter::TwitterText::Unicode::Ufeb6).to be === Twitter::TwitterText::Unicode::UFEB6
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should allow underscore notation" do
|
27
|
+
expect(Twitter::TwitterText::Unicode::U_FEB6).to be == Twitter::TwitterText::Unicode::UFEB6
|
28
|
+
expect(Twitter::TwitterText::Unicode::U_FEB6).to be === Twitter::TwitterText::Unicode::UFEB6
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise on invalid codepoints" do
|
32
|
+
expect(lambda { Twitter::TwitterText::Unicode::FFFFFF }).to raise_error(NameError)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Copyright 2018 Twitter, Inc.
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
3
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
# encoding: utf-8
|
6
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
7
|
+
|
8
|
+
class TestValidation
|
9
|
+
include Twitter::TwitterText::Validation
|
10
|
+
end
|
11
|
+
|
12
|
+
describe Twitter::TwitterText::Validation do
|
13
|
+
|
14
|
+
it "should disallow invalid BOM character" do
|
15
|
+
expect(TestValidation.new.tweet_invalid?("Bom:#{Twitter::TwitterText::Unicode::UFFFE}")).to be == :invalid_characters
|
16
|
+
expect(TestValidation.new.tweet_invalid?("Bom:#{Twitter::TwitterText::Unicode::UFEFF}")).to be == :invalid_characters
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should disallow invalid U+FFFF character" do
|
20
|
+
expect(TestValidation.new.tweet_invalid?("Bom:#{Twitter::TwitterText::Unicode::UFFFF}")).to be == :invalid_characters
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should allow direction change characters" do
|
24
|
+
[0x202A, 0x202B, 0x202C, 0x202D, 0x202E].map{|cp| [cp].pack('U') }.each do |char|
|
25
|
+
expect(TestValidation.new.tweet_invalid?("Invalid:#{char}")).to be false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should disallow non-Unicode" do
|
30
|
+
expect(TestValidation.new.tweet_invalid?("not-Unicode:\xfff0")).to be == :invalid_characters
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should allow <= 140 combined accent characters" do
|
34
|
+
char = [0x65, 0x0301].pack('U')
|
35
|
+
expect(TestValidation.new.tweet_invalid?(char * 139)).to be false
|
36
|
+
expect(TestValidation.new.tweet_invalid?(char * 140)).to be false
|
37
|
+
expect(TestValidation.new.tweet_invalid?(char * 141)).to eq(:too_long)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should allow <= 140 multi-byte characters" do
|
41
|
+
char = [ 0x1d106 ].pack('U')
|
42
|
+
expect(TestValidation.new.tweet_invalid?(char * 139)).to be false
|
43
|
+
expect(TestValidation.new.tweet_invalid?(char * 140)).to be false
|
44
|
+
expect(TestValidation.new.tweet_invalid?(char * 141)).to eq(:too_long)
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when returning results" do
|
48
|
+
it "should properly create new fully-populated results from arguments" do
|
49
|
+
results = Twitter::TwitterText::Validation::ParseResults.new(weighted_length: 26, permillage: 92, valid: true, display_range_start: 0, display_range_end: 16, valid_range_start: 0, valid_range_end:16)
|
50
|
+
expect(results).to_not be nil
|
51
|
+
expect(results[:weighted_length]).to eq(26)
|
52
|
+
expect(results[:permillage]).to eq(92)
|
53
|
+
expect(results[:valid]).to be true
|
54
|
+
expect(results[:display_range_start]).to eq(0)
|
55
|
+
expect(results[:display_range_end]).to eq(16)
|
56
|
+
expect(results[:valid_range_start]).to eq(0)
|
57
|
+
expect(results[:valid_range_end]).to eq(16)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should properly create empty results" do
|
61
|
+
results = Twitter::TwitterText::Validation::ParseResults.empty()
|
62
|
+
expect(results[:weighted_length]).to eq(0)
|
63
|
+
expect(results[:permillage]).to eq(0)
|
64
|
+
expect(results[:valid]).to be true
|
65
|
+
expect(results[:display_range_start]).to eq(0)
|
66
|
+
expect(results[:display_range_end]).to eq(0)
|
67
|
+
expect(results[:valid_range_start]).to eq(0)
|
68
|
+
expect(results[:valid_range_end]).to eq(0)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when parsing tweet text" do
|
73
|
+
it "should properly parse ZWJ and ZWNJ when grapheme clusters are enabled", ruby: ">= 2.5.0" do
|
74
|
+
# Grapheme clustering of devenghali script differs based on platform implementation
|
75
|
+
text = "ZWJ: क्ष -> क्\u200Dष; ZWNJ: क्ष -> क्\u200Cष"
|
76
|
+
config = Twitter::TwitterText::Configuration::configuration_from_file(Twitter::TwitterText::Configuration::CONFIG_V3)
|
77
|
+
results = Twitter::TwitterText::Validation::parse_tweet(text, config: config)
|
78
|
+
expect(results[:weighted_length]).to eq(29)
|
79
|
+
expect(results[:permillage]).to eq(103)
|
80
|
+
expect(results[:valid]).to be true
|
81
|
+
expect(results[:display_range_start]).to eq(0)
|
82
|
+
expect(results[:display_range_end]).to eq(34)
|
83
|
+
expect(results[:valid_range_start]).to eq(0)
|
84
|
+
expect(results[:valid_range_end]).to eq(34)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# Copyright 2018 Twitter, Inc.
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
3
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
require 'multi_json'
|
6
|
+
require 'nokogiri'
|
7
|
+
require 'test/unit'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
# Detect Ruby 1.8 and older to apply necessary encoding fixes
|
11
|
+
major, minor, patch = RUBY_VERSION.split('.')
|
12
|
+
OLD_RUBY = major.to_i == 1 && minor.to_i < 9
|
13
|
+
|
14
|
+
if OLD_RUBY
|
15
|
+
$KCODE='u'
|
16
|
+
end
|
17
|
+
|
18
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
19
|
+
require 'twitter-text'
|
20
|
+
|
21
|
+
class ConformanceTest < Test::Unit::TestCase
|
22
|
+
include Twitter::TwitterText::Extractor
|
23
|
+
include Twitter::TwitterText::Autolink
|
24
|
+
include Twitter::TwitterText::HitHighlighter
|
25
|
+
include Twitter::TwitterText::Validation
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
%w(description expected json hits).each do |key|
|
30
|
+
define_method key.to_sym do
|
31
|
+
@test_info[key]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if OLD_RUBY
|
36
|
+
def text
|
37
|
+
@test_info['text'].gsub(/\\u([0-9a-f]{8})/i) do
|
38
|
+
[$1.to_i(16)].pack('U*')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
else
|
42
|
+
def text
|
43
|
+
@test_info['text']
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def assert_equal_without_attribute_order(expected, actual, failure_message = nil)
|
48
|
+
assert_block(build_message(failure_message, "<?> expected but was\n<?>", expected, actual)) do
|
49
|
+
equal_nodes?(Nokogiri::HTML(expected).root, Nokogiri::HTML(actual).root)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def equal_nodes?(expected, actual)
|
54
|
+
return false unless expected.name == actual.name
|
55
|
+
return false unless ordered_attributes(expected) == ordered_attributes(actual)
|
56
|
+
return false if expected.text? && actual.text? && expected.content != actual.content
|
57
|
+
|
58
|
+
expected.children.each_with_index do |child, index|
|
59
|
+
return false unless equal_nodes?(child, actual.children[index])
|
60
|
+
end
|
61
|
+
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def ordered_attributes(element)
|
66
|
+
element.attribute_nodes.map{|attr| [attr.name, attr.value]}.sort
|
67
|
+
end
|
68
|
+
|
69
|
+
def assert_equal_parse_results(expected, actual, failure_message = nil)
|
70
|
+
e = {}
|
71
|
+
# Note that we don't assert display and valid ranges because of differences
|
72
|
+
# in how ruby counts characters (wrt surrogate pairs) vs. other platforms
|
73
|
+
range_keys = [:display_range_start, :display_range_end, :valid_range_start, :valid_range_end]
|
74
|
+
expected.keys.each { |k| e[k.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase.to_sym] = expected[k] }
|
75
|
+
[e, actual].each { |a| range_keys.each { |k| a.delete(k) } }
|
76
|
+
assert_equal e, actual, failure_message
|
77
|
+
end
|
78
|
+
|
79
|
+
CONFORMANCE_DIR = ENV['CONFORMANCE_DIR'] || File.expand_path("../../../conformance", __FILE__)
|
80
|
+
|
81
|
+
def self.def_conformance_test(file, test_type, &block)
|
82
|
+
yaml = YAML.load_file(File.join(CONFORMANCE_DIR, file))
|
83
|
+
raise "No such test suite: #{test_type.to_s}" unless yaml["tests"][test_type.to_s]
|
84
|
+
|
85
|
+
file_name = file.split('.').first
|
86
|
+
|
87
|
+
yaml["tests"][test_type.to_s].each do |test_info|
|
88
|
+
name = :"test_#{file_name}_#{test_type} #{test_info['description']}"
|
89
|
+
define_method name do
|
90
|
+
@test_info = test_info
|
91
|
+
instance_eval(&block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
public
|
97
|
+
|
98
|
+
# Extractor Conformance
|
99
|
+
def_conformance_test("extract.yml", :replies) do
|
100
|
+
assert_equal expected, extract_reply_screen_name(text), description
|
101
|
+
end
|
102
|
+
|
103
|
+
def_conformance_test("extract.yml", :mentions) do
|
104
|
+
assert_equal expected, extract_mentioned_screen_names(text), description
|
105
|
+
end
|
106
|
+
|
107
|
+
def_conformance_test("extract.yml", :mentions_with_indices) do
|
108
|
+
e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
|
109
|
+
assert_equal e, extract_mentioned_screen_names_with_indices(text), description
|
110
|
+
end
|
111
|
+
|
112
|
+
def_conformance_test("extract.yml", :mentions_or_lists_with_indices) do
|
113
|
+
e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
|
114
|
+
assert_equal e, extract_mentions_or_lists_with_indices(text), description
|
115
|
+
end
|
116
|
+
|
117
|
+
def_conformance_test("extract.yml", :urls) do
|
118
|
+
assert_equal expected, extract_urls(text), description
|
119
|
+
expected.each do |expected_url|
|
120
|
+
assert_equal true, valid_url?(expected_url, true, false), "expected url [#{expected_url}] not valid"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def_conformance_test("tlds.yml", :generic) do
|
125
|
+
assert_equal expected, extract_urls(text), description
|
126
|
+
end
|
127
|
+
|
128
|
+
def_conformance_test("tlds.yml", :country) do
|
129
|
+
assert_equal expected, extract_urls(text), description
|
130
|
+
end
|
131
|
+
|
132
|
+
def_conformance_test("extract.yml", :urls_with_indices) do
|
133
|
+
e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
|
134
|
+
assert_equal e, extract_urls_with_indices(text), description
|
135
|
+
end
|
136
|
+
|
137
|
+
def_conformance_test("extract.yml", :urls_with_directional_markers) do
|
138
|
+
e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
|
139
|
+
assert_equal e, extract_urls_with_indices(text), description
|
140
|
+
end
|
141
|
+
|
142
|
+
def_conformance_test("extract.yml", :hashtags) do
|
143
|
+
assert_equal expected, extract_hashtags(text), description
|
144
|
+
end
|
145
|
+
|
146
|
+
def_conformance_test("extract.yml", :hashtags_from_astral) do
|
147
|
+
assert_equal expected, extract_hashtags(text), description
|
148
|
+
end
|
149
|
+
|
150
|
+
def_conformance_test("extract.yml", :hashtags_with_indices) do
|
151
|
+
e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
|
152
|
+
assert_equal e, extract_hashtags_with_indices(text), description
|
153
|
+
end
|
154
|
+
|
155
|
+
def_conformance_test("extract.yml", :cashtags) do
|
156
|
+
assert_equal expected, extract_cashtags(text), description
|
157
|
+
end
|
158
|
+
|
159
|
+
def_conformance_test("extract.yml", :cashtags_with_indices) do
|
160
|
+
e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
|
161
|
+
assert_equal e, extract_cashtags_with_indices(text), description
|
162
|
+
end
|
163
|
+
|
164
|
+
# Autolink Conformance
|
165
|
+
def_conformance_test("autolink.yml", :usernames) do
|
166
|
+
assert_equal_without_attribute_order expected, auto_link_usernames_or_lists(text, :suppress_no_follow => true), description
|
167
|
+
end
|
168
|
+
|
169
|
+
def_conformance_test("autolink.yml", :lists) do
|
170
|
+
assert_equal_without_attribute_order expected, auto_link_usernames_or_lists(text, :suppress_no_follow => true), description
|
171
|
+
end
|
172
|
+
|
173
|
+
def_conformance_test("autolink.yml", :urls) do
|
174
|
+
assert_equal_without_attribute_order expected, auto_link_urls(text, :suppress_no_follow => true), description
|
175
|
+
end
|
176
|
+
|
177
|
+
def_conformance_test("autolink.yml", :hashtags) do
|
178
|
+
assert_equal_without_attribute_order expected, auto_link_hashtags(text, :suppress_no_follow => true), description
|
179
|
+
end
|
180
|
+
|
181
|
+
def_conformance_test("autolink.yml", :cashtags) do
|
182
|
+
assert_equal_without_attribute_order expected, auto_link_cashtags(text, :suppress_no_follow => true), description
|
183
|
+
end
|
184
|
+
|
185
|
+
def_conformance_test("autolink.yml", :all) do
|
186
|
+
assert_equal_without_attribute_order expected, auto_link(text, :suppress_no_follow => true), description
|
187
|
+
end
|
188
|
+
|
189
|
+
def_conformance_test("autolink.yml", :json) do
|
190
|
+
assert_equal_without_attribute_order expected, auto_link_with_json(text, MultiJson.load(json), :suppress_no_follow => true), description
|
191
|
+
end
|
192
|
+
|
193
|
+
# HitHighlighter Conformance
|
194
|
+
def_conformance_test("hit_highlighting.yml", :plain_text) do
|
195
|
+
assert_equal expected, hit_highlight(text, hits), description
|
196
|
+
end
|
197
|
+
|
198
|
+
def_conformance_test("hit_highlighting.yml", :with_links) do
|
199
|
+
assert_equal expected, hit_highlight(text, hits), description
|
200
|
+
end
|
201
|
+
|
202
|
+
# Validation Conformance
|
203
|
+
def_conformance_test("validate.yml", :tweets) do
|
204
|
+
assert_equal expected, parse_tweet(text)[:valid], description
|
205
|
+
end
|
206
|
+
|
207
|
+
def_conformance_test("validate.yml", :usernames) do
|
208
|
+
assert_equal expected, valid_username?(text), description
|
209
|
+
end
|
210
|
+
|
211
|
+
def_conformance_test("validate.yml", :lists) do
|
212
|
+
assert_equal expected, valid_list?(text), description
|
213
|
+
end
|
214
|
+
|
215
|
+
def_conformance_test("validate.yml", :urls) do
|
216
|
+
assert_equal expected, valid_url?(text), description
|
217
|
+
end
|
218
|
+
|
219
|
+
def_conformance_test("validate.yml", :urls_without_protocol) do
|
220
|
+
assert_equal expected, valid_url?(text, true, false), description
|
221
|
+
end
|
222
|
+
|
223
|
+
def_conformance_test("validate.yml", :hashtags) do
|
224
|
+
assert_equal expected, valid_hashtag?(text), description
|
225
|
+
end
|
226
|
+
|
227
|
+
def_conformance_test("validate.yml", :WeightedTweetsCounterTest) do
|
228
|
+
# Force v2 configuration, basic weighted code point support
|
229
|
+
config = Twitter::TwitterText::Configuration::configuration_from_file(Twitter::TwitterText::Configuration::CONFIG_V2)
|
230
|
+
assert_equal_parse_results expected, parse_tweet(text, config: config), description
|
231
|
+
end
|
232
|
+
|
233
|
+
def_conformance_test("validate.yml", :WeightedTweetsWithDiscountedEmojiCounterTest) do
|
234
|
+
# Force v3 configuration, which supports discounting grapheme clusters that are emoji
|
235
|
+
config = Twitter::TwitterText::Configuration::configuration_from_file(Twitter::TwitterText::Configuration::CONFIG_V3)
|
236
|
+
assert_equal_parse_results expected, parse_tweet(text, config: config), description
|
237
|
+
end
|
238
|
+
|
239
|
+
def_conformance_test("validate.yml", :UnicodeDirectionalMarkerCounterTest) do
|
240
|
+
assert_equal_parse_results expected, parse_tweet(text), description
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Copyright 2018 Twitter, Inc.
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
3
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
4
|
+
|
5
|
+
# encoding: utf-8
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "twitter-text-kow"
|
9
|
+
s.version = "1.3.1.0"
|
10
|
+
s.authors = ["David LaMacchia", "Sudheer Guntupalli", "Kaushik Lakshmikanth", "Jose Antonio Marquez Russo", "Lee Adams",
|
11
|
+
"Yoshimasa Niwa"]
|
12
|
+
s.email = ["opensource@twitter.com"]
|
13
|
+
s.homepage = "http://twitter.com"
|
14
|
+
s.description = s.summary = "A KOW fixed gem that provides text handling for Twitter"
|
15
|
+
s.license = "Apache 2.0"
|
16
|
+
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.summary = "Twitter text handling library"
|
19
|
+
|
20
|
+
s.add_development_dependency "test-unit"
|
21
|
+
s.add_development_dependency "multi_json", "~> 1.3"
|
22
|
+
s.add_development_dependency "nokogiri", "~> 1.17.2"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "rdoc"
|
25
|
+
s.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
s.add_development_dependency "simplecov"
|
27
|
+
s.add_runtime_dependency "unf", "~> 0.1.0"
|
28
|
+
# Use of idn-ruby requires libidn to be installed separately
|
29
|
+
s.add_runtime_dependency "idn-ruby"
|
30
|
+
|
31
|
+
s.files = `git ls-files`.split("\n") + ['lib/assets/tld_lib.yml'] + Dir['config/*']
|
32
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
33
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
34
|
+
s.require_paths = ["lib"]
|
35
|
+
end
|