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