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,2 @@
1
+ require 'bundler/setup'
2
+ require File.expand_path "../../spec/rails_app/config/environment", __FILE__
@@ -0,0 +1,34 @@
1
+ Cmsimple::Engine.routes.draw do
2
+
3
+ match '/cmsimple/snippets/:name/preview' => 'snippets#preview'
4
+ match '/cmsimple/snippets/:name/options' => 'snippets#options'
5
+ match '/cmsimple/snippets' => 'snippets#index', :as => :snippets
6
+
7
+ resources :pages do
8
+ member do
9
+ get :publish
10
+ end
11
+ resources :versions, :only => [:index] do
12
+ member do
13
+ put :revert_to
14
+ end
15
+ end
16
+ end
17
+
18
+ resources :paths, :only => [:index, :create, :destroy]
19
+
20
+ scope '/cmsimple' do
21
+ resources :images
22
+ end
23
+
24
+ get '/mercury/:type/:resource' => "mercury#resource"
25
+
26
+ get '/editor(/*path)' => "pages#editor", :as => :mercury_editor
27
+
28
+ get '*path' => 'front#show'
29
+ root :to => 'front#show', :via => :get
30
+
31
+ put '*path' => 'pages#update_content'
32
+ root :to => 'pages#update_content', :via => :put
33
+
34
+ end
@@ -0,0 +1,10 @@
1
+ class CreateCmsimplePages < ActiveRecord::Migration
2
+ def change
3
+ create_table :cmsimple_pages do |t|
4
+ t.string :path, :null => false
5
+ t.string :template
6
+ t.text :content
7
+ end
8
+ add_index :cmsimple_pages, :path
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class AddTitleParentIdAndPositionToPages < ActiveRecord::Migration
2
+ def change
3
+ change_table :cmsimple_pages do |t|
4
+ t.string :title
5
+ t.integer :parent_id
6
+ t.integer :position, :default => 0
7
+ end
8
+ add_index :cmsimple_pages, :parent_id
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ class AddSlugToPages < ActiveRecord::Migration
2
+ def change
3
+ change_table :cmsimple_pages do |t|
4
+ t.string :slug
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class AddIsRootToPage < ActiveRecord::Migration
2
+ def change
3
+ add_column :cmsimple_pages, :is_root, :boolean, default: false
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class AddMetaInfoToCmsimplePages < ActiveRecord::Migration
2
+ def change
3
+ change_table :cmsimple_pages do |t|
4
+ t.string :keywords
5
+ t.text :description
6
+ t.string :browser_title
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class CreateCmsimplePath < ActiveRecord::Migration
2
+ def change
3
+ create_table :cmsimple_paths do |t|
4
+ t.string :uri
5
+ t.string :redirect_uri
6
+ t.integer :page_id
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCmsimpleImages < ActiveRecord::Migration
2
+ def change
3
+ create_table :cmsimple_images do |t|
4
+ t.string :attachment
5
+ t.string :width
6
+ t.string :height
7
+ t.string :file_size
8
+ t.string :content_type
9
+ t.string :title
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class AddTimestampsToCmsimplePages < ActiveRecord::Migration
2
+ def change
3
+ change_table :cmsimple_pages do |t|
4
+ t.timestamps
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class AddPublishedAtToCmsimplePages < ActiveRecord::Migration
2
+ def change
3
+ add_column :cmsimple_pages, :published_at, :datetime
4
+ add_index :cmsimple_pages, :published_at
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ class CreateCmsimpleVersions < ActiveRecord::Migration
2
+ def up
3
+ create_table :cmsimple_versions do |t|
4
+ t.text :content
5
+ t.string :template
6
+ t.datetime :published_at
7
+ t.integer :page_id
8
+ t.timestamps
9
+ end
10
+ add_index :cmsimple_versions, :page_id
11
+ add_index :cmsimple_versions, :published_at
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class RenamePathToCurrentUriOnPaths < ActiveRecord::Migration
2
+ def change
3
+ rename_column :cmsimple_pages, :path, :uri
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ @javascript
2
+ Feature: As a user I should be able to create pages on the site
3
+
4
+ Background:
5
+ Given a page exists at a custom path with custom content
6
+ And I visit that page's edit path
7
+
8
+ Scenario: As a user I add a page from the sitemap panel
9
+ When I open the sitemap
10
+ And I add a new page
11
+ Then I should be redirected to the new page
12
+ And I should see the page in the sitemap
13
+
14
+ Scenario: As a user I add a page from the sitemap panel
15
+ When I open the sitemap
16
+ And I add a new home page
17
+ Then I should be redirected to the home page
18
+ And I should see the page in the sitemap
@@ -0,0 +1,12 @@
1
+ @javascript
2
+ Feature: As a user I should be able to delete pages on the site
3
+
4
+ Scenario: As a user I should be able to delete a page
5
+ Given a page exists at a custom path with custom content
6
+ And I visit that page's edit path
7
+ And I edit the page's metadata
8
+ And the editor won't prompt when leaving the page
9
+ And I accept confirmations
10
+ When I follow "Delete"
11
+ Then I should be redirected to the home page
12
+
@@ -0,0 +1,54 @@
1
+ @javascript
2
+ Feature: As a user I should be able to edit pages on the site
3
+
4
+ Scenario: As a user I should be able to view a page with custom content at a specified path
5
+ Given a page exists at a custom path with custom content
6
+ When I visit that page's edit path
7
+ Then I should be able to edit that page's content
8
+
9
+ Scenario: As a user I want to be able to insert a snippet into a pages content
10
+ Given a page exists at a custom path with custom content
11
+ And I visit that page's edit path
12
+ And the content of the editable region is simple content
13
+ And I make a selection
14
+
15
+ When I open the snippet panel
16
+ And I drag the example snippet into the editable region
17
+ Then the modal window should be visible
18
+ And I should see "Snippet Options" within the modal title
19
+
20
+ When I fill in "First name" with "Fred"
21
+ And fill in "Last name" with "Flinstone"
22
+ And press "Create Snippet"
23
+ Then the modal window should not be visible
24
+ And the contents of the editable region should be "this is <div data-version='1' data-snippet='snippet_0' class='example-snippet' contenteditable='false'><h1>Fred Flinstone</h1></div><span>simple</span> <b>content</b>"
25
+
26
+ When I save and reload the page
27
+ Then the contents of the editable region should be "this is <div data-version='1' data-snippet='snippet_0' class='example-snippet' contenteditable='false'><h1>Fred Flinstone</h1></div><span>simple</span> <b>content</b>"
28
+
29
+ When I edit the snippet
30
+ Then the "First name" field should contain "Fred"
31
+ And the "Last name" field should contain "Flinstone"
32
+ And the editor won't prompt when leaving the page
33
+
34
+ Scenario: As a user I want to be able to edit the meta data of a page
35
+ Given a page exists at a custom path with custom content
36
+ And I visit that page's edit path
37
+ When I edit the page's metadata
38
+ And I change the slug to "new_path"
39
+ Then I should be on "/editor/new_path"
40
+
41
+ Scenario: As a user I want to be able to change the template of a page
42
+ Given a page exists at a custom path with custom content
43
+ And I visit that page's edit path
44
+ When I edit the page's metadata
45
+ And I change the template to "test"
46
+ Then I should see "Test template with content" in the content frame
47
+
48
+ Scenario: As a user I want to be able to change the SEO info of a page
49
+ Given a page exists at a custom path with custom content
50
+ And I visit that page's edit path
51
+ When I edit the page's metadata
52
+ And I change the seo info of the page
53
+ Then I should see that seo info on the page
54
+
@@ -0,0 +1,31 @@
1
+ @javascript
2
+ Feature: As a user I should be able to create pages on the site
3
+
4
+ Background:
5
+ Given a page exists at a custom path with custom content
6
+ And I visit that page's edit path
7
+
8
+ Scenario: As a user I have to publish pages for them to be public
9
+ Given I open the sitemap
10
+ And I add a new page
11
+ When I publish the current page
12
+ Then the current page should be publicly available
13
+
14
+ Scenario: As a user changes to a page don't appear to the public until they are published
15
+ Given I open the sitemap
16
+ And I add a new page
17
+ And I publish the current page
18
+ When I make changes to the current page
19
+ Then the current page should only show published content
20
+
21
+ Scenario: A user should see an indication of having unpublished changes
22
+ Given I open the sitemap
23
+ When I add a new page
24
+ Then there should be an indication of unpublished changes
25
+
26
+ When I publish the current page
27
+ Then there should not be an indication of unpublished changes
28
+
29
+ When I make changes to the current page
30
+ Then there should be an indication of unpublished changes
31
+
@@ -0,0 +1,27 @@
1
+ @javascript
2
+ Feature: As a user I should be able to add redirects on the site
3
+
4
+ Background:
5
+ Given a page exists at a custom path with custom content
6
+ And I visit that page's edit path
7
+
8
+ Scenario: As a user I add a redirect from the redirects panel
9
+ When I open the redirects
10
+ And I add a new redirect
11
+ Then I should see the path in the redirects
12
+
13
+ When I visit the new redirect
14
+ Then I should be redirected to the about page
15
+
16
+ Scenario: As a user I should not be able to add a duplicate redirect from the redirects panel
17
+ When I open the redirects
18
+ And I add a new duplicate redirect
19
+ Then I should not see the duplicate path in the redirects
20
+ And I should be alerted to the duplicate redirect
21
+
22
+ Scenario: As a user I delete a redirect from the redirects panel
23
+ Given I open the redirects
24
+ And I add a new redirect
25
+ When I delete the redirect
26
+ Then I should not see the path in the redirects
27
+
@@ -0,0 +1,440 @@
1
+
2
+ # Taken from /jejacks0n/mercury/master/features/step_definitions/mercury_steps.rb
3
+ ## Generic web steps
4
+ #------------------------------------------------------------------------------
5
+ When /^(?:|I )click on (.+)$/ do |locator|
6
+ selector = selector_for(locator)
7
+ find(selector, :message => "Unable to locate the element '#{selector}' to click on").click
8
+ end
9
+
10
+ Then /^(.+) should (not )?be visible$/ do |locator, boolean|
11
+ selector = selector_for(locator)
12
+ if boolean == 'not '
13
+ page.has_no_css?("#{selector}")
14
+ else
15
+ page.has_css?("#{selector}", :visible => true)
16
+ end
17
+ end
18
+
19
+ # scoping step for different windows
20
+ When /^(.*) in the "([^"]*)" window$/ do |s, window|
21
+ page.driver.within_window(window) do
22
+ step(s)
23
+ end
24
+ end
25
+
26
+
27
+ ## Mercury general steps
28
+ #------------------------------------------------------------------------------
29
+ Given /^(?:|I )adjust the configuration to have: \{([^\}]*)\}$/ do |javascript|
30
+ Rails.application.config.mercury_config = JSON.parse("{#{javascript}}")
31
+ end
32
+
33
+ # scoping step for the mercury content frame
34
+ When /^(.*) in the content frame$/ do |s|
35
+ page.driver.within_frame('mercury_iframe') do
36
+ step(s)
37
+ end
38
+ end
39
+
40
+ # silence mercury's onbeforeunload confirmation
41
+ Given /^the editor won't prompt when leaving the page$/ do
42
+ page.driver.execute_script('Mercury.silent = true;')
43
+ end
44
+
45
+
46
+ ## Toolbar specific steps
47
+ #------------------------------------------------------------------------------
48
+ # for the select dropdowns
49
+ When /^(?:|I )select (.*?) from the dropdown$/ do |locator|
50
+ selector = selector_for(locator)
51
+ find(selector, :message => "Unable to locate the element '#{selector}' to click on").click
52
+ end
53
+
54
+
55
+ ## Panel specific steps
56
+ #------------------------------------------------------------------------------
57
+ When /^(?:I )(?:open|close|toggle) the (.*?) panel$/ do |panel_locator|
58
+ step(%Q{I click on the "#{panel_locator}" button})
59
+ end
60
+
61
+
62
+ ## Modal specific steps
63
+ #------------------------------------------------------------------------------
64
+ When /^(?:I )close the modal(?: window)?$/ do
65
+ step(%Q{I click on the modal close button})
66
+ end
67
+
68
+
69
+ ## Region specific steps
70
+ #------------------------------------------------------------------------------
71
+ # setting content
72
+ Given /^the content of (.*?) (?:is|are|has|includes) (.*?)$/ do |region_locator, contents|
73
+ step(%Q{I set the contents of #{region_locator} to #{contents}})
74
+ end
75
+
76
+ When /^(?:|I )(?:change|set) the contents? of (.*?) to (.*?)$/ do |region_locator, contents|
77
+ region_id = region_selector_for(region_locator).gsub('#', '')
78
+ content = contents[0] == '"' ? contents : "\"#{contents_for(contents)}\""
79
+ page.driver.within_frame('mercury_iframe') do
80
+ find("##{region_id}", :message => "Unable to locate a region matching '##{region_id}'")
81
+ page.driver.execute_script <<-JAVASCRIPT
82
+ var element = top.jQuery(document).find('##{region_id}');
83
+ if (element.data('mercury') == 'markdown') {
84
+ element.find('textarea').val(#{content});
85
+ } else {
86
+ var region = top.mercuryInstance.getRegionByName('#{region_id}');
87
+ region.content(#{content});
88
+ }
89
+ JAVASCRIPT
90
+ end
91
+ end
92
+
93
+ # setting/making selections
94
+ When /^(?:|I )(?:make|have) a selection$/ do
95
+ step(%Q{I have a selection for "span"})
96
+ end
97
+
98
+ When /^(?:|I )(?:make|have) a selection (?:in (.*?) )?for "([^"]*)"$/ do |region_locator, selector|
99
+ step(%Q{I can simulate complex javascript events})
100
+ # assume the first editable region if one wasn't provided'
101
+ region_selector = region_selector_for(region_locator || 'the editable region')
102
+ page.driver.within_frame('mercury_iframe') do
103
+ find("#{region_selector}", :message => "Unable to locate a region matching '#{region_selector}'")
104
+ find("#{region_selector} #{selector}", :message => "Unable to locate a match for '#{selector}' inside '#{region_locator}'")
105
+ page.driver.execute_script <<-JAVASCRIPT
106
+ var element = top.jQuery(document).find('#{region_selector}');
107
+ if (element.data('mercury') == 'markdown') {
108
+ alert('unimplemented');
109
+ throw('unimplemented');
110
+ } else {
111
+ var selectedElement = element.find('#{selector}');
112
+ var selection = new top.Mercury.Regions.Full.Selection(window.getSelection(), document);
113
+ selection.selectNode(selectedElement.get(0));
114
+ selectedElement.simulate('mouseup');
115
+ }
116
+ JAVASCRIPT
117
+ end
118
+ end
119
+
120
+ # other events
121
+ When /^(?:|I )double click on (.*?) in (.*?)$/ do |locator, region_locator|
122
+ step(%Q{I can simulate complex javascript events})
123
+ selector = selector_for(locator)
124
+ # assume the first editable region if one wasn't provided'
125
+ region_selector = region_selector_for(region_locator || 'the editable region')
126
+ page.driver.within_frame('mercury_iframe') do
127
+ find("#{region_selector}", :message => "Unable to locate a region matching '#{region_selector}'")
128
+ find("#{region_selector} #{selector}", :message => "Unable to locate a match for '#{selector}' inside '#{region_locator}'")
129
+ page.driver.execute_script <<-JAVASCRIPT
130
+ top.jQuery(document).find('#{region_selector} #{selector}').simulate('dblclick');
131
+ JAVASCRIPT
132
+ end
133
+ end
134
+
135
+ # getting contents
136
+ Then /^the contents? of (.*?) should be "([^"]*)"$/ do |region_locator, content|
137
+ region_selector = region_selector_for(region_locator)
138
+ page.driver.within_frame('mercury_iframe') do
139
+ find("#{region_selector}", :message => "Unable to locate a region matching '#{region_selector}'")
140
+ results = page.driver.execute_script <<-JAVASCRIPT
141
+ var element = top.jQuery(document).find('#{region_selector}');
142
+ if (element.data('mercury') == 'markdown') {
143
+ return element.find('textarea').val();
144
+ } else {
145
+ return element.html();
146
+ }
147
+ JAVASCRIPT
148
+ assert_equal content, results.gsub('"', "'").gsub("\n", '')
149
+ end
150
+ end
151
+
152
+
153
+ ## Saving specific steps
154
+ #------------------------------------------------------------------------------
155
+ # caching for the last save -- a request will still be made
156
+ Given /^save results will be cached$/ do
157
+ page.driver.execute_script <<-JAVASCRIPT
158
+ Mercury.PageEditor.prototype.save = function() {
159
+ window.cachedResults = this.serialize();
160
+ }
161
+ JAVASCRIPT
162
+ end
163
+
164
+ # check for the last save cached results
165
+ Then /^the save should have (.*?) for (.*?)$/ do |contents, region_locator|
166
+ region_id = region_selector_for(region_locator).gsub('#', '')
167
+ content = contents[0] == '"' ? contents : "\"#{contents_for(contents)}\""
168
+ results = page.driver.execute_script <<-JAVASCRIPT
169
+ return (window.cachedResults['#{region_id}']) ?
170
+ window.cachedResults['#{region_id}']['value'] : null;
171
+ JAVASCRIPT
172
+ assert_equal content, "\"#{results}\""
173
+ end
174
+
175
+
176
+ ## Table editing specific steps
177
+ #------------------------------------------------------------------------------
178
+ # in the modal window
179
+ When /^(?:|I )(?:add|insert) a (row|column) (before|after)(?: it)?$/ do |row_or_column, before_or_after|
180
+ name = "insert_#{row_or_column}_#{before_or_after}".camelcase(:lower)
181
+ step(%Q{I click on ".mercury-modal-content input[name=#{name}]"})
182
+ end
183
+
184
+ When /^(?:|I )delete the(?: current)? (row|column)$/ do |row_or_column|
185
+ name = "delete_#{row_or_column}".camelcase(:lower)
186
+ step(%Q{I click on ".mercury-modal-content input[name=#{name}]"})
187
+ end
188
+
189
+ When /^(?:|I )(increase|decrease) the (rowspan|colspan)$/ do |increase_or_decrease, rowspan_or_colspan|
190
+ name = "#{increase_or_decrease}_#{rowspan_or_colspan}".camelcase(:lower)
191
+ step(%Q{I click on ".mercury-modal-content input[name=#{name}]"})
192
+ end
193
+
194
+ Then /^the selected cell should be (.*?)$/ do |locator|
195
+ selector = selector_for(locator).gsub('td:', 'td.selected:')
196
+ find("#{selector}", :message => "Unable to locate the selected cell for '#{selector}'")
197
+ end
198
+
199
+ # in general
200
+ Then /^the(?: table)? (row|column) count should be (\d+)$/ do |row_or_column, expected_count|
201
+ method = "get_#{row_or_column}_count".camelcase(:lower)
202
+ actual_count = page.driver.execute_script("return Mercury.tableEditor.#{method}()")
203
+ assert_equal expected_count.to_i, actual_count.to_i
204
+ end
205
+
206
+
207
+ ## Snippet specific steps
208
+ #------------------------------------------------------------------------------
209
+ # setting snippet options
210
+ Given /^the options for the (.*?) snippet "([^"]*)" are (.*?)$/ do |snippet_name, snippet_id, options|
211
+ @snippet_id = snippet_id
212
+ options_json = parse_snippet_options_from(options)
213
+ page.driver.execute_script <<-JAVASCRIPT
214
+ Mercury.Snippet.load({#{snippet_id}: {name: '#{snippet_name}', options: #{options_json}}});
215
+ JAVASCRIPT
216
+ end
217
+
218
+ # dragging/dropping
219
+ When /^(?:|I )(?:drag|drop) (.*?) (?:into|on) (.*?)$/ do |snippet_locator, region_locator|
220
+ snippet_name = snippet_name_for(snippet_locator)
221
+ region_id = region_selector_for(region_locator).gsub('#', '')
222
+ page.driver.within_frame('mercury_iframe') do
223
+ find("##{region_id}", :message => "Unable to locate a region matching '##{region_id}'")
224
+ page.driver.execute_script <<-JAVASCRIPT
225
+ var element = top.jQuery(document).find('##{region_id}');
226
+ if (element.data('mercury') == 'markdown') {
227
+ alert('unimplemented');
228
+ throw('unimplemented');
229
+ } else {
230
+ var region = top.mercuryInstance.getRegionByName('#{region_id}');
231
+ region.selection().range.collapse(true);
232
+ document.execCommand('insertHTML', false, '<img data-snippet="#{snippet_name}" src="/assets/mercury/default-snippet.png">');
233
+ element.trigger('possible:drop');
234
+ }
235
+ JAVASCRIPT
236
+ end
237
+ end
238
+
239
+ When /^(?:|I )hover over (.*?)(?: in (.*?))?$/ do |locator, region_locator|
240
+ step(%Q{I can simulate complex javascript events})
241
+ selector = selector_for(locator)
242
+ region_selector = region_selector_for(region_locator || 'the editable region')
243
+ page.driver.within_frame('mercury_iframe') do
244
+ find("#{region_selector}", :message => "Unable to locate a region matching '#{region_selector}'")
245
+ page.driver.execute_script <<-JAVASCRIPT
246
+ var element = top.jQuery(document).find('#{region_selector}');
247
+ if (element.data('mercury') == 'markdown') {
248
+ alert('unimplemented');
249
+ throw('unimplemented');
250
+ } else {
251
+ element.find('#{selector}').simulate('mousemove');
252
+ }
253
+ JAVASCRIPT
254
+ end
255
+ end
256
+
257
+ When /^(?:|I )edit the snippet$/ do
258
+ step(%{I hover over the snippet})
259
+ step(%{click on the edit snippet settings toolbar button})
260
+ end
261
+
262
+
263
+ ## Dropping image specific steps
264
+ #------------------------------------------------------------------------------
265
+ #When /^(?:|I )drop an image into (.*?) from a different browser/ do |region_locator|
266
+ # Given(%Q{I can simulate complex javascript events})
267
+ # region_selector = region_selector_for(region_locator || 'the editable region')
268
+ # page.driver.within_frame('mercury_iframe') do
269
+ # find("#{region_selector}", :message => "Unable to locate a region matching '#{region_selector}'")
270
+ # page.driver.execute_script <<-JAVASCRIPT
271
+ # var element = top.jQuery(document).find('#{region_selector}');
272
+ # if (element.data('type') == 'markupable') {
273
+ # alert('unimplemented');
274
+ # throw('unimplemented');
275
+ # } else {
276
+ # element.find('#{region_selector}').simulate('drop', {'text/html': '<img src="testing.gif"/>'});
277
+ # }
278
+ # JAVASCRIPT
279
+ # end
280
+ #end
281
+
282
+
283
+ ## Javascript event simulation steps
284
+ #------------------------------------------------------------------------------
285
+ Given /^(?:|I )can simulate complex javascript events$/ do
286
+ page.driver.execute_script(EVENT_SIMULATION_JAVASCRIPT)
287
+ end
288
+
289
+ #------------------------------------------------------------------------------
290
+
291
+ EVENT_SIMULATION_JAVASCRIPT = <<-JAVASCRIPT
292
+ /*
293
+ * jquery.simulate - simulate browser mouse and keyboard events
294
+ *
295
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
296
+ * Dual licensed under the MIT or GPL Version 2 licenses.
297
+ * http://jquery.org/license
298
+ *
299
+ */
300
+ ;(function($) {
301
+
302
+ $.fn.extend({
303
+ simulate: function(type, options) {
304
+ return this.each(function() {
305
+ var opt = $.extend({}, $.simulate.defaults, options || {});
306
+ new $.simulate(this, type, opt);
307
+ });
308
+ }
309
+ });
310
+
311
+ $.simulate = function(el, type, options) {
312
+ this.target = el;
313
+ this.options = options;
314
+
315
+ if (/^drag$/.test(type)) {
316
+ this[type].apply(this, [this.target, options]);
317
+ } else {
318
+ this.simulateEvent(el, type, options);
319
+ }
320
+ }
321
+
322
+ $.extend($.simulate.prototype, {
323
+ simulateEvent: function(el, type, options) {
324
+ var evt = this.createEvent(type, options);
325
+ this.dispatchEvent(el, type, evt, options);
326
+ return evt;
327
+ },
328
+ createEvent: function(type, options) {
329
+ if (/^mouse(over|out|down|up|move)|(dbl)?click$/.test(type)) {
330
+ return this.mouseEvent(type, options);
331
+ } else if (/^key(up|down|press)$/.test(type)) {
332
+ return this.keyboardEvent(type, options);
333
+ }
334
+ },
335
+ mouseEvent: function(type, options) {
336
+ var evt;
337
+ var e = $.extend({
338
+ bubbles: true, cancelable: (type != "mousemove"), view: window, detail: 0,
339
+ screenX: 0, screenY: 0, clientX: 0, clientY: 0,
340
+ ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
341
+ button: 0, relatedTarget: undefined
342
+ }, options);
343
+
344
+ var relatedTarget = $(e.relatedTarget)[0];
345
+
346
+ if ($.isFunction(document.createEvent)) {
347
+ evt = document.createEvent("MouseEvents");
348
+ evt.initMouseEvent(type, e.bubbles, e.cancelable, e.view, e.detail,
349
+ e.screenX, e.screenY, e.clientX, e.clientY,
350
+ e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
351
+ e.button, e.relatedTarget || document.body.parentNode);
352
+ } else if (document.createEventObject) {
353
+ evt = document.createEventObject();
354
+ $.extend(evt, e);
355
+ evt.button = { 0:1, 1:4, 2:2 }[evt.button] || evt.button;
356
+ }
357
+ return evt;
358
+ },
359
+ keyboardEvent: function(type, options) {
360
+ var evt;
361
+
362
+ var e = $.extend({ bubbles: true, cancelable: true, view: window,
363
+ ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
364
+ keyCode: 0, charCode: 0
365
+ }, options);
366
+
367
+ if ($.isFunction(document.createEvent)) {
368
+ try {
369
+ evt = document.createEvent("KeyEvents");
370
+ evt.initKeyEvent(type, e.bubbles, e.cancelable, e.view,
371
+ e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
372
+ e.keyCode, e.charCode);
373
+ } catch(err) {
374
+ evt = document.createEvent("Events");
375
+ evt.initEvent(type, e.bubbles, e.cancelable);
376
+ $.extend(evt, { view: e.view,
377
+ ctrlKey: e.ctrlKey, altKey: e.altKey, shiftKey: e.shiftKey, metaKey: e.metaKey,
378
+ keyCode: e.keyCode, charCode: e.charCode
379
+ });
380
+ }
381
+ } else if (document.createEventObject) {
382
+ evt = document.createEventObject();
383
+ $.extend(evt, e);
384
+ }
385
+ if ($.browser.msie || $.browser.opera) {
386
+ evt.keyCode = (e.charCode > 0) ? e.charCode : e.keyCode;
387
+ evt.charCode = undefined;
388
+ }
389
+ return evt;
390
+ },
391
+ dispatchEvent: function(el, type, evt) {
392
+ if (el.dispatchEvent) {
393
+ el.dispatchEvent(evt);
394
+ } else if (el.fireEvent) {
395
+ el.fireEvent('on' + type, evt);
396
+ }
397
+ return evt;
398
+ },
399
+ drag: function(el) {
400
+ var self = this, center = this.findCenter(this.target),
401
+ options = this.options, x = Math.floor(center.x), y = Math.floor(center.y),
402
+ dx = options.dx || 0, dy = options.dy || 0, target = this.target;
403
+ var coord = { clientX: x, clientY: y };
404
+ this.simulateEvent(target, "mousedown", coord);
405
+ coord = { clientX: x + 1, clientY: y + 1 };
406
+ this.simulateEvent(document, "mousemove", coord);
407
+ coord = { clientX: x + dx, clientY: y + dy };
408
+ this.simulateEvent(document, "mousemove", coord);
409
+ this.simulateEvent(document, "mousemove", coord);
410
+ this.simulateEvent(target, "mouseup", coord);
411
+ this.simulateEvent(target, "click", coord);
412
+ },
413
+ findCenter: function(el) {
414
+ var el = $(this.target), o = el.offset(), d = $(document);
415
+ return {
416
+ x: o.left + el.outerWidth() / 2 - d.scrollLeft(),
417
+ y: o.top + el.outerHeight() / 2 - d.scrollTop()
418
+ };
419
+ }
420
+ });
421
+
422
+ $.extend($.simulate, {
423
+ defaults: {
424
+ speed: 'sync'
425
+ },
426
+ VK_TAB: 9,
427
+ VK_ENTER: 13,
428
+ VK_ESC: 27,
429
+ VK_PGUP: 33,
430
+ VK_PGDN: 34,
431
+ VK_END: 35,
432
+ VK_HOME: 36,
433
+ VK_LEFT: 37,
434
+ VK_UP: 38,
435
+ VK_RIGHT: 39,
436
+ VK_DOWN: 40
437
+ });
438
+
439
+ })(jQuery);
440
+ JAVASCRIPT