rails_blog_engine 0.0.1 → 0.0.2

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.
Files changed (79) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +24 -0
  4. data/Guardfile +21 -0
  5. data/Procfile +1 -0
  6. data/TODO.txt +31 -0
  7. data/app/assets/images/rails_blog_engine/.gitkeep +0 -0
  8. data/app/controllers/rails_blog_engine/comments_controller.rb +1 -1
  9. data/app/controllers/rails_blog_engine/posts_controller.rb +1 -1
  10. data/lib/rails_blog_engine.rb +2 -0
  11. data/lib/rails_blog_engine/version.rb +1 -1
  12. data/rails_blog_engine.gemspec +64 -0
  13. data/script/rails +6 -0
  14. data/spec/acceptance/acceptance_helper.rb +4 -0
  15. data/spec/acceptance/rails_blog_engine/posts_admin_spec.rb +33 -0
  16. data/spec/acceptance/rails_blog_engine/posts_spec.rb +74 -0
  17. data/spec/acceptance/rails_blog_engine/spam_filtering_spec.rb +110 -0
  18. data/spec/acceptance/support/helpers.rb +18 -0
  19. data/spec/acceptance/support/paths.rb +5 -0
  20. data/spec/controllers/rails_blog_engine/comments_controller_spec.rb +22 -0
  21. data/spec/controllers/rails_blog_engine/posts_controller_spec.rb +81 -0
  22. data/spec/dummy/Rakefile +7 -0
  23. data/spec/dummy/app/assets/javascripts/application.js +10 -0
  24. data/spec/dummy/app/assets/stylesheets/application.css +8 -0
  25. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  26. data/spec/dummy/app/controllers/home_controller.rb +4 -0
  27. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  28. data/spec/dummy/app/mailers/.gitkeep +0 -0
  29. data/spec/dummy/app/models/.gitkeep +0 -0
  30. data/spec/dummy/app/models/ability.rb +11 -0
  31. data/spec/dummy/app/models/user.rb +9 -0
  32. data/spec/dummy/app/views/home/index.html.haml +2 -0
  33. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  34. data/spec/dummy/config.ru +4 -0
  35. data/spec/dummy/config/application.rb +54 -0
  36. data/spec/dummy/config/boot.rb +10 -0
  37. data/spec/dummy/config/database.yml +25 -0
  38. data/spec/dummy/config/environment.rb +5 -0
  39. data/spec/dummy/config/environments/development.rb +30 -0
  40. data/spec/dummy/config/environments/production.rb +60 -0
  41. data/spec/dummy/config/environments/test.rb +42 -0
  42. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  43. data/spec/dummy/config/initializers/devise.rb +211 -0
  44. data/spec/dummy/config/initializers/inflections.rb +10 -0
  45. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  46. data/spec/dummy/config/initializers/rails_blog_engine.rb +13 -0
  47. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  48. data/spec/dummy/config/initializers/session_store.rb +8 -0
  49. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  50. data/spec/dummy/config/locales/devise.en.yml +58 -0
  51. data/spec/dummy/config/locales/en.yml +5 -0
  52. data/spec/dummy/config/routes.rb +6 -0
  53. data/spec/dummy/db/migrate/20110913113004_devise_create_users.rb +28 -0
  54. data/spec/dummy/db/schema.rb +67 -0
  55. data/spec/dummy/lib/assets/.gitkeep +0 -0
  56. data/spec/dummy/log/.gitkeep +0 -0
  57. data/spec/dummy/public/404.html +26 -0
  58. data/spec/dummy/public/422.html +26 -0
  59. data/spec/dummy/public/500.html +26 -0
  60. data/spec/dummy/public/favicon.ico +0 -0
  61. data/spec/dummy/script/rails +6 -0
  62. data/spec/helpers/rails_blog_engine/application_helper_spec.rb +56 -0
  63. data/spec/helpers/rails_blog_engine/comments_helper_spec.rb +14 -0
  64. data/spec/helpers/rails_blog_engine/posts_helper_spec.rb +14 -0
  65. data/spec/lib/generators/rails_blog_engine/install_generator_spec.rb +78 -0
  66. data/spec/lib/rails_blog_engine/filters/base_spec.rb +13 -0
  67. data/spec/lib/rails_blog_engine/filters/code_spec.rb +16 -0
  68. data/spec/lib/rails_blog_engine/filters_spec.rb +58 -0
  69. data/spec/models/rails_blog_engine/comment_spec.rb +84 -0
  70. data/spec/models/rails_blog_engine/post_spec.rb +121 -0
  71. data/spec/spec_helper.rb +36 -0
  72. data/spec/support/blueprints.rb +25 -0
  73. data/spec/support/javascript.rb +27 -0
  74. data/spec/support/vcr.rb +8 -0
  75. data/spec/vcr_cassettes/rakismet-ham.yml +30 -0
  76. data/spec/vcr_cassettes/rakismet-spam.yml +30 -0
  77. data/spec/vcr_cassettes/rakismet-train-as-ham.yml +32 -0
  78. data/spec/vcr_cassettes/rakismet-train-as-spam.yml +32 -0
  79. metadata +175 -101
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
24
+ </div>
25
+ </body>
26
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::ApplicationHelper do
4
+ class SillyFilter < RailsBlogEngine::Filters::Base
5
+ register_filter :silly
6
+
7
+ def process(text, options)
8
+ "Whee"
9
+ end
10
+ end
11
+
12
+ describe ".markdown" do
13
+ it "converts markdown text to HTML" do
14
+ helper.markdown("_foo_ bar").should match(/<em>foo<\/em> bar/)
15
+ end
16
+
17
+ it "does not pass scripts" do
18
+ helper.markdown("<script>foo</script>").
19
+ should_not match(/script/)
20
+ end
21
+
22
+ it "applies filters" do
23
+ helper.markdown("<filter:silly/>").should match(/Whee/)
24
+ end
25
+
26
+ context "for trusted users" do
27
+ it "allows images" do
28
+ helper.markdown("<img src='foo.png'>", :trusted? => true).
29
+ should match(/<img/)
30
+ end
31
+
32
+ it "does not add nofollow to links" do
33
+ helper.markdown("<a href='foo.html'>", :trusted? => true).
34
+ should_not match(/nofollow/)
35
+ end
36
+
37
+ it "allows code highlighting" do
38
+ formatted = helper.markdown(<<EOD, :trusted? => true)
39
+ <div class='foo'><span class='bar'></span></div>
40
+ EOD
41
+ formatted.should match(/foo/)
42
+ formatted.should match(/bar/)
43
+ end
44
+ end
45
+
46
+ context "for untrusted users" do
47
+ it "does not allow images" do
48
+ helper.markdown("<img src='foo.png'>").should_not match(/<img/)
49
+ end
50
+
51
+ it "adds nofollow to links" do
52
+ helper.markdown("<a href='foo.html'>").should match(/nofollow/)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ # Specs in this file have access to a helper object that includes
4
+ # the CommentsHelper. For example:
5
+ #
6
+ # describe CommentsHelper do
7
+ # describe "string concat" do
8
+ # it "concats two strings with spaces" do
9
+ # helper.concat_strings("this","that").should == "this that"
10
+ # end
11
+ # end
12
+ # end
13
+ describe RailsBlogEngine::CommentsHelper do
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ # Specs in this file have access to a helper object that includes
4
+ # the PostsHelper. For example:
5
+ #
6
+ # describe PostsHelper do
7
+ # describe "string concat" do
8
+ # it "concats two strings with spaces" do
9
+ # helper.concat_strings("this","that").should == "this that"
10
+ # end
11
+ # end
12
+ # end
13
+ describe RailsBlogEngine::PostsHelper do
14
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper.rb"
2
+ require "generator_spec/test_case"
3
+ require "generators/rails_blog_engine/install/install_generator"
4
+
5
+ describe RailsBlogEngine::InstallGenerator do
6
+ include GeneratorSpec::TestCase
7
+ destination File.expand_path("../../../../tmp", __FILE__)
8
+
9
+ # Create a file with the specified contents in our fake application tree.
10
+ # This is faster than generating a real application every time we test.
11
+ def create_file(path, contents)
12
+ full_path = ::File.join(destination_root, path)
13
+ mkdir_p(::File.dirname(full_path))
14
+ ::File.open(full_path, 'w') {|f| f.write(contents) }
15
+ end
16
+
17
+ def prepare_destination
18
+ super
19
+ create_file('config/routes.rb', <<EOD)
20
+ Dummy::Application.routes.draw do
21
+ end
22
+ EOD
23
+ create_file('app/assets/stylesheets/application.css', <<EOD)
24
+ /*
25
+ *= require_self
26
+ *= require_tree .
27
+ */
28
+ EOD
29
+ create_file('app/assets/javascripts/application.js', <<EOD)
30
+ //= require jquery
31
+ //= require jquery_ujs
32
+ //= require_tree .
33
+ EOD
34
+ end
35
+
36
+ before do
37
+ prepare_destination
38
+ run_generator
39
+ end
40
+
41
+ it "generates the expected files" do
42
+ destination_root.should have_structure {
43
+ directory "app" do
44
+ directory "assets" do
45
+ directory "javascripts" do
46
+ file "application.js" do
47
+ contains "//= require rails_blog_engine"
48
+ end
49
+ end
50
+ directory "stylesheets" do
51
+ file "application.css" do
52
+ contains " *= require rails_blog_engine"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ directory "config" do
58
+ file "routes.rb" do
59
+ contains 'mount RailsBlogEngine::Engine => "/blog"'
60
+ end
61
+ directory "initializers" do
62
+ file "rails_blog_engine.rb"
63
+ end
64
+ directory "locales" do
65
+ file "rails_blog_engine.en.yml"
66
+ end
67
+ end
68
+ directory "db" do
69
+ directory "migrate" do
70
+ # We don't need to test every single one of these--just make sure
71
+ # a couple are copied over, and the rest should be OK.
72
+ file "20110912153527_create_rails_blog_engine_posts.rb"
73
+ file "20110913190319_add_fields_to_rails_blog_engine_post.rb"
74
+ end
75
+ end
76
+ }
77
+ end
78
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Filters::Base do
4
+ let(:filter) { RailsBlogEngine::Filters::Base.new }
5
+
6
+ describe "#process" do
7
+ it "raises an error if not overridden" do
8
+ lambda do
9
+ filter.process("text", {})
10
+ end.should raise_error(/override/)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Filters::Code do
4
+ let(:filter) { RailsBlogEngine::Filters::Code.new }
5
+
6
+ describe ".process" do
7
+ it "applies syntax highlighting to code blocks" do
8
+ output = filter.process(<<END_OF_CODE, :lang => 'ruby')
9
+ def foo
10
+ 42
11
+ end
12
+ END_OF_CODE
13
+ output.should match(/foo/)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Filters do
4
+
5
+ # A sample filter.
6
+ class HelloFilter < RailsBlogEngine::Filters::Base
7
+ register_filter :hello
8
+
9
+ def process(text, options)
10
+ attrs =
11
+ if options[:class] then " class=\"#{options[:class]}\"" else "" end
12
+ name = if text then ", #{text}" else "" end
13
+ "<p#{attrs}>Hello#{name}!</p>"
14
+ end
15
+ end
16
+
17
+ describe ".find" do
18
+ it "returns the filter registered for a name" do
19
+ RailsBlogEngine::Filters.find(:hello).should be_kind_of(HelloFilter)
20
+ end
21
+ end
22
+
23
+ describe ".apply_all_to" do
24
+ %w(filter macro typo).each do |tag|
25
+ it "applies registered filters to empty '#{tag}' tags" do
26
+ RailsBlogEngine::Filters.apply_all_to(<<"END_OF_INPUT").should == <<END_OF_OUTPUT
27
+ <#{tag}:hello/>
28
+ <#{tag}:hello class="example" />
29
+ END_OF_INPUT
30
+ <p>Hello!</p>
31
+ <p class="example">Hello!</p>
32
+ END_OF_OUTPUT
33
+ end
34
+
35
+ it "applies registered filters to '#{tag}' tags with content" do
36
+ RailsBlogEngine::Filters.apply_all_to(<<"END_OF_INPUT").should == <<END_OF_OUTPUT
37
+ <#{tag}:hello>Judy</#{tag}:hello>
38
+ <#{tag}:hello class='example' extra="" >Mike
39
+ Smith</#{tag}:hello>
40
+ END_OF_INPUT
41
+ <p>Hello, Judy!</p>
42
+ <p class="example">Hello, Mike
43
+ Smith!</p>
44
+ END_OF_OUTPUT
45
+ end
46
+ end
47
+
48
+ it "reports errors inline" do
49
+ RailsBlogEngine::Filters.apply_all_to(<<END_OF_INPUT).should == <<END_OF_OUTPUT
50
+ <filter:invalid/>
51
+ <filter:hello class= >Mike</filter:hello>
52
+ END_OF_INPUT
53
+ <p><strong>Text filter not installed: invalid</strong></p>
54
+ <p><strong>Can't parse filter arguments: {{ class= }}</strong></p>
55
+ END_OF_OUTPUT
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Comment do
4
+ it { should belong_to(:post) }
5
+
6
+ it { should_not allow_value('').for(:author_byline) }
7
+ it { should_not allow_value('').for(:body) }
8
+
9
+ describe ".visible" do
10
+ it "includes unfiltered and ham messages, but not spam" do
11
+ @unfiltered = RailsBlogEngine::Comment.make!
12
+ @ham = RailsBlogEngine::Comment.make!(:state => 'filtered_as_ham')
13
+ @ham2 = RailsBlogEngine::Comment.make!(:state => 'marked_as_ham')
14
+ @spam = RailsBlogEngine::Comment.make!(:state => 'filtered_as_spam')
15
+ @spam2 = RailsBlogEngine::Comment.make!(:state => 'marked_as_spam')
16
+ RailsBlogEngine::Comment.visible.should include(@unfiltered)
17
+ RailsBlogEngine::Comment.visible.should include(@ham)
18
+ RailsBlogEngine::Comment.visible.should include(@ham2)
19
+ RailsBlogEngine::Comment.visible.should_not include(@spam)
20
+ RailsBlogEngine::Comment.visible.should_not include(@spam2)
21
+ end
22
+ end
23
+
24
+ describe "#state" do
25
+ subject { RailsBlogEngine::Comment.make! }
26
+
27
+ def enable_spam_filter
28
+ Rakismet.key = "fakekey"
29
+ end
30
+
31
+ def disable_spam_filter
32
+ Rakismet.key = nil
33
+ end
34
+
35
+ it "begins in state unfiltered" do
36
+ subject.should be_unfiltered
37
+ end
38
+
39
+ it "transitions to filtered_as_ham if rakismet likes it" do
40
+ enable_spam_filter
41
+ subject.stub(:spam?) { false }
42
+ subject.run_spam_filter
43
+ subject.should be_filtered_as_ham
44
+ end
45
+
46
+ it "transitions to filtered_as_spam if rakismet doesn't like it" do
47
+ enable_spam_filter
48
+ subject.stub(:spam?) { true }
49
+ subject.run_spam_filter
50
+ subject.should be_filtered_as_spam
51
+ end
52
+
53
+ it "remains unfiltered if the rakismet is not configured" do
54
+ disable_spam_filter
55
+ subject.run_spam_filter
56
+ subject.should be_unfiltered
57
+ end
58
+
59
+ it "supports manually marking a filtered post as ham" do
60
+ enable_spam_filter
61
+ subject.filter_as_spam
62
+ subject.can_mark_as_ham?.should == true
63
+ subject.should_receive(:ham!)
64
+ subject.mark_as_ham
65
+ subject.should be_marked_as_ham
66
+ end
67
+
68
+ it "supports manually marking a filtered post as spam" do
69
+ enable_spam_filter
70
+ subject.filter_as_ham
71
+ subject.can_mark_as_spam?.should == true
72
+ subject.should_receive(:spam!)
73
+ subject.mark_as_spam
74
+ subject.should be_marked_as_spam
75
+ end
76
+
77
+ it "supports manually marking an unfiltered post as spam" do
78
+ disable_spam_filter
79
+ subject.can_mark_as_spam?.should == true
80
+ subject.mark_as_spam
81
+ subject.should be_marked_as_spam
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBlogEngine::Post do
4
+ Post = RailsBlogEngine::Post
5
+
6
+ it { should have_many(:comments) }
7
+
8
+ describe "validations" do
9
+ before { Post.make! }
10
+
11
+ it { should allow_value("Title").for(:title) }
12
+ it { should_not allow_value("").for(:title) }
13
+
14
+ it { should allow_value("Body").for(:body) }
15
+ it { should_not allow_value("").for(:body) }
16
+
17
+ %w(unpublished published).each do |state|
18
+ it { should allow_value(state).for(:state) }
19
+ end
20
+ it { should_not allow_value("invalid").for(:state) }
21
+ it { should validate_presence_of(:state) }
22
+
23
+ it { should allow_value("abc-def").for(:permalink) }
24
+ it { should validate_presence_of(:permalink) }
25
+ it { should validate_uniqueness_of(:permalink) }
26
+
27
+ it { should validate_presence_of(:author) }
28
+ end
29
+
30
+ describe ".recently_published" do
31
+ before do
32
+ base = Time.now
33
+ @posts = (0...3).map do |i|
34
+ Post.make!(:published, :published_at => base + i.seconds)
35
+ end
36
+ @posts[1].published_at = base + 10.seconds
37
+ @posts[1].save!
38
+ @unpublished = Post.make!
39
+ end
40
+
41
+ it "does not include unpublished posts" do
42
+ Post.recently_published.should_not include(@unpublished)
43
+ end
44
+
45
+ it "returns in order of descending published_at" do
46
+ Post.recently_published.should == [@posts[1], @posts[2], @posts[0]]
47
+ end
48
+
49
+ it "excludes posts which have been explicitly unpublished" do
50
+ @posts[1].unpublish!
51
+ Post.recently_published.should == [@posts[2], @posts[0]]
52
+ end
53
+ end
54
+
55
+ describe "state machine" do
56
+ let(:post) { Post.make }
57
+
58
+ it "begins as unpublished" do
59
+ post.should be_unpublished
60
+ end
61
+
62
+ it "can be published and unpublished" do
63
+ post.publish!
64
+ post.should be_published
65
+
66
+ lambda do
67
+ post.publish!
68
+ end.should raise_error(StateMachine::InvalidTransition)
69
+
70
+ post.unpublish!
71
+ post.should be_unpublished
72
+
73
+ lambda do
74
+ post.unpublish!
75
+ end.should raise_error(StateMachine::InvalidTransition)
76
+ end
77
+ end
78
+
79
+ describe "published_at" do
80
+ it "is set automatically when post is first published" do
81
+ first_publication = Time.utc(1980, 1, 1)
82
+ second_publication = Time.utc(1980, 1, 2)
83
+
84
+ post = Post.make
85
+ post.published_at.should be_nil
86
+
87
+ Time.stub(:now) { first_publication }
88
+ post.publish
89
+ post.published_at.should == first_publication
90
+
91
+ Time.stub(:now) { second_publication }
92
+ post.publish
93
+ post.published_at.should == first_publication
94
+ end
95
+ end
96
+
97
+ describe "#author_byline" do
98
+ it "is set from the #author method before save" do
99
+ author = User.make(:email => 'jane@example.com')
100
+ Post.make!(:author => author).author_byline.
101
+ should == 'jane'
102
+ end
103
+ end
104
+
105
+ describe ".author_byline" do
106
+ it "returns .byline if present" do
107
+ author = mock('author', :byline => 'Jane Smith')
108
+ Post.author_byline(author).should == 'Jane Smith'
109
+ end
110
+
111
+ it "returns .email without domain if present" do
112
+ author = mock('author', :email => 'jane@example.com')
113
+ Post.author_byline(author).should == 'jane'
114
+ end
115
+
116
+ it "returns 'unknown' otherwise" do
117
+ author = mock('author')
118
+ Post.author_byline(author).should == 'unknown'
119
+ end
120
+ end
121
+ end