Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Version 0.3 (exported from SVN)

  • Loading branch information...
commit 2518f90be02c1e7ce1fbcfb033f3e0b42405b54d 1 parent 406751f
@chrisparrish chrisparrish authored
Showing with 931 additions and 547 deletions.
  1. +48 −0 CHANGELOG
  2. +41 −13 README
  3. +0 −8 app/controllers/admin/css_controller.rb
  4. +0 −8 app/controllers/admin/js_controller.rb
  5. +22 −8 app/controllers/admin/text_asset_controller.rb
  6. +3 −3 app/controllers/text_asset_site_controller.rb
  7. +1 −18 app/models/javascript.rb
  8. +1 −18 app/models/stylesheet.rb
  9. +35 −42 app/models/text_asset.rb
  10. +6 −0 app/models/text_asset_dependencies.rb
  11. +38 −0 app/models/text_asset_observer.rb
  12. +0 −74 app/models/text_asset_tags.rb
  13. +0 −3  app/views/admin/text_asset/edit.html.erb
  14. +2 −2 app/views/admin/text_asset/remove.html.erb
  15. +30 −8 db/migrate/002_add_dependencies_to_text_assets.rb
  16. +3 −3 lib/extended_page_tags.rb
  17. +134 −63 spec/controllers/admin/{css_and_js_controllers_common_spec.rb → text_asset_controller_spec.rb}
  18. +165 −98 spec/controllers/text_asset_site_controller_spec.rb
  19. +3 −0  spec/lib/config_spec.rb
  20. +0 −99 spec/models/extended_page_tags_spec.rb
  21. +157 −0 spec/models/stylesheet_and_javascript_dependency_spec.rb
  22. +11 −2 spec/models/{css_asset_and_js_asset_common_spec.rb → stylesheet_and_javascript_spec.rb}
  23. +186 −0 spec/models/stylesheet_and_javascript_tags_spec.rb
  24. +15 −9 spec/models/text_asset_response_cache_spec.rb
  25. +0 −45 spec/models/text_asset_tags_spec.rb
  26. +7 −4 spec/models/user_action_observer_spec.rb
  27. +5 −6 spec/scenarios/javascripts_scenario.rb
  28. +5 −6 spec/scenarios/stylesheets_scenario.rb
  29. +13 −7 styles_n_scripts_extension.rb
View
48 CHANGELOG
@@ -0,0 +1,48 @@
+v0.3 Big new features plus code refactoring and a bug fix. First, the new
+ fetures include:
+
+ * Added <r:stylesheet> and <r:javascript> tags for use in pages. This
+ lets you inject your stylesheet or javascript code into your Page
+ much like using a snippet or just render the URL to the referenced
+ CSS or JS file (see the readme for more on this).
+
+ * Added <r:stylesheet> and <r:javascript> tags for use in other
+ Stylesheets or Javascripts. This lets you maintain mulitple files
+ yet just serve one master-stylesheet or master-javascript to your
+ end users (cut down on server requests)
+
+ Along with this, I did a fancy trick with tracking dependencies so
+ that the cache knows when to update (add, edit, or remove a file
+ referenced by a <r:stylesheet> or <r:javascript> tag and the
+ dependant file's effectively_updated_at time changes to reflect it).
+
+
+v0.2.2 Bugfix - I added the UserActionObserver behavior and accompanying specs
+ to the Stylesheet and Javascript models
+
+v0.2.1 Bugfix - I corrected the ActionController::InvalidAuthenticityToken that
+ comes from my not using the form helpers in my templates. I also weakly
+ spec-ed it.
+
+ NOTE: This was only a partial fix. I corrected the edit.html.erb
+ template but not the remove.html.erb one. See v0.3 for the completely
+ fixed version.
+
+
+v0.2 Initial public release. Complete rebuild of the extension to work with
+ Rails 2.0 and Radiant 0.6.6. Added *lots* of specs and focused on
+ making this thing robust.
+
+ Basic functionality included:
+
+ * Stores stylesheet and javascript files
+
+ * Caches them with their own, improved response cache (improved in
+ that it handles this file type better)
+
+ * Configuration management utility to allow extension users to adjust
+ default settings
+
+
+v0.1 Private release to John and Sean as a proof-of-concept (initially called
+ the Files extension).
View
54 README
@@ -1,13 +1,46 @@
= Styles 'n Scripts Extension
-The Styles 'n Scripts extension is a proof-of-concept suggested by John Long
+The Styles 'n Scripts extension was an extension requested by John Long
as a means of separating javascripts & stylesheets from other site content
stored in pages.
-Even though I call it "proof-of-concept," it's already been through one
-iteration with John and is pretty thoroughly tested so don't think that it's not
-usable yet. (The versioning is more to denote the other features that I still
-want to put in it).
+
+
+CONTENTS
+========
+In this README you'll find:
+ 1. Usage
+ 2. Why Change Things?
+ 3. Installation
+ 4. To Do
+
+
+
+USAGE
+=====
+Using this extension is rather painless. If you can use the rest of Radiant,
+these additions will seem obvious. There are a couple of things to take note
+of, however.
+
+ * The CSS and JS tabs are where you create, edit, and delete stylesheets and
+ javascripts. But you need Administrator or Developer permissions to see
+ these tabs.
+
+ * If you want to reference or otherwise use your script or stylesheet in one
+ of your pages, there are <r:stylesheet> and <r:javascript> tags. These tags
+ can be used to inject your CSS or JS code into the page or just render a
+ link to the file itself. (See the 'available tags' link on the Page edit
+ form to learn more about options for these tags).
+
+ * If you want to cut down on server requests for CSS and JS files, you can
+ also create a master CSS or JS file for each and use the appropriate
+ <r:stylesheet> or <r:javascript> tags to incorporate other files of the same
+ type. Viola, now Radiant offers asset packaging just like Rails!
+
+
+That's it. Everything else is either too obvious to bother with here or
+automagical and too top secret to disclose.
+
WHY CHANGE THINGS?
@@ -33,7 +66,7 @@ There are a number of interesting benefits gained by this approach:
stylesheet page (I mean, do javascripts really need a layout?).
* Makes it easy to include search functionality across your pages without
- terms like "background" turning up all your stylesheets.
+ terms like "background" yielding all your stylesheets.
* Declutter the pages tree view so that it truly only shows what your clients
see -- the things they'd aim their browser at.
@@ -47,6 +80,7 @@ There are a number of interesting benefits gained by this approach:
flash, and the like.
+
INSTALLATION
============
1. Copy this extension into your existing Radiant project (place it at:
@@ -72,6 +106,7 @@ INSTALLATION
file.
+
TO-DO
=====
Create a way for users to upload stylesheets and javascripts (maybe even upload
@@ -81,13 +116,6 @@ Improve caching. I'd like to see a scenario where files are cached-on-save.
Rails page caching might be a great choice. (Maintaining the cache -- sweepers,
etc. -- wouldn't be all that hard for these items.).
-Create <r:stylesheet> (or maybe <r:text_asset>) tag to let users reference
-stylesheets from pages, snippets, and layouts. Is this really helpful?
-
-Create a <r:include_stylesheet> tag for stylesheets and one for scripts (similar
-to the <r:content> tag for pages) to merge stylesheets/scripts into one big file
-to reduce the number of requests and speed serving.
-
Add file minification to strip out comments and whitespace (and maybe obfuscate)
javascripts and stylesheets. (I already have this started).
View
8 app/controllers/admin/css_controller.rb
@@ -1,8 +0,0 @@
-class Admin::CssController < Admin::TextAssetController
- model_class Stylesheet
-
- only_allow_access_to :index, :new, :edit, :remove,
- :when => [:developer, :admin],
- :denied_url => { :controller => 'page', :action => 'index' },
- :denied_message => 'You must have developer or administrator privileges to perform this action.'
-end
View
8 app/controllers/admin/js_controller.rb
@@ -1,8 +0,0 @@
-class Admin::JsController < Admin::TextAssetController
- model_class Javascript
-
- only_allow_access_to :index, :new, :edit, :remove,
- :when => [:developer, :admin],
- :denied_url => { :controller => 'page', :action => 'index' },
- :denied_message => 'You must have developer or administrator privileges to perform this action.'
-end
View
30 app/controllers/admin/text_asset_controller.rb
@@ -1,5 +1,13 @@
class Admin::TextAssetController < Admin::AbstractModelController
+ only_allow_access_to :index, :new, :edit, :remove,
+ :when => [:developer, :admin],
+ :denied_url => { :controller => 'page', :action => 'index' },
+ :denied_message => 'You must have developer or administrator privileges to perform this action.'
+
+ before_filter :set_model
+
+
def initialize()
super
# overwrite @cache from super with our special cache
@@ -11,22 +19,20 @@ def index
# we CANNOT use the plural 'self.models' as this creates a @stylesheets
# instance variable which conflictss with Radiants admin @stylesheets
@text_assets = self.model = model_class.find(:all)
- # force child controllers to render template in the admin/text_asset directory
- render :template => "admin/text_asset/index", :object => @model_name = model_name
+ @model_name = model_name
end
def new
@text_asset = self.model = model_class.new
# force child controllers to render template in the admin/text_asset directory
- render :template => "admin/text_asset/edit" if handle_new_or_edit_post
+ render :action => :edit if handle_new_or_edit_post
end
def edit
@text_asset = self.model = model_class.find_by_id(params[:id])
- # force child controllers to render template in the admin/text_asset directory
- render :template => "admin/text_asset/edit" if handle_new_or_edit_post
+ handle_new_or_edit_post
end
@@ -37,10 +43,18 @@ def remove
announce_removed
clear_model_cache # <-- Added this line to clear cache on remove
redirect_to model_index_url
- else
- # force child controllers to render template in the admin/text_asset directory
- render :template => "admin/text_asset/remove"
end
end
+
+ private
+
+ # since the model name comes from the params, the model_class cannot
+ # be set until after initialization (seems like params are only available
+ # to the action methods). So we'll process 'em as part of a before_filter
+ def set_model
+ self.class.model_class params[:asset_type].camelize.constantize
+ end
+
+
end
View
6 app/controllers/text_asset_site_controller.rb
@@ -19,7 +19,7 @@ def show_text_asset
@text_asset_cache.update_response(url, response, request)
@performed_render = true
else
- show_uncached_text_asset(filename, params[:asset_class], url)
+ show_uncached_text_asset(filename, params[:asset_type], url)
end
end
@@ -39,10 +39,10 @@ def show_uncached_text_asset(filename, asset_class, url)
response.headers['Content-Type'] = mime_type
# set the last modified date based on updated_at time for the asset
# we can do this as long as there is no dynamic content in the assets
- response.headers['Last-Modified'] = @text_asset.updated_at
+ response.headers['Last-Modified'] = @text_asset.effectively_updated_at
response.body = @text_asset.render
- # for text_assets, we cache no matter what (there's no status setting for them)
+ # for text_assets, we cache no matter what (no 'status' setting for them)
text_asset_cache.cache_response(url, response) if request.get?
@performed_render = true
else
View
19 app/models/javascript.rb
@@ -1,18 +1 @@
-class Javascript < TextAsset
-
- class TagError < StandardError; end
-
- tag('javascript') do |tag|
- if name = tag.attr['name']
- self.dependencies << tag.attr['name'].strip
- if javascript = Javascript.find_by_filename(tag.attr['name'].strip)
- javascript.render
- else
- raise TagError.new("javascript not found")
- end
- else
- raise TagError.new("`javascript' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
- end
- end
-
-end
+class Javascript < TextAsset; end
View
19 app/models/stylesheet.rb
@@ -1,18 +1 @@
-class Stylesheet < TextAsset
-
- class TagError < StandardError; end
-
- tag('stylesheet') do |tag|
- if name = tag.attr['name']
- self.dependencies << tag.attr['name'].strip
- if stylesheet = Stylesheet.find_by_filename(tag.attr['name'].strip)
- stylesheet.render
- else
- raise TagError.new("stylesheet not found")
- end
- else
- raise TagError.new("`stylesheet' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
- end
- end
-
-end
+class Stylesheet < TextAsset; end
View
77 app/models/text_asset.rb
@@ -2,10 +2,11 @@ class TextAsset < ActiveRecord::Base
set_inheritance_column :class_name
order_by 'filename'
-
+
# Associations
belongs_to :created_by, :class_name => 'User'
belongs_to :updated_by, :class_name => 'User'
+ has_one :dependencies, :class_name => 'TextAssetDependencies', :dependent => :destroy
validates_presence_of :filename, :message => 'required'
validates_length_of :filename, :maximum => 100, :message => '%d-character limit'
@@ -14,71 +15,63 @@ class TextAsset < ActiveRecord::Base
validates_format_of :filename, :with => %r{\A[-_.A-Za-z0-9]*\Z}, :message => 'invalid format'
include Radiant::Taggable
+ class TagError < StandardError; end
- serialize :dependencies, Array
- before_save :update_dependencies
- # for some reason setting dependencies to serialize as Array still lets this
- # value be nil. AR should do this step for me. Oh well.
def after_initialize
- self.dependencies = [] if dependencies.nil?
+ self.dependencies = TextAssetDependencies.new(:list => []) if new_record?
+ create_tag #adds the tag to the inherited class now that it has initialized
end
-# after_save :effectively_updated_at
def url
StylesNScripts::Config["#{self.class.to_s.underscore}_directory"] +
"/" + self.filename
end
-
- def update_dependencies
- self.dependencies = []
- parse(content, false)
- self.dependencies.uniq!
- end
-
def effectively_updated_at
- if self.dependencies.empty?
- self.updated_at
- else
- dependency_assets = self.class.find_all_by_filename(self.dependencies).compact
- dependencies_last_updated_at = dependency_assets.sort_by{|i| i[:updated_at]}.last.updated_at
- puts dependencies_last_updated_at.to_s
- end
+ dependencies.effectively_updated_at
end
-# def update_dependents_timestamps()
-# self.class.find(:all).each do |text_asset|
-# unless text_asset.dependencies.nil?
-# if text_asset.dependencies.include?(filename)
-# puts "updating #{text_asset.filename} to dependencies_updated_at: #{updated_at}"
-# text_asset.dependencies_updated_at = updated_at
-# TextAsset.record_timestamps = false
-# text_asset.save
-# TextAsset.record_timestamps = true
-# end
-# end
-# end
-# end
-
def render
text = self.content
text = parse(text)
end
+ def parse(text, show_errors = true)
+ unless @parser and @context
+ @context = TextAssetContext.new(self)
+ @parser = Radius::Parser.new(@context, :tag_prefix => 'r')
+ end
+ @context.show_errors = show_errors
+ @parser.parse(text)
+ end
+
+
private
- def parse(text, show_errors = true)
- unless @parser and @context
- @context = TextAssetContext.new(self)
- @parser = Radius::Parser.new(@context, :tag_prefix => 'r')
+ # Adds a tag named after the inheriting class name (so <r:javascript> or
+ # <r:stylesheet>. This method is kind of funky since we wanted to define
+ # the tag in only one place yet we don't have the inheriting class' name
+ # until after initialization.
+ def create_tag
+ self.class.class_eval do
+ tag(self.name.underscore) do |tag|
+ if name = tag.attr['name']
+ self.dependencies.list << tag.attr['name'].strip
+ if text_asset = self.class.find_by_filename(tag.attr['name'].strip)
+ text_asset.render
+ else
+ raise TagError.new("#{self.class.to_s.underscore} not found")
+ end
+ else
+ raise TagError.new("`#{self.class.to_s.underscore}' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
+ end
+ end
end
- @context.show_errors = show_errors
- @parser.parse(text)
end
-end
+end
View
6 app/models/text_asset_dependencies.rb
@@ -0,0 +1,6 @@
+class TextAssetDependencies < ActiveRecord::Base
+
+ belongs_to :text_asset
+ serialize :list, Array
+
+end
View
38 app/models/text_asset_observer.rb
@@ -0,0 +1,38 @@
+class TextAssetObserver < ActiveRecord::Observer
+ observe Stylesheet, Javascript
+
+ def after_save(model)
+ update_dependencies(model, model.updated_at)
+ update_other_dependants(model, model.updated_at)
+ end
+
+
+ def after_destroy(model)
+ update_other_dependants(model, Time.now)
+ end
+
+
+ def update_dependencies(model, time = nil)
+ # the parse operation populates the dependencies list (clear it first)
+ model.dependencies.list = []
+ model.parse(model.content, false)
+ model.dependencies.list.uniq!
+
+ # set the effectively_updated_at time too
+ model.dependencies.effectively_updated_at = time unless time.nil?
+
+ model.dependencies.save!
+ end
+
+
+ def update_other_dependants(model, time)
+ model.class.find(:all).each do |text_asset|
+ unless text_asset.filename == model.filename || text_asset.dependencies.list.empty?
+ if text_asset.dependencies.list.include?(model.filename)
+ text_asset.dependencies.update_attribute('effectively_updated_at', time)
+ end
+ end
+ end
+ end
+
+end
View
74 app/models/text_asset_tags.rb
@@ -1,74 +0,0 @@
-module TextAssetTags
-
- include Radiant::Taggable
- class TagError < StandardError; end
-
-# [ { :name => 'stylesheet',
-# :asset_class => Stylesheet,
-# :inline_tag => 'style',
-# :sample_file => 'my_file.css' },
-#
-# { :name => 'javascript',
-# :asset_class => Javascript,
-# :inline_tag => 'script',
-# :sample_file => 'my_file.js' }
-# ].each do |current_tag|
-# desc %{
-# Renders the content from or url for a #{current_tag[:name]} asset. The
-# @name@ attribute is required to identify the desired #{current_tag[:name]}.
-# file. Additionally, the @as@ attribute can be used to choose whether to
-# render the tag as one of the following:
-#
-# * @content@ - the content of the #{current_tag[:name]} is rendered (this
-# is the default).
-#
-# * @url@ - the url of the #{current_tag[:name]} file (relative to the
-# web root).
-#
-# * @inline@ - same as @content@ but wraps the content in an (X)HTML
-# &lt;#{current_tag[:inline_tag]}> element. By default, the @type@
-# attribute of the &lt;#{current_tag[:inline_tag]}> element will
-# automatically match the default #{current_tag[:name]} content-type
-# (but you can override this -- see Additional Options below).
-#
-# *Additional Options:*
-# When rendering &lt;r:#{current_tag[:name]} @as@="inline" />, any
-# additional attributes you provide will be passed on directly to the
-# &lt;#{current_tag[:inline_tag]}> element, like:
-# <pre><code><r:#{current_tag[:name]} name="#{current_tag[:sample_file]}" as="inline" id="my_id" type="text/custom" />
-# produces:
-# <#{current_tag[:inline_tag]} type="text/custom" id="my_id">
-# <!--
-# Your #{current_tag[:name]}'s content here...
-# -->
-# </#{current_tag[:inline_tag]}></code></pre>
-#
-# *Usage:*
-# <pre><code><r:#{current_tag[:name]} name="#{current_tag[:sample_file]}" [as="content | inline | url"] /></code></pre>
-# }
-# tag(current_tag[:name]) do |tag|
-# if name = tag.attr['name']
-# if text_asset = current_tag[:asset_class].find_by_filename(tag.attr['name'].strip)
-# case tag.attr['as']
-# when 'content', nil
-# text_asset.render
-# when 'url'
-# text_asset.url
-# when 'inline'
-# mime_type = tag.attr['type'] || StylesNScripts::Config["#{current_tag[:name]}_mime_type"]
-# optional_attribs = tag.attr.except('name', 'as', 'type').inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
-# optional_attribs = " #{optional_attribs}" unless optional_attribs.empty?
-# %{<#{current_tag[:inline_tag]} type="#{mime_type}"#{optional_attribs}>\n<!--\n#{text_asset.render}\n-->\n</#{current_tag[:inline_tag]}>}
-# end
-# else
-# raise TagError.new("#{current_tag[:name]} not found")
-# end
-# else
-# raise TagError.new("`#{current_tag[:name]}' tag must contain a `name' attribute.") unless tag.attr.has_key?('name')
-# end
-# end
-#
-# end
-#
-
-end
View
3  app/views/admin/text_asset/edit.html.erb
@@ -16,9 +16,6 @@
<label for="<%= model_name %>_content">Body</label>
<%= text_area(model_name, "content", :class => "textarea", :style => "width: 100%") %>
</p>
-
- <span class="clear">&nbsp;</span>
-
<%= updated_stamp @text_asset %>
</div>
View
4 app/views/admin/text_asset/remove.html.erb
@@ -12,6 +12,6 @@
</tbody>
</table>
-<form method="post" action="">
+<% form_tag do %>
<p class="buttons"><%= submit_tag("Delete #{proper_model_name}", :class => 'button') %> or <%= link_to('Cancel', send("#{model_name}_index_url") ) %></p>
-</form>
+<% end %>
View
38 db/migrate/002_add_dependencies_to_text_assets.rb
@@ -1,16 +1,38 @@
class AddDependenciesToTextAssets < ActiveRecord::Migration
def self.up
- add_column :text_assets, :dependencies, :string
- # iterate through Javascripts and Stylesheets and save! each to trigger
- # update_dependencies and save the results to the new dependencies field
- TextAsset.find(:all).each do |text_asset|
- text_asset.class.record_timestamps = false
- text_asset.save!
- text_asset.class.record_timestamps = true
+ create_table :text_asset_dependencies do |t|
+ t.integer :text_asset_id
+ t.string :list
+ t.datetime :effectively_updated_at
end
+
+ # Calculate values for new table. So, iterate over all TextAssets and...
+ TextAsset.find(:all).sort_by{|i| i.id}.each do |text_asset|
+ # Set the dependency list
+ text_asset.dependencies = TextAssetDependencies.new(:list => [])
+ TextAssetObserver.instance.update_dependencies(text_asset)
+
+ # get the actual dependency objects referenced by dependencies.list
+ dependencies = text_asset.class.find_all_by_filename(text_asset.dependencies.list).compact
+ unless dependencies.empty?
+
+ # find the most recently updated dependency
+ dependencies_updated_at = dependencies.sort_by{|i| i.updated_at}.last.updated_at
+
+ # figure out which is newer -> the asset or it's dependencies
+ if dependencies_updated_at > text_asset.updated_at
+ # Adjust the effectively_updated_at value (if different than set by updated_dependencies)
+ text_asset.dependencies.effectively_updated_at = dependencies_updated_at
+ text_asset.dependencies.save!
+ end
+ end
+ say "Completed dependency calculations for #{text_asset.url}"
+ end
+
end
+
def self.down
- remove_column :text_assets, :dependencies
+ drop_table :text_asset_dependencies
end
end
View
6 lib/extended_page_tags.rb
@@ -3,12 +3,12 @@ module ExtendedPageTags
class TagError < StandardError; end
[ { :name => 'stylesheet',
- :asset_class => Stylesheet,
+ :class => Stylesheet,
:inline_tag => 'style',
:sample_file => 'my_file.css' },
{ :name => 'javascript',
- :asset_class => Javascript,
+ :class => Javascript,
:inline_tag => 'script',
:sample_file => 'my_file.js' }
].each do |current_tag|
@@ -47,7 +47,7 @@ class TagError < StandardError; end
}
tag(current_tag[:name]) do |tag|
if name = tag.attr['name']
- if text_asset = current_tag[:asset_class].find_by_filename(tag.attr['name'].strip)
+ if text_asset = current_tag[:class].find_by_filename(tag.attr['name'].strip)
case tag.attr['as']
when 'content', nil
text_asset.render
View
197 ...min/css_and_js_controllers_common_spec.rb → ...llers/admin/text_asset_controller_spec.rb
@@ -1,22 +1,27 @@
+# These specs set the behavior of TextAssetController (which behaves as both a
+# StylesheetController and JavascriptController).
+#
+# Many of these specs are adaptations of the AbstractController Specs. Should
+# this exension become part of core, this spec could be greatly reduced. But
+# for now, I have decided not to assume any behavior on ActionController's part.
+#
+# Basicaly, if we requre that it must work a certain way, then it needs to be
+# confirmed. That way, if the core team sees fit to change the behavior of
+# AbstractController, we'll know our extension just broke.
+
require File.dirname(__FILE__) + '/../../spec_helper'
-[ { :controller => Admin::CssController,
- :controller_path => 'admin/text_asset',
- :model => Stylesheet,
+[ { :class => Stylesheet,
:name => 'stylesheet',
- :symbol => :stylesheet,
- :main_scenario => :main_css },
+ :symbol => :stylesheet },
- { :controller => Admin::JsController,
- :controller_path => 'admin/text_asset',
- :model => Javascript,
+ { :class => Javascript,
:name => 'javascript',
- :symbol => :javascript,
- :main_scenario => :main_js }
+ :symbol => :javascript }
].each do |current_asset|
- describe current_asset[:controller] do
+ describe Admin::TextAssetController, "(rendering #{current_asset[:name].pluralize}" do
integrate_views
@@ -42,12 +47,15 @@
end
- it "should have a model_class of #{current_asset[:name]}" do
- controller.class.model_class.should == current_asset[:model]
- end
-
[:index, :new, :edit, :remove].each do |action|
+
+ # moved this test into the actions as the controller inits this before action
+ it "should have a model_class of #{current_asset[:name]}" do
+ controller.class.model_class.should == current_asset[:class]
+ end
+
+
it "should require login to access the #{action} action" do
logout
lambda { get action }.should require_login
@@ -55,24 +63,32 @@
it "should allow access to developers" do
- lambda { get action, :id => text_asset_id(current_asset[:main_scenario]) }.should restrict_access(:allow => [users(:developer)])
+ lambda { get action, :id => text_asset_id('main'),
+ :asset_type => current_asset[:name]
+ }.should restrict_access(:allow => [users(:developer)])
end
it "should allow access to admins" do
- lambda { get action, :id => text_asset_id(current_asset[:main_scenario]) }.should restrict_access(:allow => [users(:admin)])
+ lambda { get action, :id => text_asset_id('main'),
+ :asset_type => current_asset[:name]
+ }.should restrict_access(:allow => [users(:admin)])
end
it "should deny non-developers and non-admins" do
- lambda { get action, :id => text_asset_id(current_asset[:main_scenario]) }.should restrict_access(:deny => [users(:non_admin), users(:existing)])
+ lambda { get action }.should restrict_access(:deny => [users(:non_admin), users(:existing)])
end
+
end
+
+
describe "index action" do
+
before :each do
- get :index
+ get :index, :asset_type => current_asset[:name]
end
@@ -82,21 +98,24 @@
it "should render the index template" do
- response.should render_template("#{current_asset[:controller_path]}/index")
+ response.should render_template("admin/text_asset/index")
end
it "should load an array of models" do
assigns[:text_assets].should be_kind_of(Array)
- assigns[:text_assets].all? { |i| i.kind_of?(current_asset[:model]) }.should be_true
+ assigns[:text_assets].all? { |i| i.kind_of?(current_asset[:class]) }.should be_true
end
+
end
+
+
describe "new action" do
describe "via GET" do
before :each do
- get :new
+ get :new, :asset_type => current_asset[:name]
end
@@ -106,24 +125,29 @@
it "should render the edit template" do
- response.should render_template("#{current_asset[:controller_path]}/edit")
+ response.should render_template("admin/text_asset/edit")
end
it "should load a new #{current_asset[:name]}" do
assigns[:text_asset].should_not be_nil
- assigns[:text_asset].should be_kind_of(current_asset[:model])
+ assigns[:text_asset].should be_kind_of(current_asset[:class])
assigns[:text_asset].should be_new_record
end
end
+
+
describe "via POST" do
+
describe "when the #{current_asset[:name]} validates" do
+
before :each do
post :new,
- current_asset[:symbol] => send("#{current_asset[:name]}_params")
- @text_asset = current_asset[:model].find_by_filename('Test')
+ current_asset[:symbol] => send("#{current_asset[:name]}_params"),
+ :asset_type => current_asset[:name]
+ @text_asset = current_asset[:class].find_by_filename('Test')
end
@@ -142,20 +166,25 @@
flash[:notice].should_not be_nil
flash[:notice].should =~ /saved/
end
+
end
+
+
describe "when the #{current_asset[:name]} fails validation" do
+
before :each do
post :new,
current_asset[:symbol] => send("#{current_asset[:name]}_params",
- :filename => nil)
- @text_asset = current_asset[:model].find_by_filename('Test')
+ :filename => nil),
+ :asset_type => current_asset[:name]
+ @text_asset = current_asset[:class].find_by_filename('Test')
end
it "should render the edit template" do
- response.should render_template("#{current_asset[:controller_path]}/edit")
+ response.should render_template("admin/text_asset/edit")
end
@@ -168,16 +197,21 @@
flash[:error].should_not be_nil
flash[:error].should =~ /error/
end
+
end
+
+
describe "when 'Save and Continue Editing' was clicked" do
+
before :each do
post :new,
current_asset[:symbol] => send("#{current_asset[:name]}_params",
:filename => 'Test'),
- :continue => 'Save and Continue Editing'
- @text_asset = current_asset[:model].find_by_filename('Test')
+ :continue => 'Save and Continue Editing',
+ :asset_type => current_asset[:name]
+ @text_asset = current_asset[:class].find_by_filename('Test')
end
@@ -192,10 +226,16 @@
end
+
+
describe "edit action" do
+
describe "via GET" do
+
before :each do
- get :edit, :id => text_asset_id(current_asset[:main_scenario])
+ get :edit,
+ :id => text_asset_id('main'),
+ :asset_type => current_asset[:name]
end
@@ -205,24 +245,29 @@
it "should render the edit template" do
- response.should render_template("#{current_asset[:controller_path]}/edit")
+ response.should render_template("admin/text_asset/edit")
end
it "should load the existing #{current_asset[:name]}" do
assigns[:text_asset].should_not be_nil
- assigns[:text_asset].should be_kind_of(current_asset[:model])
- assigns[:text_asset].should == text_assets(current_asset[:main_scenario])
+ assigns[:text_asset].should be_kind_of(current_asset[:class])
+ assigns[:text_asset].should == text_assets('main')
end
end
+
+
describe "via POST" do
+
describe "when the #{current_asset[:name]} validates" do
+
before :each do
post :edit,
- :id => text_asset_id(current_asset[:main_scenario]),
- current_asset[:symbol] => send("#{current_asset[:name]}_params")
+ :id => text_asset_id('main'),
+ current_asset[:symbol] => send("#{current_asset[:name]}_params"),
+ :asset_type => current_asset[:name]
end
@@ -246,20 +291,25 @@
it "should clear the TextAssetResponseCache" do
@cache.should be_cleared
end
+
end
+
+
describe "when the #{current_asset[:name]} fails validation" do
+
before :each do
post :edit,
- :id => text_asset_id(current_asset[:main_scenario]),
+ :id => text_asset_id('main'),
current_asset[:symbol] => send("#{current_asset[:name]}_params",
- :filename => nil)
+ :filename => nil),
+ :asset_type => current_asset[:name]
end
it "should render the edit template" do
- response.should render_template("#{current_asset[:controller_path]}/edit")
+ response.should render_template("admin/text_asset/edit")
end
@@ -277,22 +327,27 @@
it "should not clear the TextAssetResponseCache" do
@cache.should_not be_cleared
end
+
end
+
+
describe "when 'Save and Continue Editing' was clicked" do
+
before :each do
post :edit,
- :id => text_asset_id(current_asset[:main_scenario]),
+ :id => text_asset_id('main'),
current_asset[:symbol] => send("#{current_asset[:name]}_params"),
- :continue => 'Save and Continue Editing'
+ :continue => 'Save and Continue Editing',
+ :asset_type => current_asset[:name]
end
it "should redirect to the edit action" do
response.should be_redirect
response.should redirect_to(send("#{current_asset[:name]}_edit_url",
- :id => text_asset_id(current_asset[:main_scenario])))
+ :id => text_asset_id('main')))
end
@@ -301,62 +356,78 @@
end
end
+
end
end
+
+
describe "remove action" do
+
describe "via GET" do
+
before :each do
- get :remove, :id => text_asset_id(current_asset[:main_scenario])
+ get :remove,
+ :id => text_asset_id('main'),
+ :asset_type => current_asset[:name]
end
-
+
+
it "should be successful" do
response.should be_success
end
-
+
+
it "should render the remove template" do
- response.should render_template("#{current_asset[:controller_path]}/remove")
+ response.should render_template("admin/text_asset/remove")
end
-
+
+
it "should load the specified #{current_asset[:name]}" do
- assigns[:text_asset].should == text_assets(current_asset[:main_scenario])
+ assigns[:text_asset].should == text_assets('main')
end
+
end
-
+
+
+
+
describe "via POST" do
+
before :each do
- post :remove, :id => text_asset_id(current_asset[:main_scenario])
+ post :remove,
+ :id => text_asset_id('main'),
+ :asset_type => current_asset[:name]
end
-
+
+
it "should destroy the #{current_asset[:name]}" do
- current_asset[:model].find_by_filename(main_scenario_filename(current_asset[:main_scenario])).should be_nil
+ current_asset[:class].find_by_filename('main').should be_nil
end
-
+
+
it "should redirect to the index action" do
response.should be_redirect
response.should redirect_to(send("#{current_asset[:name]}_index_url"))
end
-
+
+
it "should add a flash notice" do
flash[:notice].should_not be_nil
flash[:notice].should =~ /deleted/
end
+
it "should clear the TextAssetResponseCache" do
@cache.should be_cleared
end
+
end
+
end
end
-end
-
-private
-
- # reverse calculates the filename created by the scenario (main_js -> main.js)
- def main_scenario_filename(main_scenario_symbolic_name)
- main_scenario_symbolic_name.to_s.gsub('_', '.')
- end
+end
View
263 spec/controllers/text_asset_site_controller_spec.rb
@@ -1,6 +1,12 @@
+# This specifies the behavior of the TextAssetSiteController which behaves much
+# like Radiant's SiteController except that it serves up the stylesheets and
+# javascripts to the public. Many of these specs confirm SiteController's
+# behavior from which TextAssetSiteController inherits.
+#
+
require File.dirname(__FILE__) + '/../spec_helper'
-describe TextAssetSiteController, "routes text_asset requests" do
+describe TextAssetSiteController do
integrate_views
@@ -19,184 +25,245 @@
controller.text_asset_cache.clear
end
+
it "should be a SiteController" do
controller.should be_kind_of(SiteController)
end
+
it "should offer a #text_asset_cache method to access TextAssetResponseCache" do
controller.text_asset_cache.should be_kind_of(TextAssetResponseCache)
end
- [ { :asset_class => 'stylesheet',
- :directory_key => 'stylesheet_directory',
- :directory_value => 'css',
- :main_scenario => 'main.css' },
- { :asset_class => 'javascript',
- :directory_key => 'javascript_directory',
- :directory_value => 'js',
- :main_scenario => 'main.js' }
+
+
+ [ { :class => Stylesheet,
+ :name => 'stylesheet',
+ :default_directory => 'css' },
+
+ { :class => Javascript,
+ :name => 'javascript',
+ :default_directory => 'js' }
].each do |current_asset|
- describe current_asset[:asset_class] do
+ describe "#{current_asset[:name]} request rendering" do
- it "should route urls based on a simple, customized setting for :#{current_asset[:directory_key]}" do
+ it "should route urls based on a customized setting for: #{current_asset[:name]}_directory" do
# change the css_ or js_direectory and recreate routes to use them
- StylesNScripts::Config[current_asset[:directory_key]] = 'foo'
+ StylesNScripts::Config[current_asset[:name] + '_directory'] = 'foo'
ActionController::Routing::Routes.reload!
- params_from(:get, "/foo/#{current_asset[:main_scenario]}").should ==
+ params_from(:get, "/foo/main").should ==
{ :controller => 'text_asset_site',
:action => 'show_text_asset',
- :filename => [current_asset[:main_scenario]],
+ :filename => ['main'],
:directory => 'foo',
- :asset_class => current_asset[:asset_class] }
+ :asset_type => current_asset[:name] }
end
- it "should route urls based on a multi-level, customized setting for :#{current_asset[:directory_key]}" do
+
+ it "should route urls based on a multi-level, customized setting for: #{current_asset[:name]}_directory" do
# change the css_ or js_direectory and recreate routes to use them
- StylesNScripts::Config[current_asset[:directory_key]] = 'foo/bar/baz'
+ StylesNScripts::Config[current_asset[:name] + '_directory'] = 'foo/bar/baz'
ActionController::Routing::Routes.reload!
- params_from(:get, "/foo/bar/baz/#{current_asset[:main_scenario]}").should ==
+ params_from(:get, "/foo/bar/baz/main").should ==
{ :controller => 'text_asset_site',
:action => 'show_text_asset',
- :filename => [current_asset[:main_scenario]],
+ :filename => ['main'],
:directory => 'foo/bar/baz',
- :asset_class => current_asset[:asset_class] }
+ :asset_type => current_asset[:name] }
end
- it "should route urls based on the default when no custom settings for :#{current_asset[:directory_key]}" do
- params_from(:get, "/#{current_asset[:directory_value]}/#{current_asset[:main_scenario]}").should ==
+
+ it "should route urls based on the default if #{current_asset[:name]}_directory isn't customized" do
+ params_from(:get, "/#{current_asset[:default_directory]}/main").should ==
{ :controller => 'text_asset_site',
:action => 'show_text_asset',
- :filename => [current_asset[:main_scenario]],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class] }
+ :filename => ['main'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name] }
end
+
it "should find and render an existing asset" do
get :show_text_asset,
- :filename => [current_asset[:main_scenario]],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
+ :filename => ['main'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
# For SOME reason, the response.header does not include a 'status' key so it is
# not possible to check for success.
-# puts response.headers.keys.sort.inspect
# response.should be_success
- response.body.should == "Main #{current_asset[:asset_class]} content"
+ response.body.should == "Main #{current_asset[:name]} content"
end
- it "should render an existing asset with a Last-Modified header matching the file's updated_at date/time" do
- get :show_text_asset,
- :filename => [current_asset[:main_scenario]],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
- response.headers['Last-Modified'].should ==
- text_assets(current_asset[:main_scenario].symbolize).updated_at
- end
it "should find and render an existing asset on the default dev site" do
request.host = "dev.site.com"
get :show_text_asset,
- :filename => [current_asset[:main_scenario]],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
+ :filename => ['main'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
# For SOME reason, the response.header does not include a 'status' key so it is
# not possible to check for success.
# response.should be_success
- response.body.should == "Main #{current_asset[:asset_class]} content"
+ response.body.should == "Main #{current_asset[:name]} content"
end
+
it "should render a 404 page for a non-existing asset" do
get :show_text_asset,
:filename => ['non-existent.file'],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
response.should render_template('site/not_found')
response.headers["Status"].should == "404 Not Found"
end
- it "should render a 404 page for #{current_asset[:directory_key]} (/#{current_asset[:directory_value]}/)" do
+
+ it "should render a 404 page for #{current_asset[:name]}_directory (/#{current_asset[:default_directory]}/)" do
get :show_text_asset,
:filename => [],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
response.should render_template('site/not_found')
response.headers["Status"].should == "404 Not Found"
end
-
- it "should render a 404 page if url includes a deeper path than :#{current_asset[:directory_key]}" do
+
+
+ it "should render a 404 page if url includes a deeper path than :#{current_asset[:name]}_directory" do
get :show_text_asset,
- :filename => ['bogus', 'extra', 'path', 'segments', [current_asset[:main_scenario]]],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
+ :filename => ['bogus', 'extra', 'path', 'segments', 'main'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
response.headers["Status"].should == "404 Not Found"
response.should render_template('site/not_found')
end
+
+
# This is sort of dumb. Really, users should not go anywhere near creating
# a page with the same name as the css directory. If they do, here's what
# should happen.
- it "should render a page that is competing with :#{current_asset[:directory_key]}" do
- create_page current_asset[:directory_value]
- get :show_text_asset,
- :filename => [],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
- response.should be_success
- response.body.should == "#{current_asset[:directory_value]} body."
- end
+ describe "where page urls conflict with text asset urls" do
- # Nor should they be putting child pages inside that competing page...
- it "should render a page inside :#{current_asset[:directory_key]} (immediate child of /#{current_asset[:directory_value]}/)" do
- create_page current_asset[:directory_value] do
- create_page 'page-inside'
+ it "should render a page that is competing with :#{current_asset[:name]}_directory (the directory)" do
+ create_page current_asset[:default_directory]
+ get :show_text_asset,
+ :filename => [],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
+ response.should be_success
+ response.body.should == "#{current_asset[:default_directory]} body."
end
- get :show_text_asset,
- :filename => ['page-inside'],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
-# WHY DOES THIS NEXT LINE FAIL????? AAAARRRRGGGHHH!!
- response.should be_success
- response.body.should == 'page-inside body.'
- end
- # Or grandchildren -- but we'll test for 'em anyway.
- it "should render a page inside :#{current_asset[:directory_key]} (grandchild of /#{current_asset[:directory_value]}/)" do
- create_page current_asset[:directory_value] do
- create_page 'page-inside' do
- create_page 'another-page'
+
+ it "should render a page inside: #{current_asset[:name]}_directory (immediate child of /#{current_asset[:default_directory]}/)" do
+ create_page current_asset[:default_directory] do
+ create_page 'page-inside'
end
+ get :show_text_asset,
+ :filename => ['page-inside'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
+
+ response.should be_success
+ response.body.should == 'page-inside body.'
end
- get :show_text_asset,
- :filename => ['page-inside', 'another-page'],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
- response.should be_success
- response.body.should == 'another-page body.'
- end
-
- # But, if there's a text_asset and page -- both with the same url, then
- # the asset wins.
- it "should render the text_asset and not the page if both have the same url" do
- create_page current_asset[:directory_value] do
- create_page 'abc.123'
+
+ it "should render a page inside: #{current_asset[:name]}_directory (grandchild of /#{current_asset[:default_directory]}/)" do
+ create_page current_asset[:default_directory] do
+ create_page 'page-inside' do
+ create_page 'another-page'
+ end
+ end
+ get :show_text_asset,
+ :filename => ['page-inside', 'another-page'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
+ response.should be_success
+ response.body.should == 'another-page body.'
end
- send("create_#{current_asset[:asset_class]}", 'abc.123')
- get :show_text_asset,
- :filename => ['abc.123'],
- :directory => current_asset[:directory_value],
- :asset_class => current_asset[:asset_class]
-# response.should be_success
- response.body.should == 'dummy content'
+ it "should render the #{current_asset[:name]} and not the page if both have the same url" do
+ create_page current_asset[:default_directory] do
+ create_page 'abc.123'
+ end
+ send("create_#{current_asset[:name]}", 'abc.123')
+ get :show_text_asset,
+ :filename => ['abc.123'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
+# For SOME reason, the response.header does not include a 'status' key so it is
+# not possible to check for success.
+# response.should be_success
+ response.body.should == "#{current_asset[:name]} content for abc.123"
+ end
+
end
+
+
+ describe "with regard to Last-Modified date" do
+
+ before :each do
+ @dependant = current_asset[:class].new(:filename => 'dependant')
+ @dependency = current_asset[:class].new(:filename => 'dependency')
+ save_asset_at(@dependant, 1990)
+ end
+
+
+ it "should reflect the #{current_asset[:name]}'s updated_at date/time if the file has no dependencies" do
+ get :show_text_asset,
+ :filename => ['dependant'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
+ response.headers['Last-Modified'].should == Time.local(1990)
+ end
+
+
+ it "should reflect the #{current_asset[:name]}'s updated_at date/time if its dependencies are older" do
+ @dependant.content = %{<r:#{current_asset[:name]} name="dependency" />}
+ save_asset_at(@dependency, 1991)
+ save_asset_at(@dependant, 1992)
+
+ get :show_text_asset,
+ :filename => ['dependant'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
+ response.headers['Last-Modified'].should == Time.local(1992)
+ end
+
+
+ it "should reflect the #{current_asset[:name]}'s dependency's updated_at date/time if its dependencies are newer" do
+ @dependant.content = %{<r:#{current_asset[:name]} name="dependency" />}
+ save_asset_at(@dependant, 1993)
+ save_asset_at(@dependency, 1994)
+
+ get :show_text_asset,
+ :filename => ['dependant'],
+ :directory => current_asset[:default_directory],
+ :asset_type => current_asset[:name]
+ response.headers['Last-Modified'].should == Time.local(1994)
+ end
+
+ end
+
end
+
end
+
end
+
+
+private
+
+ def save_asset_at(text_asset, year)
+ Time.stub!(:now).and_return(Time.local(year))
+ text_asset.save!
+ end
View
3  spec/lib/config_spec.rb
@@ -1,3 +1,6 @@
+# This specifies the behavior for the configuration module that allows extension
+# users to modify the default settings used by this extension.
+
require File.dirname(__FILE__) + '/../spec_helper'
describe StylesNScripts::Config do
View
99 spec/models/extended_page_tags_spec.rb
@@ -1,99 +0,0 @@
-require File.dirname(__FILE__) + '/../spec_helper'
-
-[ {:name => 'stylesheet',
- :main_scenario => 'main.css',
- :default_mime_type => 'text/css',
- :inline_element => 'style' },
-
- {:name => 'javascript',
- :main_scenario => 'main.js',
- :default_mime_type => 'text/javascript',
- :inline_element => 'script' }
-].each do |current_tag|
- describe Page, "with <r:#{current_tag[:name]}> tags" do
- scenario :pages, :javascripts, :stylesheets
-
- before :each do
- create_page "text_asset_tags"
- @page = pages(:text_asset_tags)
- end
-
-
- it 'should render an error if no filename provided' do
- @page.should render(%{<r:#{current_tag[:name]} />}).with_error("`#{current_tag[:name]}' tag must contain a `name' attribute.")
- end
-
-
- it 'should render an error with an invalid filename provided' do
- @page.should render(%{<r:#{current_tag[:name]} name="bogus asset name" />}).with_error("#{current_tag[:name]} not found")
- end
-
-
- it "should render the content of the #{current_tag[:name]} when the 'as' attribute is not provided" do
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" />}).as("Main #{current_tag[:name]} content")
- end
-
-
- it "should render the content of the #{current_tag[:name]} when the 'as' attribute is set to 'content'" do
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" as="content" />}).as("Main #{current_tag[:name]} content")
- end
-
-
- it "should render the url of the #{current_tag[:name]} when the 'as' attribute is set to 'url'" do
- StylesNScripts::Config["#{current_tag[:name]}_directory"] = 'foo/bar/baz'
- ActionController::Routing::Routes.reload!
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" as="url" />}).as("foo/bar/baz/#{current_tag[:main_scenario]}")
-
- StylesNScripts::Config.restore_defaults
- ActionController::Routing::Routes.reload!
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" as="url" />}).as("#{StylesNScripts::Config["#{current_tag[:name]}_directory"]}/#{current_tag[:main_scenario]}")
- end
-
-
- it "should render a <#{current_tag[:inline_element]}> element containing the content of the #{current_tag[:name]} and with the type attribute matching the #{current_tag[:name]}_mime_type setting when the 'as' attribute is set to 'inline'" do
- StylesNScripts::Config["#{current_tag[:name]}_mime_type"] = 'bologna'
- ActionController::Routing::Routes.reload!
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" as="inline" />}).as(
-%{<#{current_tag[:inline_element]} type="bologna">
-<!--
-Main #{current_tag[:name]} content
--->
-</#{current_tag[:inline_element]}>}
- )
-
- StylesNScripts::Config.restore_defaults
- ActionController::Routing::Routes.reload!
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" as="inline" />}).as(
-%{<#{current_tag[:inline_element]} type="#{current_tag[:default_mime_type]}">
-<!--
-Main #{current_tag[:name]} content
--->
-</#{current_tag[:inline_element]}>}
- )
-
- end
-
-
- it %{should override the default <#{current_tag[:inline_element]}> element's 'type' attribute if one is defined in the <r:#{current_tag[:name]}> tag} do
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" as="inline" type="oscar" />}).as(
-%{<#{current_tag[:inline_element]} type="oscar">
-<!--
-Main #{current_tag[:name]} content
--->
-</#{current_tag[:inline_element]}>}
- )
- end
-
-
- it %{should pass additional attributes into the <#{current_tag[:inline_element]}> element (and downcase the attribute name)} do
- @page.should render(%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" as="inline" another="mayer" ATTRIB="WEINER" />}).as(
-%{<#{current_tag[:inline_element]} type="#{current_tag[:default_mime_type]}" another="mayer" attrib="WEINER">
-<!--
-Main #{current_tag[:name]} content
--->
-</#{current_tag[:inline_element]}>}
- )
- end
-
- end
-end
View
157 spec/models/stylesheet_and_javascript_dependency_spec.rb
@@ -0,0 +1,157 @@
+# This specifies the dependency behaviors of Javascripts and Stylesheets (added
+# in version 0.3). These behaviors allow the text assets to track dependencies
+# created by <r:stylesheet> or <r:javascript> tags when used in a text asset
+# context and maintain an effectively_updated_at date to ensure proper caching
+# of stylesheets and javascripts.
+
+require File.dirname(__FILE__) + '/../spec_helper'
+
+[ { :class => Stylesheet,
+ :name => 'stylesheet',
+ :default_mime_type => 'text/css',
+ :inline_element => 'style' },
+
+ { :class => Javascript,
+ :name => 'javascript',
+ :default_mime_type => 'text/javascript',
+ :inline_element => 'script' }
+
+].each do |current_tag|
+
+ describe "During save, #{current_tag[:name].pluralize} containing <r:#{current_tag[:name]}> tags" do
+
+ before :each do
+ @text_asset = current_tag[:class].new(:filename => 'dependant')
+ @text_asset.content = %{<r:#{current_tag[:name]} name="main" />}
+ end
+
+
+ it 'should log one dependency if only one tag' do
+ @text_asset.save!
+ @text_asset.dependencies.list.should == ["main"]
+ end
+
+
+ it 'should log a dependency even if the reference file does not exist' do
+ @text_asset.content = %{<r:#{current_tag[:name]} name="a_nonexistent_file" />}
+ @text_asset.save!
+ @text_asset.dependencies.list.should == ["a_nonexistent_file"]
+ end
+
+
+ it 'should log only one dependency if mulitiple tags all reference the the same file' do
+ @text_asset.content << %{<r:#{current_tag[:name]} name="main" />}
+ @text_asset.save!
+ @text_asset.dependencies.list.should == ["main"]
+ end
+
+
+ it 'should log multiple dependencies if multiple files are referenced' do
+ @text_asset.content << %{<r:#{current_tag[:name]} name="another_file" />}
+ @text_asset.save!
+ @text_asset.dependencies.list.sort.should == ["another_file", "main"]
+ end
+
+
+ it 'should log only each dependency only once' do
+ @text_asset.content << %{<r:#{current_tag[:name]} name="another_file" />}
+ @text_asset.content << %{<r:#{current_tag[:name]} name="main" />}
+ @text_asset.save!
+ @text_asset.dependencies.list.sort.should == ["another_file", "main"]
+ end
+
+ end
+
+
+
+
+ describe "A saved #{current_tag[:name]}'s effectively_updated_at method" do
+ before :each do
+ @dependant = current_tag[:class].new(:filename => 'dependant')
+ save_asset_at(@dependant, 1990)
+ end
+
+
+ it "should reflect its own creation date/time (created_at) if the file hasn't been updated and has no dependencies" do
+ @dependant.dependencies.effectively_updated_at.should == Time.local(1990)
+ end
+
+
+ it "should reflect its own change date/time (updated_at) if the file has no dependencies" do
+ save_asset_at(@dependant, 1994)
+ @dependant.dependencies.effectively_updated_at.should == Time.local(1994)
+ end
+
+
+ it "should reflect its own change date/time if it references dependencies which do not exist" do
+ @dependant.content = %{<r:#{current_tag[:name]}name="a_bogus_text_asset" />}
+ save_asset_at(@dependant, 1994)
+ @dependant.dependencies.effectively_updated_at.should == Time.local(1994)
+ end
+
+
+ it "should reflect a dependency's creation date/time when that dependency (which previously didn't exist) is created" do
+ @dependant.content = %{<r:#{current_tag[:name]} name="dependency" />}
+ save_asset_at(@dependant, 1994)
+
+ @dependency = current_tag[:class].new(:filename => 'dependency')
+ save_asset_at(@dependency, 1995)
+
+ @dependant = current_tag[:class].find_by_filename('dependant')
+ @dependant.dependencies.effectively_updated_at.should == Time.local(1995)
+ end
+
+
+ it "should reflect its own change date/time when it is updated" do
+ @dependant.content = %{<r:#{current_tag[:name]} name="dependency" />}
+ save_asset_at(@dependant, 1991)
+
+ @dependency = current_tag[:class].new(:filename => 'dependency')
+ save_asset_at(@dependency, 1995)
+
+ save_asset_at(@dependant, 1999)
+
+ @dependant = current_tag[:class].find_by_filename('dependant')
+ @dependant.dependencies.effectively_updated_at.should == Time.local(1999)
+ end
+
+
+ it "should reflect a dependency's change date/time once that dependency is updated" do
+ @dependency = current_tag[:class].new(:filename => 'dependency')
+ save_asset_at(@dependency, 1990)
+
+ @dependant.content = %{<r:#{current_tag[:name]} name="dependency" />}
+ save_asset_at(@dependant, 1992)
+
+ save_asset_at(@dependency, 1993)
+
+ @dependant = current_tag[:class].find_by_filename('dependant')
+ @dependant.dependencies.effectively_updated_at.should == Time.local(1993)
+ end
+
+
+ it "should reflect a dependency's deletion date/time when that dependency file is removed" do
+ @dependency = current_tag[:class].new(:filename => 'dependency')
+ save_asset_at(@dependency, 1996)
+
+ @dependant.content = %{<r:#{current_tag[:name]} name="dependency" />}
+ save_asset_at(@dependant, 1997)
+
+ Time.stub!(:now).and_return(Time.local(1998))
+ @dependency.destroy
+
+ @dependant = current_tag[:class].find_by_filename('dependant')
+ @dependant.dependencies.effectively_updated_at.should == Time.local(1998)
+ end
+
+ end
+
+end
+
+
+private
+
+ def save_asset_at(text_asset, year)
+ Time.stub!(:now).and_return(Time.local(year))
+ text_asset.save!
+ end
View
13 ...els/css_asset_and_js_asset_common_spec.rb → .../models/stylesheet_and_javascript_spec.rb
@@ -1,3 +1,11 @@
+# This specs the behavior of the Stylesheet and Javascript models where they
+# share much the same behavior (this is better than spec-ing the TextAsset model
+# since, frankly, we care about the Stylesheet and Javascript models, not their
+# parent).
+#
+# In the future, should unique behaviors appear, they would go at the bottom of
+# this spec or in their own specs.
+
require File.dirname(__FILE__) + '/../spec_helper'
[ Stylesheet, Javascript].each do |current_asset|
@@ -6,7 +14,7 @@
before(:each) do
@record = current_asset.new
end
-
+
it 'should limit the filename to 100 characters' do
@record.filename = 'a' * 100
@@ -41,7 +49,7 @@
if current_asset == Stylesheet
@record_of_other_subclass = Javascript.new(:filename => 'abc.123')
elsif current_asset == Javascript
- @record_of_other_subclass = Stylesheet.new(:filename => 'abc.123')
+ @record_of_other_subclass = Stylesheet.new(:filename => 'abc.123')
end
@record_of_other_subclass.should be_valid
end
@@ -84,6 +92,7 @@
end
end
+
it 'should automatically sort by filename' do
@record.filename = 'a_is_for_apple'
@record.save!
View
186 spec/models/stylesheet_and_javascript_tags_spec.rb
@@ -0,0 +1,186 @@
+# This specifies the behavior of the <r:stylesheet> and <r:javascript> tags.
+# This includes their usage within a Page and within a Stylesheet or Javascript.
+#
+# Since much of their behavior is the same, it is written as such. Should the
+# tags ever take on different behavior, those differences should be added at the
+# bottom of this spec or else in thier own, new spec.
+
+require File.dirname(__FILE__) + '/../spec_helper'
+
+[ { :asset_class => Stylesheet,
+ :name => 'stylesheet',
+ :default_mime_type => 'text/css',
+ :inline_element => 'style' },
+
+ { :asset_class => Javascript,
+ :name => 'javascript',
+ :default_mime_type => 'text/javascript',
+ :inline_element => 'script' }
+
+].each do |current_tag|
+
+ describe "<r:#{current_tag[:name]}> tags in a Page context" do
+ scenario :pages, :javascripts, :stylesheets
+
+ before :each do
+ create_page "text_asset_tags"
+ @page = pages(:text_asset_tags)
+ end
+
+
+ it "should render an error if no 'name' attribute is provided" do
+ @page.should render(%{<r:#{current_tag[:name]} />}).with_error(
+ "`#{current_tag[:name]}' tag must contain a `name' attribute.")
+ end
+
+
+ it "should render an error when the 'name' attribute calls out an invalid filename" do
+ @page.should render(%{<r:#{current_tag[:name]} name="bogus asset name" />}).with_error(
+ "#{current_tag[:name]} not found")
+ end
+
+
+ it "should render the content of the #{current_tag[:name]} when the 'as' attribute is not provided" do
+ @page.should render(%{<r:#{current_tag[:name]} name="main" />}).as(
+ "Main #{current_tag[:name]} content")
+ end
+
+
+ it "should render the content of the #{current_tag[:name]} when the 'as' attribute is set to 'content'" do
+ @page.should render(%{<r:#{current_tag[:name]} name="main" as="content" />}).as(
+ "Main #{current_tag[:name]} content")
+ end
+
+
+ it "should render the url of the #{current_tag[:name]} when the 'as' attribute is set to 'url'" do
+ StylesNScripts::Config["#{current_tag[:name]}_directory"] = 'foo/bar/baz'
+ ActionController::Routing::Routes.reload!
+ @page.should render(%{<r:#{current_tag[:name]} name="main" as="url" />}).as(
+ "foo/bar/baz/main")
+
+ StylesNScripts::Config.restore_defaults
+ ActionController::Routing::Routes.reload!
+ @page.should render(%{<r:#{current_tag[:name]} name="main" as="url" />}).as(
+ "#{StylesNScripts::Config["#{current_tag[:name]}_directory"]}/main")
+ end
+
+
+
+
+ describe "when the 'as' attribute is set to 'inline'" do
+
+ it "should render a <#{current_tag[:inline_element]}> element containing the content of the #{current_tag[:name]} with the type attribute matching the #{current_tag[:name]}_mime_type setting" do
+ # try with a custom mime_type value
+ StylesNScripts::Config["#{current_tag[:name]}_mime_type"] = 'bologna'
+ ActionController::Routing::Routes.reload!
+ @page.should render(%{<r:#{current_tag[:name]} name="main" as="inline" />}).as(
+ %{<#{current_tag[:inline_element]} type="bologna">\n} <<
+ %{<!--\n} <<
+ %{Main #{current_tag[:name]} content\n} <<
+ %{-->\n} <<
+ %{</#{current_tag[:inline_element]}>}
+ )
+
+ # try with the default mime_type value
+ StylesNScripts::Config.restore_defaults
+ ActionController::Routing::Routes.reload!
+ @page.should render(%{<r:#{current_tag[:name]} name="main" as="inline" />}).as(
+ %{<#{current_tag[:inline_element]} type="#{current_tag[:default_mime_type]}">\n} <<
+ %{<!--\n} <<
+ %{Main #{current_tag[:name]} content\n} <<
+ %{-->\n} <<
+ %{</#{current_tag[:inline_element]}>}
+ )
+ end
+
+
+ it %{if a 'type' element is defined in the <r:#{current_tag[:name]}> tag, this should override the default value as the <#{current_tag[:inline_element]}> element's 'type' attribute} do
+ @page.should render(%{<r:#{current_tag[:name]} name="main" as="inline" type="oscar" />}).as(
+ %{<#{current_tag[:inline_element]} type="oscar">\n} <<
+ %{<!--\n} <<
+ %{Main #{current_tag[:name]} content\n} <<
+ %{-->\n} <<
+ %{</#{current_tag[:inline_element]}>}
+ )
+ end
+
+
+ it %{should pass additional attributes into the <#{current_tag[:inline_element]}> element (and downcase each attribute name)} do
+ StylesNScripts::Config.restore_defaults
+ @page.should render(%{<r:#{current_tag[:name]} name="main" as="inline" another="mayer" ATTRIB="WEINER" />}).as(
+ %{<#{current_tag[:inline_element]} type="#{current_tag[:default_mime_type]}" another="mayer" attrib="WEINER">\n} <<
+ %{<!--\n} <<
+ %{Main #{current_tag[:name]} content\n} <<
+ %{-->\n} <<
+ %{</#{current_tag[:inline_element]}>}
+ )
+ end
+
+ end
+
+ end
+
+
+
+
+
+ describe "<r:#{current_tag[:name]}> tags in a #{current_tag[:asset_class].to_s} context" do
+ scenario :pages, :javascripts, :stylesheets
+
+ before :each do
+ @text_asset = current_tag[:asset_class].new
+ end
+
+
+ it "should render an error if no 'name' attribute is provided" do
+ @text_asset.content = %{<r:#{current_tag[:name]} />}
+ lambda{@text_asset.render}.should raise_error(
+ current_tag[:asset_class]::TagError,
+ "`#{current_tag[:name]}' tag must contain a `name' attribute."
+ )
+ end
+
+
+ it "should render an error when the 'name' attribute calls out an invalid filename" do
+ @text_asset.content = %{<r:#{current_tag[:name]} name="bogus asset name" />}
+ lambda{@text_asset.render}.should raise_error(
+ current_tag[:asset_class]::TagError,
+ "#{current_tag[:name]} not found"
+ )
+ end
+
+
+ it "should render the referenced file's content for each tag with a valid 'name' attribute" do
+ send("create_#{current_tag[:name]}", "another_file")
+
+ @text_asset.content = %{<r:#{current_tag[:name]} name="main" />}
+ @text_asset.content << %{<r:#{current_tag[:name]} name="another_file" />}
+ @text_asset.content << %{<r:#{current_tag[:name]} name="main" />}
+ @text_asset.render.should == "Main #{current_tag[:name]} content" <<
+ "#{current_tag[:name]} content for another_file" <<
+ "Main #{current_tag[:name]} content"
+ end
+
+ end
+
+
+
+
+ alternate_tag = case current_tag[:name]
+ when "stylesheet"
+ "javascript"
+ when "javascript"
+ "stylesheet"
+ end
+
+ describe "<r:#{alternate_tag}> tags in a #{current_tag[:asset_class].to_s} context" do
+ it "should not be available (should throw a tag-not-found error)" do
+ @text_asset = current_tag[:asset_class].new(:content => "<r:#{alternate_tag} />")
+ lambda{@text_asset.render}.should raise_error(
+ StandardTags::TagError,
+ "undefined tag `#{alternate_tag}'"
+ )
+ end
+ end
+
+end
View
24 spec/models/text_asset_response_cache_spec.rb
@@ -1,3 +1,6 @@
+# This specifies the behavior of the TextAssetResponseCache which controlls the
+# caching of stylesheets and javascripts.
+
require File.dirname(__FILE__) + '/../spec_helper'
describe TextAssetResponseCache do
@@ -17,6 +20,7 @@ def initialize(body = '', headers = {})
TextAssetResponseCache.defaults[:perform_caching] = true
end
+
before(:each) do
FileUtils.rm_rf @baddir
@cache = TextAssetResponseCache.new(
@@ -26,10 +30,12 @@ def initialize(body = '', headers = {})
@cache.clear
end
+
after :each do
FileUtils.rm_rf @dir if File.exists? @dir
end
+
after :all do
TextAssetResponseCache.defaults[:perform_caching] = @old_preform_caching
end
@@ -65,7 +71,7 @@ def initialize(body = '', headers = {})
end
-# These specs are stolen directly from the response_cache_spec. Mostly these
+# These two specs are stolen directly from the response_cache_spec. Mostly these
# are redundant tests but I at least wanted to ensure that it actually caches
['test/me', '/test/me', 'test/me/', '/test/me/', 'test//me'].each do |url|
it "should cache response for url: #{url.inspect}" do
@@ -87,16 +93,16 @@ def initialize(body = '', headers = {})
File.exists?("#{@dir}/styles.css.yml").should == true
end
+end
- private
- def file(filename)
- open(filename) { |f| f.read } rescue ''
- end
+private
+ def file(filename)
+ open(filename) { |f| f.read } rescue ''
+ end
- def response(*args)
- TestResponse.new(*args)
- end
-end
+ def response(*args)
+ TestResponse.new(*args)
+ end
View
45 spec/models/text_asset_tags_spec.rb
@@ -1,45 +0,0 @@
-require File.dirname(__FILE__) + '/../spec_helper'
-
-[ { :asset_class => Stylesheet,
- :name => 'stylesheet',
- :main_scenario => 'main.css' },
-
- { :asset_class => Javascript,
- :name => 'javascript',
- :main_scenario => 'main.js' }
-].each do |current_tag|
- describe current_tag[:asset_class], "with <r:#{current_tag[:name]}> tags" do
- scenario :pages, :javascripts, :stylesheets
-
- before :each do
-# @text_asset = create_stylesheet "text_asset_tags" if current_tag[:name] == 'stylesheet'
-# @text_asset = create_javascript "text_asset_tags" if current_tag[:name] == 'javascript'
- @text_asset = current_tag[:asset_class].new
- end
-
-
- it 'should render an error if no filename provided' do
- @text_asset.content = %{<r:#{current_tag[:name]} />}
- lambda{@text_asset.render}.should raise_error(
- current_tag[:asset_class]::TagError,
- "`#{current_tag[:name]}' tag must contain a `name' attribute."
- )
- end
-
-
- it 'should render an error with an invalid filename provided' do
- @text_asset.content = %{<r:#{current_tag[:name]} name="bogus asset name" />}
- lambda{@text_asset.render}.should raise_error(
- current_tag[:asset_class]::TagError,
- "#{current_tag[:name]} not found"
- )
- end
-
-
- it "should render the content of the #{current_tag[:name]} when a valid filename is provided" do
- @text_asset.content =%{<r:#{current_tag[:name]} name="#{current_tag[:main_scenario]}" />}
- @text_asset.render.should == "Main #{current_tag[:name]} content"
- end
-
- end
-end
View
11 spec/models/user_action_observer_spec.rb
@@ -1,8 +1,11 @@
+# This specifies the behavior of enhancements added to UserActionObserver by
+# this extension (basically, it now observes stylesheets and javascripts too).
+
require File.dirname(__FILE__) + '/../spec_helper'
describe UserActionObserver do
scenario :users, :stylesheets, :javascripts
-
+
before(:each) do
@user = users(:existing)
UserActionObserver.current_user = @user
@@ -20,15 +23,15 @@
it 'should observe stylesheet update' do
- model = Stylesheet.find_by_filename('main.css')
+ model = Stylesheet.find_by_filename('main')
model.attributes = model.attributes.dup
model.save.should == true
model.updated_by.should == @user
end
-
+
it 'should observe javascript update' do
- model = Javascript.find_by_filename('main.js')
+ model = Javascript.find_by_filename('main')
model.attributes = model.attributes.dup
model.save.should == true
model.updated_by.should == @user
View
11 spec/scenarios/javascripts_scenario.rb
@@ -1,18 +1,17 @@
class JavascriptsScenario < Scenario::Base
def load
- create_javascript "main.js",
- :content => 'Main javascript content',
- :created_at => DateTime.parse('1775-04-19 05:30:00'),
- :updated_at => DateTime.parse('1776-12-01 14:00:00')
+ create_javascript "main", :content => 'Main javascript content'
end
helpers do
+
def create_javascript(filename, attributes={})
create_model :javascript,
filename.symbolize,
- javascript_params(attributes.reverse_merge(:filename => filename))
+ javascript_params(
+ attributes.reverse_merge(:filename => filename) )
end
@@ -20,7 +19,7 @@ def javascript_params(attributes={})
filename = attributes[:filename] || unique_javascript_filename
{
:filename => filename,
- :content => "dummy content"<