mist 0.6.0

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 (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