cmsimple 0.2.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 (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