storytime 2.1.6 → 2.1.7

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 (236) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +54 -0
  3. data/.gitignore +2 -1
  4. data/.ruby-version +1 -1
  5. data/.tool-versions +1 -0
  6. data/Gemfile +5 -3
  7. data/Gemfile.lock +414 -425
  8. data/Guardfile +1 -1
  9. data/app/assets/fonts/storytime-icons.eot +0 -0
  10. data/app/assets/fonts/storytime-icons.svg +23 -0
  11. data/app/assets/fonts/storytime-icons.ttf +0 -0
  12. data/app/assets/fonts/storytime-icons.woff +0 -0
  13. data/app/assets/javascripts/storytime/application.js +1 -8
  14. data/app/assets/javascripts/storytime/off_canvas.coffee +16 -0
  15. data/app/assets/javascripts/storytime/wysiwyg.js.coffee +9 -7
  16. data/app/assets/stylesheets/storytime/_buttons.scss +33 -0
  17. data/app/assets/stylesheets/storytime/_list-group.scss +1 -1
  18. data/app/assets/stylesheets/storytime/application.scss +14 -2
  19. data/app/assets/stylesheets/storytime/icons.scss +5 -5
  20. data/app/assets/stylesheets/storytime/leather/_buttons.scss +31 -0
  21. data/app/assets/stylesheets/storytime/leather/_devise.scss +72 -0
  22. data/app/assets/stylesheets/storytime/leather/_grid.scss +19 -0
  23. data/app/assets/stylesheets/storytime/leather/_list_groups.scss +33 -0
  24. data/app/assets/stylesheets/storytime/leather/_nav_menus.scss +125 -0
  25. data/app/assets/stylesheets/storytime/leather/_navbar_transparent.scss +23 -0
  26. data/app/assets/stylesheets/storytime/leather/_off_canvas.scss +109 -0
  27. data/app/assets/stylesheets/storytime/leather/_scroll_panels.scss +85 -0
  28. data/app/assets/stylesheets/storytime/leather/_toggle_columns.scss +23 -0
  29. data/app/assets/stylesheets/storytime/leather/_typography.scss +12 -0
  30. data/app/assets/stylesheets/storytime/leather/_utilities.scss +54 -0
  31. data/app/assets/stylesheets/storytime/leather/_variables.scss +8 -0
  32. data/app/assets/stylesheets/storytime/posts.scss +9 -1
  33. data/app/controllers/storytime/application_controller.rb +1 -1
  34. data/app/controllers/storytime/dashboard/blog_posts_controller.rb +5 -3
  35. data/app/controllers/storytime/dashboard/blogs_controller.rb +1 -1
  36. data/app/controllers/storytime/dashboard/media_controller.rb +4 -4
  37. data/app/controllers/storytime/dashboard/memberships_controller.rb +1 -1
  38. data/app/controllers/storytime/dashboard/pages_controller.rb +26 -6
  39. data/app/controllers/storytime/dashboard/posts_controller.rb +30 -5
  40. data/app/controllers/storytime/dashboard/subscriptions_controller.rb +1 -1
  41. data/app/controllers/storytime/pages_controller.rb +31 -1
  42. data/app/controllers/storytime/posts_controller.rb +1 -1
  43. data/app/controllers/storytime/subscriptions_controller.rb +5 -1
  44. data/app/helpers/storytime/application_helper.rb +0 -4
  45. data/app/models/concerns/storytime/post_featured_images.rb +2 -2
  46. data/app/models/concerns/storytime/post_partial_inheritance.rb +2 -2
  47. data/app/models/storytime/autosave.rb +1 -1
  48. data/app/models/storytime/comment.rb +1 -1
  49. data/app/models/storytime/media.rb +1 -1
  50. data/app/models/storytime/membership.rb +2 -2
  51. data/app/models/storytime/post.rb +7 -3
  52. data/app/models/storytime/site.rb +4 -6
  53. data/app/models/storytime/snippet.rb +12 -1
  54. data/app/models/storytime/subscription.rb +2 -2
  55. data/app/models/storytime/version.rb +1 -1
  56. data/app/policies/storytime/post_policy.rb +1 -1
  57. data/app/views/storytime/dashboard/_navigation.html.erb +0 -9
  58. data/app/views/storytime/dashboard/blog_posts/_form.html.erb +5 -1
  59. data/app/views/storytime/dashboard/blogs/edit.json.jbuilder +1 -1
  60. data/app/views/storytime/dashboard/blogs/index.json.jbuilder +1 -1
  61. data/app/views/storytime/dashboard/blogs/new.json.jbuilder +1 -1
  62. data/app/views/storytime/dashboard/media/_gallery.html.erb +1 -1
  63. data/app/views/storytime/dashboard/media/_modal.html.erb +2 -2
  64. data/app/views/storytime/dashboard/media/show.json.jbuilder +1 -1
  65. data/app/views/storytime/dashboard/memberships/_edit.html.erb +1 -1
  66. data/app/views/storytime/dashboard/memberships/_index.html.erb +3 -3
  67. data/app/views/storytime/dashboard/memberships/_new.html.erb +1 -1
  68. data/app/views/storytime/dashboard/memberships/edit.json.jbuilder +1 -1
  69. data/app/views/storytime/dashboard/memberships/index.json.jbuilder +1 -1
  70. data/app/views/storytime/dashboard/memberships/new.json.jbuilder +1 -1
  71. data/app/views/storytime/dashboard/memberships/save.json.jbuilder +1 -1
  72. data/app/views/storytime/dashboard/pages/_form.html.erb +1 -1
  73. data/app/views/storytime/dashboard/posts/_directory.html.erb +18 -0
  74. data/app/views/storytime/dashboard/posts/_form.html.erb +1 -1
  75. data/app/views/storytime/dashboard/posts/_list.html.erb +2 -26
  76. data/app/views/storytime/dashboard/posts/_new_button.html.erb +1 -1
  77. data/app/views/storytime/dashboard/posts/_post.html.erb +28 -0
  78. data/app/views/storytime/dashboard/posts/_sorts.html.erb +21 -0
  79. data/app/views/storytime/dashboard/posts/index.html.erb +5 -3
  80. data/app/views/storytime/dashboard/roles/_form.html.erb +1 -1
  81. data/app/views/storytime/dashboard/roles/edit.json.jbuilder +1 -1
  82. data/app/views/storytime/dashboard/sites/_form.html.erb +1 -1
  83. data/app/views/storytime/dashboard/sites/site.json.jbuilder +1 -1
  84. data/app/views/storytime/dashboard/snippets/_index.html.erb +1 -1
  85. data/app/views/storytime/dashboard/snippets/edit.json.jbuilder +1 -1
  86. data/app/views/storytime/dashboard/snippets/index.json.jbuilder +1 -1
  87. data/app/views/storytime/dashboard/snippets/new.json.jbuilder +1 -1
  88. data/app/views/storytime/dashboard/subscriptions/_form.html.erb +1 -1
  89. data/app/views/storytime/dashboard/subscriptions/_index.html.erb +2 -2
  90. data/app/views/storytime/dashboard/subscriptions/form.json.jbuilder +1 -1
  91. data/app/views/storytime/dashboard/subscriptions/index.json.jbuilder +1 -1
  92. data/app/views/storytime/dashboard/versions/_versions_info.html.erb +1 -1
  93. data/app/views/storytime/posts/show.html.erb +6 -0
  94. data/app/views/storytime/sites/_google_analytics_code.html.erb +5 -8
  95. data/config/initializers/assets.rb +2 -1
  96. data/config/initializers/friendly_id.rb +1 -1
  97. data/config/initializers/url_for_patch.rb +19 -8
  98. data/config/locales/devise.zh-CN.yml +59 -0
  99. data/config/locales/kaminari.zh-CN.yml +17 -0
  100. data/config/locales/simple_form.zh-CN.yml +26 -0
  101. data/config/locales/zh-CN.yml +141 -0
  102. data/config/routes.rb +2 -4
  103. data/db/migrate/20140501174341_create_storytime_posts.rb +1 -1
  104. data/db/migrate/20140509191309_create_friendly_id_slugs.rb +1 -1
  105. data/db/migrate/20140511200849_create_storytime_media.rb +1 -1
  106. data/db/migrate/20140513161233_create_storytime_sites.rb +1 -1
  107. data/db/migrate/20140514200234_create_storytime_tags.rb +1 -1
  108. data/db/migrate/20140514200304_create_storytime_taggings.rb +1 -1
  109. data/db/migrate/20140516141252_create_storytime_versions.rb +1 -1
  110. data/db/migrate/20140521190606_create_storytime_roles.rb +1 -1
  111. data/db/migrate/20140521191048_add_storytime_role_id_to_users.rb +1 -1
  112. data/db/migrate/20140521191728_create_storytime_permissions.rb +1 -1
  113. data/db/migrate/20140521191744_create_storytime_actions.rb +1 -1
  114. data/db/migrate/20140813014447_create_storytime_comments.rb +1 -1
  115. data/db/migrate/20140813130534_add_storytime_name_to_users.rb +1 -1
  116. data/db/migrate/20140916183056_create_storytime_autosaves.rb +1 -1
  117. data/db/migrate/20141020213343_add_secondary_media_id_to_storytime_post.rb +1 -1
  118. data/db/migrate/20141021073356_create_storytime_snippets.rb +1 -1
  119. data/db/migrate/20141111164439_create_storytime_subscriptions.rb +1 -1
  120. data/db/migrate/20150122200805_add_title_and_content_index_to_storytime_post.rb +1 -1
  121. data/db/migrate/20150128185746_seed_new_actions_and_permissions.rb +1 -1
  122. data/db/migrate/20150129215308_add_site_id_to_storytime_subscription.rb +1 -1
  123. data/db/migrate/20150206201847_add_site_id_to_storytime_post.rb +1 -1
  124. data/db/migrate/20150206201919_add_site_id_to_storytime_snippet.rb +1 -1
  125. data/db/migrate/20150206201931_add_site_id_to_storytime_tag.rb +1 -1
  126. data/db/migrate/20150206205256_add_notification_fields_to_storytime_post.rb +1 -1
  127. data/db/migrate/20150216211257_add_subdomain_to_storytime_sites.rb +1 -1
  128. data/db/migrate/20150216225045_add_site_to_storytime_media.rb +1 -1
  129. data/db/migrate/20150219210528_remove_root_page_content_from_storytime_sites.rb +1 -1
  130. data/db/migrate/20150220184902_add_blog_id_to_posts.rb +1 -1
  131. data/db/migrate/20150224192138_add_homepage_path_to_storytime_sites.rb +1 -1
  132. data/db/migrate/20150224193151_add_subscription_email_from_to_storytime_sites.rb +1 -1
  133. data/db/migrate/20150224193551_add_layout_to_storytime_sites.rb +1 -1
  134. data/db/migrate/20150224194559_add_disqus_forum_shortname_to_storytime_sites.rb +1 -1
  135. data/db/migrate/20150224212453_remove_homepage_path_from_storytime_sites.rb +1 -1
  136. data/db/migrate/20150225143516_add_site_id_to_storytime_autosaves.rb +1 -1
  137. data/db/migrate/20150225143826_add_site_id_to_storytime_comments.rb +1 -1
  138. data/db/migrate/20150225145119_add_site_id_to_storytime_versions.rb +1 -1
  139. data/db/migrate/20150225145316_add_site_id_to_storytime_taggings.rb +1 -1
  140. data/db/migrate/20150225145608_update_storytime_site_id_columns.rb +1 -1
  141. data/db/migrate/20150225164232_add_site_id_to_storytime_permissions.rb +1 -1
  142. data/db/migrate/20150225212917_create_storytime_memberships.rb +1 -1
  143. data/db/migrate/20150225213535_create_memberships_for_storytime_users.rb +1 -1
  144. data/db/migrate/20150226201739_add_custom_domain_to_storytime_sites.rb +1 -1
  145. data/db/migrate/20150302171500_add_site_id_to_storytime_media.rb +1 -1
  146. data/db/migrate/20150302171722_set_site_layout.rb +1 -1
  147. data/db/migrate/20150302185138_remove_storytime_role_id_from_users.rb +1 -1
  148. data/db/migrate/20150302192525_transfer_posts_to_blogs.rb +1 -1
  149. data/db/migrate/20150302192759_seed_permissions.rb +1 -1
  150. data/db/migrate/20150331162329_add_discourse_name_to_storytime_sites.rb +1 -1
  151. data/db/migrate/20150402161427_remove_subdomain_from_storytime_site.rb +1 -1
  152. data/db/migrate/20150520181115_create_storytime_navigations.rb +1 -1
  153. data/db/migrate/20150520185227_create_storytime_links.rb +1 -1
  154. data/db/migrate/20150520190700_add_position_to_storytime_links.rb +1 -1
  155. data/db/migrate/20150529192058_add_url_to_storytime_links.rb +1 -1
  156. data/db/migrate/20260408001637_add_canonical_url_to_storytime_posts.rb +5 -0
  157. data/db/migrate/20260701000000_sanitize_existing_storytime_snippets.rb +22 -0
  158. data/lib/storytime/cli/install.rb +2 -20
  159. data/lib/storytime/concerns/action_controller_extension.rb +36 -0
  160. data/lib/storytime/constraints/page_constraint.rb +8 -2
  161. data/lib/storytime/engine.rb +3 -5
  162. data/lib/storytime/migrators/v1.rb +3 -3
  163. data/lib/storytime/post_notifier.rb +1 -1
  164. data/lib/storytime/post_url_handler.rb +18 -5
  165. data/lib/storytime/storytime_helpers.rb +4 -0
  166. data/lib/storytime/version.rb +1 -1
  167. data/lib/storytime.rb +1 -1
  168. data/spec/controllers/dashboard_controller_spec.rb +5 -6
  169. data/spec/dummy/app/assets/config/manifest.js +3 -0
  170. data/spec/dummy/config/database.yml +3 -3
  171. data/spec/dummy/config/initializers/devise.rb +1 -1
  172. data/spec/dummy/db/migrate/20140530185250_devise_create_users.rb +1 -1
  173. data/spec/dummy/db/migrate/20150127172846_create_widgets.rb +1 -1
  174. data/spec/dummy/db/migrate/20150206203824_add_video_url_to_storytime_posts.rb +1 -1
  175. data/spec/dummy/db/schema.rb +226 -244
  176. data/spec/factories/action_factories.rb +3 -3
  177. data/spec/factories/comment_factories.rb +1 -1
  178. data/spec/factories/media_factories.rb +1 -1
  179. data/spec/factories/membership_factories.rb +1 -1
  180. data/spec/factories/navigation_factories.rb +3 -3
  181. data/spec/factories/permission_factories.rb +1 -1
  182. data/spec/factories/post_factories.rb +2 -2
  183. data/spec/factories/role_factories.rb +11 -11
  184. data/spec/factories/site_factories.rb +3 -3
  185. data/spec/factories/snippet_factories.rb +1 -1
  186. data/spec/factories/subscription_factories.rb +1 -1
  187. data/spec/factories/user_factories.rb +2 -2
  188. data/spec/factories/widget_factories.rb +2 -2
  189. data/spec/features/blogs_spec.rb +7 -7
  190. data/spec/features/comments_spec.rb +11 -11
  191. data/spec/features/dashboard/media_spec.rb +17 -11
  192. data/spec/features/dashboard/memberships_spec.rb +13 -19
  193. data/spec/features/dashboard/navigations_spec.rb +1 -1
  194. data/spec/features/dashboard/pages_spec.rb +8 -8
  195. data/spec/features/dashboard/posts_spec.rb +19 -19
  196. data/spec/features/dashboard/sites_spec.rb +1 -1
  197. data/spec/features/dashboard/snippets_spec.rb +3 -3
  198. data/spec/features/dashboard/subscription_spec.rb +2 -2
  199. data/spec/features/pages_spec.rb +3 -3
  200. data/spec/features/posts_spec.rb +2 -2
  201. data/spec/features/subscription_spec.rb +3 -3
  202. data/spec/importers/wordpress_spec.rb +1 -1
  203. data/spec/lib/mysql_fulltext_search_adapter_spec.rb +3 -3
  204. data/spec/lib/mysql_search_adapter_spec.rb +3 -3
  205. data/spec/lib/postgres_search_adapter_spec.rb +3 -3
  206. data/spec/lib/sqlite3_search_adapter_spec.rb +3 -3
  207. data/spec/lib/storytime/constraints/page_constraint_spec.rb +40 -0
  208. data/spec/lib/storytime_helpers_spec.rb +2 -2
  209. data/spec/models/navigation_spec.rb +3 -3
  210. data/spec/models/post_spec.rb +29 -29
  211. data/spec/models/snippet_spec.rb +31 -0
  212. data/spec/models/subscription_spec.rb +4 -4
  213. data/spec/models/tagging_spec.rb +14 -14
  214. data/spec/models/version_spec.rb +29 -29
  215. data/spec/policies/comment_policy_spec.rb +11 -11
  216. data/spec/policies/post_policy_spec.rb +13 -13
  217. data/spec/requests/pages_spec.rb +37 -0
  218. data/spec/requests/routings_spec.rb +14 -15
  219. data/spec/spec_helper.rb +8 -15
  220. data/spec/support/domains.rb +2 -2
  221. data/spec/support/feature_macros.rb +5 -5
  222. data/spec/support/pundit_matcher.rb +3 -3
  223. data/storytime.gemspec +15 -20
  224. data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +195 -0
  225. data/vendor/assets/javascripts/codemirror/addons/edit/closetag.js +169 -0
  226. data/vendor/assets/javascripts/codemirror/addons/fold/xml-fold.js +182 -0
  227. data/vendor/assets/javascripts/codemirror.js +8922 -0
  228. data/vendor/assets/javascripts/medium-editor.min.js +3 -3
  229. data/vendor/assets/stylesheets/codemirror/themes/solarized.css +169 -0
  230. data/vendor/assets/stylesheets/codemirror.css +347 -0
  231. metadata +94 -134
  232. data/config/initializers/storytime_admin.rb +0 -5
  233. data/config/spring.rb +0 -1
  234. data/spec/dummy/app/controllers/storytime_admin/widgets_controller.rb +0 -5
  235. data/spec/dummy/db/development.sqlite3 +0 -0
  236. data/spec/dummy/db/test.sqlite3 +0 -0
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Storytime::Constraints::PageConstraint do
4
+ let(:site) { FactoryBot.create(:site) }
5
+ let(:constraint) { described_class.new }
6
+
7
+ def request_for(id)
8
+ instance_double("ActionDispatch::Request", params: { id: id }, host: site.custom_domain)
9
+ end
10
+
11
+ before do
12
+ Storytime::Site.current_id = site.id
13
+ end
14
+
15
+ after do
16
+ Storytime::Site.current_id = nil
17
+ end
18
+
19
+ it "matches an existing page slug" do
20
+ page = FactoryBot.create(:page, site: site)
21
+ expect(constraint.matches?(request_for(page.slug))).to be true
22
+ end
23
+
24
+ it "does not match an unknown slug" do
25
+ expect(constraint.matches?(request_for("no-such-page"))).to be false
26
+ end
27
+
28
+ context "with a path-traversal payload" do
29
+ let(:payload) { "a/../../../../../../../../etc/passwd" }
30
+
31
+ it "does not match" do
32
+ expect(constraint.matches?(request_for(payload))).to be false
33
+ end
34
+
35
+ it "never reaches the filesystem check" do
36
+ expect(File).not_to receive(:exist?)
37
+ constraint.matches?(request_for(payload))
38
+ end
39
+ end
40
+ end
@@ -6,7 +6,7 @@ include Storytime::StorytimeHelpers
6
6
  describe "Storytime::StorytimeHelpers", :type => :helper do
7
7
  describe "storytime_snippet" do
8
8
  before(:each) do
9
- @site = FactoryGirl.create(:site)
9
+ @site = FactoryBot.create(:site)
10
10
  expect_any_instance_of(Storytime::StorytimeHelpers).to receive(:current_storytime_site).at_least(:once).and_return(@site)
11
11
  expect_any_instance_of(ApplicationHelper).to receive(:logged_in_storytime_user?).at_least(:once).and_return(false)
12
12
  end
@@ -27,7 +27,7 @@ describe "Storytime::StorytimeHelpers", :type => :helper do
27
27
 
28
28
  describe "when snippet is found" do
29
29
  it "returns snippet partial" do
30
- snippet = FactoryGirl.create(:snippet, site: @site)
30
+ snippet = FactoryBot.create(:snippet, site: @site)
31
31
  snippet_div = storytime_snippet(snippet.name)
32
32
 
33
33
  expect(snippet_div).to include(snippet.content)
@@ -3,17 +3,17 @@ require 'spec_helper'
3
3
  describe Storytime::Navigation do
4
4
  describe "#handle" do
5
5
  it "parameterizes the title to set the handle" do
6
- nav = FactoryGirl.create(:navigation, handle: nil)
6
+ nav = FactoryBot.create(:navigation, handle: nil)
7
7
  expect(nav.handle).to eq nav.name.parameterize
8
8
  end
9
9
 
10
10
  it "parameterizes the handle" do
11
- nav = FactoryGirl.create(:navigation, handle: "my Handle")
11
+ nav = FactoryBot.create(:navigation, handle: "my Handle")
12
12
  expect(nav.handle).to eq "my-handle"
13
13
  end
14
14
 
15
15
  it "does not change the handle if the title changes" do
16
- nav = FactoryGirl.create(:navigation, handle: "my Handle")
16
+ nav = FactoryBot.create(:navigation, handle: "my Handle")
17
17
  nav.update(name: "New Name")
18
18
  expect(nav.handle).to eq "my-handle"
19
19
  end
@@ -7,11 +7,11 @@ describe Storytime::Post do
7
7
  after{ Storytime::BlogPost.instance_variable_set "@_to_partial_path", nil }
8
8
 
9
9
  it "includes site in the path" do
10
- allow(File).to receive(:exists?).and_return(true)
10
+ allow(File).to receive(:exist?).and_return(true)
11
+
12
+ site = FactoryBot.create(:site, title: "Test Site")
13
+ blog_post = FactoryBot.create(:post, site: site)
11
14
 
12
- site = FactoryGirl.create(:site, title: "Test Site")
13
- blog_post = FactoryGirl.create(:post, site: site)
14
-
15
15
  partial_path = blog_post.to_partial_path
16
16
  expect(partial_path).to eq("storytime/test-site/blog_posts/blog_post")
17
17
  Storytime::BlogPost.instance_variable_set "@_to_partial_path", nil
@@ -20,7 +20,7 @@ describe Storytime::Post do
20
20
  it "looks up the inheritance chain" do
21
21
  allow(File).to receive(:exists?).and_return(false)
22
22
 
23
- site = FactoryGirl.build(:site, title: "Test Site")
23
+ site = FactoryBot.build(:site, title: "Test Site")
24
24
  video_post = VideoPost.new(site: site)
25
25
  partial_path = video_post.to_partial_path
26
26
  expect(partial_path).to eq("storytime/posts/post")
@@ -28,60 +28,60 @@ describe Storytime::Post do
28
28
  end
29
29
 
30
30
  it "sets the page slug on create" do
31
- post = FactoryGirl.create(:post)
32
- post.slug.should == post.title.parameterize
31
+ post = FactoryBot.create(:post)
32
+ expect(post.slug).to eq(post.title.parameterize)
33
33
  end
34
34
 
35
35
  it "sets slug to user inputted value" do
36
- post = FactoryGirl.create(:post)
36
+ post = FactoryBot.create(:post)
37
37
 
38
38
  post.slug = "random slug here"
39
39
  post.save
40
40
 
41
- post.slug.should == "random-slug-here"
41
+ expect(post.slug).to eq("random-slug-here")
42
42
  end
43
43
 
44
44
  it "does not allow the same slug" do
45
- post_1 = FactoryGirl.create(:post)
46
- post_2 = FactoryGirl.create(:post)
45
+ post_1 = FactoryBot.create(:post)
46
+ post_2 = FactoryBot.create(:post)
47
47
 
48
48
  post_2.slug = post_1.slug
49
49
  post_2.save
50
50
 
51
- post_2.slug.should_not == post_1.slug
52
- post_2.slug.should include(post_1.slug)
51
+ expect(post_2.slug).to_not eq(post_1.slug)
52
+ expect(post_2.slug).to include(post_1.slug)
53
53
  end
54
54
 
55
55
  it "does not allow a blank slug" do
56
- post = FactoryGirl.create(:post)
56
+ post = FactoryBot.create(:post)
57
57
  post.slug = ""
58
58
  post.save
59
59
 
60
- post.slug.should_not == ""
61
- post.slug.should == post.title.parameterize
60
+ expect(post.slug).to_not eq("")
61
+ expect(post.slug).to eq(post.title.parameterize)
62
62
  end
63
63
 
64
64
  it "creates tags from tag_list attribute" do
65
- post = FactoryGirl.create(:post)
65
+ post = FactoryBot.create(:post)
66
66
  post.tag_list = ["tag1", "tag2"]
67
- post.tags.count.should == 2
67
+ expect(post.tags.count).to eq(2)
68
68
  end
69
69
 
70
70
  it "scopes posts by tag" do
71
- post_1 = FactoryGirl.create(:post, tag_list: ["tag1", "tag2"])
72
- post_2 = FactoryGirl.create(:post, tag_list: ["tag1"])
71
+ post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
72
+ post_2 = FactoryBot.create(:post, tag_list: ["tag1"])
73
73
 
74
- Storytime::Post.tagged_with("tag1").should include(post_1)
75
- Storytime::Post.tagged_with("tag1").should include(post_2)
76
- Storytime::Post.tagged_with("tag2").should include(post_1)
77
- Storytime::Post.tagged_with("tag2").should_not include(post_2)
74
+ expect(Storytime::Post.tagged_with("tag1")).to include(post_1)
75
+ expect(Storytime::Post.tagged_with("tag1")).to include(post_2)
76
+ expect(Storytime::Post.tagged_with("tag2")).to include(post_1)
77
+ expect(Storytime::Post.tagged_with("tag2")).to_not include(post_2)
78
78
  end
79
79
 
80
80
  it "counts tags across posts" do
81
- post_1 = FactoryGirl.create(:post, tag_list: ["tag1", "tag2"])
82
- post_2 = FactoryGirl.create(:post, tag_list: ["tag1"])
81
+ post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
82
+ post_2 = FactoryBot.create(:post, tag_list: ["tag1"])
83
83
 
84
- Storytime::Post.tag_counts.find_by(name: "tag1").count.should == 2
85
- Storytime::Post.tag_counts.find_by(name: "tag2").count.should == 1
84
+ expect(Storytime::Post.tag_counts.find_by(name: "tag1").count).to eq(2)
85
+ expect(Storytime::Post.tag_counts.find_by(name: "tag2").count).to eq(1)
86
86
  end
87
- end
87
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Storytime::Snippet do
4
+ describe "#sanitize_content" do
5
+ it "strips script-bearing attributes from content on save" do
6
+ snippet = FactoryBot.create(:snippet, content: %(<img src=x onerror="alert(1)">))
7
+ expect(snippet.content).to_not include("onerror")
8
+ end
9
+
10
+ it "strips <script> tags from content on save" do
11
+ snippet = FactoryBot.create(:snippet, content: %(hello<script>alert(1)</script>))
12
+ expect(snippet.content).to_not include("<script")
13
+ end
14
+
15
+ it "re-sanitizes content on update" do
16
+ snippet = FactoryBot.create(:snippet, content: "safe")
17
+ snippet.update(content: %(<a href="javascript:alert(1)">x</a>))
18
+ expect(snippet.content).to_not include("javascript:")
19
+ end
20
+
21
+ it "preserves allowed markup" do
22
+ snippet = FactoryBot.create(:snippet, content: %(<p>hello <strong>world</strong></p>))
23
+ expect(snippet.content).to include("<strong>world</strong>")
24
+ end
25
+
26
+ it "leaves plain text unchanged" do
27
+ snippet = FactoryBot.create(:snippet, content: "just plain text")
28
+ expect(snippet.content).to eq("just plain text")
29
+ end
30
+ end
31
+ end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Storytime::Subscription do
4
4
  describe "generate_token" do
5
5
  it "generates a token before creation" do
6
- subscription = FactoryGirl.build(:subscription)
6
+ subscription = FactoryBot.build(:subscription)
7
7
 
8
8
  expect(subscription.token).to eq(nil)
9
9
 
@@ -13,10 +13,10 @@ describe Storytime::Subscription do
13
13
  end
14
14
 
15
15
  it "generates a token that can be regenerated from the email" do
16
- subscription = FactoryGirl.create(:subscription)
16
+ subscription = FactoryBot.create(:subscription)
17
17
  token = subscription.token
18
18
 
19
- key = Rails.application.secrets.secret_key_base
19
+ key = Rails.application.secret_key_base
20
20
  digest = OpenSSL::Digest.new('sha1')
21
21
  regenerated_token = OpenSSL::HMAC.hexdigest(digest, key, subscription.email)
22
22
 
@@ -26,7 +26,7 @@ describe Storytime::Subscription do
26
26
 
27
27
  describe "unsubscribe!" do
28
28
  it "sets subscribed to false" do
29
- subscription = FactoryGirl.create(:subscription)
29
+ subscription = FactoryBot.create(:subscription)
30
30
 
31
31
  expect(subscription.subscribed?).to eq(true)
32
32
 
@@ -2,28 +2,28 @@ require 'spec_helper'
2
2
 
3
3
  describe Storytime::Tagging do
4
4
  it "unused tags remain after deleting a post" do
5
- post_1 = FactoryGirl.create(:post, tag_list: ["tag1", "tag2"])
6
- post_2 = FactoryGirl.create(:post, tag_list: ["tag1"])
7
- Storytime::Tag.all.count.should == 2
5
+ post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
6
+ post_2 = FactoryBot.create(:post, tag_list: ["tag1"])
7
+ expect(Storytime::Tag.all.count).to eq(2)
8
8
  post_1.destroy
9
- Storytime::Tag.all.count.should == 2
9
+ expect(Storytime::Tag.all.count).to eq(2)
10
10
  post_2.destroy
11
- Storytime::Tag.all.count.should == 2
11
+ expect(Storytime::Tag.all.count).to eq(2)
12
12
  end
13
13
 
14
14
  it "does not remove unused tags after updating tag list" do
15
- post_1 = FactoryGirl.create(:post, tag_list: ["tag1", "tag2"])
16
- Storytime::Tag.all.count.should == 2
15
+ post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
16
+ expect(Storytime::Tag.all.count).to eq(2)
17
17
  post_1.update(tag_list: ["tag1"])
18
- Storytime::Tag.all.count.should == 2
19
- post_1.tags.count.should == 1
18
+ expect(Storytime::Tag.all.count).to eq(2)
19
+ expect(post_1.tags.count).to eq(1)
20
20
  end
21
21
 
22
22
  it "does not removes tags used by other posts after updating tag list" do
23
- post_1 = FactoryGirl.create(:post, tag_list: ["tag1", "tag2"])
24
- post_2 = FactoryGirl.create(:post, tag_list: ["tag2"])
25
- Storytime::Tag.all.count.should == 2
23
+ post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
24
+ post_2 = FactoryBot.create(:post, tag_list: ["tag2"])
25
+ expect(Storytime::Tag.all.count).to eq(2)
26
26
  post_1.update(tag_list: ["tag1"])
27
- Storytime::Tag.all.count.should == 2
27
+ expect(Storytime::Tag.all.count).to eq(2)
28
28
  end
29
- end
29
+ end
@@ -3,55 +3,55 @@ require 'spec_helper'
3
3
  describe Storytime::Version do
4
4
 
5
5
  it "creates a version when creating a Post" do
6
- Storytime::Version.all.count.should == 0
7
- user = FactoryGirl.create(:user)
8
- post = FactoryGirl.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
9
- post.should_not be_published
10
- Storytime::Version.all.count.should == 1
11
- post.latest_version.should == Storytime::Version.last
12
- post.latest_version.content.should == "Testing 123"
13
- post.latest_version.user.should == user
6
+ expect(Storytime::Version.all.count).to eq(0)
7
+ user = FactoryBot.create(:user)
8
+ post = FactoryBot.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
9
+ expect(post).to_not be_published
10
+ expect(Storytime::Version.all.count).to eq(1)
11
+ expect(post.latest_version).to eq(Storytime::Version.last)
12
+ expect(post.latest_version.content).to eq("Testing 123")
13
+ expect(post.latest_version.user).to eq(user)
14
14
  end
15
15
 
16
16
  it "publishes a post with latest_version content" do
17
- user = FactoryGirl.create(:user)
18
- post = FactoryGirl.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
19
- post.should_not be_published
20
- post.content.should_not == "Testing 123"
17
+ user = FactoryBot.create(:user)
18
+ post = FactoryBot.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
19
+ expect(post).to_not be_published
20
+ expect(post.content).to_not eq("Testing 123")
21
21
  post.publish!
22
- post.should be_published
23
- post.content.should == "Testing 123"
22
+ expect(post).to be_published
23
+ expect(post.content).to eq("Testing 123")
24
24
  end
25
25
 
26
26
  it "returns the latest version content as draft_content" do
27
- user = FactoryGirl.create(:user)
28
- post = FactoryGirl.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
27
+ user = FactoryBot.create(:user)
28
+ post = FactoryBot.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
29
29
  post.reload
30
- post.draft_content.should == post.latest_version.content
30
+ expect(post.draft_content).to eq(post.latest_version.content)
31
31
  end
32
32
 
33
33
  it "does not create a version when content does not change" do
34
- post = FactoryGirl.create(:post, published_at: nil, draft_content: "Testing 123")
35
- Storytime::Version.all.count.should == 1
34
+ post = FactoryBot.create(:post, published_at: nil, draft_content: "Testing 123")
35
+ expect(Storytime::Version.all.count).to eq(1)
36
36
  post.update(excerpt: "New Excerpt")
37
- Storytime::Version.all.count.should == 1
37
+ expect(Storytime::Version.all.count).to eq(1)
38
38
  end
39
39
 
40
40
  it "creates a version when content is updated" do
41
- post = FactoryGirl.create(:post, draft_content: "Testing 123")
42
- Storytime::Version.all.count.should == 1
41
+ post = FactoryBot.create(:post, draft_content: "Testing 123")
42
+ expect(Storytime::Version.all.count).to eq(1)
43
43
  post.update(draft_content: "New Content")
44
- Storytime::Version.all.count.should == 2
45
- post.content.should == "New Content"
44
+ expect(Storytime::Version.all.count).to eq(2)
45
+ expect(post.content).to eq("New Content")
46
46
  end
47
47
 
48
48
  it "reverts to a previous version" do
49
- user = FactoryGirl.create(:user)
50
- post = FactoryGirl.create(:post, user: user)
49
+ user = FactoryBot.create(:user)
50
+ post = FactoryBot.create(:post, user: user)
51
51
  version1 = Storytime::Version.last
52
52
  post.update(draft_content: "New Content", draft_user_id: user.id)
53
- post.content.should == "New Content"
53
+ expect(post.content).to eq("New Content")
54
54
  post.update(draft_version_id: version1.id)
55
- post.content.should == version1.content
55
+ expect(post.content).to eq(version1.content)
56
56
  end
57
- end
57
+ end
@@ -2,8 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe Storytime::CommentPolicy do
4
4
  subject { Storytime::CommentPolicy.new(user, comment) }
5
- let(:site) { FactoryGirl.create(:site) }
6
- let(:post) { FactoryGirl.create(:post) }
5
+ let(:site) { FactoryBot.create(:site) }
6
+ let(:post) { FactoryBot.create(:post) }
7
7
 
8
8
  context "as a normal user" do
9
9
  before do
@@ -11,10 +11,10 @@ describe Storytime::CommentPolicy do
11
11
  allow_any_instance_of(Storytime.user_class).to receive(:storytime_editor?).and_return(false)
12
12
  end
13
13
 
14
- let(:user) { FactoryGirl.create(:user) }
14
+ let(:user) { FactoryBot.create(:user) }
15
15
 
16
16
  context "a comment owned by the user" do
17
- let(:comment) { FactoryGirl.build(:comment, user: user, post: post, site: site) }
17
+ let(:comment) { FactoryBot.build(:comment, user: user, post: post, site: site) }
18
18
 
19
19
  it { should permit!(:create) }
20
20
  it { should permit!(:destroy) }
@@ -22,7 +22,7 @@ describe Storytime::CommentPolicy do
22
22
 
23
23
 
24
24
  context "a comment not owned by the user" do
25
- let(:comment) { FactoryGirl.build(:comment, post: post, site: site) }
25
+ let(:comment) { FactoryBot.build(:comment, post: post, site: site) }
26
26
 
27
27
  it { should_not permit!(:create) }
28
28
  it { should_not permit!(:destroy) }
@@ -35,17 +35,17 @@ describe Storytime::CommentPolicy do
35
35
  allow_any_instance_of(Storytime.user_class).to receive(:storytime_editor?).and_return(true)
36
36
  end
37
37
 
38
- let(:user){ FactoryGirl.create(:editor) }
38
+ let(:user){ FactoryBot.create(:editor) }
39
39
 
40
40
  context "a comment owned by the user" do
41
- let(:comment) { FactoryGirl.build(:comment, user: user, post: post, site: site) }
41
+ let(:comment) { FactoryBot.build(:comment, user: user, post: post, site: site) }
42
42
 
43
43
  it { should permit!(:create) }
44
44
  it { should permit!(:destroy) }
45
45
  end
46
46
 
47
47
  context "a comment not owned by the user" do
48
- let(:comment) { FactoryGirl.build(:comment, post: post, site: site) }
48
+ let(:comment) { FactoryBot.build(:comment, post: post, site: site) }
49
49
 
50
50
  it { should_not permit!(:create) }
51
51
  it { should permit!(:destroy) }
@@ -57,17 +57,17 @@ describe Storytime::CommentPolicy do
57
57
  allow_any_instance_of(Storytime.user_class).to receive(:storytime_admin?).and_return(true)
58
58
  allow_any_instance_of(Storytime.user_class).to receive(:storytime_editor?).and_return(false)
59
59
  end
60
- let(:user){ FactoryGirl.create(:admin) }
60
+ let(:user){ FactoryBot.create(:admin) }
61
61
 
62
62
  context "a comment owned by the user" do
63
- let(:comment) { FactoryGirl.build(:comment, user: user, post: post, site: site) }
63
+ let(:comment) { FactoryBot.build(:comment, user: user, post: post, site: site) }
64
64
 
65
65
  it { should permit!(:create) }
66
66
  it { should permit!(:destroy) }
67
67
  end
68
68
 
69
69
  context "a comment not owned by the user" do
70
- let(:comment) { FactoryGirl.build(:comment, post: post, site: site) }
70
+ let(:comment) { FactoryBot.build(:comment, post: post, site: site) }
71
71
 
72
72
  it { should_not permit!(:create) }
73
73
  it { should permit!(:destroy) }
@@ -5,7 +5,7 @@ require 'spec_helper'
5
5
  describe Storytime::PostPolicy do
6
6
  subject { Storytime::PostPolicy.new(user, post) }
7
7
 
8
- let(:site) { FactoryGirl.create(:site) }
8
+ let(:site) { FactoryBot.create(:site) }
9
9
  before do
10
10
  site.save_with_seeds(user)
11
11
  allow(Storytime::Site).to receive(:current).and_return(site)
@@ -17,17 +17,17 @@ describe Storytime::PostPolicy do
17
17
  allow_any_instance_of(Storytime.user_class).to receive(:storytime_role_in_site).and_return(Storytime::Role.find_by(name: "writer"))
18
18
  end
19
19
 
20
- let(:user) { FactoryGirl.create(:writer) }
20
+ let(:user) { FactoryBot.create(:writer) }
21
21
 
22
22
  context "creating a new post" do
23
- let(:post) { FactoryGirl.build(:post, user: user) }
23
+ let(:post) { FactoryBot.build(:post, user: user) }
24
24
 
25
25
  it { should permit!(:new) }
26
26
  it { should permit!(:create) }
27
27
  end
28
28
 
29
29
  context "who owns the post" do
30
- let(:post) { FactoryGirl.build_stubbed :post, user: user }
30
+ let(:post) { FactoryBot.build_stubbed :post, user: user }
31
31
 
32
32
  it { should permit!(:index) }
33
33
  it { should permit!(:manage) } # edit, update, destroy
@@ -35,7 +35,7 @@ describe Storytime::PostPolicy do
35
35
  end
36
36
 
37
37
  context "who does not own the post" do
38
- let(:post) { FactoryGirl.build_stubbed :post, user: FactoryGirl.build(:user) }
38
+ let(:post) { FactoryBot.build_stubbed :post, user: FactoryBot.build(:user) }
39
39
 
40
40
  it { should permit!(:index) }
41
41
  it { should_not permit!(:manage) } # edit, update, destroy
@@ -48,17 +48,17 @@ describe Storytime::PostPolicy do
48
48
  allow_any_instance_of(Storytime.user_class).to receive(:storytime_role).and_return(Storytime::Role.find_by(name: "editor"))
49
49
  end
50
50
 
51
- let(:user) { FactoryGirl.create(:editor) }
51
+ let(:user) { FactoryBot.create(:editor) }
52
52
 
53
53
  context "creating a new post" do
54
- let(:post) { FactoryGirl.build(:post, user: user) }
54
+ let(:post) { FactoryBot.build(:post, user: user) }
55
55
 
56
56
  it { should permit!(:new) }
57
57
  it { should permit!(:create) }
58
58
  end
59
59
 
60
60
  context "who owns the post" do
61
- let(:post) { FactoryGirl.build_stubbed :post, user: user }
61
+ let(:post) { FactoryBot.build_stubbed :post, user: user }
62
62
 
63
63
  it { should permit!(:index) }
64
64
  it { should permit!(:manage) } # edit, update, destroy
@@ -66,7 +66,7 @@ describe Storytime::PostPolicy do
66
66
  end
67
67
 
68
68
  context "who does not own the post" do
69
- let(:post) { FactoryGirl.build_stubbed :post, user: FactoryGirl.build(:user) }
69
+ let(:post) { FactoryBot.build_stubbed :post, user: FactoryBot.build(:user) }
70
70
 
71
71
  it { should permit!(:index) }
72
72
  it { should permit!(:manage) } # edit, update, destroy
@@ -79,17 +79,17 @@ describe Storytime::PostPolicy do
79
79
  allow_any_instance_of(Storytime.user_class).to receive(:storytime_role).and_return(Storytime::Role.find_by(name: "admin"))
80
80
  end
81
81
 
82
- let(:user) { FactoryGirl.create(:admin) }
82
+ let(:user) { FactoryBot.create(:admin) }
83
83
 
84
84
  context "creating a new post" do
85
- let(:post) { FactoryGirl.build(:post, user: user) }
85
+ let(:post) { FactoryBot.build(:post, user: user) }
86
86
 
87
87
  it { should permit!(:new) }
88
88
  it { should permit!(:create) }
89
89
  end
90
90
 
91
91
  context "who owns the post" do
92
- let(:post) { FactoryGirl.build_stubbed :post, user: user }
92
+ let(:post) { FactoryBot.build_stubbed :post, user: user }
93
93
 
94
94
  it { should permit!(:index) }
95
95
  it { should permit!(:manage) } # edit, update, destroy
@@ -97,7 +97,7 @@ describe Storytime::PostPolicy do
97
97
  end
98
98
 
99
99
  context "who does not own the post" do
100
- let(:post) { FactoryGirl.build_stubbed :post, user: FactoryGirl.build(:user) }
100
+ let(:post) { FactoryBot.build_stubbed :post, user: FactoryBot.build(:user) }
101
101
 
102
102
  it { should permit!(:index) }
103
103
  it { should permit!(:manage) } # edit, update, destroy
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ # Request spec rather than controller spec so we go through the real engine
4
+ # routes – the bug only manifests after the route's PageConstraint admits the
5
+ # request to the controller.
6
+ describe "Pages", type: :request do
7
+ let(:user) { FactoryBot.create(:admin) }
8
+ let!(:site) do
9
+ s = FactoryBot.create(:site, custom_domain: "www.example.com")
10
+ s.save_with_seeds(user)
11
+ s
12
+ end
13
+
14
+ before do
15
+ # Force the route's PageConstraint to admit the request so we can exercise
16
+ # the controller for ids the constraint would normally reject (like the
17
+ # leading-slash slugs that bots probe).
18
+ allow_any_instance_of(Storytime::Constraints::PageConstraint)
19
+ .to receive(:matches?).and_return(true)
20
+ end
21
+
22
+ describe "GET /:id when no page or slug-specific template matches" do
23
+ it "404s instead of falling through to a show template that requires @page" do
24
+ get "http://www.example.com/no-such-page"
25
+ expect(response).to have_http_status(:not_found)
26
+ end
27
+
28
+ it "404s for malformed ids with a leading slash" do
29
+ # Bots hit URL-encoded paths like /%2Fcontact. The PageConstraint can let
30
+ # these through because POSIX collapses // in File.exist?, but the
31
+ # template resolver does not, so the controller must 404 explicitly
32
+ # rather than rendering the @page-dependent show template.
33
+ get "http://www.example.com/%2Fno-such-page"
34
+ expect(response).to have_http_status(:not_found)
35
+ end
36
+ end
37
+ end