enki-engine 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 (206) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +284 -0
  6. data/README.textile +112 -0
  7. data/Rakefile +13 -0
  8. data/TODO.textile +8 -0
  9. data/app/assets/images/admin/flash_bg.gif +0 -0
  10. data/app/assets/images/admin/future_bg.png +0 -0
  11. data/app/assets/images/admin/gray_bg.gif +0 -0
  12. data/app/assets/images/admin/new_button.png +0 -0
  13. data/app/assets/images/admin/silver_bg.gif +0 -0
  14. data/app/assets/images/admin/submit_bg.gif +0 -0
  15. data/app/assets/images/admin/subnav_bg.gif +0 -0
  16. data/app/assets/images/openid_icon.png +0 -0
  17. data/app/assets/images/rails.png +0 -0
  18. data/app/assets/images/silk/arrow_undo.png +0 -0
  19. data/app/assets/images/silk/delete.png +0 -0
  20. data/app/assets/images/silk/pencil.png +0 -0
  21. data/app/assets/javascripts/admin/actions.js +18 -0
  22. data/app/assets/javascripts/admin/comments.js +3 -0
  23. data/app/assets/javascripts/admin/common.js +109 -0
  24. data/app/assets/javascripts/admin/dashboard.js +33 -0
  25. data/app/assets/javascripts/admin/edit-preview.js +40 -0
  26. data/app/assets/javascripts/admin/pages.js +1 -0
  27. data/app/assets/javascripts/admin/posts.js +1 -0
  28. data/app/assets/javascripts/admin/shortcut.js +223 -0
  29. data/app/assets/javascripts/admin.js +17 -0
  30. data/app/assets/javascripts/application.js +15 -0
  31. data/app/assets/javascripts/common.js +22 -0
  32. data/app/assets/javascripts/enki.js +13 -0
  33. data/app/assets/javascripts/live-comment-preview.js +36 -0
  34. data/app/assets/stylesheets/admin.css +304 -0
  35. data/app/assets/stylesheets/application.css.scss +486 -0
  36. data/app/assets/stylesheets/humanmsg.css +112 -0
  37. data/app/assets/stylesheets/login.css +65 -0
  38. data/app/controllers/enki/admin/base_controller.rb +15 -0
  39. data/app/controllers/enki/admin/comments_controller.rb +52 -0
  40. data/app/controllers/enki/admin/dashboard_controller.rb +12 -0
  41. data/app/controllers/enki/admin/health_controller.rb +20 -0
  42. data/app/controllers/enki/admin/pages_controller.rb +97 -0
  43. data/app/controllers/enki/admin/posts_controller.rb +104 -0
  44. data/app/controllers/enki/admin/undo_items_controller.rb +43 -0
  45. data/app/controllers/enki/application_controller.rb +21 -0
  46. data/app/controllers/enki/archives_controller.rb +7 -0
  47. data/app/controllers/enki/base_controller.rb +5 -0
  48. data/app/controllers/enki/comments_controller.rb +43 -0
  49. data/app/controllers/enki/pages_controller.rb +7 -0
  50. data/app/controllers/enki/posts_controller.rb +27 -0
  51. data/app/helpers/enki/admin/navigation_helper.rb +10 -0
  52. data/app/helpers/enki/application_helper.rb +35 -0
  53. data/app/helpers/enki/date_helper.rb +15 -0
  54. data/app/helpers/enki/form_helper.rb +7 -0
  55. data/app/helpers/enki/host_helper.rb +19 -0
  56. data/app/helpers/enki/navigation_helper.rb +23 -0
  57. data/app/helpers/enki/page_title_helper.rb +33 -0
  58. data/app/helpers/enki/posts_helper.rb +9 -0
  59. data/app/helpers/enki/tag_helper.rb +7 -0
  60. data/app/helpers/enki/url_helper.rb +25 -0
  61. data/app/models/enki/base/post.rb +153 -0
  62. data/app/models/enki/comment.rb +75 -0
  63. data/app/models/enki/comment_activity.rb +33 -0
  64. data/app/models/enki/delete_comment_undo.rb +33 -0
  65. data/app/models/enki/delete_page_undo.rb +32 -0
  66. data/app/models/enki/delete_post_undo.rb +36 -0
  67. data/app/models/enki/page.rb +42 -0
  68. data/app/models/enki/post.rb +4 -0
  69. data/app/models/enki/stats.rb +19 -0
  70. data/app/models/enki/tag.rb +19 -0
  71. data/app/models/enki/tagging.rb +7 -0
  72. data/app/models/enki/undo_item.rb +10 -0
  73. data/app/views/enki/admin/comments/_comment.html.erb +12 -0
  74. data/app/views/enki/admin/comments/index.html.erb +30 -0
  75. data/app/views/enki/admin/comments/show.html.erb +9 -0
  76. data/app/views/enki/admin/dashboard/show.html.erb +61 -0
  77. data/app/views/enki/admin/health/index.html.erb +3 -0
  78. data/app/views/enki/admin/pages/_form.html.erb +3 -0
  79. data/app/views/enki/admin/pages/_page.html.erb +12 -0
  80. data/app/views/enki/admin/pages/index.html.erb +25 -0
  81. data/app/views/enki/admin/pages/new.html.erb +8 -0
  82. data/app/views/enki/admin/pages/show.html.erb +8 -0
  83. data/app/views/enki/admin/posts/_form.html.erb +11 -0
  84. data/app/views/enki/admin/posts/_post.html.erb +12 -0
  85. data/app/views/enki/admin/posts/_taggings_form.html.erb +4 -0
  86. data/app/views/enki/admin/posts/_upload_form.html.erb +0 -0
  87. data/app/views/enki/admin/posts/index.html.erb +25 -0
  88. data/app/views/enki/admin/posts/new.html.erb +8 -0
  89. data/app/views/enki/admin/posts/show.html.erb +8 -0
  90. data/app/views/enki/admin/undo_items/index.html.erb +24 -0
  91. data/app/views/enki/archives/index.html.erb +17 -0
  92. data/app/views/enki/comments/_comment.html.erb +4 -0
  93. data/app/views/enki/pages/_page.html.erb +3 -0
  94. data/app/views/enki/pages/show.html.erb +5 -0
  95. data/app/views/enki/posts/_post.html.erb +13 -0
  96. data/app/views/enki/posts/index.atom.builder +27 -0
  97. data/app/views/enki/posts/index.html.erb +15 -0
  98. data/app/views/enki/posts/show.html.erb +37 -0
  99. data/autotest/discover.rb +2 -0
  100. data/config/cucumber.yml +8 -0
  101. data/config/enki.yml.sample +16 -0
  102. data/config/initializers/enki_ext.rb +3 -0
  103. data/config/initializers/set_chronic_timezone.rb +1 -0
  104. data/config/initializers/verification.rb +135 -0
  105. data/config/routes.rb +34 -0
  106. data/db/migrate/20110709024316_initialize_db.rb +97 -0
  107. data/db/seeds.rb +8 -0
  108. data/enki-engine.gemspec +47 -0
  109. data/features/admin_dashboard.feature +10 -0
  110. data/features/admin_health.feature +10 -0
  111. data/features/admin_undo.feature +20 -0
  112. data/features/browsing.feature +16 -0
  113. data/features/step_definitions/admin.rb +27 -0
  114. data/features/step_definitions/browsing.rb +3 -0
  115. data/features/step_definitions/posts.rb +11 -0
  116. data/features/step_definitions/web_steps.rb +1 -0
  117. data/features/support/env.rb +59 -0
  118. data/features/support/paths.rb +35 -0
  119. data/features/support/selectors.rb +39 -0
  120. data/lib/core_extensions/object.rb +9 -0
  121. data/lib/core_extensions/string.rb +22 -0
  122. data/lib/enki/config.rb +44 -0
  123. data/lib/enki/engine.rb +19 -0
  124. data/lib/enki/html5_tags.rb +8 -0
  125. data/lib/enki/pagination_shim.rb +25 -0
  126. data/lib/enki/version.rb +3 -0
  127. data/lib/enki.rb +14 -0
  128. data/lib/enki_formatter.rb +11 -0
  129. data/lib/tag_list.rb +2 -0
  130. data/lib/tags_helper.rb +13 -0
  131. data/lib/tasks/cucumber.rake +65 -0
  132. data/lib/tasks/enki.rake +29 -0
  133. data/lib/undo_failed.rb +2 -0
  134. data/script/cucumber +10 -0
  135. data/spec/controllers/admin/comments_controller_spec.rb +140 -0
  136. data/spec/controllers/admin/dashboard_controller_spec.rb +47 -0
  137. data/spec/controllers/admin/health_controller_spec.rb +49 -0
  138. data/spec/controllers/admin/pages_controller_spec.rb +136 -0
  139. data/spec/controllers/admin/posts_controller_spec.rb +183 -0
  140. data/spec/controllers/admin/undo_items_controller_spec.rb +93 -0
  141. data/spec/controllers/archives_controller_spec.rb +37 -0
  142. data/spec/controllers/comments_controller_spec.rb +126 -0
  143. data/spec/controllers/pages_controller_spec.rb +46 -0
  144. data/spec/controllers/posts_controller_spec.rb +168 -0
  145. data/spec/dummy/Gemfile +5 -0
  146. data/spec/dummy/Rakefile +7 -0
  147. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  148. data/spec/dummy/config/application.rb +34 -0
  149. data/spec/dummy/config/boot.rb +10 -0
  150. data/spec/dummy/config/database.yml +6 -0
  151. data/spec/dummy/config/enki.yml +20 -0
  152. data/spec/dummy/config/environment.rb +5 -0
  153. data/spec/dummy/config/environments/development.rb +34 -0
  154. data/spec/dummy/config/environments/test.rb +32 -0
  155. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  156. data/spec/dummy/config/initializers/session_store.rb +8 -0
  157. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  158. data/spec/dummy/config/locales/en.yml +5 -0
  159. data/spec/dummy/config/routes.rb +14 -0
  160. data/spec/dummy/config.ru +6 -0
  161. data/spec/dummy/script/rails +6 -0
  162. data/spec/factories/factories.rb +36 -0
  163. data/spec/helpers/page_title_helper_spec.rb +54 -0
  164. data/spec/helpers/url_helper_spec.rb +23 -0
  165. data/spec/lib/slugorize_spec.rb +44 -0
  166. data/spec/models/comment_activity_spec.rb +60 -0
  167. data/spec/models/comment_spec.rb +125 -0
  168. data/spec/models/delete_comment_undo_spec.rb +52 -0
  169. data/spec/models/delete_post_undo_spec.rb +18 -0
  170. data/spec/models/page_spec.rb +75 -0
  171. data/spec/models/post_spec.rb +257 -0
  172. data/spec/models/stats_spec.rb +28 -0
  173. data/spec/models/tag_spec.rb +13 -0
  174. data/spec/models/tagging_spec.rb +30 -0
  175. data/spec/rcov.opts +2 -0
  176. data/spec/routing/admin/pages_routing_spec.rb +29 -0
  177. data/spec/routing/archives_routing_spec.rb +9 -0
  178. data/spec/routing/comments_routing_spec.rb +17 -0
  179. data/spec/routing/pages_routing_spec.rb +9 -0
  180. data/spec/routing/posts_routing_spec.rb +26 -0
  181. data/spec/spec.opts +4 -0
  182. data/spec/spec_helper.rb +60 -0
  183. data/spec/support/be_valid_html5.rb +150 -0
  184. data/spec/support/be_valid_xhtml.rb +148 -0
  185. data/spec/support/routes_override_helper.rb +12 -0
  186. data/spec/views/admin/comments/index.html_spec.rb +26 -0
  187. data/spec/views/admin/comments/show.html_spec.rb +28 -0
  188. data/spec/views/admin/dashboard/show.html_spec.rb +37 -0
  189. data/spec/views/admin/pages/index.html_spec.rb +23 -0
  190. data/spec/views/admin/pages/new.html_spec.rb +16 -0
  191. data/spec/views/admin/pages/show.html_spec.rb +16 -0
  192. data/spec/views/admin/posts/index.html_spec.rb +24 -0
  193. data/spec/views/admin/posts/new.html_spec.rb +16 -0
  194. data/spec/views/admin/posts/show.html_spec.rb +16 -0
  195. data/spec/views/admin/undo_items/index.html_spec.rb +19 -0
  196. data/spec/views/archives/index.html_spec.rb +34 -0
  197. data/spec/views/pages/show.html_spec.rb +23 -0
  198. data/spec/views/posts/index.atom.builder_spec.rb +36 -0
  199. data/spec/views/posts/index.html_spec.rb +39 -0
  200. data/spec/views/posts/show.html_spec.rb +49 -0
  201. data/vendor/assets/javascripts/humanmsg.js +86 -0
  202. data/vendor/assets/javascripts/jquery.easing.1.3.js +205 -0
  203. data/vendor/assets/javascripts/jquery.form.js +869 -0
  204. data/vendor/assets/javascripts/jquery.jfeed.js +143 -0
  205. data/vendor/assets/javascripts/jquery.livequery.js +250 -0
  206. metadata +464 -0
@@ -0,0 +1,125 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe Comment do
6
+ def valid_comment_attributes(extra = {})
7
+ {
8
+ :author => 'Don Alias',
9
+ :body => 'This is a comment',
10
+ :post => Post.new
11
+ }.merge(extra)
12
+ end
13
+
14
+ def set_comment_attributes(comment, extra = {})
15
+ valid_comment_attributes(extra).each_pair do |key, value|
16
+ comment.send("#{key}=", value)
17
+ end
18
+ end
19
+
20
+ before(:each) do
21
+ @comment = Comment.new
22
+ end
23
+
24
+ it "is invalid with no post" do
25
+ set_comment_attributes(@comment, :post => nil)
26
+ @comment.should_not be_valid
27
+ @comment.errors.should_not be_empty
28
+ end
29
+
30
+ it "is invalid with no body" do
31
+ set_comment_attributes(@comment, :body => '')
32
+ @comment.should_not be_valid
33
+ @comment.errors.should_not be_empty
34
+ end
35
+
36
+ it "is invalid with no author" do
37
+ set_comment_attributes(@comment, :author => '')
38
+ @comment.should_not be_valid
39
+ @comment.errors.should_not be_empty
40
+ end
41
+
42
+ it "is valid with a full set of valid attributes" do
43
+ set_comment_attributes(@comment)
44
+ @comment.should be_valid
45
+ end
46
+
47
+ it "asks post to update it's comment counter after save" do
48
+ set_comment_attributes(@comment)
49
+ @comment.post.update_attributes(:title => 'My Post', :body => "body")
50
+ @comment.post.save
51
+ @comment.save
52
+ @comment.post.approved_comments.count.should == 1
53
+ end
54
+
55
+ it "asks post to update it's comment counter after destroy" do
56
+ set_comment_attributes(@comment)
57
+ @comment.post.update_attributes(:title => 'My Post', :body => "body")
58
+ @comment.post.save
59
+ @comment.save
60
+ @comment.destroy
61
+ @comment.post.approved_comments.count.should == 0
62
+ end
63
+
64
+ it "applies a Lesstile filter to body and store it in body_html before save" do
65
+ set_comment_attributes(@comment)
66
+ @comment.post.update_attributes(:title => 'My Post', :body => "body")
67
+ @comment.post.save
68
+ @comment.save
69
+ @comment.body_html.should_not be_nil
70
+ end
71
+
72
+ it "responds to trusted_user? for defensio integration" do
73
+ @comment.respond_to?(:trusted_user?).should == true
74
+ end
75
+
76
+ it "responds to user_logged_in? for defensio integration" do
77
+ @comment.respond_to?(:user_logged_in?).should == true
78
+ end
79
+
80
+ it "delegates post_tile to post" do
81
+ @comment.post = mock_model(Post)
82
+ @comment.post.should_receive(:title).and_return("hello")
83
+ @comment.post_title.should == "hello"
84
+ end
85
+
86
+ # TODO: acts_as_defensio_comment tests
87
+ # TODO: OpenID error model
88
+ end
89
+
90
+ describe Comment, '.find_recent' do
91
+ it 'finds the most recent comments that were posted before now' do
92
+ now = Time.now
93
+ Time.stub!(:now).and_return(now)
94
+ Comment.should_receive(:find).with(:all, {
95
+ :order => 'created_at DESC',
96
+ :limit => Comment::DEFAULT_LIMIT
97
+ }).and_return(comments = [mock_model(Comment)])
98
+ Comment.find_recent.should == comments
99
+ end
100
+
101
+ it 'allows and override of the default limit' do
102
+ Comment.should_receive(:find).with(:all, hash_including(:limit => 999))
103
+ Comment.find_recent(:limit => 999)
104
+ end
105
+ end
106
+
107
+ describe Comment, '.build_for_preview' do
108
+ before(:each) do
109
+ @comment = Comment.build_for_preview(:author => 'Don Alias', :body => 'A Comment')
110
+ end
111
+
112
+ it 'returns a new comment' do
113
+ @comment.should be_new_record
114
+ end
115
+
116
+ it 'sets created_at' do
117
+ @comment.created_at.should_not be_nil
118
+ end
119
+
120
+ it 'applies filter to body' do
121
+ @comment.body_html.should == 'A Comment'
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe DeleteCommentUndo do
6
+ describe '#process!' do
7
+ it 'creates a new comment based on the attributes stored in #data' do
8
+ Comment.stub!(:find_by_id).and_return(nil)
9
+
10
+ item = DeleteCommentUndo.new(:data => "---\nid: 1\na: b")
11
+ item.stub!(:transaction).and_yield
12
+ item.stub!(:destroy)
13
+
14
+ Comment.should_receive(:create).with('a' => 'b').and_return(mock("comment", :new_record? => false))
15
+ item.process!
16
+ end
17
+ end
18
+
19
+ describe '#process! with existing comment' do
20
+ it 'raises' do
21
+ Comment.stub!(:find_by_id).and_return(Object.new)
22
+ lambda { DeleteCommentUndo.new(:data => "---\nid: 1").process! }.should raise_error(UndoFailed)
23
+ end
24
+ end
25
+
26
+ describe '#process! with invalid comment' do
27
+ it 'raises' do
28
+ Comment.stub!(:find_by_id).and_return(nil)
29
+
30
+ Comment.stub!(:create).and_return(mock("comment", :new_record? => true))
31
+ lambda { DeleteCommentUndo.new(:data => "---\nid: 1").process! }.should raise_error(UndoFailed)
32
+ end
33
+ end
34
+
35
+ describe '#description' do
36
+ it("should not be nil") { DeleteCommentUndo.new(:data => '---').description.should_not be_nil }
37
+ end
38
+
39
+ describe '#complete_description' do
40
+ it("should not be nil") { DeleteCommentUndo.new(:data => '---').complete_description.should_not be_nil }
41
+ end
42
+
43
+ describe '.create_undo' do
44
+ it "creates a new undo item based on the attributes of the given comment" do
45
+ comment = Comment.new(:author => 'Don Alias')
46
+ DeleteCommentUndo.should_receive(:create!).with(:data => comment.attributes.to_yaml).and_return(obj = Object.new)
47
+ DeleteCommentUndo.create_undo(comment).should == obj
48
+ end
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe DeletePostUndo do
6
+ describe '#process!' do
7
+ it 'creates a new post with comments based on the attributes stored in #data' do
8
+ post = Post.create!(:title => 'a', :body => 'b').tap do |post|
9
+ post.comments.create!(:author => 'Don', :author_url => '', :author_email => '', :body => 'comment')
10
+ end
11
+ item = post.destroy_with_undo
12
+ new_post = item.process!
13
+ new_post.comments.count.should == 1
14
+ end
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,75 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe Page, '#generate_slug' do
6
+ it 'makes a slug from the title if slug if blank' do
7
+ page = Page.new(:slug => '', :title => 'my title')
8
+ page.generate_slug
9
+ page.slug.should == 'my-title'
10
+ end
11
+
12
+ it 'replaces & with and' do
13
+ page = Page.new(:slug => 'a & b & c')
14
+ page.generate_slug
15
+ page.slug.should == 'a-and-b-and-c'
16
+ end
17
+
18
+ it 'replaces non alphanumeric characters with -' do
19
+ page = Page.new(:slug => 'a@#^*(){}b')
20
+ page.generate_slug
21
+ page.slug.should == 'a-b'
22
+ end
23
+
24
+ it 'does not modify title' do
25
+ page = Page.new(:title => 'My Page')
26
+ page.generate_slug
27
+ page.title.should == 'My Page'
28
+ end
29
+ end
30
+
31
+ describe Page, 'before validation' do
32
+ it 'calls #generate_slug' do
33
+ page = Page.new(:title => "My Page", :body => "body")
34
+ page.valid?
35
+ page.slug.should_not be_blank
36
+ end
37
+ end
38
+
39
+ describe Page, 'validations' do
40
+ def valid_page_attributes
41
+ {
42
+ :title => "My Page",
43
+ :slug => "my-page",
44
+ :body => "body"
45
+ }
46
+ end
47
+
48
+ it 'is valid with valid_page_attributes' do
49
+ Page.new(valid_page_attributes).should be_valid
50
+ end
51
+
52
+ it 'is invalid with no title' do
53
+ Page.new(valid_page_attributes.merge(:title => '')).should_not be_valid
54
+ end
55
+
56
+ it 'is invalid with no body' do
57
+ Page.new(valid_page_attributes.merge(:body => '')).should_not be_valid
58
+ end
59
+ end
60
+
61
+ describe Page, '.build_for_preview' do
62
+ before(:each) do
63
+ @page = Page.build_for_preview(:title => 'My Page', :body => "body")
64
+ end
65
+
66
+ it 'returns a new page' do
67
+ @page.should be_new_record
68
+ end
69
+
70
+ it 'applies filter to body' do
71
+ @page.body_html.should == '<p>body</p>'
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,257 @@
1
+ require 'spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe "Scopes" do
6
+
7
+ describe "find_recent" do
8
+
9
+ before do
10
+ Base::Post.send(:remove_const, :DEFAULT_LIMIT)
11
+ Base::Post::DEFAULT_LIMIT = 3
12
+
13
+ now = Time.now
14
+ Time.stub!(:now).and_return(now)
15
+
16
+ @post1 = create(:post, :published_at => 1.day.ago)
17
+ @post2 = create(:post, :published_at => 1.month.ago, :tag_list => 'yikes')
18
+ @post3 = create(:post, :published_at => 1.week.ago)
19
+ @post4 = create(:post, :published_at => 1.year.ago, :tag_list => 'yikes')
20
+ 2.times { create(:post, :published_at => 1.days.since) }
21
+ end
22
+
23
+ it 'finds the most recent posts that were published before now' do
24
+ Post.find_recent.should == [@post1, @post3, @post2]
25
+ end
26
+
27
+ it 'finds the most recent posts that were published before now with a tag' do
28
+ Post.find_recent(:tag => 'yikes').should == [@post2, @post4]
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ describe Post, "integration" do
36
+ describe 'setting tag_list' do
37
+ it 'increments tag counter cache' do
38
+ post1 = Post.create!(:title => 'My Post', :body => "body", :tag_list => "ruby")
39
+ post2 = Post.create!(:title => 'My Post', :body => "body", :tag_list => "ruby")
40
+ Tag.find_by_name('ruby').taggings_count.should == 2
41
+ Post.last.destroy
42
+ Tag.find_by_name('ruby').taggings_count.should == 1
43
+ end
44
+ end
45
+ end
46
+
47
+ describe Post, ".find_recent" do
48
+
49
+ it 'finds all posts grouped by month' do
50
+ now = Time.now
51
+ Time.stub!(:now).and_return(now)
52
+ posts = [1, 1, 2].collect {|month| mock_model(Post, :month => month) }
53
+ Post.should_receive(:find).with(:all, {
54
+ :order => 'posts.published_at DESC',
55
+ :conditions => ['published_at < ?', now]
56
+ }).and_return(posts)
57
+ months = Post.find_all_grouped_by_month.collect {|month| [month.date, month.posts]}
58
+ months.should == [[1, [posts[0], posts[1]]], [2, [posts[2]]]]
59
+ end
60
+ end
61
+
62
+ describe Post, '#generate_slug' do
63
+ it 'makes a slug from the title if slug if blank' do
64
+ post = Post.new(:slug => '', :title => 'my title')
65
+ post.generate_slug
66
+ post.slug.should == 'my-title'
67
+ end
68
+
69
+ it 'replaces & with and' do
70
+ post = Post.new(:slug => 'a & b & c')
71
+ post.generate_slug
72
+ post.slug.should == 'a-and-b-and-c'
73
+ end
74
+
75
+ it 'replaces non alphanumeric characters with -' do
76
+ post = Post.new(:slug => 'a@#^*(){}b')
77
+ post.generate_slug
78
+ post.slug.should == 'a-b'
79
+ end
80
+
81
+ it 'does not modify title' do
82
+ post = Post.new(:title => 'My Post')
83
+ post.generate_slug
84
+ post.title.should == 'My Post'
85
+ end
86
+ end
87
+
88
+ describe Post, '#tag_list=' do
89
+ it 'accept an array argument so it is symmetrical with the reader' do
90
+ p = Post.new
91
+ p.tag_list = ["a", "b"]
92
+ p.tag_list.should == ["a", "b"]
93
+ end
94
+ end
95
+
96
+ describe Post, "#set_dates" do
97
+ describe 'when minor_edit is false' do
98
+ it 'sets edited_at to current time' do
99
+ now = Time.now
100
+ Time.stub!(:now).and_return(now)
101
+
102
+ post = Post.new(:edited_at => 1.day.ago)
103
+ post.stub!(:minor_edit?).and_return(false)
104
+ post.set_dates
105
+ post.edited_at.should == now
106
+ end
107
+ end
108
+
109
+ describe 'when edited_at is nil' do
110
+ it 'sets edited_at to current time' do
111
+ now = Time.now
112
+ Time.stub!(:now).and_return(now)
113
+
114
+ post = Post.new
115
+ post.stub!(:minor_edit?).and_return(true)
116
+ post.set_dates
117
+ post.edited_at.should == now
118
+ end
119
+ end
120
+
121
+ describe 'when minor_edit is true' do
122
+ it 'does not changed edited_at' do
123
+ post = Post.new(:edited_at => now = 1.day.ago)
124
+ post.stub!(:minor_edit?).and_return(true)
125
+ post.set_dates
126
+ post.edited_at.should == now
127
+ end
128
+ end
129
+
130
+ it 'sets published_at by parsing published_at_natural with chronic' do
131
+ now = Time.now
132
+ post = Post.new(:published_at_natural => 'now')
133
+ Chronic.should_receive(:parse).with('now').and_return(now)
134
+ post.set_dates
135
+ post.published_at.should == now
136
+ end
137
+
138
+ it 'does not set published_at if published_at_natural is invalid' do
139
+ now = Time.now
140
+ post = Post.new(:published_at_natural => 'bogus', :published_at => now)
141
+ Chronic.should_receive(:parse).with('bogus').and_return(nil)
142
+ post.set_dates
143
+ post.published_at.should == now
144
+ end
145
+ end
146
+
147
+ describe Post, "#minor_edit" do
148
+ it('returns "1" by default') { Post.new.minor_edit.should == "1" }
149
+ end
150
+
151
+ describe Post, '#published?' do
152
+ before(:each) do
153
+ @post = Post.new
154
+ end
155
+
156
+ it "should return false if published_at is not filled" do
157
+ @post.should_not be_published
158
+ end
159
+
160
+ it "should return true if published_at is filled" do
161
+ @post.published_at = Time.now
162
+ @post.should be_published
163
+ end
164
+ end
165
+
166
+ describe Post, "#minor_edit?" do
167
+ it('returns true when minor_edit is 1') { Post.new(:minor_edit => "1").minor_edit?.should == true }
168
+ it('returns false when minor_edit is 0') { Post.new(:minor_edit => "0").minor_edit?.should == false }
169
+ it('returns true by default') { Post.new.minor_edit?.should == true }
170
+ end
171
+
172
+ describe Post, 'before validation' do
173
+ it 'calls #generate_slug' do
174
+ post = Post.new(:title => "My Post", :body => "body")
175
+ post.valid?
176
+ post.slug.should_not be_blank
177
+ end
178
+
179
+ it 'calls #set_dates' do
180
+ post = Post.new(:title => "My Post", :body => "body")
181
+ post.valid?
182
+ post.edited_at.should_not be_blank
183
+ post.published_at.should_not be_blank
184
+ end
185
+ end
186
+
187
+ describe Post, '#denormalize_comments_count!' do
188
+ it 'updates approved_comments_count without triggering AR callbacks' do
189
+ p = Post.new
190
+ p.id = 999
191
+ p.stub!(:approved_comments).and_return(stub("approved_comments association", :count => 9))
192
+ Base::Post.should_receive(:update_all).with(["approved_comments_count = ?", 9], ["id = ?", 999])
193
+ p.denormalize_comments_count!
194
+ end
195
+ end
196
+
197
+ describe Post, 'validations' do
198
+ def valid_post_attributes
199
+ {
200
+ :title => "My Post",
201
+ :slug => "my-post",
202
+ :body => "hello this is my post",
203
+ :published_at_natural => 'now'
204
+ }
205
+ end
206
+
207
+ it 'is valid with valid_post_attributes' do
208
+ Post.new(valid_post_attributes).should be_valid
209
+ end
210
+
211
+ it 'is invalid with no title' do
212
+ Post.new(valid_post_attributes.merge(:title => '')).should_not be_valid
213
+ end
214
+
215
+ it 'is invalid with no body' do
216
+ Post.new(valid_post_attributes.merge(:body => '')).should_not be_valid
217
+ end
218
+
219
+ it 'is invalid with bogus published_at_natural' do
220
+ Post.new(valid_post_attributes.merge(:published_at_natural => 'bogus')).should_not be_valid
221
+ end
222
+ end
223
+
224
+ describe Post, 'being destroyed' do
225
+ it 'destroys all comments' do
226
+ Post.reflect_on_association(:comments).options[:dependent].should == :destroy
227
+ end
228
+ end
229
+
230
+ describe Post, '.build_for_preview' do
231
+ before(:each) do
232
+ @post = Post.build_for_preview(:title => 'My Post', :body => "body", :tag_list => "ruby")
233
+ end
234
+
235
+ it 'returns a new post' do
236
+ @post.should be_new_record
237
+ end
238
+
239
+ it 'generates slug' do
240
+ @post.slug.should_not be_nil
241
+ end
242
+
243
+ it 'sets date' do
244
+ @post.edited_at.should_not be_nil
245
+ @post.published_at.should_not be_nil
246
+ end
247
+
248
+ it 'applies filter to body' do
249
+ @post.body_html.should == '<p>body</p>'
250
+ end
251
+
252
+ it 'generates tags from tag_list' do
253
+ @post.tags.collect {|tag| tag.name}.should == ['ruby']
254
+ end
255
+ end
256
+
257
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe Stats do
6
+ describe '#post_count' do
7
+ it 'returns the total number of posts, published or not' do
8
+ Post.should_receive(:count).and_return(2)
9
+ Stats.new.post_count.should == 2
10
+ end
11
+ end
12
+
13
+ describe '#comment_count' do
14
+ it 'returns the total number of comments' do
15
+ Comment.should_receive(:count).and_return(2)
16
+ Stats.new.comment_count.should == 2
17
+ end
18
+ end
19
+
20
+ describe '#tag_count' do
21
+ it 'returns the total number of tags' do
22
+ Tag.should_receive(:count).and_return(2)
23
+ Stats.new.tag_count.should == 2
24
+ end
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe Tag do
6
+ describe 'on delete' do
7
+ it 'also deletes all associated taggings' do
8
+ Tag.reflect_on_association(:taggings).options[:dependent].should == :destroy
9
+ end
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ module Enki
4
+
5
+ describe Tagging do
6
+ before(:each) do
7
+ @taggable = Post.create!(:title => 'My Post', :body => 'body', :tag_list => 'oblong, square, triangle')
8
+ end
9
+
10
+ it 'destroys unused tags on taggable update' do
11
+ @taggable.tag_list = ''
12
+ @taggable.save
13
+ Tag.where(:taggings_count => 0).count.should == 0
14
+ end
15
+
16
+ it 'destroys unused tags on taggable destroy' do
17
+ Enki::Tag.remove_unused = true
18
+
19
+ @taggable.destroy
20
+ Tag.where(:taggings_count => 0).count.should == 0
21
+ end
22
+
23
+ it 'does not destroy tags if they are still in use' do
24
+ another_taggable = Post.create!(:title => 'My Post', :body => 'body', :tag_list => 'oblong, square')
25
+ @taggable.destroy
26
+ Tag.where(:name => ['oblong', 'square']).count.should == 2
27
+ end
28
+ end
29
+
30
+ end
data/spec/rcov.opts ADDED
@@ -0,0 +1,2 @@
1
+ --exclude "spec/*,gems/*"
2
+ --rails
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Enki::Admin::PagesController routes' do
4
+
5
+ it "routes get /admin/pages to admin/pages#create" do
6
+ get("/admin/pages").should route_to('enki/admin/pages#index')
7
+ end
8
+
9
+ it "routes post /admin/pages to admin/pages#create" do
10
+ post("/admin/pages").should route_to('enki/admin/pages#create')
11
+ end
12
+
13
+ it "routes get /admin/pages/new to admin/pages#new" do
14
+ get("/admin/pages/new").should route_to('enki/admin/pages#new')
15
+ end
16
+
17
+ it "routes get /admin/pages/my-page to admin/pages#show id: my-page" do
18
+ get("/admin/pages/my-page").should route_to('enki/admin/pages#show', :id => 'my-page')
19
+ end
20
+
21
+ it "routes put /admin/pages/my-page to admin/pages#update id: my-page" do
22
+ put("admin/pages/my-page").should route_to('enki/admin/pages#update', :id => 'my-page')
23
+ end
24
+
25
+ it "routes delete /admin/pages/my-page to admin/pages#destroy id: my-page" do
26
+ delete("/admin/pages/my-page").should route_to('enki/admin/pages#destroy', :id => 'my-page')
27
+ end
28
+
29
+ end
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe 'Enki::ArchivesController routes' do
4
+
5
+ it 'routes get /archives to archives#index' do
6
+ get('/archives').should route_to('enki/archives#index')
7
+ end
8
+
9
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'CommentsController routes' do
4
+
5
+ it "should route get /2008/02/01/a-post/comments to comments#index with year: 2008, month: 02, day: 01, slug: a-post" do
6
+ get("/2008/02/01/a-post/comments").should route_to("enki/comments#index", :year => '2008', :month => '02', :day => '01', :slug => 'a-post')
7
+ end
8
+
9
+ it "should route post /2008/02/01/a-post/comments to comments#create with year: 2008, month: 02, day: 01, slug: a-post" do
10
+ post("/2008/02/01/a-post/comments").should route_to("enki/comments#create", :year => '2008', :month => '02', :day => '01', :slug => 'a-post')
11
+ end
12
+
13
+ it "should route /2008/02/01/a-post/comments/nes to comments#new with year: 2008, month: 02, day: 01, slug: a-post" do
14
+ get("/2008/02/01/a-post/comments/new").should route_to("enki/comments#new", :year => '2008', :month => '02', :day => '01', :slug => 'a-post')
15
+ end
16
+
17
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Enki::PagesController routes' do
4
+
5
+ it "routes get /pages/my-page to pages#show id: my-page" do
6
+ get("pages/my-page").should route_to('enki/pages#show', :id => 'my-page')
7
+ end
8
+
9
+ end