trusty-snippets-extension 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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