rad_kit 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/lib/components/captcha.rb +24 -0
  2. data/lib/components/captcha.yml +2 -0
  3. data/lib/kit/{http_controller → controller}/authorized.rb +1 -1
  4. data/lib/kit/controller/captcha.rb +23 -0
  5. data/lib/kit/{http_controller → controller}/localized.rb +1 -1
  6. data/lib/kit/controller.rb +5 -0
  7. data/lib/kit/gems.rb +10 -6
  8. data/lib/kit/i18n/locales/ru/pluralization.rb +62 -0
  9. data/lib/kit/i18n.rb +16 -0
  10. data/lib/kit/kit.rb +10 -7
  11. data/lib/kit/kit_text_utils.rb +30 -0
  12. data/lib/kit/misc/prepare_model.rb +16 -0
  13. data/lib/kit/misc/user_error.rb +12 -0
  14. data/lib/kit/models/attachments_uploader_helper.rb +1 -1
  15. data/lib/kit/models/authorized.rb +6 -8
  16. data/lib/kit/models/authorized_object.rb +3 -4
  17. data/lib/kit/models/{micelaneous.rb → miscellaneous.rb} +0 -0
  18. data/lib/kit/models_after.rb +1 -1
  19. data/lib/kit/mongoid/{rad_micelaneous.rb → rad_miscellaneous.rb} +1 -1
  20. data/lib/kit/mongoid/text_processor.rb +1 -1
  21. data/lib/kit/mongoid.rb +2 -2
  22. data/lib/kit/spec.rb +1 -2
  23. data/lib/kit/tasks.rb +1 -1
  24. data/lib/text_utils/code_highlighter.rb +75 -0
  25. data/lib/text_utils/custom_markdown.rb +64 -0
  26. data/lib/text_utils/ensure_utf.rb +14 -0
  27. data/lib/text_utils/format_qualifier.rb +13 -0
  28. data/lib/{kit/text_utils → text_utils}/html_sanitizer.rb +8 -6
  29. data/lib/text_utils/markdown.rb +33 -0
  30. data/lib/text_utils/pipe.rb +16 -0
  31. data/lib/text_utils/processor.rb +14 -0
  32. data/lib/text_utils/support.rb +15 -0
  33. data/lib/text_utils/truncate.rb +30 -0
  34. data/lib/text_utils.rb +23 -0
  35. data/readme.md +1 -8
  36. data/spec/controller/authorization_spec.rb +1 -1
  37. data/spec/controller/captcha_spec.rb +66 -0
  38. data/spec/i18n/i18n_spec/locales/en/general.yml +5 -0
  39. data/spec/i18n/i18n_spec/locales/ru/general.yml +7 -0
  40. data/spec/i18n/i18n_spec.rb +32 -0
  41. data/spec/misc/kit_text_utils_spec.rb +29 -0
  42. data/spec/misc/prepare_model_spec.rb +37 -0
  43. data/spec/misc/user_error_spec.rb +38 -0
  44. data/spec/models/authorized_object_spec.rb +10 -3
  45. data/spec/spec_helper/factories.rb +4 -0
  46. data/spec/spec_helper/user.rb +2 -3
  47. data/spec/spec_helper.rb +0 -5
  48. data/spec/text_utils/code_highlighter_spec.rb +38 -0
  49. data/spec/text_utils/custom_markdown_spec.rb +82 -0
  50. data/spec/text_utils/format_qualifier_spec.rb +37 -0
  51. data/spec/text_utils/html_sanitizer_spec.rb +88 -0
  52. data/spec/text_utils/markdown_spec.rb +114 -0
  53. data/spec/text_utils/pipe_spec.rb +35 -0
  54. data/spec/text_utils/spec_helper.rb +26 -0
  55. data/spec/text_utils/text_processor_shared.rb +9 -0
  56. data/spec/text_utils/truncate_spec.rb +22 -0
  57. metadata +57 -47
  58. data/lib/kit/http_controller.rb +0 -4
  59. data/lib/kit/text_utils/code_highlighter.rb +0 -58
  60. data/lib/kit/text_utils/custom_markdown.rb +0 -90
  61. data/lib/kit/text_utils/ensure_utf.rb +0 -8
  62. data/lib/kit/text_utils/github_flavoured_markdown.rb +0 -32
  63. data/lib/kit/text_utils/image_box.rb +0 -35
  64. data/lib/kit/text_utils/markup.rb +0 -43
  65. data/lib/kit/text_utils/processor.rb +0 -25
  66. data/lib/kit/text_utils/tag_shortcuts.rb +0 -14
  67. data/lib/kit/text_utils/truncate.rb +0 -29
  68. data/lib/kit/text_utils/truncator.rb +0 -15
  69. data/lib/kit/text_utils/urls.rb +0 -13
  70. data/lib/kit/text_utils.rb +0 -43
  71. data/spec/utils/text_utils_spec.rb +0 -280
@@ -0,0 +1,16 @@
1
+ class TextUtils::Pipe
2
+ def initialize *processors
3
+ @processor = processors.reverse.inject nil do |next_processor, meta|
4
+ klass, args = if meta.is_a? Array
5
+ [meta[0], meta[1..-1]]
6
+ else
7
+ [meta, []]
8
+ end
9
+ klass.new next_processor, *args
10
+ end
11
+ end
12
+
13
+ def call text
14
+ @processor.call text, {}
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ class TextUtils::Processor
2
+ def initialize next_processor = nil
3
+ @next_processor = next_processor
4
+ end
5
+
6
+ protected
7
+ def call_next data, env
8
+ if @next_processor
9
+ @next_processor.call data, env
10
+ else
11
+ data
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ Object.class_eval do
2
+ unless method_defined? :try
3
+ def try method
4
+ self && self.send(method)
5
+ end
6
+ end
7
+ end
8
+
9
+
10
+ class String
11
+ def self.random length = 3
12
+ @digits ||= ('a'..'z').to_a + (0..9).to_a
13
+ (0..(length-1)).map{@digits[rand(@digits.size)]}.join
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ class TextUtils::Truncate < TextUtils::Processor
2
+ def initialize processor, length
3
+ super processor
4
+ @length = length
5
+ end
6
+
7
+ def call data, env
8
+ data ||= ""
9
+
10
+ # strip from HTML tags
11
+ data = data.gsub("<br", " <br").gsub("<p", " <p") # to preserve space in place of <> html elements
12
+ doc = Nokogiri::XML("<div class='root'>#{data}</div>")
13
+ data = doc.css('.root').first.content
14
+
15
+ # remove clear space
16
+ data = data.gsub(/\s+/, ' ')
17
+
18
+ # truncate with no broken words
19
+ data = if data.length >= @length
20
+ shortened = data[0, @length]
21
+ splitted = shortened.split(/\s/)
22
+ words = splitted.length
23
+ splitted[0, words-1].join(" ") + ' ...'
24
+ else
25
+ data
26
+ end
27
+
28
+ call_next data, env
29
+ end
30
+ end
data/lib/text_utils.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'digest/md5'
2
+ require 'iconv'
3
+
4
+ require 'nokogiri'
5
+ require 'bluecloth'
6
+ require 'sanitize'
7
+
8
+ module TextUtils; end
9
+
10
+ %w(
11
+ support
12
+
13
+ processor
14
+
15
+ ensure_utf
16
+ html_sanitizer
17
+ format_qualifier
18
+ truncate
19
+ markdown
20
+ custom_markdown
21
+ pipe
22
+ code_highlighter
23
+ ).each{|f| require "text_utils/#{f}"}
data/readme.md CHANGED
@@ -1,10 +1,3 @@
1
1
  # Rapid Application Development Kit for Rad Framework
2
2
 
3
- [add docs]
4
-
5
- # Low
6
-
7
- - WYSIWYG from WordPress/GitHub
8
- - remove unused locale tokens
9
-
10
- # TODO
3
+ [add docs]
@@ -15,7 +15,7 @@ describe "Authorizations" do
15
15
  class ::AuthorizationController
16
16
  inherit Rad::Controller::Http
17
17
 
18
- inherit Rad::Controller::Http::Authorized
18
+ inherit Rad::Controller::Authorized
19
19
 
20
20
  require_permission :call_controller_level, only: :controller_level
21
21
 
@@ -0,0 +1,66 @@
1
+ require "spec_helper"
2
+
3
+ describe "Captcha" do
4
+ before :all do
5
+ class TheController
6
+ inherit Rad::Controller::Http, Rad::Controller::Captcha
7
+
8
+ def action
9
+ render inline: 'ok'
10
+ end
11
+ end
12
+ end
13
+ after(:all){remove_constants :TheController}
14
+
15
+ before do
16
+ rad.captcha.stub!(:enabled).and_return(true)
17
+
18
+ @request = Rad::Http::Request.stub
19
+ @request.stub!(:from_browser?).and_return(true)
20
+
21
+ @params = Rad::Conveyors::Params.new
22
+
23
+ @controller = TheController.new
24
+ @controller.stub!(:request).and_return(@request)
25
+ @controller.stub!(:params).and_return(@params)
26
+ end
27
+
28
+ it "should allow get for anyone" do
29
+ @request.stub!(:get?).and_return(true)
30
+
31
+ rad.user = Factory.build :anonymous
32
+ @controller.call(:action).should == 'ok'
33
+
34
+ rad.user = Factory.build :registered
35
+ @controller.call(:action).should == 'ok'
36
+ end
37
+
38
+ it "shouldn't allow non-get for anonymous" do
39
+ @request.stub!(:get?).and_return(false)
40
+
41
+ rad.user = Factory.build :anonymous
42
+ -> {@controller.call(:action)}.should raise_error(UserError)
43
+
44
+ rad.user = Factory.build :registered
45
+ @controller.call(:action).should == 'ok'
46
+ end
47
+
48
+ it "should display captcha form for anonymous if format is :js" do
49
+ @request.stub!(:get?).and_return(false)
50
+ @params.format = 'js'
51
+ rad.captcha.stub!(:verify).and_return(false)
52
+ rad.user = Factory.build :anonymous
53
+ @controller.stub!(:render).and_return('form')
54
+
55
+ @controller.call(:action).should == 'form'
56
+ end
57
+
58
+ it "should allow anonymous access if it solved captcha" do
59
+ @request.stub!(:get?).and_return(false)
60
+ @params.format = 'js'
61
+ rad.captcha.stub!(:verify).and_return(true)
62
+ rad.user = Factory.build :anonymous
63
+
64
+ @controller.call(:action).should == 'ok'
65
+ end
66
+ end
@@ -0,0 +1,5 @@
1
+ en:
2
+ name: "Name"
3
+ comments:
4
+ one: "%{count} comment"
5
+ many: "%{count} comments"
@@ -0,0 +1,7 @@
1
+ ru:
2
+ name: "Имя"
3
+ comments_count:
4
+ one: "%{count} комментарий"
5
+ few: "%{count} комментария"
6
+ many: "%{count} комментариев"
7
+ other: "%{count} комментариев"
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe 'I18n' do
6
+ before :all do
7
+ rad.web
8
+ rad.reset :conveyors
9
+
10
+ I18n.load_path += Dir["#{spec_dir}/locales/*/*.{rb,yml}"]
11
+ end
12
+
13
+ def t *args
14
+ I18n.t *args
15
+ end
16
+
17
+ it "basic" do
18
+ I18n.locale = 'en'
19
+ t(:name).should == "Name"
20
+ t(:name).is_a?(String).should be_true
21
+
22
+ I18n.locale = 'ru'
23
+ t(:name).should == "Имя"
24
+ end
25
+
26
+ it "pluggable pluralization" do
27
+ I18n.locale = 'ru'
28
+ t(:comments_count, count: 1).should == "1 комментарий"
29
+ t(:comments_count, count: 2).should == "2 комментария"
30
+ t(:comments_count, count: 5).should == "5 комментариев"
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ describe "TextUtils with Kit extensions" do
4
+ it "should truncate" do
5
+ TextUtils.truncate('lorem ipsum', 10).should == 'lorem ...'
6
+ end
7
+
8
+ it "should process markdown (should qualify markdown format automatically)" do
9
+ TextUtils.markup('lorem **ipsum**').should == '<p>lorem <strong>ipsum</strong></p>'
10
+ end
11
+
12
+ it "should process html (should qualify html format automatically)" do
13
+ TextUtils.markup('<p>lorem **ipsum**</p>').should == '<p>lorem **ipsum**</p>'
14
+ end
15
+
16
+ it "code highlight" do
17
+ rad.config.stub!(:use_code_highlighter).and_return(true)
18
+
19
+ markdown = <<MARKDOWN
20
+ code
21
+
22
+ ``` ruby
23
+ puts "Hello World"
24
+ ```
25
+ MARKDOWN
26
+
27
+ TextUtils.markup(markdown).should =~ /span/i
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe "User Error" do
4
+ isolate :conveyors, :router, before: :all
5
+
6
+ before :all do
7
+ rad.delete :conveyors
8
+ rad.conveyors.web do |web|
9
+ web.use Rad::Controller::Processors::ControllerCaller
10
+ end
11
+
12
+ class ::SomeModel
13
+ def self.find! id
14
+ id.should == 'some id'
15
+ SomeModel.new
16
+ end
17
+ end
18
+ end
19
+ after :all do
20
+ remove_constants %w(SomeModel ControllerSpec)
21
+ end
22
+
23
+ it "user error" do
24
+ class ::ControllerSpec
25
+ inherit Rad::Controller::Http
26
+
27
+ prepare_model SomeModel, id: :some_model, variable: 'some_model'
28
+
29
+ def action
30
+ @some_model.should_not == nil
31
+ render inline: 'ok'
32
+ end
33
+ end
34
+
35
+ ccall(ControllerSpec, :action, some_model: 'some id').should == 'ok'
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe "User Error" do
4
+ isolate :conveyors, :router, before: :all
5
+
6
+ before(:all) do
7
+ rad.delete :conveyors
8
+ rad.conveyors.web do |web|
9
+ web.use Rad::Controller::Processors::ControllerCaller
10
+ end
11
+ end
12
+
13
+ after :all do
14
+ remove_constants %w(UserErrorSpec)
15
+ end
16
+
17
+ it "user error" do
18
+ class ::UserErrorSpec
19
+ inherit Rad::Controller::Http
20
+
21
+ def action
22
+ raise_user_error "some error"
23
+ end
24
+
25
+ protected
26
+ def catch_user_error
27
+ begin
28
+ yield
29
+ rescue UserError => ue
30
+ render inline: "Catched #{ue.message}"
31
+ end
32
+ end
33
+ around :catch_user_error
34
+ end
35
+
36
+ ccall(UserErrorSpec, :action).should == "Catched some error"
37
+ end
38
+ end
@@ -98,6 +98,13 @@ describe "Authorized Object" do
98
98
  o.normalized_collaborators.should == %w{manager member user:auser}
99
99
  end
100
100
 
101
+ it "anonymous should never be collaborator (from error)" do
102
+ @user = Factory.build :anonymous
103
+ o = AModel.new
104
+ o.owner = @user
105
+ o.normalized_collaborators.should == []
106
+ end
107
+
101
108
  it "viewers and collaborators dependance" do
102
109
  o = AModel.new
103
110
  o.owner = @user
@@ -174,8 +181,8 @@ describe "Authorized Object" do
174
181
  @object = AModel.new
175
182
  @owned_object = AModel.new
176
183
  @owned_object.owner = @user
177
- end
178
-
184
+ end
185
+
179
186
  it "owner?" do
180
187
  @user.should_not be_owner(@object)
181
188
  @user.should be_owner(@owned_object)
@@ -189,7 +196,7 @@ describe "Authorized Object" do
189
196
 
190
197
  @user.should_not be_owner(@owned_object)
191
198
  end
192
-
199
+
193
200
  it 'permissions for owner' do
194
201
  @user.can?(:manage, @object).should be_false
195
202
  @user.can?(:manage, @owned_object).should be_true
@@ -11,6 +11,10 @@ Factory.define :user, class: 'Models::User' do |u|
11
11
  u.sequence(:name){|i| "user#{i}"}
12
12
  end
13
13
 
14
+ Factory.define :registered, parent: :user do |u|
15
+ u.name 'alex'
16
+ end
17
+
14
18
  Factory.define :admin, parent: :user do |u|
15
19
  u.admin true
16
20
  end
@@ -5,9 +5,8 @@ class Models::User
5
5
 
6
6
  include Mongoid::Authorized
7
7
 
8
- def anonymous?
9
- name == 'anonymous'
10
- end
8
+ # def self.anonymous_name; 'anonymous' end
9
+ # def anonymous?; name == self.class.anonymous_name end
11
10
 
12
11
  class << self
13
12
  def avatar_url *a; end
data/spec/spec_helper.rb CHANGED
@@ -3,11 +3,6 @@ require 'rspec_ext'
3
3
  require 'rad'
4
4
  require 'rad/spec'
5
5
 
6
- require 'rad_ext'
7
- require 'rad_ext/spec'
8
-
9
- # require 'mongoid_rad'
10
-
11
6
  require 'kit/spec'
12
7
 
13
8
  # rad.config.runtime_path = 'tmp'
@@ -0,0 +1,38 @@
1
+ require 'text_utils/spec_helper'
2
+
3
+ describe "Markdown" do
4
+ include RSpec::TextUtilsHelper
5
+
6
+ before do
7
+ @processor = TextUtils::CodeHighlighter.new
8
+ @options = {format: :html}
9
+ end
10
+
11
+ it_should_behave_like 'text processor'
12
+
13
+ it "basic" do
14
+ process(%{<p> text </p><code lang='ruby'>class A; p "Hello World" end</code><p> text </p>}).should =~ /span/i
15
+ process(%{<p> text </p><code language='ruby'>class A; p "Hello World" end</code><p> text </p>}).should =~ /span/i
16
+ process(%{<code lang='ruby'>\nclass A \n def p\n 10\n end\nend \n</code>}).should =~ /span/i
17
+ end
18
+
19
+ it "should works with < and > in code" do
20
+ process(%{<code lang='ruby'>class A < ClassB; end</code>}).should include('ClassB')
21
+ end
22
+
23
+ it 'should preserve custom classes in <code>' do
24
+ process(%{<code lang='ruby' class='my_code'>\nclass A \n def p\n 10\n end\nend \n</code>}).should =~ /my_code/i
25
+ end
26
+
27
+ it 'markdown' do
28
+ markdown = <<MARKDOWN
29
+ text
30
+
31
+ ``` ruby
32
+ print "Hello World"
33
+ ```
34
+ MARKDOWN
35
+
36
+ process(markdown).should =~ /span/i
37
+ end
38
+ end
@@ -0,0 +1,82 @@
1
+ require 'text_utils/spec_helper'
2
+
3
+ describe "Markdown" do
4
+ include RSpec::TextUtilsHelper
5
+
6
+ before do
7
+ markdown = TextUtils::Markdown.new
8
+ @processor = TextUtils::CustomMarkdown.new markdown
9
+ @options = {format: :markdown}
10
+ end
11
+
12
+ it_should_behave_like 'text processor'
13
+
14
+ it "should apply markup inside of html elements (from error)" do
15
+ html = <<HTML
16
+ <div class='right'>
17
+ ![img]
18
+ </div>
19
+
20
+ [img]: /some_link
21
+ HTML
22
+
23
+ to_doc(html).css('.right img').size.should == 1
24
+ end
25
+
26
+ it "should leave existing links intact" do
27
+ doc = to_doc(%{<a href="http://some_domain.com">http://some_domain.com</a>})
28
+ doc.css('a').size.should == 1
29
+ doc.css('a').first['href'].should == "http://some_domain.com"
30
+ end
31
+
32
+ describe 'image box' do
33
+ it "should use simplifyed syntax for image boxes (!![img_thumb] => [![img_thumb]][img_full_version])" do
34
+ html = <<HTML
35
+ !![img]
36
+ ![img]
37
+
38
+ !![img_2]
39
+ ![img_2]
40
+
41
+ [img]: /some_prefix/image_name.png
42
+ [img_2]: /some_prefix/image_name2.icon.png
43
+ HTML
44
+
45
+ doc = to_doc html
46
+ doc.css('a').first.to_fuzzy_hash.should == {href: "/some_prefix/image_name.png"}
47
+ doc.css('a img').first.to_fuzzy_hash.should == {src: "/some_prefix/image_name.thumb.png"}
48
+
49
+ doc.css('a').last.to_fuzzy_hash.should == {href: "/some_prefix/image_name2.png"}
50
+ doc.css('a img').last.to_fuzzy_hash.should == {src: "/some_prefix/image_name2.icon.png"}
51
+
52
+ doc.css('img').size.should == 4
53
+ end
54
+
55
+ it "simplifyed syntax for image boxes should be robust (from error)" do
56
+ html = "!![img] " # without resolved reference
57
+ lambda{process(html)}.should_not raise_error
58
+ end
59
+ end
60
+
61
+ # discarded
62
+ # it "clear div" do
63
+ # html = "[clear]"
64
+ #
65
+ # doc = to_doc html
66
+ # doc.css('div.clear').size.should == 1
67
+ # end
68
+
69
+ # discarded
70
+ # it "space div" do
71
+ # html = "[space]"
72
+ #
73
+ # doc = to_doc html
74
+ # doc.css('div.space').size.should == 1
75
+ # end
76
+
77
+ # discarded
78
+ # it "should skip empty paragraphs" do
79
+ # html = "line 1\n\nline 2\n\n\n\nline 3"
80
+ # process(html).should =~ /<p>\s*line 1<\/p>\n<p>line 2<\/p>\n<p>line 3\s*<\/p>.?/
81
+ # end
82
+ end
@@ -0,0 +1,37 @@
1
+ require 'text_utils/spec_helper'
2
+
3
+ describe "FormatQualifier" do
4
+ before do
5
+ @processor = TextUtils::FormatQualifier.new
6
+ end
7
+
8
+ it_should_behave_like 'text processor'
9
+
10
+ it "should qualify format" do
11
+ [
12
+ '<b>some</b>', :html,
13
+ '<b>some</b> <p>other</p>', :html,
14
+ ' <b> some</b> ', :html,
15
+ '<b/>', :html,
16
+ '<b>', :markdown,
17
+ 'abc', :markdown
18
+ ].each_slice 2 do |text, format|
19
+ env = {}
20
+ @processor.call(text, env)
21
+ env[:format].should == format
22
+ end
23
+ end
24
+
25
+ it "should qualify :html (from error)" do
26
+ html = <<HTML
27
+ <h2>Title</h2>
28
+
29
+ <p>body</p>
30
+ HTML
31
+
32
+ env = {}
33
+ @processor.call(html, env)
34
+ env[:format].should == :html
35
+ end
36
+
37
+ end
@@ -0,0 +1,88 @@
1
+ require 'text_utils/spec_helper'
2
+
3
+ describe "HtmlSanitizer" do
4
+ include RSpec::TextUtilsHelper
5
+
6
+ before do
7
+ @processor = TextUtils::HtmlSanitizer.new
8
+ @options = {format: :html}
9
+ end
10
+
11
+ it_should_behave_like 'text processor'
12
+
13
+ it "should escape restricted tags" do
14
+ %w(script object).each do |tag|
15
+ html = "<#{tag}}>some text</#{tag}>"
16
+ process(html).should_not include(tag)
17
+ end
18
+ end
19
+
20
+ it "shouldn't escape non-restricted tags" do
21
+ common_attr_names = %w(class style)
22
+ {
23
+ iframe: %w(height scrolling src width),
24
+ a: %w(href title rel)
25
+ }.each do |tag, attr_names|
26
+ attrs = {}; (common_attr_names + attr_names).each{|n| attrs[n] = 'value'}
27
+
28
+ attrs_html = ""; attrs.each{|n, v| attrs_html << "#{n}='#{v}'"}
29
+ html = "<#{tag} #{attrs_html}}>some text</#{tag}>"
30
+
31
+ to_doc(html).css(tag.to_s).first.to_fuzzy_hash.should == attrs
32
+ end
33
+ end
34
+
35
+ it "should allow image inside of link (from error)" do
36
+ html = <<HTML
37
+ <a rel="images" class="image_box" href="/some_image">
38
+ <img src="/some_image"/>
39
+ </a>
40
+ HTML
41
+
42
+ doc = to_doc html
43
+ doc.css('a').first.to_fuzzy_hash.should == {href: "/some_image", class: "image_box"}
44
+ doc.css('a img').first.to_fuzzy_hash.should == {src: "/some_image"}
45
+ end
46
+
47
+ it "should allow 'a' elements (from error)" do
48
+ html = <<HTML
49
+ <a href="http://www.some.com/some">Absolute Link</a>
50
+ <a href="/some">Relative Link</a>
51
+ HTML
52
+
53
+ doc = to_doc html
54
+ doc.css("a").first[:href].should == "http://www.some.com/some"
55
+ doc.css("a").last[:href].should == "/some"
56
+ end
57
+
58
+ it "should allow div with any classes (from error)" do
59
+ html = %{<div class="col3 left"><a href='#'>text</a></div>}
60
+ to_doc(html).css("div.col3.left a").size.should == 1
61
+ end
62
+
63
+ it "should embed YouTube Videos" do
64
+ html =<<HTML
65
+ <object width="425" height="344">
66
+ <param name="movie" value="http://www.youtube.com/v/s8hYKKXV5wU&hl=en_US&fs=1&"></param>
67
+ <param name="allowFullScreen" value="true"></param>
68
+ <param name="allowscriptaccess" value="always"></param>
69
+ <embed src="http://www.youtube.com/v/s8hYKKXV5wU&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed>
70
+ </object>
71
+ HTML
72
+
73
+ doc = to_doc html
74
+ obj = doc.css("object").first.to_fuzzy_hash.should == {width: '425', height: '344'}
75
+ p1, p2, p3, embed = doc.css('object *')
76
+ p1.to_fuzzy_hash.should == {name: 'movie', value: 'http://www.youtube.com/v/s8hYKKXV5wU&hl=en_US&fs=1&'}
77
+ p2.to_fuzzy_hash.should == {name: 'allowFullScreen', value: 'true'}
78
+ p3.to_fuzzy_hash.should == {name: 'allowscriptaccess', value: 'always'}
79
+ embed.to_fuzzy_hash.should == {
80
+ src: 'http://www.youtube.com/v/s8hYKKXV5wU&hl=en_US&fs=1&',
81
+ type: 'application/x-shockwave-flash',
82
+ allowscriptaccess: 'always',
83
+ allowfullscreen: 'true',
84
+ width: '425',
85
+ height: '344'
86
+ }
87
+ end
88
+ end