cmsimple 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. data/.gitignore +31 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +16 -0
  4. data/Gemfile +20 -0
  5. data/Gemfile.ci +5 -0
  6. data/LICENSE +19 -0
  7. data/README.md +118 -0
  8. data/Rakefile +47 -0
  9. data/app/assets/javascripts/cmsimple.js +61 -0
  10. data/app/assets/javascripts/cmsimple/controllers/editor.coffee +102 -0
  11. data/app/assets/javascripts/cmsimple/lib/namespaces.coffee +3 -0
  12. data/app/assets/javascripts/cmsimple/lib/support.coffee +4 -0
  13. data/app/assets/javascripts/cmsimple/mercury/config.js +462 -0
  14. data/app/assets/javascripts/cmsimple/mercury/handlers.coffee +9 -0
  15. data/app/assets/javascripts/cmsimple/models/image.coffee +49 -0
  16. data/app/assets/javascripts/cmsimple/models/page.coffee +106 -0
  17. data/app/assets/javascripts/cmsimple/models/path.coffee +31 -0
  18. data/app/assets/javascripts/cmsimple/models/version.coffee +29 -0
  19. data/app/assets/javascripts/cmsimple/panels/images/index.coffee +2 -0
  20. data/app/assets/javascripts/cmsimple/panels/images/list.coffee +48 -0
  21. data/app/assets/javascripts/cmsimple/panels/images/panel.coffee +69 -0
  22. data/app/assets/javascripts/cmsimple/panels/images/selection.coffee +52 -0
  23. data/app/assets/javascripts/cmsimple/panels/page_metadata.coffee +42 -0
  24. data/app/assets/javascripts/cmsimple/panels/publish.coffee +32 -0
  25. data/app/assets/javascripts/cmsimple/panels/redirects/form.coffee +51 -0
  26. data/app/assets/javascripts/cmsimple/panels/redirects/index.coffee +2 -0
  27. data/app/assets/javascripts/cmsimple/panels/redirects/list.coffee +34 -0
  28. data/app/assets/javascripts/cmsimple/panels/redirects/panel.coffee +39 -0
  29. data/app/assets/javascripts/cmsimple/panels/sitemap/index.coffee +2 -0
  30. data/app/assets/javascripts/cmsimple/panels/sitemap/panel.coffee +40 -0
  31. data/app/assets/javascripts/cmsimple/panels/sitemap/sortable.coffee +55 -0
  32. data/app/assets/javascripts/cmsimple/panels/sitemap/tree.coffee +61 -0
  33. data/app/assets/javascripts/cmsimple/panels/versions/index.coffee +2 -0
  34. data/app/assets/javascripts/cmsimple/panels/versions/list.coffee +51 -0
  35. data/app/assets/javascripts/cmsimple/panels/versions/panel.coffee +53 -0
  36. data/app/assets/javascripts/cmsimple/views/image_library/_item.jst.hamlc +24 -0
  37. data/app/assets/javascripts/cmsimple/views/image_library/list.jst.hamlc +8 -0
  38. data/app/assets/javascripts/cmsimple/views/redirects/_path.jst.hamlc +6 -0
  39. data/app/assets/javascripts/cmsimple/views/redirects/list.jst.hamlc +19 -0
  40. data/app/assets/javascripts/cmsimple/views/sitemap/_page.jst.hamlc +9 -0
  41. data/app/assets/javascripts/cmsimple/views/sitemap/tree.jst.hamlc +5 -0
  42. data/app/assets/javascripts/cmsimple/views/versions/_item.jst.hamlc +6 -0
  43. data/app/assets/javascripts/cmsimple/views/versions/list.jst.hamlc +4 -0
  44. data/app/assets/stylesheets/cmsimple.css +15 -0
  45. data/app/assets/stylesheets/cmsimple/forms.css +10 -0
  46. data/app/assets/stylesheets/cmsimple/panels.css +399 -0
  47. data/app/assets/stylesheets/cmsimple/toolbar-images.css +27 -0
  48. data/app/assets/stylesheets/mercury_overrides.css +2 -0
  49. data/app/controllers/cmsimple/front_controller.rb +38 -0
  50. data/app/controllers/cmsimple/images_controller.rb +28 -0
  51. data/app/controllers/cmsimple/mercury_controller.rb +7 -0
  52. data/app/controllers/cmsimple/pages_controller.rb +89 -0
  53. data/app/controllers/cmsimple/paths_controller.rb +23 -0
  54. data/app/controllers/cmsimple/snippets_controller.rb +23 -0
  55. data/app/controllers/cmsimple/versions_controller.rb +17 -0
  56. data/app/helpers/cmsimple/regions_helper.rb +38 -0
  57. data/app/models/cmsimple/image.rb +19 -0
  58. data/app/models/cmsimple/page.rb +203 -0
  59. data/app/models/cmsimple/path.rb +63 -0
  60. data/app/models/cmsimple/region.rb +43 -0
  61. data/app/models/cmsimple/snippet.rb +64 -0
  62. data/app/models/cmsimple/version.rb +18 -0
  63. data/app/uploaders/image_attachment_uploader.rb +53 -0
  64. data/app/views/cmsimple/images/_form.html.haml +13 -0
  65. data/app/views/cmsimple/images/new.html.haml +5 -0
  66. data/app/views/cmsimple/pages/_form.html.haml +49 -0
  67. data/app/views/cmsimple/pages/_form_extras.html.haml +2 -0
  68. data/app/views/cmsimple/pages/edit.html.haml +3 -0
  69. data/app/views/cmsimple/pages/new.html.haml +3 -0
  70. data/app/views/cmsimple/pages/publish.html.haml +12 -0
  71. data/app/views/cmsimple/snippets/index.html.haml +5 -0
  72. data/app/views/cmsimple/templates/default.html.haml +4 -0
  73. data/app/views/layouts/editor.html.haml +12 -0
  74. data/app/views/layouts/panel.html.haml +9 -0
  75. data/cmsimple.gemspec +44 -0
  76. data/config.ru +4 -0
  77. data/config/application.rb +1 -0
  78. data/config/cucumber.yml +8 -0
  79. data/config/environment.rb +2 -0
  80. data/config/routes.rb +34 -0
  81. data/db/migrate/20120201221410_create_cmsimple_pages.rb +10 -0
  82. data/db/migrate/20120222185901_add_title_parent_id_and_position_to_pages.rb +10 -0
  83. data/db/migrate/20120229185214_add_slug_to_pages.rb +7 -0
  84. data/db/migrate/20120323200439_add_is_root_to_page.rb +5 -0
  85. data/db/migrate/20120328174339_add_meta_info_to_cmsimple_pages.rb +9 -0
  86. data/db/migrate/20120329172857_create_cmsimple_path.rb +11 -0
  87. data/db/migrate/20120407024752_create_cmsimple_images.rb +13 -0
  88. data/db/migrate/20120410041105_add_timestamps_to_cmsimple_pages.rb +7 -0
  89. data/db/migrate/20120425211937_add_published_at_to_cmsimple_pages.rb +6 -0
  90. data/db/migrate/20120430175047_create_cmsimple_versions.rb +13 -0
  91. data/db/migrate/20120831233927_rename_path_to_current_uri_on_paths.rb +5 -0
  92. data/features/create.feature +18 -0
  93. data/features/delete.feature +12 -0
  94. data/features/edit.feature +54 -0
  95. data/features/publishing.feature +31 -0
  96. data/features/redirects.feature +27 -0
  97. data/features/step_definitions/mercury_steps.rb +440 -0
  98. data/features/step_definitions/page_steps.rb +269 -0
  99. data/features/step_definitions/web_steps.rb +254 -0
  100. data/features/support/env.rb +77 -0
  101. data/features/support/mercury_content.rb +25 -0
  102. data/features/support/mercury_selectors.rb +148 -0
  103. data/features/support/paths.rb +16 -0
  104. data/features/support/selectors.rb +26 -0
  105. data/features/support/utils.rb +18 -0
  106. data/features/versioning.feature +27 -0
  107. data/features/visit.feature +7 -0
  108. data/lib/cmsimple.rb +22 -0
  109. data/lib/cmsimple/configuration.rb +65 -0
  110. data/lib/cmsimple/exts/to_bool.rb +25 -0
  111. data/lib/cmsimple/page_responder.rb +46 -0
  112. data/lib/cmsimple/rails.rb +5 -0
  113. data/lib/cmsimple/regions_proxy.rb +32 -0
  114. data/lib/cmsimple/template_resolver.rb +14 -0
  115. data/lib/cmsimple/template_responder.rb +12 -0
  116. data/lib/cmsimple/version.rb +3 -0
  117. data/lib/generators/cmsimple/install/install_generator.rb +46 -0
  118. data/lib/generators/cmsimple/install/templates/default.html.erb +10 -0
  119. data/lib/generators/cmsimple/install/templates/default.html.haml +6 -0
  120. data/lib/generators/cmsimple/install/templates/initializer.rb +22 -0
  121. data/lib/generators/cmsimple/snippet/snippet_generator.rb +56 -0
  122. data/lib/generators/cmsimple/snippet/templates/cell.rb +18 -0
  123. data/lib/generators/cmsimple/snippet/templates/display.html.erb +14 -0
  124. data/lib/generators/cmsimple/snippet/templates/display.html.haml +11 -0
  125. data/lib/generators/cmsimple/snippet/templates/options.html.erb +18 -0
  126. data/lib/generators/cmsimple/snippet/templates/options.html.haml +12 -0
  127. data/script/app +2 -0
  128. data/script/migrate +2 -0
  129. data/script/rails +8 -0
  130. data/spec/fixtures/rails.png +0 -0
  131. data/spec/helpers/regions_helper_spec.rb +62 -0
  132. data/spec/javascripts/controllers/editor_spec.coffee +57 -0
  133. data/spec/javascripts/models/image_geometry_spec.coffee +64 -0
  134. data/spec/javascripts/models/image_spec.coffee +4 -0
  135. data/spec/javascripts/models/page_spec.coffee +121 -0
  136. data/spec/javascripts/models/path_spec.coffee +39 -0
  137. data/spec/javascripts/models/version_spec.coffee +31 -0
  138. data/spec/javascripts/panels/images/selection_spec.coffee +104 -0
  139. data/spec/javascripts/panels/sitemap/panel_spec.coffee +10 -0
  140. data/spec/javascripts/panels/sitemap/sortable_spec.coffee +63 -0
  141. data/spec/javascripts/panels/sitemap/tree_spec.coffee +62 -0
  142. data/spec/javascripts/spec_helper.coffee +25 -0
  143. data/spec/javascripts/support/stubs.coffee +16 -0
  144. data/spec/lib/page_responder_spec.rb +58 -0
  145. data/spec/lib/regions_proxy_spec.rb +47 -0
  146. data/spec/models/image_spec.rb +46 -0
  147. data/spec/models/page_spec.rb +237 -0
  148. data/spec/models/path_spec.rb +71 -0
  149. data/spec/models/region_spec.rb +14 -0
  150. data/spec/models/snippet_spec.rb +57 -0
  151. data/spec/models/version_spec.rb +7 -0
  152. data/spec/rails_app/.gitignore +33 -0
  153. data/spec/rails_app/Rakefile +7 -0
  154. data/spec/rails_app/app/assets/images/rails.png +0 -0
  155. data/spec/rails_app/app/assets/javascripts/application.js +9 -0
  156. data/spec/rails_app/app/assets/stylesheets/application.css +7 -0
  157. data/spec/rails_app/app/cells/example/display.html.haml +2 -0
  158. data/spec/rails_app/app/cells/example/options.html.haml +15 -0
  159. data/spec/rails_app/app/cells/example_cell.rb +19 -0
  160. data/spec/rails_app/app/cells/image_example/display.html.haml +4 -0
  161. data/spec/rails_app/app/cells/image_example/options.html.haml +15 -0
  162. data/spec/rails_app/app/cells/image_example_cell.rb +18 -0
  163. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  164. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  165. data/spec/rails_app/app/mailers/.gitkeep +0 -0
  166. data/spec/rails_app/app/models/.gitkeep +0 -0
  167. data/spec/rails_app/app/views/cmsimple/_snippet_list.html.haml +8 -0
  168. data/spec/rails_app/app/views/cmsimple/templates/default.html.haml +19 -0
  169. data/spec/rails_app/app/views/cmsimple/templates/test.html.haml +2 -0
  170. data/spec/rails_app/app/views/layouts/application.html.haml +13 -0
  171. data/spec/rails_app/config.ru +4 -0
  172. data/spec/rails_app/config/application.rb +65 -0
  173. data/spec/rails_app/config/boot.rb +9 -0
  174. data/spec/rails_app/config/database.example.yml +22 -0
  175. data/spec/rails_app/config/database.travis.yml +8 -0
  176. data/spec/rails_app/config/environment.rb +5 -0
  177. data/spec/rails_app/config/environments/development.rb +30 -0
  178. data/spec/rails_app/config/environments/test.rb +39 -0
  179. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  180. data/spec/rails_app/config/initializers/cells.rb +1 -0
  181. data/spec/rails_app/config/initializers/cmsimple.rb +3 -0
  182. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  183. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  184. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  185. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  186. data/spec/rails_app/config/initializers/teabag.rb +58 -0
  187. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  188. data/spec/rails_app/config/locales/en.yml +5 -0
  189. data/spec/rails_app/config/routes.rb +61 -0
  190. data/spec/rails_app/db/seeds.rb +11 -0
  191. data/spec/rails_app/lib/assets/.gitkeep +0 -0
  192. data/spec/rails_app/lib/tasks/.gitkeep +0 -0
  193. data/spec/rails_app/public/404.html +26 -0
  194. data/spec/rails_app/public/422.html +26 -0
  195. data/spec/rails_app/public/500.html +26 -0
  196. data/spec/rails_app/public/robots.txt +5 -0
  197. data/spec/rails_app/script/rails +6 -0
  198. data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
  199. data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
  200. data/spec/spec_helper.rb +31 -0
  201. data/spec/support/active_model_lint_example.rb +14 -0
  202. data/spec/teabag_env.rb +28 -0
  203. data/vendor/assets/images/sitemap/has-changes.png +0 -0
  204. data/vendor/assets/images/sitemap/published.png +0 -0
  205. data/vendor/assets/images/sitemap/unpublished.png +0 -0
  206. data/vendor/assets/images/toolbar/metadata.png +0 -0
  207. data/vendor/assets/images/toolbar/publish.png +0 -0
  208. data/vendor/assets/images/toolbar/redirect.png +0 -0
  209. data/vendor/assets/images/toolbar/sitemap.png +0 -0
  210. data/vendor/assets/images/toolbar/snippets.png +0 -0
  211. data/vendor/assets/images/toolbar/unpublished.png +0 -0
  212. data/vendor/assets/images/tree-line-horizontal-last.png +0 -0
  213. data/vendor/assets/images/tree-line-horizontal.png +0 -0
  214. data/vendor/assets/images/tree-line-vertical.png +0 -0
  215. data/vendor/assets/javascripts/jquery-serializeObject.js +107 -0
  216. data/vendor/assets/javascripts/jquery.to-json.js +74 -0
  217. data/vendor/assets/javascripts/jquery.ui.nestedSortable-1.3.4.js +391 -0
  218. data/vendor/assets/javascripts/moment-1.6.2.js +918 -0
  219. data/vendor/assets/javascripts/underscore-1.3.1.js +31 -0
  220. data/vendor/assets/stylesheets/jasmine-modified.css +216 -0
  221. metadata +686 -0
@@ -0,0 +1,25 @@
1
+ # You can require javascript files here. A good place to start is by requiring your application.js.
2
+ # require application
3
+ #
4
+ # Teabag includes some support files, but you can use anything from your own support path too.
5
+ # require support/jasmine-jquery
6
+ # require support/sinon
7
+ # require support/your-support-file
8
+ #
9
+ # Deferring execution
10
+ # If you're using CommonJS, RequireJS or some other asynchronous library you can defer execution. Call Teabag.execute()
11
+ # after everything has been loaded. Simple example of a timeout:
12
+ #
13
+ # Teabag.defer = true
14
+ # setTimeout(Teabag.execute, 1000)
15
+ #
16
+ # Matching files
17
+ # By default Teabag will look for files that match _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your
18
+ # spec path and it'll be included in the default suite automatically. If you want to customize suites, check out the
19
+ # configuration in config/initializers/teabag.rb
20
+ #
21
+ # Manifest
22
+ # If you'd rather require your spec files manually (to control order for instance) you can disable the suite matcher in
23
+ # the configuration and use this file as a manifest.
24
+ #
25
+ # For more information: http://github.com/modeset/teabag
@@ -0,0 +1,16 @@
1
+ @.stubs = {}
2
+ stubs.ajax = ->
3
+ jqXHR = $.Deferred()
4
+
5
+ $.extend jqXHR,
6
+ readyState: 0
7
+ setRequestHeader: -> @
8
+ getAllResponseHeaders: -> null
9
+ getResponseHeader: -> null
10
+ overrideMimeType: -> @
11
+ abort: -> @
12
+ success: jqXHR.done
13
+ error: jqXHR.fail
14
+ complete: jqXHR.done
15
+
16
+ spyOn($, "ajax").andReturn(jqXHR)
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cmsimple::PageResponder do
4
+ let(:controller) { OpenStruct.new request: OpenStruct.new(params: @params || {}),
5
+ current_page: @page,
6
+ in_editor_iframe?: @editable }
7
+
8
+ it 'raises ActiveRecord::RecordnotFound if the page is not viewable' do
9
+ responder = Cmsimple::PageResponder.new(controller)
10
+ responder.should_receive(:current_page_is_viewable?).and_return(false)
11
+ expect { responder.respond }.to raise_error(ActiveRecord::RecordNotFound)
12
+ end
13
+
14
+ describe 'current page is viewable' do
15
+ let(:responder) { responder = Cmsimple::PageResponder.new(controller) }
16
+ it 'is viewable if the request is in the editor iframe' do
17
+ @editable = true
18
+ responder.current_page_is_viewable?.should be_true
19
+ end
20
+
21
+ it 'is viewable if the page is published' do
22
+ @editable = false
23
+ @page = mock('page', :published? => true)
24
+ responder.current_page_is_viewable?.should be_true
25
+ end
26
+
27
+ it 'is not viewable if the request is not the editor iframe and the page is not published' do
28
+ @editable = false
29
+ @page = mock('page', :published? => false)
30
+ responder.current_page_is_viewable?.should be_false
31
+ end
32
+ end
33
+
34
+ describe 'page for context' do
35
+ let(:responder) { responder = Cmsimple::PageResponder.new(controller) }
36
+ it 'tries to use the published version of the page if the app is not in edit mode' do
37
+ @editable = false
38
+ @page = mock('page', :published? => true)
39
+ @page.should_receive(:as_published!).and_return(true)
40
+ responder.page_for_context
41
+ end
42
+
43
+ it 'tries to use the published version of the page if the app is not in edit mode' do
44
+ @editable = true
45
+ @page = mock('page', :published? => true)
46
+ @params = {version: 1}
47
+ @page.should_receive(:at_version!).and_return(true)
48
+ responder.page_for_context
49
+ end
50
+
51
+ it 'uses the current draft version by default' do
52
+ @editable = true
53
+ @page = mock('page', :published? => false)
54
+ responder.should_receive(:draft_context?)
55
+ responder.page_for_context
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ describe Cmsimple::RegionsProxy do
3
+
4
+ describe 'region proxy' do
5
+ it 'defines instance methods for each region in the hash' do
6
+ regions = Cmsimple::RegionsProxy.new content: {value: 'hello'}
7
+ regions.should respond_to :content
8
+ end
9
+
10
+ it 'defines instance methods for each region in the hash' do
11
+ regions = Cmsimple::RegionsProxy.new content: {value: 'hello'}
12
+ regions.content.should be_a(Cmsimple::Region)
13
+ end
14
+
15
+ it 'returns a NillRegion if no region present' do
16
+ regions = Cmsimple::RegionsProxy.new nil
17
+ regions.content.should be_a(Cmsimple::Region)
18
+ end
19
+ end
20
+
21
+ describe '#snippets_hash' do
22
+ before do
23
+ @regions = Cmsimple::RegionsProxy.new(
24
+ content: {value: "<div>some content</div><div>[snippet_0/0]</div>",
25
+ snippets: {snippet_0: {name: 'example',
26
+ options: { first_name: 'Fred',
27
+ last_name: 'Flinstone'
28
+ }}}},
29
+ header: {value: "<div>some content</div><div>[snippet_1/0]</div>",
30
+ snippets: {snippet_1: {name: 'example',
31
+ options: { first_name: 'Fred',
32
+ last_name: 'Flinstone'
33
+ }}}}
34
+ )
35
+ end
36
+
37
+ it "pull each snippet up into a single hash" do
38
+ @regions.snippets_hash.should have_key(:snippet_1)
39
+ @regions.snippets_hash.should have_key(:snippet_0)
40
+ end
41
+
42
+ it "has only the snippets in the hash" do
43
+ @regions.snippets_hash.keys.length.should == 2
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+ describe Cmsimple::Image do
3
+ include CarrierWave::Test::Matchers
4
+
5
+ before do
6
+ @file_fixture_path = Cmsimple::Engine.root.join('spec', 'fixtures', 'rails.png')
7
+ @file_fixture = File.open(@file_fixture_path)
8
+ end
9
+
10
+ after do
11
+ @file_fixture.close
12
+ Cmsimple::Image.all.each { |img| img.attachment.remove! }
13
+ end
14
+
15
+ subject { Cmsimple::Image.new }
16
+ it { should validate_presence_of(:attachment) }
17
+
18
+ describe 'image attachment uploader' do
19
+ it 'has an uploader mounted to attachment' do
20
+ subject.attachment.should be_a(ImageAttachmentUploader)
21
+ end
22
+ end
23
+
24
+ describe 'attachment attributes' do
25
+ before do
26
+ @image = Cmsimple::Image.new attachment: @file_fixture
27
+ @image.save!
28
+ end
29
+
30
+ it 'sets content_type from the file' do
31
+ @image.content_type.should == 'image/png'
32
+ end
33
+
34
+ it 'sets file_size from the file' do
35
+ @image.file_size.should == @file_fixture.size
36
+ end
37
+
38
+ it 'sets width from the file' do
39
+ @image.width.should == 50
40
+ end
41
+
42
+ it 'sets height from the file' do
43
+ @image.height.should == 64
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+ describe Cmsimple::Page do
3
+ subject { Cmsimple::Page.new }
4
+ it {should validate_presence_of(:title) }
5
+ it {should have_many(:children)}
6
+ it {should belong_to(:parent)}
7
+ it {should have_many(:versions)}
8
+
9
+ describe '#update_content' do
10
+ before do
11
+ subject.uri = '/about'
12
+ subject.save
13
+ end
14
+
15
+ it "updates content to a hash when json is given to #update_content" do
16
+ subject.update_content({'content' => 'hello world'})
17
+ subject.content.should be_a Hash
18
+ subject.content['content'].should == 'hello world'
19
+ end
20
+ end
21
+
22
+ describe '#descendants' do
23
+ before do
24
+ @childless = Cmsimple::Page.create(title: 'Misc', uri: '/Misc')
25
+ @page = Cmsimple::Page.create(title: 'Home', uri: '/')
26
+ @child = Cmsimple::Page.create(title: 'About', uri: '/about')
27
+ @page.children << @child
28
+ @descendant = Cmsimple::Page.create(title: 'Contact', uri: '/about/contact')
29
+ @child.children << @descendant
30
+ end
31
+
32
+ it 'retrieves all the descendants for a page' do
33
+ @page.descendants.map(&:title).should =~ ['About', 'Contact']
34
+ end
35
+
36
+ it 'returns nothing for a page with no descendants' do
37
+ @childless.descendants.should be_empty
38
+ end
39
+
40
+ describe '.for_parent_select' do
41
+ it 'only returns pages that arent direct descendants' do
42
+ Cmsimple::Page.for_parent_select(@page).map(&:title).should =~ ['Misc']
43
+ Cmsimple::Page.for_parent_select(@child).map(&:title).should =~ ['Misc', 'Home']
44
+ Cmsimple::Page.for_parent_select(@childless).map(&:title).should =~ ['Home', 'About', 'Contact']
45
+ end
46
+ end
47
+
48
+ describe '#root' do
49
+ it "returns the root of the page tree" do
50
+ @descendant.root.id.should == @page.id
51
+ end
52
+
53
+ it "returns the current page if it is a root" do
54
+ @page.root.id.should == @page.id
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'path generation' do
60
+
61
+ it 'returns a default slug from the title' do
62
+ subject.title = 'About Us'
63
+ subject.slug.should == 'about-us'
64
+ end
65
+
66
+ it "doesn't override the slug if it is manually set" do
67
+ subject.slug = 'about'
68
+ subject.title = 'About Us'
69
+ subject.slug.should == 'about'
70
+ end
71
+
72
+ it 'saves the default slug' do
73
+ subject.title = 'About Us'
74
+ subject.save
75
+ Cmsimple::Page.find(subject.id).slug.should == 'about-us'
76
+ end
77
+
78
+ it 'sets the path to the the slug on save if no parent' do
79
+ subject.title = 'About Us'
80
+ subject.save
81
+ Cmsimple::Page.find(subject.id).uri.should == '/about-us'
82
+ end
83
+
84
+ it 'sets the path to the the slug on save if no parent' do
85
+ about = Cmsimple::Page.create title: 'About'
86
+ subject.parent = about
87
+ subject.title = 'Contact Us'
88
+ subject.save
89
+ Cmsimple::Page.find(subject.id).uri.should == '/about/contact-us'
90
+ end
91
+ end
92
+
93
+ describe 'can only have one root page' do
94
+ before do
95
+ subject.title = 'About Us'
96
+ subject.is_root = true
97
+ subject.save
98
+ end
99
+
100
+ it 'can only have one root' do
101
+ Cmsimple::Page.create title: 'About', is_root: true
102
+ Cmsimple::Page.where(is_root: true).count.should == 1
103
+ end
104
+
105
+ it 'must have at least one root' do
106
+ subject.is_root = false
107
+ subject.should_not be_valid
108
+ end
109
+ end
110
+
111
+ describe 'paths' do
112
+ it 'creates a path after save' do
113
+ expect { Cmsimple::Page.create title: 'About' }.to change{ Cmsimple::Path.count }.by(1)
114
+ end
115
+
116
+ it "doesn't create a path if one already exists with the same uri" do
117
+ Cmsimple::Path.create uri: '/about', redirect_uri: '/'
118
+ expect { Cmsimple::Page.create title: 'About' }.to change{ Cmsimple::Path.count }.by(0)
119
+ end
120
+ end
121
+
122
+ describe 'publishing' do
123
+ let(:page) { Cmsimple::Page.create title: 'About' }
124
+
125
+ it "has published_at set to nil by default" do
126
+ page.published_at.should be_nil
127
+ end
128
+
129
+ describe '#publish!' do
130
+ it 'sets the published_at date to now' do
131
+ Timecop.freeze(Time.now) do
132
+ page.publish!
133
+ page.published_at.to_s.should == Time.zone.now.to_s
134
+ end
135
+ end
136
+
137
+ it 'creates a new published verson' do
138
+ expect { page.publish! }.to change { page.versions.count }.by(1)
139
+ end
140
+ end
141
+
142
+ describe '#unpublished_changes?' do
143
+ it 'has unpublished changes if it was never published' do
144
+ page.published_at.should be_nil
145
+ page.unpublished_changes?.should be_true
146
+ end
147
+
148
+ it 'does not have unpublished changes once it is published' do
149
+ page.publish!
150
+ page.unpublished_changes?.should be_false
151
+ end
152
+ end
153
+
154
+ describe '#unpublish!' do
155
+ before do
156
+ page.publish!
157
+ end
158
+
159
+ it 'sets published_at to nil' do
160
+ page.unpublish!
161
+ page.published_at.should be_nil
162
+ end
163
+ end
164
+
165
+ describe 'published scope' do
166
+ it 'returns the published page' do
167
+ page.publish!
168
+ Cmsimple::Page.published.first.should == page
169
+ end
170
+
171
+ it 'does not return the page if it is not published' do
172
+ Cmsimple::Page.published.first.should be_nil
173
+ end
174
+
175
+ it 'does not return the page if it is to be published in the future' do
176
+ page.published_at = 2.weeks.from_now
177
+ page.save!
178
+ Cmsimple::Page.published.first.should be_nil
179
+ end
180
+ end
181
+
182
+ describe 'consuming published content' do
183
+ before do
184
+ page.update_content({:content => {:value => 'content version 1'}})
185
+ page.publish!
186
+ end
187
+
188
+ it 'returns puslished content when in published mode if there are unpublished changes' do
189
+ page.update_content({:content => {:value => 'content version 2'}})
190
+ page.as_published!
191
+ page.regions.content.to_s.should == 'content version 1'
192
+ end
193
+ end
194
+ end
195
+
196
+ describe 'versioning' do
197
+ let(:page) { Cmsimple::Page.create title: 'About' }
198
+
199
+ before do
200
+ page.update_content({:content => {:value => 'content version 1'}})
201
+ page.publish!
202
+ page.update_content({:content => {:value => 'content version 2'}})
203
+ page.publish!
204
+ page.update_content({:content => {:value => 'content version 3'}})
205
+ page.publish!
206
+ end
207
+
208
+ describe 'can return a page at a specific version' do
209
+ it 'returns the page with the content from the requested version' do
210
+ version = page.versions.first
211
+ page.at_version!(version.id)
212
+ page.regions.content.to_s.should == 'content version 1'
213
+ end
214
+
215
+ it 'returns the page with the content from another requested version' do
216
+ version = page.versions.second
217
+ page.at_version!(version.id)
218
+ page.regions.content.to_s.should == 'content version 2'
219
+ end
220
+ end
221
+
222
+ describe 'reverting' do
223
+ it 'can revert to a specific version' do
224
+ version = page.versions.first
225
+ page.revert_to!(version.id)
226
+ Cmsimple::Page.find(page.id).regions.content.to_s.should == 'content version 1'
227
+ end
228
+
229
+ it 'can not revert to a version that is not from the pages history' do
230
+ other_page = Cmsimple::Page.create title: 'Contact'
231
+ other_page.update_content({:content => {:value => 'content version 1'}})
232
+ other_page.publish!
233
+ expect{ page.revert_to!(other_page.versions.first.id) }.to raise_error(ActiveRecord::RecordNotFound)
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+ describe Cmsimple::Path do
3
+ subject { Cmsimple::Path.new }
4
+ it { should validate_presence_of(:uri) }
5
+ it { should belong_to(:page) }
6
+
7
+ it 'downcases its URI on save' do
8
+ page = Cmsimple::Page.create title: 'Test'
9
+ path = Cmsimple::Path.create page: page, uri: '/TestRedirect'
10
+ path.uri.should == '/testredirect'
11
+ end
12
+
13
+ describe 'destination' do
14
+
15
+ it 'is required' do
16
+ subject.should_not be_valid
17
+ subject.errors[:destination].should_not be_empty
18
+ end
19
+
20
+ it 'returns the destination path when a page is associated' do
21
+ subject.page = Cmsimple::Page.new uri: '/some-path'
22
+ subject.destination.uri.should == '/some-path'
23
+ end
24
+
25
+ it 'returns the destination path when it is a redirect' do
26
+ subject.redirect_uri = '/some-other-path'
27
+ subject.destination.uri.should == '/some-other-path'
28
+ subject.should be_redirect
29
+ end
30
+
31
+ it 'returns a redirect if the path does not match the page path' do
32
+ subject.uri = '/path'
33
+ subject.page = Cmsimple::Page.new uri: '/some-other-path'
34
+ subject.destination.should be_a(Cmsimple::Path::Redirect)
35
+ subject.should be_redirect
36
+ end
37
+
38
+ end
39
+
40
+ describe 'from_request' do
41
+
42
+ it 'returns the path that has a uri mathing the request path' do
43
+ subject.uri = '/path'
44
+ subject.redirect_uri = '/some-other-path'
45
+ subject.save
46
+ Cmsimple::Path.from_request(OpenStruct.new(path: '/path')).destination.uri.should == '/some-other-path'
47
+ end
48
+
49
+ it "returns the path with the associated page" do
50
+ page = Cmsimple::Page.create title: 'About'
51
+ Cmsimple::Path.create uri: '/about', page: page
52
+ Cmsimple::Path.from_request(OpenStruct.new(path: '/about')).destination.title.should == 'About'
53
+ end
54
+
55
+ it "returns the path where the associated page is marked as root" do
56
+ page = Cmsimple::Page.create title: 'Home', is_root: true
57
+ Cmsimple::Path.create uri: '/home', page: page
58
+ Cmsimple::Path.from_request(OpenStruct.new(path: '/')).destination.title.should == 'Home'
59
+ end
60
+
61
+ it "normalizes the path before querying" do
62
+ subject.uri = '/path'
63
+ subject.redirect_uri = '/some-other-path'
64
+ subject.save
65
+ Cmsimple::Path.from_request(OpenStruct.new(path: '//path')).destination.uri.should == '/some-other-path'
66
+ Cmsimple::Path.from_request(OpenStruct.new(path: '//path/')).destination.uri.should == '/some-other-path'
67
+ Cmsimple::Path.from_request(OpenStruct.new(path: 'path')).destination.uri.should == '/some-other-path'
68
+ Cmsimple::Path.from_request(OpenStruct.new(path: '/Path')).destination.uri.should == '/some-other-path'
69
+ end
70
+ end
71
+ end