twitter-text-editted 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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', __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
@@ -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'
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', __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-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