twitter-text-relative 1.6.2.pre.3

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.
@@ -0,0 +1,127 @@
1
+ $TESTING=true
2
+
3
+ # Ruby 1.8 encoding check
4
+ major, minor, patch = RUBY_VERSION.split('.')
5
+ if major.to_i == 1 && minor.to_i < 9
6
+ $KCODE='u'
7
+ end
8
+
9
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
10
+
11
+ require 'nokogiri'
12
+ require 'simplecov'
13
+ SimpleCov.start do
14
+ add_group 'Libraries', 'lib'
15
+ end
16
+
17
+ require File.expand_path('../../lib/twitter-text-relative', __FILE__)
18
+ require File.expand_path('../test_urls', __FILE__)
19
+
20
+ RSpec.configure do |config|
21
+ config.include TestUrls
22
+ end
23
+
24
+ RSpec::Matchers.define :match_autolink_expression do
25
+ match do |string|
26
+ !Twitter::Extractor.extract_urls(string).empty?
27
+ end
28
+ end
29
+
30
+ RSpec::Matchers.define :match_autolink_expression_in do |text|
31
+ match do |url|
32
+ @match_data = Twitter::Regex[:valid_url].match(text)
33
+ @match_data && @match_data.to_s.strip == url
34
+ end
35
+
36
+ failure_message_for_should do |url|
37
+ "Expected to find url '#{url}' in text '#{text}', but the match was #{@match_data.captures}'"
38
+ end
39
+ end
40
+
41
+ RSpec::Matchers.define :have_autolinked_url do |url, inner_text|
42
+ match do |text|
43
+ @link = Nokogiri::HTML(text).search("a[@href='#{url}']")
44
+ @link &&
45
+ @link.inner_text &&
46
+ (inner_text && @link.inner_text == inner_text) || (!inner_text && @link.inner_text == url)
47
+ end
48
+
49
+ failure_message_for_should do |text|
50
+ "Expected url '#{url}'#{", inner_text '#{inner_text}'" if inner_text} to be autolinked in '#{text}'"
51
+ end
52
+ end
53
+
54
+ RSpec::Matchers.define :link_to_screen_name do |screen_name, inner_text|
55
+ expected = inner_text ? inner_text : screen_name
56
+
57
+ match do |text|
58
+ @link = Nokogiri::HTML(text).search("a.username")
59
+ @link &&
60
+ @link.inner_text == expected &&
61
+ "https://twitter.com/#{screen_name}".downcase.should == @link.first['href']
62
+ end
63
+
64
+ failure_message_for_should do |text|
65
+ if @link.first
66
+ "Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' to match screen_name '#{expected}', but it does not."
67
+ else
68
+ "Expected screen name '#{screen_name}' to be autolinked in '#{text}', but no link was found."
69
+ end
70
+ end
71
+
72
+ failure_message_for_should_not do |text|
73
+ "Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' not to match screen_name '#{expected}', but it does."
74
+ end
75
+
76
+ description do
77
+ "contain a link with the name and href pointing to the expected screen_name"
78
+ end
79
+ end
80
+
81
+ RSpec::Matchers.define :link_to_list_path do |list_path, inner_text|
82
+ expected = inner_text ? inner_text : list_path
83
+
84
+ match do |text|
85
+ @link = Nokogiri::HTML(text).search("a.list-slug")
86
+ @link &&
87
+ @link.inner_text == expected &&
88
+ "https://twitter.com/#{list_path}".downcase.should == @link.first['href']
89
+ end
90
+
91
+ failure_message_for_should do |text|
92
+ if @link.first
93
+ "Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' to match the list path '#{expected}', but it does not."
94
+ else
95
+ "Expected list path '#{list_path}' to be autolinked in '#{text}', but no link was found."
96
+ end
97
+ end
98
+
99
+ failure_message_for_should_not do |text|
100
+ "Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' not to match the list path '#{expected}', but it does."
101
+ end
102
+
103
+ description do
104
+ "contain a link with the list title and an href pointing to the list path"
105
+ end
106
+ end
107
+
108
+ RSpec::Matchers.define :have_autolinked_hashtag do |hashtag|
109
+ match do |text|
110
+ @link = Nokogiri::HTML(text).search("a[@href='https://twitter.com/#!/search?q=#{hashtag.sub(/^#/, '%23')}']")
111
+ @link &&
112
+ @link.inner_text &&
113
+ @link.inner_text == hashtag
114
+ end
115
+
116
+ failure_message_for_should do |text|
117
+ if @link.first
118
+ "Expected link text to be [#{hashtag}], but it was [#{@link.inner_text}] in #{text}"
119
+ else
120
+ "Expected hashtag #{hashtag} to be autolinked in '#{text}', but no link was found."
121
+ end
122
+ end
123
+
124
+ failure_message_for_should_not do |text|
125
+ "Expected link '#{@link.inner_text}' with href '#{@link.first['href']}' not to match the hashtag '#{hashtag}', but it does."
126
+ end
127
+ end
data/spec/test_urls.rb ADDED
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+
3
+ module TestUrls
4
+ VALID = [
5
+ "http://google.com",
6
+ "http://foobar.com/#",
7
+ "http://google.com/#foo",
8
+ "http://google.com/#search?q=iphone%20-filter%3Alinks",
9
+ "http://twitter.com/#search?q=iphone%20-filter%3Alinks",
10
+ "http://somedomain.com/index.php?path=/abc/def/",
11
+ "http://www.boingboing.net/2007/02/14/katamari_damacy_phon.html",
12
+ "http://somehost.com:3000",
13
+ "http://xo.com/~matthew+%-x",
14
+ "http://en.wikipedia.org/wiki/Primer_(film)",
15
+ "http://www.ams.org/bookstore-getitem/item=mbk-59",
16
+ "http://chilp.it/?77e8fd",
17
+ "http://tell.me/why",
18
+ "http://longtlds.info",
19
+ "http://✪df.ws/ejp",
20
+ "http://日本.com",
21
+ "http://search.twitter.com/search?q=avro&lang=en",
22
+ "http://mrs.domain-dash.biz",
23
+ "http://x.com/has/one/char/domain",
24
+ "http://t.co/nwcLTFF",
25
+ "http://sub_domain-dash.twitter.com",
26
+ "http://a.b.cd",
27
+ "http://a_b.c-d.com",
28
+ "http://a-b.b.com",
29
+ "http://twitter-dash.com",
30
+ "http://msdn.microsoft.com/ja-jp/library/system.net.httpwebrequest(v=VS.100).aspx",
31
+ "www.foobar.com",
32
+ "WWW.FOOBAR.COM",
33
+ "www.foobar.co.jp",
34
+ "http://t.co",
35
+ "t.co/nwcLTFF"
36
+ ] unless defined?(TestUrls::VALID)
37
+
38
+ INVALID = [
39
+ "http://no-tld",
40
+ "http://tld-too-short.x",
41
+ "http://-doman_dash.com",
42
+ "http://_leadingunderscore.twitter.com",
43
+ "http://trailingunderscore_.twitter.com",
44
+ "http://-leadingdash.twitter.com",
45
+ "http://trailingdash-.twitter.com",
46
+ "http://-leadingdash.com",
47
+ "http://trailingdash-.com",
48
+ "http://no_underscores.com",
49
+ "http://test.c_o_m",
50
+ "http://test.c-o-m",
51
+ "http://twitt#{[0x202A].pack('U')}er.com",
52
+ "http://twitt#{[0x202B].pack('U')}er.com",
53
+ "http://twitt#{[0x202C].pack('U')}er.com",
54
+ "http://twitt#{[0x202D].pack('U')}er.com",
55
+ "http://twitt#{[0x202E].pack('U')}er.com"
56
+ ] unless defined?(TestUrls::INVALID)
57
+
58
+ TCO = [
59
+ "http://t.co/P53cv5yO!",
60
+ "http://t.co/fQJmiPGg***",
61
+ "http://t.co/pbY2NfTZ's",
62
+ "http://t.co/2vYHpAc5;",
63
+ "http://t.co/ulYGBYSo:",
64
+ "http://t.co/GeT4bSiw=win",
65
+ "http://t.co/8MkmHU0k+fun",
66
+ "http://t.co/TKLp64dY.yes,",
67
+ "http://t.co/8vuO27cI$$",
68
+ "http://t.co/rPYTvdA8/",
69
+ "http://t.co/WvtMw5ku%",
70
+ "http://t.co/8t7G3ddS#",
71
+ "http://t.co/nfHNJDV2/#!",
72
+ "http://t.co/gK6NOXHs[good]",
73
+ "http://t.co/dMrT0o1Y]bad",
74
+ "http://t.co/FNkPfmii-",
75
+ "http://t.co/sMgS3pjI_oh",
76
+ "http://t.co/F8Dq3Plb~",
77
+ "http://t.co/ivvH58vC&help",
78
+ "http://t.co/iUBL15zD|NZ5KYLQ8"
79
+ ] unless defined?(TestUrls::TCO)
80
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ major, minor, patch = RUBY_VERSION.split('.')
5
+ if major.to_i == 1 && minor.to_i < 9
6
+ describe "base" do
7
+ before do
8
+ $KCODE = 'NONE'
9
+ end
10
+
11
+ after do
12
+ $KCODE = 'u'
13
+ end
14
+
15
+ it "should raise with invalid KCODE on Ruby < 1.9" do
16
+ lambda do
17
+ require 'twitter-text-relative'
18
+ end.should raise_error
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe Twitter::Unicode do
5
+
6
+ it "should lazy-init constants" do
7
+ Twitter::Unicode.const_defined?(:UFEB6).should == false
8
+ Twitter::Unicode::UFEB6.should_not be_nil
9
+ Twitter::Unicode::UFEB6.should be_kind_of(String)
10
+ Twitter::Unicode.const_defined?(:UFEB6).should == true
11
+ end
12
+
13
+ it "should return corresponding character" do
14
+ Twitter::Unicode::UFEB6.should == [0xfeb6].pack('U')
15
+ end
16
+
17
+ it "should allow lowercase notation" do
18
+ Twitter::Unicode::Ufeb6.should == Twitter::Unicode::UFEB6
19
+ Twitter::Unicode::Ufeb6.should === Twitter::Unicode::UFEB6
20
+ end
21
+
22
+ it "should allow underscore notation" do
23
+ Twitter::Unicode::U_FEB6.should == Twitter::Unicode::UFEB6
24
+ Twitter::Unicode::U_FEB6.should === Twitter::Unicode::UFEB6
25
+ end
26
+
27
+ it "should raise on invalid codepoints" do
28
+ lambda { Twitter::Unicode::FFFFFF }.should raise_error(NameError)
29
+ end
30
+
31
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: binary
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ class TestValidation
5
+ include Twitter::Validation
6
+ end
7
+
8
+ describe Twitter::Validation do
9
+
10
+ it "should disallow invalid BOM character" do
11
+ TestValidation.new.tweet_invalid?("Bom:#{Twitter::Unicode::UFFFE}").should == :invalid_characters
12
+ TestValidation.new.tweet_invalid?("Bom:#{Twitter::Unicode::UFEFF}").should == :invalid_characters
13
+ end
14
+
15
+ it "should disallow invalid U+FFFF character" do
16
+ TestValidation.new.tweet_invalid?("Bom:#{Twitter::Unicode::UFFFF}").should == :invalid_characters
17
+ end
18
+
19
+ it "should disallow direction change characters" do
20
+ [0x202A, 0x202B, 0x202C, 0x202D, 0x202E].map{|cp| [cp].pack('U') }.each do |char|
21
+ TestValidation.new.tweet_invalid?("Invalid:#{char}").should == :invalid_characters
22
+ end
23
+ end
24
+
25
+ it "should disallow non-Unicode" do
26
+ TestValidation.new.tweet_invalid?("not-Unicode:\xfff0").should == :invalid_characters
27
+ end
28
+
29
+ it "should allow <= 140 combined accent characters" do
30
+ char = [0x65, 0x0301].pack('U')
31
+ TestValidation.new.tweet_invalid?(char * 139).should == false
32
+ TestValidation.new.tweet_invalid?(char * 140).should == false
33
+ TestValidation.new.tweet_invalid?(char * 141).should == :too_long
34
+ end
35
+
36
+ it "should allow <= 140 multi-byte characters" do
37
+ char = [ 0x1d106 ].pack('U')
38
+ TestValidation.new.tweet_invalid?(char * 139).should == false
39
+ TestValidation.new.tweet_invalid?(char * 140).should == false
40
+ TestValidation.new.tweet_invalid?(char * 141).should == :too_long
41
+ end
42
+
43
+ end
@@ -0,0 +1,182 @@
1
+ require 'multi_json'
2
+ require 'nokogiri'
3
+ require 'test/unit'
4
+ require 'yaml'
5
+
6
+ # Ruby 1.8 encoding check
7
+ major, minor, patch = RUBY_VERSION.split('.')
8
+ if major.to_i == 1 && minor.to_i < 9
9
+ $KCODE='u'
10
+ end
11
+
12
+ require File.expand_path('../../lib/twitter-text-relative', __FILE__)
13
+
14
+ class ConformanceTest < Test::Unit::TestCase
15
+ include Twitter::Extractor
16
+ include Twitter::Autolink
17
+ include Twitter::HitHighlighter
18
+ include Twitter::Validation
19
+
20
+ private
21
+
22
+ %w(description expected text json hits).each do |key|
23
+ define_method key.to_sym do
24
+ @test_info[key]
25
+ end
26
+ end
27
+
28
+ def assert_equal_without_attribute_order(expected, actual, failure_message = nil)
29
+ assert_block(build_message(failure_message, "<?> expected but was\n<?>", expected, actual)) do
30
+ equal_nodes?(Nokogiri::HTML(expected).root, Nokogiri::HTML(actual).root)
31
+ end
32
+ end
33
+
34
+ def equal_nodes?(expected, actual)
35
+ return false unless expected.name == actual.name
36
+ return false unless ordered_attributes(expected) == ordered_attributes(actual)
37
+ return false if expected.text? && actual.text? && expected.content != actual.content
38
+
39
+ expected.children.each_with_index do |child, index|
40
+ return false unless equal_nodes?(child, actual.children[index])
41
+ end
42
+
43
+ true
44
+ end
45
+
46
+ def ordered_attributes(element)
47
+ element.attribute_nodes.map{|attr| [attr.name, attr.value]}.sort
48
+ end
49
+
50
+ CONFORMANCE_DIR = ENV['CONFORMANCE_DIR'] || File.expand_path("../twitter-text-relative-conformance", __FILE__)
51
+
52
+ def self.def_conformance_test(file, test_type, &block)
53
+ yaml = YAML.load_file(File.join(CONFORMANCE_DIR, file))
54
+ raise "No such test suite: #{test_type.to_s}" unless yaml["tests"][test_type.to_s]
55
+
56
+ yaml["tests"][test_type.to_s].each do |test_info|
57
+ name = :"test_#{test_type}_#{test_info['description']}"
58
+ define_method name do
59
+ @test_info = test_info
60
+ instance_eval(&block)
61
+ end
62
+ end
63
+ end
64
+
65
+ public
66
+
67
+ # Extractor Conformance
68
+ def_conformance_test("extract.yml", :replies) do
69
+ assert_equal expected, extract_reply_screen_name(text), description
70
+ end
71
+
72
+ def_conformance_test("extract.yml", :mentions) do
73
+ assert_equal expected, extract_mentioned_screen_names(text), description
74
+ end
75
+
76
+ def_conformance_test("extract.yml", :mentions_with_indices) do
77
+ e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
78
+ assert_equal e, extract_mentioned_screen_names_with_indices(text), description
79
+ end
80
+
81
+ def_conformance_test("extract.yml", :mentions_or_lists_with_indices) do
82
+ e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
83
+ assert_equal e, extract_mentions_or_lists_with_indices(text), description
84
+ end
85
+
86
+ def_conformance_test("extract.yml", :urls) do
87
+ assert_equal expected, extract_urls(text), description
88
+ expected.each do |expected_url|
89
+ assert_equal true, valid_url?(expected_url, true, false), "expected url [#{expected_url}] not valid"
90
+ end
91
+ end
92
+
93
+ def_conformance_test("extract.yml", :urls_with_indices) do
94
+ e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
95
+ assert_equal e, extract_urls_with_indices(text), description
96
+ end
97
+
98
+ def_conformance_test("extract.yml", :hashtags) do
99
+ assert_equal expected, extract_hashtags(text), description
100
+ end
101
+
102
+ def_conformance_test("extract.yml", :hashtags_with_indices) do
103
+ e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
104
+ assert_equal e, extract_hashtags_with_indices(text), description
105
+ end
106
+
107
+ def_conformance_test("extract.yml", :cashtags) do
108
+ assert_equal expected, extract_cashtags(text), description
109
+ end
110
+
111
+ def_conformance_test("extract.yml", :cashtags_with_indices) do
112
+ e = expected.map{|elem| elem.inject({}){|h, (k,v)| h[k.to_sym] = v; h} }
113
+ assert_equal e, extract_cashtags_with_indices(text), description
114
+ end
115
+
116
+ # Autolink Conformance
117
+ def_conformance_test("autolink.yml", :usernames) do
118
+ assert_equal_without_attribute_order expected, auto_link_usernames_or_lists(text, :suppress_no_follow => true), description
119
+ end
120
+
121
+ def_conformance_test("autolink.yml", :lists) do
122
+ assert_equal_without_attribute_order expected, auto_link_usernames_or_lists(text, :suppress_no_follow => true), description
123
+ end
124
+
125
+ def_conformance_test("autolink.yml", :urls) do
126
+ assert_equal_without_attribute_order expected, auto_link_urls(text, :suppress_no_follow => true), description
127
+ end
128
+
129
+ def_conformance_test("autolink.yml", :hashtags) do
130
+ assert_equal_without_attribute_order expected, auto_link_hashtags(text, :suppress_no_follow => true), description
131
+ end
132
+
133
+ def_conformance_test("autolink.yml", :cashtags) do
134
+ assert_equal_without_attribute_order expected, auto_link_cashtags(text, :suppress_no_follow => true), description
135
+ end
136
+
137
+ def_conformance_test("autolink.yml", :all) do
138
+ assert_equal_without_attribute_order expected, auto_link(text, :suppress_no_follow => true), description
139
+ end
140
+
141
+ def_conformance_test("autolink.yml", :json) do
142
+ assert_equal_without_attribute_order expected, auto_link_with_json(text, MultiJson.load(json), :suppress_no_follow => true), description
143
+ end
144
+
145
+ # HitHighlighter Conformance
146
+ def_conformance_test("hit_highlighting.yml", :plain_text) do
147
+ assert_equal expected, hit_highlight(text, hits), description
148
+ end
149
+
150
+ def_conformance_test("hit_highlighting.yml", :with_links) do
151
+ assert_equal expected, hit_highlight(text, hits), description
152
+ end
153
+
154
+ # Validation Conformance
155
+ def_conformance_test("validate.yml", :tweets) do
156
+ assert_equal expected, valid_tweet_text?(text), description
157
+ end
158
+
159
+ def_conformance_test("validate.yml", :usernames) do
160
+ assert_equal expected, valid_username?(text), description
161
+ end
162
+
163
+ def_conformance_test("validate.yml", :lists) do
164
+ assert_equal expected, valid_list?(text), description
165
+ end
166
+
167
+ def_conformance_test("validate.yml", :urls) do
168
+ assert_equal expected, valid_url?(text), description
169
+ end
170
+
171
+ def_conformance_test("validate.yml", :urls_without_protocol) do
172
+ assert_equal expected, valid_url?(text, true, false), description
173
+ end
174
+
175
+ def_conformance_test("validate.yml", :hashtags) do
176
+ assert_equal expected, valid_hashtag?(text), description
177
+ end
178
+
179
+ def_conformance_test("validate.yml", :lengths) do
180
+ assert_equal expected, tweet_length(text), description
181
+ end
182
+ end