radiant-sheets-extension 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/LICENSE +21 -0
  2. data/README.md +33 -0
  3. data/Rakefile +129 -0
  4. data/app/controllers/admin/scripts_controller.rb +13 -0
  5. data/app/controllers/admin/sheet_resource_controller.rb +54 -0
  6. data/app/controllers/admin/styles_controller.rb +13 -0
  7. data/app/helpers/admin/scripts_helper.rb +5 -0
  8. data/app/helpers/admin/styles_helper.rb +5 -0
  9. data/app/models/javascript_page.rb +16 -0
  10. data/app/models/sheet.rb +93 -0
  11. data/app/models/stylesheet_page.rb +16 -0
  12. data/app/views/admin/scripts/edit.html.haml +30 -0
  13. data/app/views/admin/scripts/index.html.haml +33 -0
  14. data/app/views/admin/scripts/new.html.haml +30 -0
  15. data/app/views/admin/sheets/_edit_scripts.html.haml +40 -0
  16. data/app/views/admin/sheets/_upload_script.html.haml +16 -0
  17. data/app/views/admin/styles/edit.html.haml +30 -0
  18. data/app/views/admin/styles/index.html.haml +33 -0
  19. data/app/views/admin/styles/new.html.haml +30 -0
  20. data/config/locales/en.yml +17 -0
  21. data/config/routes.rb +6 -0
  22. data/cucumber.yml +1 -0
  23. data/features/admin/managing_javascripts.feature +147 -0
  24. data/features/admin/managing_stylesheets.feature +108 -0
  25. data/features/support/env.rb +11 -0
  26. data/features/support/paths.rb +11 -0
  27. data/lib/coffee_filter.rb +5 -0
  28. data/lib/javascript_tags.rb +58 -0
  29. data/lib/radiant-sheets-extension/version.rb +3 -0
  30. data/lib/radiant-sheets-extension.rb +2 -0
  31. data/lib/sass_filter.rb +18 -0
  32. data/lib/scss_filter.rb +19 -0
  33. data/lib/stylesheet_tags.rb +61 -0
  34. data/lib/tasks/sheets_extension_tasks.rake +79 -0
  35. data/public/images/admin/javascript.png +0 -0
  36. data/public/images/admin/stylesheet.png +0 -0
  37. data/radiant-sheets-extension.gemspec +32 -0
  38. data/sass.html +82 -0
  39. data/sheets_extension.rb +96 -0
  40. data/spec/controllers/admin/scripts_controller_spec.rb +123 -0
  41. data/spec/controllers/admin/styles_controller_spec.rb +124 -0
  42. data/spec/datasets/javascripts_dataset.rb +15 -0
  43. data/spec/datasets/stylesheets_dataset.rb +17 -0
  44. data/spec/helpers/admin/scripts_helper_spec.rb +5 -0
  45. data/spec/helpers/admin/styles_helper_spec.rb +5 -0
  46. data/spec/lib/javascript_tags_spec.rb +36 -0
  47. data/spec/lib/stylesheet_tags_spec.rb +41 -0
  48. data/spec/models/javascript_page_spec.rb +80 -0
  49. data/spec/models/page_spec.rb +5 -0
  50. data/spec/models/stylesheet_page_spec.rb +80 -0
  51. data/spec/spec.opts +6 -0
  52. data/spec/spec_helper.rb +36 -0
  53. metadata +169 -0
data/sass.html ADDED
@@ -0,0 +1,82 @@
1
+ <h1 id='features'>Features</h1>
2
+
3
+ <p>Although it&#8217;s nice that every CSS file is a valid SCSS file, that&#8217;s not why you&#8217;re learning about SCSS. You&#8217;re learning because it offers <em>more</em> than CSS. SCSS provides all sorts of useful features on top of CSS. This tutorial will walk you through some of the most fundamental ones, along with examples that you can add to your <code>style.scss</code> and play with.</p>
4
+
5
+ <h2 id='nesting'>Nesting</h2>
6
+
7
+ <p>Often when writing CSS, you&#8217;ll have several selectors that all begin with the same thing. For example, you might have &#8220;#navbar ul&#8221;, &#8220;#navbar li&#8221;, and &#8220;#navbar li a&#8221;. It&#8217;s a pain to repeat the beginning over and over again, especially when it gets long.</p>
8
+
9
+ <p>Sass allows you to avoid this by nesting the child selectors within the parent selector.</p>
10
+ <div class='compare'>
11
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;#navbar {&#x000A; width: 80%;&#x000A; height: 23px;&#x000A;&#x000A; ul { list-style-type: none; }&#x000A; li {&#x000A; float: left;&#x000A; a { font-weight: bold; }&#x000A; }&#x000A;}&#x000A;</pre>
12
+ <pre class='css'>/* style.css */&#x000A;&#x000A;#navbar {&#x000A; width: 80%;&#x000A; height: 23px; }&#x000A; #navbar ul {&#x000A; list-style-type: none; }&#x000A; #navbar li {&#x000A; float: left; }&#x000A; #navbar li a {&#x000A; font-weight: bold; }&#x000A;</pre>
13
+ </div>
14
+ <p>Notice how the output is formatted to reflect the original nesting of the selectors. This is the default CSS style, but there are <a href='/docs/yardoc/file.SASS_REFERENCE.html#output_style'>other styles</a> for all sorts of CSS formatting preferences. There&#8217;s even one for compressing the CSS as much as possible!</p>
15
+
16
+ <p>You can also nest properties, so you don&#8217;t have to repeat stuff like &#8220;<code>border-left-</code>&#8221; all the time.</p>
17
+ <div class='compare'>
18
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;.fakeshadow {&#x000A; border: {&#x000A; style: solid;&#x000A; left: {&#x000A; width: 4px;&#x000A; color: #888;&#x000A; }&#x000A; right: {&#x000A; width: 2px;&#x000A; color: #ccc;&#x000A; }&#x000A; }&#x000A;</pre>
19
+ <pre class='css'>/* style.css */&#x000A;&#x000A;.fakeshadow {&#x000A; border-style: solid;&#x000A; border-left-width: 4px;&#x000A; border-left-color: #888;&#x000A; border-right-width: 2px;&#x000A; border-right-color: #ccc; }&#x000A;</pre>
20
+ </div>
21
+ <h3 id='parent_references'>Parent References</h3>
22
+
23
+ <p>What about pseudoclasses, like <code>:hover</code>? There isn&#8217;t a space between them and their parent selector, but it&#8217;s still possible to nest them. You just need to use the Sass special character <code>&amp;</code>. In a selector, <code>&amp;</code> will be replaced verbatim with the parent selector.</p>
24
+ <div class='compare'>
25
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;a {&#x000A; color: #ce4dd6;&#x000A; &:hover { color: #ffb3ff; }&#x000A; &:visited { color: #c458cb; }&#x000A;}&#x000A;</pre>
26
+ <pre class='css'>/* style.css */&#x000A;&#x000A;a {&#x000A; color: #ce4dd6; }&#x000A; a:hover {&#x000A; color: #ffb3ff; }&#x000A; a:visited {&#x000A; color: #c458cb; }&#x000A;</pre>
27
+ </div>
28
+ <h2 id='variables'>Variables</h2>
29
+
30
+ <p>Sass allows you to declare variables that can be used throughout your stylesheet. Variables begin with <code>$</code> and are declared just like properties. They can have any value that&#8217;s allowed for a CSS property, such as colors, numbers (with units), or text.</p>
31
+ <div class='compare'>
32
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;$main-color: #ce4dd6;&#x000A;$style: solid;&#x000A;&#x000A;#navbar {&#x000A; border-bottom: {&#x000A; color: $main-color;&#x000A; style: $style;&#x000A; }&#x000A;}&#x000A;&#x000A;a {&#x000A; color: $main-color;&#x000A; &:hover { border-bottom: $style 1px; }&#x000A;}&#x000A;</pre>
33
+ <pre class='css'>/* style.css */&#x000A;&#x000A;#navbar {&#x000A; border-bottom-color: #ce4dd6;&#x000A; border-bottom-style: solid; }&#x000A;&#x000A;a {&#x000A; color: #ce4dd6; }&#x000A; a:hover {&#x000A; border-bottom: solid 1px; }&#x000A;</pre>
34
+ </div>
35
+ <p>Variables allow you to re-use colors, sizes, and other values without repeating yourself. This means that changes that should be small, such as tweaking the coloring or the sizing, can be done in one place, not all over the stylesheet.</p>
36
+
37
+ <h3 id='operations_and_functions'>Operations and Functions</h3>
38
+
39
+ <p>In addition to just using variable values as they&#8217;re defined, you can also modify and combine them using math and useful predefined functions. This allows you to compute element sizing and even coloration dynamically.</p>
40
+
41
+ <p>The standard math operations (<code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, and <code>%</code>) are supported for numbers, even those with units. For colors, there are all sorts of useful functions for changing the <a href='/docs/yardoc/Sass/Script/Functions.html#lighten-instance_method'>lightness</a>, <a href='/docs/yardoc/Sass/Script/Functions.html#adjust_hue-instance_method'>hue</a>, <a href='/docs/yardoc/Sass/Script/Functions.html#saturate-instance_method'>saturation</a>, and <a href='/docs/yardoc/Sass/Script/Functions.html'>more</a>.</p>
42
+ <div class='compare'>
43
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;#navbar {&#x000A; $navbar-width: 800px;&#x000A; $items: 5;&#x000A; $navbar-color: #ce4dd6;&#x000A;&#x000A; width: $navbar-width;&#x000A; border-bottom: 2px solid $navbar-color;&#x000A;&#x000A; li {&#x000A; float: left;&#x000A; width: $navbar-width/$items - 10px;&#x000A;&#x000A; background-color:&#x000A; lighten($navbar-color, 20%);&#x000A; &:hover {&#x000A; background-color:&#x000A; lighten($navbar-color, 10%);&#x000A; }&#x000A; }&#x000A;}&#x000A;</pre>
44
+ <pre class='css'>/* style.css */&#x000A;&#x000A;#navbar {&#x000A; width: 800px;&#x000A; border-bottom: 2px solid #ce4dd6; }&#x000A; #navbar li {&#x000A; float: left;&#x000A; width: 150px;&#x000A; background-color: #e5a0e9; }&#x000A; #navbar li:hover {&#x000A; background-color: #d976e0; }&#x000A;</pre>
45
+ </div>
46
+ <h3 id='interpolation'>Interpolation</h3>
47
+
48
+ <p>Variables can be used for more than just property values. You can use <code>#{}</code> to insert them into property names or selectors.</p>
49
+ <div class='compare'>
50
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;$side: top;&#x000A;$radius: 10px;&#x000A;&#x000A;.rounded- {&#x000A; border-#{$side}-radius: $radius;&#x000A; -moz-border-radius-#{$side}: $radius;&#x000A; -webkit-border-#{$side}-radius: $radius;&#x000A;}&#x000A;</pre>
51
+ <pre class='css'>/* style.css */&#x000A;&#x000A;.rounded-top {&#x000A; border-top-radius: 10px;&#x000A; -moz-border-radius-top: 10px;&#x000A; -webkit-border-top-radius: 10px; }&#x000A;</pre>
52
+ </div>
53
+ <h2 id='mixins'>Mixins</h2>
54
+
55
+ <p>Mixins are one of the most powerful Sass features. They allow re-use of styles &#8211; properties or even selectors &#8211; without having to copy and paste them or move them into a non-semantic class.</p>
56
+
57
+ <p>Mixins are defined using the &#8220;<code>@mixin</code>&#8221; directive, which takes a block of styles that can then be included in another selector using the &#8220;<code>@include</code>&#8221; directive.</p>
58
+ <div class='compare'>
59
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;@mixin rounded-top {&#x000A; $side: top;&#x000A; $radius: 10px;&#x000A;&#x000A; border-#{$side}-radius: $radius;&#x000A; -moz-border-radius-#{$side}: $radius;&#x000A; -webkit-border-#{$side}-radius: $radius;&#x000A;}&#x000A;&#x000A;#navbar li { @include rounded-top; }&#x000A;#footer { @include rounded-top; }&#x000A;</pre>
60
+ <pre class='css'>/* style.css */&#x000A;&#x000A;#navbar li {&#x000A; border-top-radius: 10px;&#x000A; -moz-border-radius-top: 10px;&#x000A; -webkit-border-top-radius: 10px; }&#x000A;&#x000A;#footer {&#x000A; border-top-radius: 10px;&#x000A; -moz-border-radius-top: 10px;&#x000A; -webkit-border-top-radius: 10px; }&#x000A;</pre>
61
+ </div>
62
+ <h3 id='arguments'>Arguments</h3>
63
+
64
+ <p>The real power of mixins comes when you pass them arguments. Arguments are declared as a parenthesized, comma-separated list of variables. Each of those variables is assigned a value each time the mixin is used.</p>
65
+
66
+ <p>Mixin arguments can also be given default values just like you&#8217;d declare them normally. Then the user of the mixin can choose not to pass that argument and it will be assigned the default value.</p>
67
+ <div class='compare'>
68
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;@mixin rounded($side, $radius: 10px) {&#x000A; border-#{$side}-radius: $radius;&#x000A; -moz-border-radius-#{$side}: $radius;&#x000A; -webkit-border-#{$side}-radius: $radius;&#x000A;}&#x000A;&#x000A;#navbar li { @include rounded(top); }&#x000A;#footer { @include rounded(top, 5px); }&#x000A;#sidebar { @include rounded(left, 8px); }&#x000A;</pre>
69
+ <pre class='css'>/* style.css */&#x000A;&#x000A;#navbar li {&#x000A; border-top-radius: 10px;&#x000A; -moz-border-radius-top: 10px;&#x000A; -webkit-border-top-radius: 10px; }&#x000A;&#x000A;#footer {&#x000A; border-top-radius: 5px;&#x000A; -moz-border-radius-top: 5px;&#x000A; -webkit-border-top-radius: 5px; }&#x000A;&#x000A;#sidebar {&#x000A; border-left-radius: 8px;&#x000A; -moz-border-radius-left: 8px;&#x000A; -webkit-border-left-radius: 8px; }&#x000A;</pre>
70
+ </div>
71
+ <h2 id='id1'><code>@import</code></h2>
72
+
73
+ <p>Stylesheets can get pretty big. CSS has an <code>@import</code> directive that allows you to break your styles up into multiple stylesheets, but each stylesheet takes a separate (slow) HTTP request. That&#8217;s why Sass&#8217;s <code>@import</code> directive pulls in the stylesheets directly. Not only that, but any variables or mixins defined in <code>@import</code>ed files are available to the files that import them.</p>
74
+
75
+ <p>Sass has a naming convention for files that are meant to be imported (called &#8220;partials&#8221;): they begin with an underscore. Let&#8217;s create a partial called <strong><code>_rounded.scss</code></strong> to hold our <code>rounded</code> mixin.</p>
76
+
77
+ <p>In order to support both <code>.scss</code> and <code>.sass</code> files, Sass allows files to be imported without specifying a file extension. That means we can just import <code>&quot;rounded&quot;</code>, rather than <code>&quot;rounded.scss&quot;</code>.</p>
78
+ <div class='compare'>
79
+ <pre class='sass'>/* _rounded.scss */&#x000A;&#x000A;@mixin rounded($side, $radius: 10px) {&#x000A; border-#{$side}-radius: $radius;&#x000A; -moz-border-radius-#{$side}: $radius;&#x000A; -webkit-border-#{$side}-radius: $radius;&#x000A;}&#x000A;</pre>
80
+ <pre class='css'>/* style.css */&#x000A;&#x000A;#navbar li {&#x000A; border-top-radius: 10px;&#x000A; -moz-border-radius-top: 10px;&#x000A; -webkit-border-top-radius: 10px; }&#x000A;&#x000A;#footer {&#x000A; border-top-radius: 5px;&#x000A; -moz-border-radius-top: 5px;&#x000A; -webkit-border-top-radius: 5px; }&#x000A;&#x000A;#sidebar {&#x000A; border-left-radius: 8px;&#x000A; -moz-border-radius-left: 8px;&#x000A; -webkit-border-left-radius: 8px; }&#x000A;</pre>
81
+ <pre class='sass'>/* style.scss */&#x000A;&#x000A;@import "rounded";&#x000A;&#x000A;#navbar li { @include rounded(top); }&#x000A;#footer { @include rounded(top, 5px); }&#x000A;#sidebar { @include rounded(left, 8px); }&#x000A;</pre>
82
+ </div>
@@ -0,0 +1,96 @@
1
+ # Uncomment this if you reference any of your controllers in activate
2
+ require_dependency 'application_controller'
3
+ begin
4
+ require 'compass'
5
+ rescue
6
+ # Running on edge, compass already exists
7
+ end
8
+ require 'radiant-sheets-extension/version'
9
+
10
+ class SheetsExtension < Radiant::Extension
11
+ version RadiantSheetsExtension::VERSION
12
+ description "Manage CSS and Javascript content in Radiant CMS as Sheets, a subset of Pages"
13
+ url "http://github.com/radiant/radiant"
14
+
15
+ cattr_accessor :stylesheet_filters
16
+ cattr_accessor :javascript_filters
17
+
18
+ @@stylesheet_filters ||= []
19
+ @@stylesheet_filters += [SassFilter, ScssFilter]
20
+ @@javascript_filters ||= []
21
+ @@javascript_filters << CoffeeFilter
22
+
23
+ extension_config do |config|
24
+ config.gem 'coffee-script', :version => '~> 2.2.0'
25
+ config.gem 'sass', :version => '~> 3.1.2'
26
+ end
27
+
28
+ def activate
29
+ SassFilter
30
+ ScssFilter
31
+ CoffeeFilter
32
+
33
+ tab 'Design' do
34
+ add_item "Stylesheets", "/admin/styles"
35
+ add_item "Javascripts", "/admin/scripts"
36
+ end
37
+
38
+ ApplicationHelper.module_eval do
39
+ def filter_options_for_select_with_sheet_restrictions(selected=nil)
40
+ sheet_filters = SheetsExtension.stylesheet_filters + SheetsExtension.javascript_filters
41
+ filters = TextFilter.descendants - sheet_filters
42
+ options_for_select([[t('select.none'), '']] + filters.map { |s| s.filter_name }.sort, selected)
43
+ end
44
+ alias_method_chain :filter_options_for_select, :sheet_restrictions
45
+ end
46
+
47
+ # Will only be called in 0.9.1 and below, avoid redeclaring
48
+ unless Page.respond_to?('in_menu')
49
+ Page.class_eval do
50
+ class_inheritable_accessor :in_menu
51
+ self.in_menu = true
52
+
53
+ class << self
54
+ alias_method :in_menu?, :in_menu
55
+ alias_method :in_menu, :in_menu=
56
+ end
57
+ end
58
+ end
59
+
60
+ Page.class_eval do
61
+ def sheet?
62
+ self.class.included_modules.include?(Sheet::Instance)
63
+ end
64
+
65
+ include JavascriptTags
66
+ include StylesheetTags
67
+ end
68
+
69
+ Admin::NodeHelper.module_eval do
70
+ def render_node_with_sheets(page, locals = {})
71
+ unless page.sheet?
72
+ render_node_without_sheets(page, locals)
73
+ end
74
+ end
75
+ alias_method_chain :render_node, :sheets
76
+ end
77
+
78
+ SiteController.class_eval do
79
+ cattr_writer :sheet_cache_timeout
80
+
81
+ def self.sheet_cache_timeout
82
+ @@sheet_cache_timeout ||= 30.days
83
+ end
84
+
85
+ def set_cache_control_with_sheets
86
+ if @page.sheet?
87
+ expires_in self.class.sheet_cache_timeout, :public => true, :private => false
88
+ else
89
+ set_cache_control_without_sheets
90
+ end
91
+ end
92
+ alias_method_chain :set_cache_control, :sheets
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,123 @@
1
+ require File.dirname(__FILE__) + "/../../spec_helper"
2
+
3
+ describe Admin::ScriptsController do
4
+ dataset :users, :home_page, :javascripts
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 JavascriptPages" do
16
+ controller.class.model_class.should == JavascriptPage
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_script_path(params[:id]))
24
+ end
25
+
26
+ it "should show xml when format is xml" do
27
+ javascript = JavascriptPage.first
28
+ get :show, :id => javascript.id, :format => "xml"
29
+ response.body.should == javascript.to_xml
30
+ end
31
+ end
32
+ describe "upload" do
33
+ it "should respond with a bad request header if given no upload" do
34
+ post :upload
35
+ response.code.should == "400"
36
+ end
37
+ it "should redirect to the index action" do
38
+ uploaded_file = mock(ActionController::UploadedFile).as_null_object
39
+ uploaded_file.stub!(:original_filename).and_return('test.doc')
40
+ uploaded_file.stub!(:read).and_return("Some content")
41
+ uploaded_file.stub!(:size).and_return(100)
42
+ uploaded_file.stub!(:kind_of?).with(ActionController::UploadedFile).and_return(true)
43
+ post :upload, :upload => {:upload => uploaded_file}
44
+ response.should redirect_to(admin_scripts_path)
45
+ end
46
+ end
47
+
48
+ describe "with invalid javascript id" do
49
+ before do
50
+ @parameters = {:id => 999}
51
+ end
52
+ it "should redirect the edit action to the index action" do
53
+ get :edit, @parameters
54
+ response.should redirect_to(admin_scripts_path)
55
+ end
56
+ it "should say that the 'JavascriptPage could not be found.' after the edit action" do
57
+ get :edit, @parameters
58
+ flash[:notice].should match(/could not be found/)
59
+ end
60
+ it 'should redirect the update action to the index action' do
61
+ put :update, @parameters
62
+ response.should redirect_to(admin_scripts_path)
63
+ end
64
+ it "should say that the 'JavascriptPage could not be found.' after the update action" do
65
+ put :update, @parameters
66
+ flash[:notice].should match(/could not be found/)
67
+ end
68
+ it 'should redirect the destroy action to the index action' do
69
+ delete :destroy, @parameters
70
+ response.should redirect_to(admin_scripts_path)
71
+ end
72
+ it "should say that the 'JavascriptPage could not be found.' after the destroy action" do
73
+ delete :destroy, @parameters
74
+ flash[:notice].should match(/could not be found/)
75
+ end
76
+ end
77
+
78
+ {:get => [:index, :show, :new, :edit],
79
+ :post => [:create, :upload],
80
+ :put => [:update],
81
+ :delete => [:destroy]}.each do |method, actions|
82
+ actions.each do |action|
83
+ it "should require login to access the #{action} action" do
84
+ logout
85
+ lambda { send(method, action, :id => page_id(:site_js)) }.should require_login
86
+ end
87
+
88
+ if action == :show
89
+ it "should request authentication for API access on show" do
90
+ logout
91
+ send(method, action, :id => page_id(:site_js), :format => "xml")
92
+ response.response_code.should == 401
93
+ end
94
+ else
95
+ it "should allow access to designers for the #{action} action" do
96
+ lambda {
97
+ send(method, action, :id => page_id(:site_js))
98
+ }.should restrict_access(:allow => [users(:designer)],
99
+ :url => '/admin/pages')
100
+ end
101
+
102
+ it "should allow access to admins for the #{action} action" do
103
+ lambda {
104
+ send(method, action, :id => page_id(:site_js))
105
+ }.should restrict_access(:allow => [users(:designer)],
106
+ :url => '/admin/pages')
107
+ end
108
+
109
+ it "should deny non-designers and non-admins for the #{action} action" do
110
+ lambda {
111
+ send(method, action, :id => JavascriptPage.first.id)
112
+ }.should restrict_access(:deny => [users(:non_admin), users(:existing)],
113
+ :url => '/admin/pages')
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ it "should clear the page cache when saved" do
120
+ Radiant::Cache.should_receive(:clear)
121
+ put :update, :id => page_id(:site_js), :javascript => {:content => "Foobar."}
122
+ end
123
+ end
@@ -0,0 +1,124 @@
1
+ require File.dirname(__FILE__) + "/../../spec_helper"
2
+
3
+ describe Admin::StylesController do
4
+ dataset :users, :home_page, :stylesheets
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 StylesheetPages" do
16
+ controller.class.model_class.should == StylesheetPage
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_style_path(params[:id]))
24
+ end
25
+
26
+ it "should show xml when format is xml" do
27
+ stylesheet = StylesheetPage.first
28
+ get :show, :id => stylesheet.id, :format => "xml"
29
+ response.body.should == stylesheet.to_xml
30
+ end
31
+ end
32
+
33
+ describe "upload" do
34
+ it "should respond with a bad request header if given no upload" do
35
+ post :upload
36
+ response.code.should == "400"
37
+ end
38
+ it "should redirect to the index action" do
39
+ uploaded_file = mock(ActionController::UploadedFile).as_null_object
40
+ uploaded_file.stub!(:original_filename).and_return('test.doc')
41
+ uploaded_file.stub!(:read).and_return("Some content")
42
+ uploaded_file.stub!(:size).and_return(100)
43
+ uploaded_file.stub!(:kind_of?).with(ActionController::UploadedFile).and_return(true)
44
+ post :upload, :upload => {:upload => uploaded_file}
45
+ response.should redirect_to(admin_styles_path)
46
+ end
47
+ end
48
+
49
+ describe "with invalid stylesheet id" do
50
+ before do
51
+ @parameters = {:id => 999}
52
+ end
53
+ it "should redirect the edit action to the index action" do
54
+ get :edit, @parameters
55
+ response.should redirect_to(admin_styles_path)
56
+ end
57
+ it "should say that the 'StylesheetPage could not be found.' after the edit action" do
58
+ get :edit, @parameters
59
+ flash[:notice].should match(/could not be found/)
60
+ end
61
+ it 'should redirect the update action to the index action' do
62
+ put :update, @parameters
63
+ response.should redirect_to(admin_styles_path)
64
+ end
65
+ it "should say that the 'StylesheetPage could not be found.' after the update action" do
66
+ put :update, @parameters
67
+ flash[:notice].should match(/could not be found/)
68
+ end
69
+ it 'should redirect the destroy action to the index action' do
70
+ delete :destroy, @parameters
71
+ response.should redirect_to(admin_styles_path)
72
+ end
73
+ it "should say that the 'StylesheetPage could not be found.' after the destroy action" do
74
+ delete :destroy, @parameters
75
+ flash[:notice].should match(/could not be found/)
76
+ end
77
+ end
78
+
79
+ {:get => [:index, :show, :new, :edit],
80
+ :post => [:create, :upload],
81
+ :put => [:update],
82
+ :delete => [:destroy]}.each do |method, actions|
83
+ actions.each do |action|
84
+ it "should require login to access the #{action} action" do
85
+ logout
86
+ lambda { send(method, action, :id => page_id(:site_css)) }.should require_login
87
+ end
88
+
89
+ if action == :show
90
+ it "should request authentication for API access on show" do
91
+ logout
92
+ send(method, action, :id => page_id(:site_css), :format => "xml")
93
+ response.response_code.should == 401
94
+ end
95
+ else
96
+ it "should allow access to designers for the #{action} action" do
97
+ lambda {
98
+ send(method, action, :id => page_id(:site_css))
99
+ }.should restrict_access(:allow => [users(:designer)],
100
+ :url => '/admin/pages')
101
+ end
102
+
103
+ it "should allow access to admins for the #{action} action" do
104
+ lambda {
105
+ send(method, action, :id => page_id(:site_css))
106
+ }.should restrict_access(:allow => [users(:designer)],
107
+ :url => '/admin/pages')
108
+ end
109
+
110
+ it "should deny non-designers and non-admins for the #{action} action" do
111
+ lambda {
112
+ send(method, action, :id => StylesheetPage.first.id)
113
+ }.should restrict_access(:deny => [users(:non_admin), users(:existing)],
114
+ :url => '/admin/pages')
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ it "should clear the page cache when saved" do
121
+ Radiant::Cache.should_receive(:clear)
122
+ put :update, :id => page_id(:site_css), :stylesheet => {:content => "Foobar."}
123
+ end
124
+ end
@@ -0,0 +1,15 @@
1
+ class JavascriptsDataset < Dataset::Base
2
+ uses :home_page
3
+
4
+ def load
5
+ create_page 'js', :slug => 'js', :class_name => 'JavascriptPage', :parent_id => pages(:home).id do
6
+ create_page 'site.js', :slug => 'site.js', :class_name => 'JavascriptPage' do
7
+ create_page_part 'site_js_body', :name => 'body', :content => 'alert("site!");'
8
+ end
9
+ create_page 'coffee.js', :slug => 'coffee.js', :class_name => 'JavascriptPage' do
10
+ create_page_part 'coffee_js_body', :name => 'body', :content => 'alert("site!");', :filter_id => 'Coffee'
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,17 @@
1
+ class StylesheetsDataset < Dataset::Base
2
+ uses :home_page
3
+
4
+ def load
5
+ create_page "css", :slug => 'css', :class_name => 'StylesheetPage' do
6
+ create_page "site.css", :slug => 'site.css', :class_name => 'StylesheetPage' do
7
+ create_page_part 'site_css_body', :name => 'body', :content => 'p { color: blue; }'
8
+ end
9
+ create_page "sassy.sass", :slug => 'sassy.sass', :class_name => 'StylesheetPage' do
10
+ create_page_part 'sass_body', :name => 'body', :content => 'header
11
+ background: red', :filter_id => 'Sass'
12
+ end
13
+ create_page "container.css", :body => '<r:stylesheet slug="sassy.css" />', :class_name => 'StylesheetPage'
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Admin::ScriptsHelper do
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Admin::StylesHelper do
4
+
5
+ end
@@ -0,0 +1,36 @@
1
+ # require 'spec_helper'
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ describe "Javascript Tags" do
5
+ dataset :javascripts
6
+
7
+ let(:page){ pages(:home) }
8
+ let(:javascript_page){ pages(:site_js)}
9
+
10
+ describe "<r:javascript>" do
11
+ subject { page }
12
+ it { should render(%{<r:javascript />}).with_error("`javascript' tag must contain a `slug' attribute.") }
13
+ it { should render(%{<r:javascript slug="bogus" />}).with_error("javascript bogus not found") }
14
+ it { should render(%{<r:javascript slug="site.js" />}).as('alert("site!");') }
15
+ it { should render(%{<r:javascript slug="site.js" as="url" />}).as("/js/site.js?#{javascript_page.updated_at.to_i}") }
16
+ it { should render(%{<r:javascript slug="site.js" as="link" />}).as(%{<script type="#{javascript_page.headers['Content-Type']}" src="#{javascript_page.url.gsub(/\/$/,'')}?#{javascript_page.updated_at.to_i.to_s}"></script>}) }
17
+ it { should render(%{<r:javascript slug="site.js" as="link" type="special/type" />}).as(%{<script type="special/type" src="#{javascript_page.url.gsub(/\/$/,'')}?#{javascript_page.updated_at.to_i.to_s}"></script>}) }
18
+ it { should render(%{<r:javascript slug="site.js" as="link" something="custom" />}).as(%{<script type="#{javascript_page.headers['Content-Type']}" src="#{javascript_page.url.gsub(/\/$/,'')}?#{javascript_page.updated_at.to_i.to_s}" something="custom"></script>}) }
19
+ it { should render(%{<r:javascript slug="site.js" as="inline" />}).as(%{<script type="#{javascript_page.headers['Content-Type']}">
20
+ //<![CDATA[
21
+ alert("site!");
22
+ //]]>
23
+ </script>}) }
24
+ it { should render(%{<r:javascript slug="site.js" as="inline" type="special/type" />}).as(%{<script type="special/type">
25
+ //<![CDATA[
26
+ alert("site!");
27
+ //]]>
28
+ </script>}) }
29
+ it { should render(%{<r:javascript slug="site.js" as="inline" something="custom" />}).as(%{<script type="#{javascript_page.headers['Content-Type']}" something="custom">
30
+ //<![CDATA[
31
+ alert("site!");
32
+ //]]>
33
+ </script>}) }
34
+ end
35
+
36
+ end
@@ -0,0 +1,41 @@
1
+ # require 'spec_helper'
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ describe "Stylesheet Tags" do
5
+ dataset :stylesheets
6
+
7
+ let(:page){ pages(:home) }
8
+
9
+ describe "<r:stylesheet>" do
10
+ let(:site_css){pages(:site_css)}
11
+ subject { page }
12
+ it { should render(%{<r:stylesheet />}).with_error("`stylesheet' tag must contain a `slug' attribute.") }
13
+ it { should render(%{<r:stylesheet slug="bogus" />}).with_error("stylesheet bogus not found") }
14
+ it { should render(%{<r:stylesheet slug="site.css" />}).as("p { color: blue; }") }
15
+ it { should render(%{<r:stylesheet slug="site.css" as="url" />}).as("/css/site.css?#{site_css.updated_at.to_i}") }
16
+ it { should render(%{<r:stylesheet slug="site.css" as="link" />}).as(%{<link rel="stylesheet" type="text/css" href="/css/site.css?#{site_css.updated_at.to_i.to_s}" />}) }
17
+ it { should render(%{<r:stylesheet slug="site.css" as="link" type="special/type" />}).as(%{<link rel="stylesheet" type="special/type" href="/css/site.css?#{site_css.updated_at.to_i.to_s}" />}) }
18
+ it { should render(%{<r:stylesheet slug="site.css" as="link" something="custom" />}).as(%{<link rel="stylesheet" type="text/css" href="/css/site.css?#{site_css.updated_at.to_i.to_s}" something="custom" />}) }
19
+ it { should render(%{<r:stylesheet slug="site.css" as="link" rel="alternate" />}).as(%{<link rel="alternate" type="text/css" href="/css/site.css?#{site_css.updated_at.to_i.to_s}" />}) }
20
+ it { should render(%{<r:stylesheet slug="site.css" as="inline" />}).as(%{<style type="text/css">
21
+ /*<![CDATA[*/
22
+ p { color: blue; }
23
+ /*]]>*/
24
+ </style>}) }
25
+ it { should render(%{<r:stylesheet slug="site.css" as="inline" type="special/type" />}).as(%{<style type="special/type">
26
+ /*<![CDATA[*/
27
+ p { color: blue; }
28
+ /*]]>*/
29
+ </style>}) }
30
+ it { should render(%{<r:stylesheet slug="site.css" as="inline" something="custom" />}).as(%{<style type="text/css" something="custom">
31
+ /*<![CDATA[*/
32
+ p { color: blue; }
33
+ /*]]>*/
34
+ </style>}) }
35
+ it "should apply text filters when outputing content" do
36
+ css_result_from_sass = Sass::Engine.new(pages(:sassy_sass).part('body').content, Compass.sass_engine_options || {}).render
37
+ site_css.should render(%{<r:stylesheet slug="sassy.sass" />}).as(css_result_from_sass)
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,80 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe JavascriptPage do
4
+ dataset :javascripts
5
+ let(:javascript){ pages(:js) }
6
+ let(:site_js){ pages(:site_js) }
7
+
8
+ subject{ javascript }
9
+ its(:cache?) { should be_true }
10
+ its(:sheet?) { should be_true }
11
+ its(:virtual?) { should be_true }
12
+ its(:layout) { should be_nil }
13
+
14
+ describe '.root' do
15
+ subject{ JavascriptPage }
16
+ its(:root) { should == javascript }
17
+ end
18
+
19
+ describe '.new_with_defaults' do
20
+ describe '#parts' do
21
+ let(:parts){ JavascriptPage.new_with_defaults.parts }
22
+ subject{ parts }
23
+ its(:length) { should == 1 }
24
+ it "should have a body part" do
25
+ parts[0].name.should == 'body'
26
+ end
27
+ end
28
+ end
29
+
30
+ describe '#headers' do
31
+ it "should have a 'Content-Type' of 'text/javascript'" do
32
+ javascript.headers['Content-Type'].should == 'text/javascript'
33
+ end
34
+ end
35
+
36
+ describe '#find_by_path' do
37
+ context 'with a valid url' do
38
+ it 'should return the child found by the given slug' do
39
+ javascript.find_by_path('/js/site.js').should == site_js
40
+ end
41
+ end
42
+ end
43
+
44
+ context 'when validating a new page' do
45
+ it "should automatically set the title to the given slug" do
46
+ j = JavascriptPage.new(:slug => 'site.js')
47
+ j.valid?
48
+ j.title.should == 'site.js'
49
+ end
50
+ it "should automatically set the breadcrumb to the given slug" do
51
+ j = JavascriptPage.new(:slug => 'site.js')
52
+ j.valid?
53
+ j.breadcrumb.should == 'site.js'
54
+ end
55
+ end
56
+
57
+ context 'when saving a new page' do
58
+ subject { s = JavascriptPage.new_with_defaults
59
+ s.slug = 'test.js'
60
+ s.save!
61
+ s }
62
+ its(:status){ should == Status[:published] }
63
+ its(:status_id){ should == Status[:published].id }
64
+
65
+ its(:published_at){ should_not be_nil }
66
+ it "should have a published_at greater than or equal to the current time" do
67
+ subject.published_at.to_i.should <= Time.zone.now.to_i
68
+ end
69
+
70
+ context 'with the default page status set to draft' do
71
+ it 'should save a new page with a published status' do
72
+ Radiant::Config['defaults.page.status'] = 'draft'
73
+ new_sheet = JavascriptPage.new_with_defaults
74
+ new_sheet.slug = 'published.js'
75
+ new_sheet.save!
76
+ new_sheet.status.should == Status[:published]
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Page do
4
+ its(:sheet?){should be_false}
5
+ end