trusty-snippets-extension 1.0.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 (41) hide show
  1. data/README.md +7 -0
  2. data/Rakefile +109 -0
  3. data/app/controllers/admin/snippets_controller.rb +8 -0
  4. data/app/helpers/admin/snippets_helper.rb +3 -0
  5. data/app/models/snippet.rb +23 -0
  6. data/app/models/snippet_finder.rb +24 -0
  7. data/app/models/snippet_tags.rb +80 -0
  8. data/app/views/admin/snippets/_form.html.haml +29 -0
  9. data/app/views/admin/snippets/_popups.html.haml +4 -0
  10. data/app/views/admin/snippets/edit.html.haml +11 -0
  11. data/app/views/admin/snippets/index.html.haml +33 -0
  12. data/app/views/admin/snippets/new.html.haml +9 -0
  13. data/app/views/admin/snippets/remove.html.haml +17 -0
  14. data/config/initializers/radiant_config.rb +3 -0
  15. data/config/locales/en.yml +16 -0
  16. data/config/routes.rb +8 -0
  17. data/cucumber.yml +1 -0
  18. data/features/admin/configuration.feature +9 -0
  19. data/features/admin/pagination.feature +27 -0
  20. data/features/admin/snippets_management.feature +82 -0
  21. data/features/step_definitions/admin/snippet_steps.rb +25 -0
  22. data/features/support/env.rb +11 -0
  23. data/features/support/paths.rb +22 -0
  24. data/lib/snippets/engine.rb +5 -0
  25. data/lib/tasks/snippets_extension_tasks.rake +47 -0
  26. data/lib/trusty-snippets-extension.rb +8 -0
  27. data/snippets_extension.rb +69 -0
  28. data/spec/ci/before_script +22 -0
  29. data/spec/ci/script +2 -0
  30. data/spec/controllers/admin/snippets_controller_spec.rb +110 -0
  31. data/spec/datasets/snippets_dataset.rb +44 -0
  32. data/spec/fixtures/snippet_template.radius +1 -0
  33. data/spec/lib/radiant/admin_ui_spec.rb +33 -0
  34. data/spec/models/page_spec.rb +81 -0
  35. data/spec/models/snippet_finder_spec.rb +20 -0
  36. data/spec/models/snippet_spec.rb +59 -0
  37. data/spec/models/user_action_observer_spec.rb +25 -0
  38. data/spec/snippets_spec_helper.rb +36 -0
  39. data/spec/spec.opts +6 -0
  40. data/trusty-snippets-extension.gemspec +26 -0
  41. metadata +120 -0
@@ -0,0 +1,82 @@
1
+ Feature: Managing snippets
2
+ In order to share content between layouts and pages, as a designer I want to
3
+ manage a collection of snippets
4
+
5
+ Background:
6
+ Given I am logged in as "designer"
7
+
8
+ Scenario: List snippets
9
+ When I follow "Design" within "#navigation"
10
+ And I follow "Snippets"
11
+ Then I should see "first"
12
+ And I should see "another"
13
+ And I should see "markdown"
14
+ # And a host of others
15
+
16
+ Scenario: Create a snippet
17
+ When I follow "Design" within "#navigation"
18
+ And I follow "Snippets"
19
+ And I follow "New Snippet"
20
+ And I fill in "Name" with "Mine"
21
+ And I fill in "Body" with "My snippet"
22
+ And I press "Create Snippet"
23
+ Then I should be on the snippets list
24
+ And I should see "Mine"
25
+
26
+ Scenario: Display form errors
27
+ When I follow "Design" within "#navigation"
28
+ And I follow "Snippets"
29
+ And I follow "New Snippet"
30
+ And I fill in "Body" with "My snippet"
31
+ And I press "Create Snippet"
32
+ Then I should see an error message
33
+ And I should see the form
34
+
35
+ Scenario: Continue editing
36
+ When I follow "Design" within "#navigation"
37
+ And I follow "Snippets"
38
+ And I follow "New Snippet"
39
+ And I fill in "Name" with "Mine"
40
+ And I fill in "Body" with "My snippet"
41
+ And I press "Save and Continue Editing"
42
+ Then I should see "Edit Snippet"
43
+ And I should see the form
44
+
45
+ Scenario: View a snippet
46
+ When I view a filtered snippet
47
+ Then I should see "Edit Snippet"
48
+ And I should see "Markdown"
49
+
50
+ Scenario: Delete a snippet with confirmation
51
+ When I follow "Design" within "#navigation"
52
+ And I follow "Snippets"
53
+ And I follow "Remove"
54
+ Then I should see "permanently remove"
55
+ And I should see "another"
56
+ When I press "Delete Snippet"
57
+ Then I should not see "another"
58
+
59
+ Scenario Outline: Admins and designers can see and edit snippets
60
+ Given I am logged in as "<username>"
61
+ And I should see "Design"
62
+ When I follow "Design" within "#navigation"
63
+ And I follow "Snippets"
64
+ And I should not see "You must have designer privileges"
65
+ And I follow "first"
66
+ Then I should see "Edit Snippet"
67
+
68
+ Examples:
69
+ | username |
70
+ | admin |
71
+ | designer |
72
+
73
+ Scenario Outline: Ordinary users cannot edit snippets
74
+ Given I am logged in as "<username>"
75
+ And I should not see "Design"
76
+ When I go to the "snippets" admin page
77
+ Then I should see "You must have designer privileges"
78
+
79
+ Examples:
80
+ | username |
81
+ | existing |
82
+ | another |
@@ -0,0 +1,25 @@
1
+ Given /^There are many snippets$/ do
2
+ 100.times do |i|
3
+ Snippet.create(:name => "snippet_#{i}", :content => "This is snippet #{i}")
4
+ end
5
+ end
6
+
7
+ Given /^There are few snippets$/ do
8
+ #
9
+ end
10
+
11
+ Then /^I should see all the snippets$/ do
12
+ Snippet.all.each do |snippet|
13
+ response.body.should have_tag('tr.snippet') do
14
+ with_tag("a", :text => snippet.name)
15
+ end
16
+ end
17
+ end
18
+
19
+ When /^I view a snippet$/ do
20
+ visit "/admin/snippets/#{snippets(:first).id}"
21
+ end
22
+
23
+ When /^I view a filtered snippet$/ do
24
+ visit "/admin/snippets/#{snippets(:markdown).id}"
25
+ end
@@ -0,0 +1,11 @@
1
+ # Sets up the Rails environment for Cucumber
2
+ ENV["RAILS_ENV"] = "test"
3
+ # Extension root
4
+ extension_env = File.expand_path(File.dirname(__FILE__) + '/../../../../../config/environment')
5
+ require extension_env+'.rb'
6
+
7
+ Dir.glob(File.join(RADIANT_ROOT, "features", "**", "*.rb")).each {|step| require step unless step =~ /datasets_loader\.rb$/}
8
+
9
+ Cucumber::Rails::World.class_eval do
10
+ dataset :snippets
11
+ end
@@ -0,0 +1,22 @@
1
+ module NavigationHelpers
2
+
3
+ # Extend the standard PathMatchers with your own paths
4
+ # to be used in your features.
5
+ #
6
+ # The keys and values here may be used in your standard web steps
7
+ # Using:
8
+ #
9
+ # When I go to the "snippets" admin page
10
+ #
11
+ # would direct the request to the path you provide in the value:
12
+ #
13
+ # admin_snippets_path
14
+ #
15
+ PathMatchers = {} unless defined?(PathMatchers)
16
+ PathMatchers.merge!({
17
+ /snippets/i => 'admin_snippets_path'
18
+ })
19
+
20
+ end
21
+
22
+ World(NavigationHelpers)
@@ -0,0 +1,5 @@
1
+ module Snippets
2
+ class Engine < Rails::Engine
3
+ paths["app/helpers"] = []
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ namespace :trusty do
2
+ namespace :extensions do
3
+ namespace :snippets do
4
+
5
+ desc "Runs the migration of the Snippets extension"
6
+ task :migrate => :environment do
7
+ require 'trusty_cms/extension_migrator'
8
+ if ENV["VERSION"]
9
+ SnippetsExtension.migrator.migrate(ENV["VERSION"].to_i)
10
+ Rake::Task['db:schema:dump'].invoke
11
+ else
12
+ SnippetsExtension.migrator.migrate
13
+ Rake::Task['db:schema:dump'].invoke
14
+ end
15
+ end
16
+
17
+ desc "Copies public assets of the Snippets to the instance public/ directory."
18
+ task :update => :environment do
19
+ is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
20
+ puts "Copying assets from SnippetsExtension"
21
+ Dir[SnippetsExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
22
+ path = file.sub(SnippetsExtension.root, '')
23
+ directory = File.dirname(path)
24
+ mkdir_p RAILS_ROOT + directory, :verbose => false
25
+ cp file, RAILS_ROOT + path, :verbose => false
26
+ end
27
+ end
28
+
29
+ desc "Syncs all available translations for this ext to the English ext master"
30
+ task :sync => :environment do
31
+ # The main translation root, basically where English is kept
32
+ language_root = SnippetsExtension.root + "/config/locales"
33
+ words = TranslationSupport.get_translation_keys(language_root)
34
+
35
+ Dir["#{language_root}/*.yml"].each do |filename|
36
+ next if filename.match('_available_tags')
37
+ basename = File.basename(filename, '.yml')
38
+ puts "Syncing #{basename}"
39
+ (comments, other) = TranslationSupport.read_file(filename, basename)
40
+ words.each { |k,v| other[k] ||= words[k] } # Initializing hash variable as empty if it does not exist
41
+ other.delete_if { |k,v| !words[k] } # Remove if not defined in en.yml
42
+ TranslationSupport.write_file(filename, basename, comments, other)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,8 @@
1
+ module TrustySnippetsExtension
2
+ VERSION = "1.0.0"
3
+ SUMMARY = "Snippets for Trusty CMS"
4
+ DESCRIPTION = "Makes Trusty better by adding snippets!"
5
+ URL = "http://github.com/pgharts/trusty-snippets-extension"
6
+ AUTHORS = ["Jim Gay", "Eric Sipple"]
7
+ EMAIL = ["sipple@trustarts.org"]
8
+ end
@@ -0,0 +1,69 @@
1
+ # Uncomment this if you reference any of your controllers in activate
2
+ # require_dependency "application_controller"
3
+ require "trusty-snippets-extension"
4
+
5
+ class SnippetsExtension < TrustyCms::Extension
6
+ version TrustySnippetsExtension::VERSION
7
+ description TrustySnippetsExtension::DESCRIPTION
8
+ url TrustySnippetsExtension::URL
9
+
10
+ def activate
11
+
12
+ if defined?(Radiant::Exporter)
13
+ TrustyCms::Exporter.exportable_models << Snippet
14
+ TrustyCms::Exporter.template_models << Snippet
15
+ end
16
+
17
+ Page.class_eval do
18
+ include SnippetTags
19
+ end
20
+
21
+ TrustyCms::AdminUI.class_eval do
22
+ attr_accessor :snippet
23
+
24
+ alias_method :snippets, :snippet
25
+
26
+ def load_default_snippet_regions
27
+ OpenStruct.new.tap do |snippet|
28
+ snippet.edit = TrustyCms::AdminUI::RegionSet.new do |edit|
29
+ edit.main.concat %w{edit_header edit_form}
30
+ edit.form.concat %w{edit_title edit_content edit_filter}
31
+ edit.form_bottom.concat %w{edit_buttons edit_timestamp}
32
+ end
33
+ snippet.index = TrustyCms::AdminUI::RegionSet.new do |index|
34
+ index.top.concat %w{}
35
+ index.thead.concat %w{title_header actions_header}
36
+ index.tbody.concat %w{title_cell actions_cell}
37
+ index.bottom.concat %w{new_button}
38
+ end
39
+ snippet.new = snippet.edit
40
+ end
41
+ end
42
+
43
+ def load_default_snippet_file_regions
44
+ OpenStruct.new.tap do |snippet|
45
+ snippet.show = TrustyCms::AdminUI::RegionSet.new do |edit|
46
+ edit.main.concat %w{ header }
47
+ edit.display_content.concat %w{ title content }
48
+ edit.bottom.concat %w{ timestamp }
49
+ end
50
+ snippet.index = TrustyCms::AdminUI::RegionSet.new do |index|
51
+ index.top.concat %w{}
52
+ index.thead.concat %w{title_header}
53
+ index.tbody.concat %w{title_cell}
54
+ index.bottom.concat %w{new_button}
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ admin.snippet ||= TrustyCms::AdminUI.load_default_snippet_regions
61
+
62
+ UserActionObserver.instance.send :add_observer!, ::Snippet
63
+
64
+ tab 'Design' do
65
+ add_item "Snippets", "/admin/snippets"
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,22 @@
1
+ git clone git://github.com/radiant/radiant.git test_app
2
+ cd test_app
3
+
4
+ if [[ $RADIANT_VERSION != "master" ]]
5
+ then
6
+ git checkout -t origin/$RADIANT_VERSION
7
+ fi
8
+ gem install bundler --pre
9
+ echo 'gemspec' >> Gemfile
10
+ echo 'gem "radiant-snippets-extension", :path => ".."' >> Gemfile
11
+ bundle install
12
+
13
+ case $DB in
14
+ "mysql" )
15
+ mysql -e 'create database radiant_test;'
16
+ cp spec/ci/database.mysql.yml config/database.yml;;
17
+ "postgres" )
18
+ psql -c 'create database radiant_test;' -U postgres
19
+ cp spec/ci/database.postgresql.yml config/database.yml;;
20
+ esac
21
+
22
+ bundle exec rake db:migrate
data/spec/ci/script ADDED
@@ -0,0 +1,2 @@
1
+ cd test_app
2
+ bundle exec rake spec:extensions EXT=snippets
@@ -0,0 +1,110 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../snippets_spec_helper.rb')
2
+
3
+ describe Admin::SnippetsController do
4
+ dataset :users, :snippets
5
+
6
+ before :each do
7
+ ActionController::Routing::Routes.reload
8
+ login_as :designer
9
+ end
10
+
11
+ it "should be an ResourceController" do
12
+ controller.should be_kind_of(Admin::ResourceController)
13
+ end
14
+
15
+ it "should handle Snippets" do
16
+ controller.class.model_class.should == Snippet
17
+ end
18
+
19
+
20
+ describe "show" do
21
+ it "should redirect to the edit action" do
22
+ get :show, :id => 1
23
+ response.should redirect_to(edit_admin_snippet_path(params[:id]))
24
+ end
25
+
26
+ it "should show xml when format is xml" do
27
+ snippet = Snippet.first
28
+ get :show, :id => snippet.id, :format => "xml"
29
+ response.body.should == snippet.to_xml
30
+ end
31
+ end
32
+
33
+ describe "with invalid snippet id" do
34
+ [:edit, :remove].each do |action|
35
+ before do
36
+ @parameters = {:id => 999}
37
+ end
38
+ it "should redirect the #{action} action to the index action" do
39
+ get action, @parameters
40
+ response.should redirect_to(admin_snippets_path)
41
+ end
42
+ it "should say that the 'Snippet could not be found.' after the #{action} action" do
43
+ get action, @parameters
44
+ flash[:notice].should == 'Snippet could not be found.'
45
+ end
46
+ end
47
+ it 'should redirect the update action to the index action' do
48
+ put :update, @parameters
49
+ response.should redirect_to(admin_snippets_path)
50
+ end
51
+ it "should say that the 'Snippet could not be found.' after the update action" do
52
+ put :update, @parameters
53
+ flash[:notice].should == 'Snippet could not be found.'
54
+ end
55
+ it 'should redirect the destroy action to the index action' do
56
+ delete :destroy, @parameters
57
+ response.should redirect_to(admin_snippets_path)
58
+ end
59
+ it "should say that the 'Snippet could not be found.' after the destroy action" do
60
+ delete :destroy, @parameters
61
+ flash[:notice].should == 'Snippet could not be found.'
62
+ end
63
+ end
64
+
65
+ {:get => [:index, :show, :new, :edit, :remove],
66
+ :post => [:create],
67
+ :put => [:update],
68
+ :delete => [:destroy]}.each do |method, actions|
69
+ actions.each do |action|
70
+ it "should require login to access the #{action} action" do
71
+ logout
72
+ lambda { send(method, action, :id => snippet_id(:first)) }.should require_login
73
+ end
74
+
75
+ if action == :show
76
+ it "should request authentication for API access on show" do
77
+ logout
78
+ send(method, action, :id => snippet_id(:first), :format => "xml")
79
+ response.response_code.should == 401
80
+ end
81
+ else
82
+ it "should allow access to designers for the #{action} action" do
83
+ lambda {
84
+ send(method, action, :id => snippet_id(:first))
85
+ }.should restrict_access(:allow => [users(:designer)],
86
+ :url => '/admin/pages')
87
+ end
88
+
89
+ it "should allow access to admins for the #{action} action" do
90
+ lambda {
91
+ send(method, action, :id => snippet_id(:first))
92
+ }.should restrict_access(:allow => [users(:designer)],
93
+ :url => '/admin/pages')
94
+ end
95
+
96
+ it "should deny non-designers and non-admins for the #{action} action" do
97
+ lambda {
98
+ send(method, action, :id => Snippet.first.id)
99
+ }.should restrict_access(:deny => [users(:non_admin), users(:existing)],
100
+ :url => '/admin/pages')
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ it "should clear the page cache when saved" do
107
+ Radiant::Cache.should_receive(:clear)
108
+ put :update, :id => snippet_id(:first), :snippet => {:content => "Foobar."}
109
+ end
110
+ end
@@ -0,0 +1,44 @@
1
+ class SnippetMarkdownFilter < TextFilter
2
+ def filter(text)
3
+ text + ' for Snippets!'
4
+ end
5
+ end
6
+
7
+ class SnippetsDataset < Dataset::Base
8
+
9
+ def load
10
+ create_snippet "first", :content => "test"
11
+ create_snippet "another", :content => "another test"
12
+ create_snippet "markdown", :filter_id => "Snippet Markdown", :content => "**markdown**"
13
+ create_snippet "radius", :content => "<r:title />"
14
+ create_snippet "global_page_cascade", :content => "<r:children:each><r:page:title /> </r:children:each>"
15
+ create_snippet "recursive", :content => "<r:children:each><r:snippet name='recursive' /></r:children:each><r:title />"
16
+ create_snippet "yielding", :content => "Before...<r:yield/>...and after"
17
+ create_snippet "div_wrap", :content => "<div><r:yield/></div>"
18
+ create_snippet "nested_yields", :content => '<snippet name="div_wrap">above <r:yield/> below</snippet>'
19
+ create_snippet "yielding_often", :content => '<r:yield/> is <r:yield/>er than <r:yield/>'
20
+ end
21
+
22
+ helpers do
23
+ def create_snippet(name, attributes={})
24
+ create_record :snippet, name.symbolize, snippet_params(attributes.reverse_merge(:name => name))
25
+ end
26
+
27
+ def snippet_params(attributes={})
28
+ name = attributes[:name] || unique_snippet_name
29
+ {
30
+ :name => name,
31
+ :content => "<r:content />"
32
+ }.merge(attributes)
33
+ end
34
+
35
+ private
36
+
37
+ def unique_snippet_name
38
+ @@unique_snippet_name_call_count ||= 0
39
+ @@unique_snippet_name_call_count += 1
40
+ "snippet-#{@@unique_snippet_name_call_count}"
41
+ end
42
+ end
43
+
44
+ end