mist 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +192 -0
  7. data/README.md +108 -0
  8. data/Rakefile +9 -0
  9. data/app/assets/images/mist/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  10. data/app/assets/images/mist/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  11. data/app/assets/images/mist/ui-bg_flat_10_000000_40x100.png +0 -0
  12. data/app/assets/images/mist/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  13. data/app/assets/images/mist/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  14. data/app/assets/images/mist/ui-bg_glass_65_ffffff_1x400.png +0 -0
  15. data/app/assets/images/mist/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  16. data/app/assets/images/mist/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  17. data/app/assets/images/mist/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  18. data/app/assets/images/mist/ui-icons_222222_256x240.png +0 -0
  19. data/app/assets/images/mist/ui-icons_228ef1_256x240.png +0 -0
  20. data/app/assets/images/mist/ui-icons_ef8c08_256x240.png +0 -0
  21. data/app/assets/images/mist/ui-icons_ffd27a_256x240.png +0 -0
  22. data/app/assets/images/mist/ui-icons_ffffff_256x240.png +0 -0
  23. data/app/assets/javascripts/mist/jquery-ui-1.8.17.custom.min.js +356 -0
  24. data/app/assets/javascripts/mist/posts.js.coffee +3 -0
  25. data/app/assets/javascripts/mist_core.js +3 -0
  26. data/app/assets/stylesheets/mist/posts.css.sass +97 -0
  27. data/app/assets/stylesheets/mist/scaffolds.css.scss +56 -0
  28. data/app/assets/stylesheets/mist/ui-lightness/jquery-ui-1.8.17.custom.css +565 -0
  29. data/app/assets/stylesheets/mist_core.css +4 -0
  30. data/app/controllers/application_controller.rb +3 -0
  31. data/app/controllers/mist/posts_controller.rb +130 -0
  32. data/app/helpers/mist/posts_helper.rb +61 -0
  33. data/app/mailers/.gitkeep +0 -0
  34. data/app/models/.gitkeep +0 -0
  35. data/app/models/mist/post.rb +240 -0
  36. data/app/models/mist/post_sweeper.rb +63 -0
  37. data/app/views/layouts/mist/posts.html.erb +66 -0
  38. data/app/views/mist/posts/_form.html.erb +55 -0
  39. data/app/views/mist/posts/_post.html.erb +19 -0
  40. data/app/views/mist/posts/_sidebar_title.html.erb +5 -0
  41. data/app/views/mist/posts/edit.html.erb +16 -0
  42. data/app/views/mist/posts/feed.atom.builder +17 -0
  43. data/app/views/mist/posts/index.html.erb +31 -0
  44. data/app/views/mist/posts/new.html.erb +15 -0
  45. data/app/views/mist/posts/show.html.erb +3 -0
  46. data/config/cucumber.yml +8 -0
  47. data/config/environment.rb +1 -0
  48. data/config/routes.rb +7 -0
  49. data/features/atom_feed.feature +20 -0
  50. data/features/authorize_create_post.feature +14 -0
  51. data/features/authorize_destroy_post.feature +27 -0
  52. data/features/authorize_update_post.feature +28 -0
  53. data/features/authorize_view_unpublished.feature +22 -0
  54. data/features/create_post.feature +13 -0
  55. data/features/destroy_post.feature +13 -0
  56. data/features/format_with_markdown.feature +13 -0
  57. data/features/popular_posts.feature +35 -0
  58. data/features/recent_posts.feature +28 -0
  59. data/features/similar_posts.feature +43 -0
  60. data/features/step_definitions/authorization_steps.rb +17 -0
  61. data/features/step_definitions/filesystem_steps.rb +13 -0
  62. data/features/step_definitions/page_steps.rb +43 -0
  63. data/features/step_definitions/post_steps.rb +58 -0
  64. data/features/step_definitions/sidebar_steps.rb +21 -0
  65. data/features/support/env.rb +58 -0
  66. data/features/support/setup.rb +15 -0
  67. data/features/update_post.feature +19 -0
  68. data/gemfiles/common +20 -0
  69. data/gemfiles/rails-3.1.3 +4 -0
  70. data/gemfiles/rails-3.1.3.lock +195 -0
  71. data/gemfiles/rails-3.2.0 +4 -0
  72. data/lib/generators/mist/USAGE +13 -0
  73. data/lib/generators/mist/setup_generator.rb +21 -0
  74. data/lib/generators/mist/templates/initializer.rb +17 -0
  75. data/lib/generators/mist/templates/mist.css.scss +2 -0
  76. data/lib/generators/mist/templates/mist.js.coffee +1 -0
  77. data/lib/generators/mist/views_generator.rb +11 -0
  78. data/lib/mist.rb +26 -0
  79. data/lib/mist/code_example_parser.rb +86 -0
  80. data/lib/mist/configuration.rb +68 -0
  81. data/lib/mist/configuration/author.rb +8 -0
  82. data/lib/mist/engine.rb +12 -0
  83. data/lib/mist/git_file_system_history.rb +66 -0
  84. data/lib/mist/git_model.rb +154 -0
  85. data/lib/mist/git_model/attributes.rb +21 -0
  86. data/lib/mist/git_model/class_methods.rb +141 -0
  87. data/lib/mist/permalink.rb +5 -0
  88. data/lib/mist/repository.rb +15 -0
  89. data/lib/mist/version.rb +8 -0
  90. data/mist.gemspec +33 -0
  91. data/spec/controllers/posts_controller_spec.rb +263 -0
  92. data/spec/dummy_rails_app/app/assets/javascripts/mist.js.coffee +1 -0
  93. data/spec/dummy_rails_app/app/assets/stylesheets/mist.css.scss +2 -0
  94. data/spec/dummy_rails_app/app/views/layouts/mist/posts.html.erb +60 -0
  95. data/spec/dummy_rails_app/config.ru +4 -0
  96. data/spec/dummy_rails_app/config/application.rb +48 -0
  97. data/spec/dummy_rails_app/config/boot.rb +7 -0
  98. data/spec/dummy_rails_app/config/database.yml +28 -0
  99. data/spec/dummy_rails_app/config/environment.rb +5 -0
  100. data/spec/dummy_rails_app/config/environments/development.rb +32 -0
  101. data/spec/dummy_rails_app/config/environments/production.rb +60 -0
  102. data/spec/dummy_rails_app/config/environments/test.rb +42 -0
  103. data/spec/dummy_rails_app/config/initializers/active_gist_credentials.rb +8 -0
  104. data/spec/dummy_rails_app/config/initializers/backtrace_silencers.rb +7 -0
  105. data/spec/dummy_rails_app/config/initializers/inflections.rb +10 -0
  106. data/spec/dummy_rails_app/config/initializers/mime_types.rb +5 -0
  107. data/spec/dummy_rails_app/config/initializers/mist.rb +17 -0
  108. data/spec/dummy_rails_app/config/initializers/secret_token.rb +7 -0
  109. data/spec/dummy_rails_app/config/initializers/session_store.rb +8 -0
  110. data/spec/dummy_rails_app/config/initializers/wrap_parameters.rb +14 -0
  111. data/spec/dummy_rails_app/config/locales/en.yml +5 -0
  112. data/spec/dummy_rails_app/config/routes.rb +3 -0
  113. data/spec/dummy_rails_app/db/schema.rb +22 -0
  114. data/spec/dummy_rails_app/db/seeds.rb +7 -0
  115. data/spec/dummy_rails_app/doc/README_FOR_APP +2 -0
  116. data/spec/dummy_rails_app/lib/assets/.gitkeep +0 -0
  117. data/spec/dummy_rails_app/lib/tasks/.gitkeep +0 -0
  118. data/spec/dummy_rails_app/lib/tasks/cucumber.rake +65 -0
  119. data/spec/dummy_rails_app/public/404.html +26 -0
  120. data/spec/dummy_rails_app/public/422.html +26 -0
  121. data/spec/dummy_rails_app/public/500.html +26 -0
  122. data/spec/dummy_rails_app/public/favicon.ico +0 -0
  123. data/spec/dummy_rails_app/public/robots.txt +5 -0
  124. data/spec/dummy_rails_app/script/cucumber +10 -0
  125. data/spec/dummy_rails_app/script/rails +6 -0
  126. data/spec/dummy_rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
  127. data/spec/dummy_rails_app/vendor/plugins/.gitkeep +0 -0
  128. data/spec/factories/posts.rb +8 -0
  129. data/spec/fixtures/gist_404 +6 -0
  130. data/spec/fixtures/gist_with_1_code_example +66 -0
  131. data/spec/helpers/posts_helper_spec.rb +64 -0
  132. data/spec/lib/mist/code_example_parser_spec.rb +135 -0
  133. data/spec/lib/mist/configuration_spec.rb +88 -0
  134. data/spec/lib/mist/permalink_spec.rb +17 -0
  135. data/spec/lib/mist/repository_spec.rb +20 -0
  136. data/spec/models/mist/git_model_spec.rb +260 -0
  137. data/spec/models/mist/post_spec.rb +575 -0
  138. data/spec/requests/posts_caching_spec.rb +152 -0
  139. data/spec/spec_helper.rb +20 -0
  140. data/spec/support/cache_helpers.rb +71 -0
  141. data/spec/support/config.rb +42 -0
  142. data/spec/support/fakeweb.rb +3 -0
  143. data/spec/views/mist/posts/edit.html.erb_spec.rb +11 -0
  144. data/spec/views/mist/posts/index.html.erb_spec.rb +27 -0
  145. data/spec/views/mist/posts/new.html.erb_spec.rb +15 -0
  146. data/spec/views/mist/posts/show.html.erb_spec.rb +11 -0
  147. metadata +371 -0
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mist::Configuration do
4
+ subject { Class.new { include Mist::Configuration }.new }
5
+
6
+ it "should have a default repo location" do
7
+ subject.repository_location.should_not be_blank
8
+ subject.repository_location.should == subject.default_repository_location
9
+ end
10
+
11
+ it "should allow repo location to be set" do
12
+ subject.repository_location = "123"
13
+ subject.repository_location.should == '123'
14
+ end
15
+
16
+ describe "authorization" do
17
+ it "should accept multiple arguments" do
18
+ Mist.authorize(:one, :two) { true }
19
+ Mist.should be_authorized(:one)
20
+ Mist.should be_authorized(:two)
21
+ end
22
+
23
+ it "should work default to :all given no arguments" do
24
+ Mist.authorize { true }
25
+ Mist.should be_authorized(:create_post)
26
+ end
27
+
28
+ it "should require a block to set up" do
29
+ proc { Mist.authorize(:test) }.should raise_error(ArgumentError)
30
+ end
31
+
32
+ describe "with authorization for a single action" do
33
+ before { Mist.authorize(:test) { |*c| @received = c; true } }
34
+
35
+ it "should return the block result" do
36
+ Mist.should be_authorized(:test)
37
+ end
38
+
39
+ it "should pass extra arguments to the block" do
40
+ Mist.authorized?(:test, 1, 2, 3)
41
+ @received.should == [1, 2, 3]
42
+ end
43
+
44
+ it "should return false for other actions" do
45
+ Mist.should_not be_authorized(:other)
46
+ end
47
+
48
+ it "should reset" do
49
+ Mist.reset_authorizations!
50
+ Mist.should_not be_authorized(:test)
51
+ end
52
+ end
53
+
54
+ describe "with authorization for all actions" do
55
+ before { Mist.authorize(:all) { true } }
56
+
57
+ it "should return block result for any action" do
58
+ Mist.should be_authorized(:test)
59
+ end
60
+ end
61
+
62
+ describe "by default" do
63
+ it "should deny authorization for all actions" do
64
+ Mist.should_not be_authorized(:test)
65
+ end
66
+ end
67
+
68
+ describe "overriding all actions with a given action" do
69
+ before { Mist.authorize(:all) { true }; Mist.authorize(:test) { false } }
70
+
71
+ it "should return overridden result for the overridden action" do
72
+ Mist.should_not be_authorized(:test)
73
+ end
74
+
75
+ it "should not return overridden result for other actions" do
76
+ Mist.should be_authorized(:other)
77
+ end
78
+ end
79
+
80
+ describe "overriding the same action" do
81
+ before { Mist.authorize(:test) { false }; Mist.authorize(:test) { true } }
82
+
83
+ it "should use the last block given" do
84
+ Mist.should be_authorized(:test)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mist::Permalink do
4
+ include Mist::Permalink
5
+
6
+ it "should not include periods" do # they screw with rails' formats
7
+ permalink("Stuck in Rails 2? Use Bundler. For everything. Right now.").should_not match(/\./)
8
+ end
9
+
10
+ it "should not create double dashes" do # they're ugly
11
+ permalink("Stuck in Rails 2? Use Bundler. For everything. Right now.").should_not match(/--/)
12
+ end
13
+
14
+ it "should not end with dashes" do # ditto
15
+ permalink("Stuck in Rails 2? Use Bundler. For everything. Right now.").should_not match(/-$/)
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mist do
4
+ it "should generate the repo automatically" do
5
+ Mist.repository
6
+ File.should be_directory(Mist.repository_location.join('.git'))
7
+ end
8
+
9
+ describe "with an existing repo" do
10
+ before do
11
+ Git.init(Mist.repository_location.to_s)
12
+ end
13
+
14
+ it "should not generate the repo" do
15
+ Git.should_not_receive(:init)
16
+
17
+ Mist.repository
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,260 @@
1
+ require 'spec_helper'
2
+ require 'mist/git_model'
3
+
4
+ describe Mist::GitModel do
5
+ let(:model_class) { Class.new(Mist::GitModel) { def self.name; "TestModel"; end } }
6
+ subject { model_class.new }
7
+
8
+ describe "when no records ever existed" do
9
+ it "last record should be nil" do
10
+ model_class.last.should be_nil
11
+ end
12
+
13
+ it "last 5 records should be empty" do
14
+ model_class.last(5).should be_empty
15
+ end
16
+ end
17
+
18
+ describe "when 1 record exists" do
19
+ before { model_class.create! }
20
+ it "last record should be the record" do
21
+ model_class.last.should be_kind_of(model_class)
22
+ end
23
+ it "last 5 records should be 1-element" do
24
+ model_class.last(5).should have(1).record
25
+ end
26
+ end
27
+
28
+ describe "when 2 records exist" do
29
+ before { model_class.create!; model_class.create! }
30
+ it "last record should be returned" do
31
+ model_class.last.should be_kind_of(model_class)
32
+ end
33
+ it 'last 5 records should contain 2' do
34
+ model_class.last(5).should have(2).records
35
+ end
36
+ it 'should return them in order, most recent first' do
37
+ model_class.last(5).collect { |i| i.id }.should == ['2', '1']
38
+ end
39
+ end
40
+
41
+ describe "when 10 records exist" do
42
+ before { 10.times { model_class.create! } }
43
+ it "last 5 records should not exceed 5" do
44
+ model_class.last(5).should have(5).records
45
+ end
46
+ it 'should return them in order, most recent first' do
47
+ model_class.last(5).collect { |i| i.id }.should == ['10', '9', '8', '7', '6']
48
+ end
49
+ end
50
+
51
+ it "should underscore and pluralize #table_name" do
52
+ subject.table_name.should == 'test_models'
53
+ end
54
+
55
+ it "should not be changed" do
56
+ # applying defaults should not be considered a change to a new record
57
+ subject.should_not be_changed
58
+ end
59
+
60
+ describe "validation" do
61
+ it "should be wrapped in callbacks" do
62
+ before = after = false
63
+ model_class.before_validation { before = true }
64
+ model_class.after_validation { after = true }
65
+
66
+ model_class.new.valid?
67
+ before.should be_true
68
+ after.should be_true
69
+ end
70
+
71
+ it "should not require id" do
72
+ subject.valid?
73
+ subject.errors[:id].should_not include("can't be blank")
74
+ end
75
+
76
+ describe "with an id" do
77
+ before { subject.id = 1 }
78
+
79
+ it "should enforce uniqueness of id" do
80
+ subject.save!
81
+
82
+ other = model_class.new(:id => 1)
83
+ other.valid?
84
+ other.errors[:id].should include("has already been taken")
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "creation" do
90
+ subject { model_class.create! }
91
+
92
+ it "should return the model" do
93
+ subject.should be_kind_of(model_class)
94
+ end
95
+
96
+ it "should not be a new record" do
97
+ subject.should_not be_new_record
98
+ end
99
+
100
+ it "should have an id" do
101
+ subject.id.should_not be_blank
102
+ end
103
+
104
+ it "should find it by its id" do
105
+ model_class.find(subject.id).should == subject
106
+ end
107
+ end
108
+
109
+ describe "new default record" do
110
+ it { should_not be_persisted }
111
+ it { should be_new_record }
112
+ it { should_not be_changed }
113
+
114
+ describe "after saving" do
115
+ before { subject.save! }
116
+
117
+ it { should be_persisted }
118
+ it { should_not be_new_record }
119
+ it { should_not be_changed }
120
+
121
+ it "should increase count" do
122
+ model_class.count.should == 1
123
+ end
124
+
125
+ it "should have created a file record" do
126
+ File.should be_file(subject.path)
127
+ end
128
+
129
+ it "should have created a commit" do
130
+ Mist.log.size.should == 1
131
+ end
132
+
133
+ it "should load the content in a separate query" do
134
+ model_class.find(subject.id).id.should == subject.id
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "an existing record" do
140
+ subject { model_class.create! }
141
+
142
+ describe "altered on file system" do
143
+ it "should load id from filename, not from file contents" do
144
+ # create a tainted record directly on the filesystem
145
+ yaml = YAML.load(File.read(subject.path))
146
+ yaml['id'] = '2'
147
+ new_path = File.expand_path('3', File.dirname(subject.path))
148
+ File.open(new_path, "w") { |f| f.print yaml.to_yaml }
149
+
150
+ # now load it
151
+ model_class.find('3').id.should == '3'
152
+ end
153
+
154
+ describe "files renamed on file system but not committed to git" do
155
+ before do
156
+ FileUtils.mv subject.path, File.expand_path('2', File.dirname(subject.path))
157
+ model
158
+ end
159
+
160
+ let :model do
161
+ model = model_class.find('2')
162
+ model.save!
163
+ model
164
+ end
165
+
166
+ it "should use the renamed file's id" do
167
+ model.id.should == '2'
168
+ end
169
+
170
+ it "should commit the new file to git" do
171
+ File.should exist(File.expand_path('2', File.dirname(model.path)))
172
+ Mist.repository.lib.diff_files.should be_empty
173
+ end
174
+
175
+ it "should remove the missing file from git" do
176
+ File.should_not exist(subject.path)
177
+ Mist.repository.lib.diff_files.should be_empty
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "destroying" do
183
+ before { subject.destroy }
184
+
185
+ it "should not be found" do
186
+ model_class.find(subject.id).should be_nil
187
+ end
188
+
189
+ it "should produce a commit" do
190
+ Mist.log.size.should == 2
191
+ end
192
+
193
+ it "should reduce count" do
194
+ model_class.count.should == 0
195
+ end
196
+ end
197
+
198
+ describe "with an attribute" do
199
+ before { model_class.attribute(:content); subject.save! }
200
+
201
+ describe "changing an attribute" do
202
+ before { subject.content = "changed"; subject.save! }
203
+
204
+ it "should create a commit" do
205
+ Mist.log.size.should == 2
206
+ end
207
+
208
+ it "should load the new content in a separate query" do
209
+ model_class.find(subject.id).content.should == "changed"
210
+ end
211
+ end
212
+
213
+ describe "changing its id and its attribute simultaneously" do
214
+ before do
215
+ @old_path = subject.path
216
+ subject.id = 2
217
+ subject.content = "changed"
218
+ subject.save!
219
+ end
220
+
221
+ it "should create a commit" do
222
+ Mist.log.size.should == 2
223
+ end
224
+
225
+ it "should have changed the content path" do
226
+ @old_path.should_not == subject.path
227
+ end
228
+
229
+ it "should have removed the old content path" do
230
+ File.should_not exist(@old_path)
231
+ end
232
+
233
+ it "should have created a new content path" do
234
+ File.should be_file(subject.path)
235
+ end
236
+
237
+ it "should load the new content in a separate query" do
238
+ model_class.find(subject.id).content.should == "changed"
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+
245
+ describe "active model lint tests" do
246
+ include Test::Unit::Assertions
247
+ include ActiveModel::Lint::Tests
248
+
249
+ def model
250
+ subject
251
+ end
252
+
253
+ # to_s is to support ruby-1.9
254
+ ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
255
+ example m.gsub('_',' ') do
256
+ send m
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,575 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mist::Post do
4
+ it "preview" do
5
+ subject.content = "# This is a header and stuff\r\nHere's a paragraph, or it would be if
6
+ I had anything much to talk about, but it's really not right now because I'm busy.
7
+ Go away.\r\n\r\nHere's some sample code to keep you sated:\r\n\r\n file: test.rb\r\n
8
+ \ one = :one\r\n\r\n## and this is another header"
9
+ proc { subject.content_as_html_preview }.should_not raise_error
10
+ end
11
+
12
+ it "should call observer" do
13
+ subject.title = subject.content = "title"
14
+ Mist::PostSweeper.instance.should_receive(:after_save)
15
+ subject.save
16
+ end
17
+
18
+ describe "Mist::Post#matching_tags" do
19
+ before do
20
+ Mist::Post.create!(attributes_for(:post).merge(:title => 'one', :tags => 'one'))
21
+ Mist::Post.create!(attributes_for(:post).merge(:title => 'two', :tags => 'two'))
22
+ Mist::Post.create!(attributes_for(:post).merge(:title => 'four', :tags => nil))
23
+ end
24
+
25
+ it "should return empty array if given nil" do
26
+ Mist::Post.matching_tags(nil).should be_empty
27
+ end
28
+
29
+ it "should return empty array if given empty string" do
30
+ Mist::Post.matching_tags([""]).should be_empty
31
+ end
32
+
33
+ it "should handle a single matching" do
34
+ Mist::Post.matching_tags(["one"]).should have(1).post
35
+ end
36
+
37
+ it "should handle a single not matching" do
38
+ Mist::Post.matching_tags(["three"]).should be_empty
39
+ end
40
+
41
+ it "should handle multiple matching" do
42
+ Mist::Post.matching_tags(['one', 'two']).should have(2).posts
43
+ end
44
+
45
+ it "should handle multiple matching and not matching" do
46
+ Mist::Post.matching_tags(['one', 'three']).should have(1).post
47
+ end
48
+ end
49
+
50
+ describe "tags" do
51
+ it "should save them" do
52
+ id = Mist::Post.create!(attributes_for(:post).merge(:tags => ['one', 'two', 'three'])).id
53
+ Mist::Post.find(id).tags.should == ['one', 'two', 'three']
54
+ end
55
+
56
+ it "should split them out of a string" do
57
+ p = Mist::Post.new
58
+ p.tags = 'one, two three,four'
59
+ p.tags.should == ['one', 'two three', 'four']
60
+ end
61
+ end
62
+
63
+ describe "recent posts" do
64
+ before do
65
+ @order = [ 1.day.ago, 2.days.ago, 3.days.ago, 4.days.ago, 5.days.ago ]
66
+ 5.times { |i| Mist::Post.create!(:title => "title#{i}", :content => "content", :published_at => @order[i]) }
67
+ end
68
+
69
+ it "should order by published_at descending" do
70
+ Mist::Post.recently_published(5)[0].published_at.should == @order[0]
71
+ Mist::Post.recently_published(5)[1].published_at.should == @order[1]
72
+ Mist::Post.recently_published(5)[2].published_at.should == @order[2]
73
+ Mist::Post.recently_published(5)[3].published_at.should == @order[3]
74
+ Mist::Post.recently_published(5)[4].published_at.should == @order[4]
75
+ end
76
+ end
77
+
78
+ describe "popularity" do
79
+ before do
80
+ subject.title = "post title"
81
+ subject.content = "content"
82
+ end
83
+
84
+ it "should start at 0" do
85
+ subject.popularity.should == 0
86
+ end
87
+
88
+ it "should not affect equality" do
89
+ # a post is equal if its ID matches, nothing else matters.
90
+ subject.save!
91
+ other = Mist::Post.last
92
+
93
+ subject.popularity = 1
94
+ subject.save!
95
+
96
+ subject.should == other
97
+ end
98
+
99
+ it "should order by descending popularity" do
100
+ 5.times { |i| Mist::Post.create!(:title => "title#{i}", :content => "content", :popularity => i) }
101
+
102
+ popular = Mist::Post.most_popular(5)
103
+ popular[0].popularity.should == 4
104
+ popular[1].popularity.should == 3
105
+ popular[2].popularity.should == 2
106
+ popular[3].popularity.should == 1
107
+ popular[4].popularity.should == 0
108
+ end
109
+
110
+ describe "after saving" do
111
+ before { subject.save! }
112
+
113
+ it "should be included in popular posts" do
114
+ Mist::Post.most_popular(5).should include(subject)
115
+ end
116
+
117
+ describe "and then deleting" do
118
+ before { subject.destroy }
119
+
120
+ it "should omit subject from popular posts" do
121
+ Mist::Post.most_popular(5).should be_empty
122
+ end
123
+ end
124
+
125
+ describe "after popularity has changed" do
126
+ before { subject.popularity = 5; subject.save! }
127
+
128
+ it "should load the popularity" do
129
+ Mist::Post.find(subject.id).popularity.should == 5
130
+ end
131
+
132
+ it "should return the post as among the most popular" do
133
+ Mist::Post.most_popular(5).should include(subject)
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ it "should omit cr's" do
140
+ subject.content = "a\r\nb"
141
+ subject.content.should == "a\nb"
142
+ end
143
+
144
+ it "should default to draft" do
145
+ subject.should_not be_published
146
+ subject.should be_draft
147
+ end
148
+
149
+ it "should be published if publish date set" do
150
+ subject.published_at = Time.now
151
+ subject.should be_published
152
+ subject.should_not be_draft
153
+ end
154
+
155
+ it "should be published now" do
156
+ time = Time.now
157
+ Time.stub!(:now).and_return(time) # to account for microseconds
158
+ subject.published = true
159
+ subject.published_at.should == time
160
+ end
161
+
162
+ describe "after publication" do
163
+ before { subject.published = true }
164
+
165
+ it "should nil out publication date" do
166
+ subject.published = false
167
+ subject.published_at.should be_blank
168
+ end
169
+
170
+ it "should not modify publication date" do
171
+ time = subject.published_at
172
+ subject.published = true
173
+ subject.published_at.should == time
174
+ end
175
+ end
176
+
177
+ describe "with 1 code example" do
178
+ before do
179
+ FakeWeb.register_uri(:post, 'https://api.github.com/gists', :response => fixture('gist_with_1_code_example'))
180
+ end
181
+
182
+ describe "reloading the post later" do
183
+ before do
184
+ FakeWeb.register_uri(:get, 'https://api.github.com/gists/1', :response => fixture('gist_with_1_code_example'))
185
+ Mist::Post.create!(:title => 'Code Example', :content => "# Test Content\n\n file: test.rb\n def one\n 1\n end\n\n# Moar test content")
186
+ end
187
+
188
+ subject { Mist::Post.find('code-example') }
189
+
190
+ describe "changing its title" do
191
+ before { subject.title = "new title" }
192
+
193
+ it "should not change its id" do
194
+ subject.id.should == "code-example"
195
+ end
196
+
197
+ describe "and then saving the post" do
198
+ before do
199
+ subject.gist.should_receive(:save).and_return(true)
200
+ subject.save!
201
+ end
202
+
203
+ it "should update the gist's description" do
204
+ subject.gist.description.should =~ /new title/
205
+ end
206
+
207
+ it "should change its id" do
208
+ subject.id.should == "new-title"
209
+ end
210
+ end
211
+ end
212
+
213
+ describe "and then adding a new code example" do
214
+ before { subject.content << "\n file: moar-file.rb\n moar = :more\n\nDone" }
215
+
216
+ it "should find 2 code examples" do
217
+ subject.code_examples.should have(2).examples
218
+ end
219
+
220
+ it "should create a new gist file" do
221
+ subject.gist.should_receive(:save).and_return(true)
222
+ subject.save
223
+ subject.gist.files.should have_key('moar-file.rb')
224
+ subject.gist.files['moar-file.rb'].should have_key(:content)
225
+ subject.gist.files['moar-file.rb'][:content].should == "moar = :more\n"
226
+ end
227
+
228
+ describe "saving and then removing one code example" do
229
+ before do
230
+ subject.gist.should_receive(:save).twice.and_return(true)
231
+ subject.save
232
+ subject.content["\n file: moar-file.rb\n moar = :more\n"] = "\n"
233
+ subject.save
234
+ end
235
+
236
+ it "should mark moar-file.rb for deletion" do
237
+ subject.gist.files.should have_key('moar-file.rb')
238
+ subject.gist.files['moar-file.rb'].should be_nil
239
+ end
240
+
241
+ it "should not mark test.rb for deletion" do
242
+ subject.gist.files.should have_key('test.rb')
243
+ subject.gist.files['test.rb'].should_not be_nil
244
+ end
245
+ end
246
+ end
247
+
248
+ describe "and then removing the code example" do
249
+ before { subject.content = "no code examples here" }
250
+
251
+ it "should find 0 code examples" do
252
+ subject.code_examples.should be_empty
253
+ end
254
+
255
+ it "should delete the gist" do
256
+ subject.gist.should_receive(:destroy).and_return(true)
257
+ subject.save
258
+ end
259
+ end
260
+
261
+ describe "with the gist now missing" do
262
+ before do
263
+ FakeWeb.register_uri(:get, 'https://api.github.com/gists/1', :response => fixture('gist_404'))
264
+ end
265
+
266
+ it "should not raise an error" do
267
+ proc { subject }.should_not raise_error
268
+ end
269
+
270
+ it "should embed code using regular markdown" do
271
+ subject.content_as_html.should_not =~ /gist.github.com/
272
+ end
273
+
274
+ describe "when saving the record" do
275
+ it "should create a new gist" do
276
+ subject.save!
277
+ subject.gist.should be_persisted
278
+ end
279
+ end
280
+ end
281
+
282
+ it "should ensure a blank line before and after gist embeds" do
283
+ # otherwise not having the blanks will cause markdown to not handle headers properly
284
+ content = subject.content_with_embedded_gists
285
+ content.should =~ /\n\n<script.*?<\/script>\n\n/
286
+ end
287
+
288
+ it "should still have the gist" do
289
+ subject.gist.should be_persisted
290
+ end
291
+
292
+ it "should contain gist code" do
293
+ # so the blogger can later edit the gist.
294
+ # Also, if the gist is externally upated, it should be reflected
295
+ # when blog post is updated. This test shows as much because in
296
+ # the fakeweb response, 1 is switched with :one.
297
+ subject.content.should == "# Test Content\n\n file: test.rb\n def one\n :one\n end\n\n# Moar test content"
298
+ end
299
+ end
300
+
301
+ describe "constructing a new post" do
302
+ before do
303
+ subject.title = "Code Example"
304
+ subject.content = "# Test Content\n\n file: test.rb\n def one\n 1\n end\n\n# Moar test content"
305
+ end
306
+
307
+ describe "after saving" do
308
+ before do
309
+ subject.gist.should_receive(:save).and_return(true)
310
+ subject.save
311
+ end
312
+
313
+ it "should know its own url" do
314
+ subject.url.should == "http://example.com/posts/code-example"
315
+ end
316
+
317
+ describe "the gist description" do
318
+ let(:desc) { subject.gist.description }
319
+
320
+ it "include link to post" do
321
+ desc.should =~ /example.com\/posts\/code-example/
322
+ end
323
+
324
+ it "should not include code example filename" do
325
+ subject.gist.description.should_not =~ /test.rb/
326
+ end
327
+ end
328
+
329
+ end
330
+ it { should have_code_examples }
331
+
332
+ it "should embed the gist in html" do
333
+ subject.save!
334
+ embed = '<script src="https://gist.github.com/1.js?file=test.rb"></script>'
335
+ subject.content_as_html.should =~ /#{Regexp::escape embed}/
336
+ end
337
+
338
+ it "should not embed the gist in html preview" do
339
+ subject.save!
340
+ subject.content_as_html_preview.should_not =~ /gist.github.com/
341
+ end
342
+
343
+ it "should not embed code in html preview" do
344
+ subject.save!
345
+ subject.content_as_html_preview.should_not =~ /file: test.rb/
346
+ subject.content_as_html_preview.should_not =~ /def one/
347
+ end
348
+
349
+ it "should include the first line in html preview" do
350
+ subject.save!
351
+ subject.content_as_html_preview.should =~ /Test Content/
352
+ end
353
+
354
+ it "should not include moar content in the html preview" do
355
+ subject.save!
356
+ subject.content_as_html_preview.should_not =~ /Moar test content/
357
+ end
358
+
359
+ it "should not embed gist info if there are no code examples" do
360
+ subject.content = "No code"
361
+ subject.save!
362
+ subject.content_as_html.should_not =~ /gist.github.com/
363
+ end
364
+
365
+ it "should use the example's filename" do
366
+ # we don't want the fake response to modify the gist this time
367
+ subject.gist.stub(:save).and_return(true)
368
+ subject.save!
369
+ subject.gist.files.should have_key('test.rb')
370
+ end
371
+
372
+ it "should identify 1 code example" do
373
+ subject.code_examples.length.should == 1
374
+ subject.code_examples.first.should == "def one\n 1\nend\n"
375
+ end
376
+
377
+ it "should not have saved a gist yet" do
378
+ subject.gist.should_not be_persisted
379
+ end
380
+
381
+ describe "saving" do
382
+ it "should save a gist" do
383
+ subject.save!
384
+ subject.gist.should be_persisted
385
+ end
386
+ end
387
+ end
388
+ end
389
+
390
+ describe "with no code examples" do
391
+ before do
392
+ subject.title = "No Code Example"
393
+ subject.content = "# Test Content"
394
+ end
395
+
396
+ it { should_not have_code_examples }
397
+
398
+ it "should not have a gist at all" do
399
+ subject.gist.should be_nil
400
+ end
401
+
402
+ it "should not create a gist" do
403
+ subject.save!
404
+ subject.gist.should be_nil
405
+ end
406
+ end
407
+
408
+ describe "validation" do
409
+ before { subject.valid? }
410
+
411
+ it "should require title" do
412
+ subject.errors[:title].should include("can't be blank")
413
+ end
414
+
415
+ it "should require content" do
416
+ subject.errors[:content].should include("can't be blank")
417
+ end
418
+
419
+ it "should enforce uniqueness of title" do
420
+ create :post
421
+ post = build :post
422
+ post.valid?
423
+ post.errors[:title].should include("has already been taken")
424
+ end
425
+ end
426
+
427
+ describe "creation" do
428
+ it "should return the post" do
429
+ Mist::Post.create!(attributes_for :post).should be_kind_of(Mist::Post)
430
+ end
431
+
432
+ it "should not be a new record" do
433
+ Mist::Post.create!(attributes_for :post).should_not be_new_record
434
+ end
435
+ end
436
+
437
+ describe "new valid record" do
438
+ subject { build :post }
439
+
440
+ it { should_not be_persisted }
441
+ it { should be_new_record }
442
+ it { should be_changed }
443
+
444
+ describe "after saving" do
445
+ before { subject.save! }
446
+
447
+ it { should be_persisted }
448
+ it { should_not be_new_record }
449
+ it { should_not be_changed }
450
+
451
+ it "should increase count" do
452
+ Mist::Post.count.should == 1
453
+ end
454
+
455
+ it "should have created an attribute file" do
456
+ File.should be_file(subject.path)
457
+ end
458
+
459
+ it "should have created a commit" do
460
+ Mist.log.size.should == 1
461
+ end
462
+
463
+ it "should load the content in a separate query" do
464
+ Mist::Post.find(subject.id).content.should == subject.content
465
+ end
466
+ end
467
+ end
468
+
469
+ describe "an existing record" do
470
+ subject { create :post }
471
+
472
+ describe "destroying" do
473
+ before { subject.destroy }
474
+
475
+ it "should not be found" do
476
+ Mist::Post.find(subject.id).should be_nil
477
+ end
478
+
479
+ it "should produce a commit" do
480
+ Mist.log.size.should == 2
481
+ end
482
+
483
+ it "should reduce count" do
484
+ Mist::Post.count.should == 0
485
+ end
486
+ end
487
+
488
+ describe "changing its content" do
489
+ before { subject.content = "changed"; subject.save! }
490
+
491
+ it "should create a commit" do
492
+ Mist.log.size.should == 2
493
+ end
494
+
495
+ it "should load the new content in a separate query" do
496
+ Mist::Post.find(subject.id).content.should == "changed"
497
+ end
498
+ end
499
+
500
+ describe "changing its subject" do
501
+ before do
502
+ @old_path = subject.path
503
+ subject.title = "changed"
504
+ subject.save!
505
+ end
506
+
507
+ it "should create a commit" do
508
+ Mist.log.size.should == 2
509
+ end
510
+
511
+ it "should have changed the content path" do
512
+ @old_path.should_not == subject.path
513
+ end
514
+
515
+ it "should have removed the old content path" do
516
+ File.should_not exist(@old_path)
517
+ end
518
+
519
+ it "should hvae created a new content path" do
520
+ File.should be_file(subject.path)
521
+ end
522
+ end
523
+
524
+ describe "changing its subject and its content simultaneously" do
525
+ before do
526
+ @old_path = subject.path
527
+ subject.title = "changed"
528
+ subject.content = "changed"
529
+ subject.save!
530
+ end
531
+
532
+ it "should create a commit" do
533
+ Mist.log.size.should == 2
534
+ end
535
+
536
+ it "should have changed the content path" do
537
+ @old_path.should_not == subject.path
538
+ end
539
+
540
+ it "should have removed the old content path" do
541
+ File.should_not exist(@old_path)
542
+ end
543
+
544
+ it "should have created a new content path" do
545
+ File.should be_file(subject.path)
546
+ end
547
+
548
+ it "should load the new content in a separate query" do
549
+ Mist::Post.find(subject.id).content.should == "changed"
550
+ end
551
+ end
552
+ end
553
+
554
+ describe "active model lint tests" do
555
+ # make sure we didn't break them
556
+
557
+ include Test::Unit::Assertions
558
+ include ActiveModel::Lint::Tests
559
+
560
+ def model
561
+ @model ||= Class.new(Mist::GitModel) do
562
+ def self.name
563
+ "TestModel"
564
+ end
565
+ end.new
566
+ end
567
+
568
+ # to_s is to support ruby-1.9
569
+ ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
570
+ example m.gsub('_',' ') do
571
+ send m
572
+ end
573
+ end
574
+ end
575
+ end