From f2258e83ec5bb7a0a8411b389b60618df0d9e227 Mon Sep 17 00:00:00 2001 From: AntonioTerceiro Date: Wed, 27 Aug 2008 23:35:21 +0000 Subject: [PATCH] ActionItem122: implementing theme editor --- app/controllers/my_profile/themes_controller.rb | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ app/helpers/application_helper.rb | 43 +++++++++++++++++++++++++++---------------- app/models/profile.rb | 10 ++++++++++ app/models/theme.rb | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- app/views/layouts/application.rhtml | 6 +++++- app/views/shared/codepress.rhtml | 2 +- app/views/shared/theme_test_panel.rhtml | 13 +++++++++++++ app/views/themes/add_css.rhtml | 11 +++++++++++ app/views/themes/add_image.rhtml | 6 ++++++ app/views/themes/css_editor.rhtml | 13 +++++++++++++ app/views/themes/edit.rhtml | 58 ++++++++++++++++++---------------------------------------- app/views/themes/index.rhtml | 48 ++++++++++++++++++++++++++++++++++++++++++++---- app/views/themes/new.rhtml | 10 ++++++++++ db/migrate/051_add_edit_appearance_permission.rb | 16 ++++++++++++++++ db/schema.rb | 10 +++++----- lib/tasks/populate.rake | 2 ++ public/stylesheets/common.css | 20 ++++++++++++++++++++ test/functional/application_controller_test.rb | 21 +++++++++++++++++++++ test/functional/themes_controller_test.rb | 177 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- test/unit/application_helper_test.rb | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/unit/profile_test.rb | 27 +++++++++++++++++++++++++++ test/unit/theme_test.rb | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 22 files changed, 801 insertions(+), 89 deletions(-) create mode 100644 app/views/shared/theme_test_panel.rhtml create mode 100644 app/views/themes/add_css.rhtml create mode 100644 app/views/themes/add_image.rhtml create mode 100644 app/views/themes/css_editor.rhtml create mode 100644 app/views/themes/new.rhtml create mode 100644 db/migrate/051_add_edit_appearance_permission.rb diff --git a/app/controllers/my_profile/themes_controller.rb b/app/controllers/my_profile/themes_controller.rb index 83d3f92..27c8c59 100644 --- a/app/controllers/my_profile/themes_controller.rb +++ b/app/controllers/my_profile/themes_controller.rb @@ -1,5 +1,6 @@ class ThemesController < MyProfileController + protect 'edit_appearance', :profile no_design_blocks def set @@ -12,4 +13,66 @@ class ThemesController < MyProfileController @selected_theme = profile.theme end + def new + if !request.xhr? + id = params[:name].to_slug + t = Theme.new(id, :name => params[:name], :owner => profile) + t.save + redirect_to :action => 'index' + else + render :action => 'new', :layout => false + end + end + + def edit + @theme = profile.find_theme(params[:id]) + @css_files = @theme.css_files + @image_files = @theme.image_files + end + + def add_css + @theme = profile.find_theme(params[:id]) + if request.xhr? + render :action => 'add_css', :layout => false + else + @theme.add_css(params[:css]) + redirect_to :action => 'edit', :id => @theme.id + end + end + + def css_editor + @theme = profile.find_theme(params[:id]) + @css = params[:css] + + @code = @theme.read_css(@css) + render :action => 'css_editor', :layout => false + end + + post_only :update_css + def update_css + @theme = profile.find_theme(params[:id]) + @theme.update_css(params[:css], params[:csscode]) + redirect_to :action => 'edit', :id => @theme.id + end + + def add_image + @theme = profile.find_theme(params[:id]) + if request.xhr? + render :action => 'add_image', :layout => false + else + @theme.add_image(params[:image].original_filename, params[:image].read) + redirect_to :action => 'edit', :id => @theme.id + end + end + + def start_test + session[:theme] = params[:id] + redirect_to :controller => 'content_viewer', :profile => profile.identifier, :action => 'view_page' + end + + def stop_test + session[:theme] = nil + redirect_to :action => 'index' + end + end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fb97c43..d2efefa 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -220,24 +220,12 @@ module ApplicationHelper link_to(content_tag('span', label), url, html_options.merge(:class => the_class )) end - def button_to_function(type, label, js_code, html_options = {}) - html_options[:class] = "button with-text" unless html_options[:class] - html_options[:class] << " icon-#{type}" - link_to_function(label, js_code, html_options) - end - def button_to_function(type, label, js_code, html_options = {}, &block) html_options[:class] = "button with-text" unless html_options[:class] html_options[:class] << " icon-#{type}" link_to_function(label, js_code, html_options, &block) end - def button_to_function_without_text(type, label, js_code, html_options = {}) - html_options[:class] = "" unless html_options[:class] - html_options[:class] << " button icon-#{type}" - link_to_function(content_tag('span', label), js_code, html_options) - end - def button_to_function_without_text(type, label, js_code, html_options = {}, &block) html_options[:class] = "" unless html_options[:class] html_options[:class] << " button icon-#{type}" @@ -323,12 +311,21 @@ module ApplicationHelper def filename_for_stylesheet(name, in_theme) result = '' if in_theme - result << '/designs/themes/' + current_theme + result << theme_path end result << '/stylesheets/' << name << '.css' end + def theme_path + if session[:theme] + '/user_themes/' + current_theme + else + '/designs/themes/' + current_theme + end + end + def current_theme + return session[:theme] if (session[:theme]) p = profile if p p.theme @@ -337,6 +334,21 @@ module ApplicationHelper end end + def theme_footer + footer = ('../../public' + theme_path + '/footer.rhtml') + if File.exists?(RAILS_ROOT + '/app/views/' + footer) + render :file => footer + end + end + + def is_testing_theme + !@controller.session[:theme].nil? + end + + def theme_owner + Theme.find(current_theme).owner.identifier + end + # generates a image tag for the profile. # # If the profile has no image set yet, then a default image is used. @@ -492,8 +504,7 @@ module ApplicationHelper def theme_option(opt = nil) conf = RAILS_ROOT.to_s() + - '/public/designs/themes/' + - current_theme.to_s() + + '/public' + theme_path + '/theme.yml' if File.exists?(conf) opt ? YAML.load_file(conf)[opt.to_s()] : YAML.load_file(conf) @@ -529,7 +540,7 @@ module ApplicationHelper return if option.nil? html = [] option.each do |file| - file = '/designs/themes/'+ current_theme.to_s() + + file = theme_path + '/javascript/'+ file +'.js' if File.exists? RAILS_ROOT.to_s() +'/public'+ file html << javascript_src_tag( file, {} ) diff --git a/app/models/profile.rb b/app/models/profile.rb index 33dd4a7..74775d5 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -35,6 +35,7 @@ class Profile < ActiveRecord::Base 'validate_enterprise' => N_('Validate enterprise'), 'perform_task' => N_('Perform task'), 'moderate_comments' => N_('Moderate comments'), + 'edit_appearance' => N_('Edit appearance'), } acts_as_accessible @@ -450,4 +451,13 @@ class Profile < ActiveRecord::Base def public? public_profile end + + def themes + Theme.find_by_owner(self) + end + + def find_theme(the_id) + themes.find { |item| item.id == the_id } + end + end diff --git a/app/models/theme.rb b/app/models/theme.rb index 57503cf..34bbb2c 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -1,22 +1,155 @@ class Theme + class << self + def system_themes + Dir.glob(RAILS_ROOT + '/public/designs/themes/*').map do |item| + File.basename(item) + end.map do |item| + new(item) + end + end + + def user_themes_dir + File.join(RAILS_ROOT, 'public', 'user_themes') + end + + def create(id, attributes = {}) + if find(id) || system_themes.map(&:id).include?(id) + raise DuplicatedIdentifier + end + Theme.new(id, attributes).save + end + + def find(the_id) + if File.directory?(File.join(user_themes_dir, the_id)) + Theme.new(the_id) + else + nil + end + end + + def find_by_owner(owner) + Dir.glob(File.join(user_themes_dir, '*', 'theme.yml')).select do |desc| + config = YAML.load_file(desc) + (config['owner_type'] == owner.class.base_class.name) && (config['owner_id'] == owner.id) + end.map do |desc| + Theme.find(File.basename(File.dirname(desc))) + end + end + + end + + class DuplicatedIdentifier < Exception; end + attr_reader :id + attr_reader :config - def initialize(id) + def initialize(id, attributes = {}) @id = id + load_config + attributes.each do |k,v| + self.send("#{k}=", v) + end end def name - id + config['name'] || id end - class << self - def system_themes - Dir.glob(RAILS_ROOT + '/public/designs/themes/*').map do |item| - File.basename(item) - end.map do |item| - new(item) - end + def name=(value) + config['name'] = value + end + + def ==(other) + other.is_a?(self.class) && (other.id == self.id) + end + + def add_css(filename) + FileUtils.mkdir_p(stylesheets_directory) + FileUtils.touch(stylesheet_path(filename)) + end + + def update_css(filename, content) + add_css(filename) + File.open(stylesheet_path(filename), 'w') do |f| + f.write(content) + end + end + + def read_css(filename) + File.read(stylesheet_path(filename)) + end + + def css_files + Dir.glob(File.join(stylesheets_directory, '*.css')).map { |f| File.basename(f) } + end + + def add_image(filename, data) + FileUtils.mkdir_p(images_directory) + File.open(image_path(filename), 'w') do |f| + f.write(data) end end + + def image_files + Dir.glob(image_path('*')).map {|item| File.basename(item)} + end + + def stylesheet_path(filename) + suffix = '' + unless filename =~ /\.css$/ + suffix = '.css' + end + File.join(stylesheets_directory, filename + suffix) + end + + def stylesheets_directory + File.join(Theme.user_themes_dir, self.id, 'stylesheets') + end + + def image_path(filename) + File.join(images_directory, filename) + end + + def images_directory + File.join(self.class.user_themes_dir, id, 'images') + end + + def save + FileUtils.mkdir_p(self.class.user_themes_dir) + FileUtils.mkdir_p(File.join(self.class.user_themes_dir, id)) + %w[ common help menu article button search blocks forms login-box ].each do |item| + add_css(item) + end + write_config + self + end + + def owner + return nil unless config['owner_type'] && config['owner_id'] + @owner ||= config['owner_type'].constantize.find(config['owner_id']) + end + + def owner=(model) + config['owner_type'] = model.class.base_class.name + config['owner_id'] = model.id + @owner = model + end + + protected + + def write_config + File.open(File.join(self.class.user_themes_dir, self.id, 'theme.yml'), 'w') do |f| + f.write(config.to_yaml) + end + end + + def load_config + if File.exists?(File.join(self.class.user_themes_dir, self.id, 'theme.yml')) + @config = YAML.load_file(File.join(self.class.user_themes_dir, self.id, 'theme.yml')) + else + @config = {} + end + end + end diff --git a/app/views/layouts/application.rhtml b/app/views/layouts/application.rhtml index 6151c50..6d51b28 100644 --- a/app/views/layouts/application.rhtml +++ b/app/views/layouts/application.rhtml @@ -154,7 +154,7 @@ <%= javascript_include_tag 'better-browser-promotion' %> + <% if is_testing_theme %> + <%= render :file => 'shared/theme_test_panel' %> + <% end %> + diff --git a/app/views/shared/codepress.rhtml b/app/views/shared/codepress.rhtml index 91dad21..ac6ee22 100644 --- a/app/views/shared/codepress.rhtml +++ b/app/views/shared/codepress.rhtml @@ -1 +1 @@ -<%= javascript_include_tag 'codepress/codepress.js' %> +<%= javascript_include_tag 'codepress/codepress' %> diff --git a/app/views/shared/theme_test_panel.rhtml b/app/views/shared/theme_test_panel.rhtml new file mode 100644 index 0000000..d198822 --- /dev/null +++ b/app/views/shared/theme_test_panel.rhtml @@ -0,0 +1,13 @@ +
+

+ <%= _('Testing theme "%s"') % current_theme %> +

+ +

<%= _('You can move this window away to have a better visualization of specific parts of screen.') %>

+ + <% button_bar do %> + <%= button(:ok, _('Finished testing'), :controller => 'themes', :profile => theme_owner, :action => 'stop_test', :id => current_theme) %> + <%= button(:edit, _('Edit theme'), :controller => 'themes', :profile => theme_owner, :action => 'edit', :id => current_theme) %> + <% end %> +
+<%= draggable_element('theme-test-panel') %> diff --git a/app/views/themes/add_css.rhtml b/app/views/themes/add_css.rhtml new file mode 100644 index 0000000..0517856 --- /dev/null +++ b/app/views/themes/add_css.rhtml @@ -0,0 +1,11 @@ +

<%= _('Add a CSS file') %>

+ +<% form_tag do %> + <%= labelled_form_field(_('File name'), text_field_tag('css')) %> + + <% button_bar do %> + <%= submit_button(:add, _('Add')) %> + <%= lightbox_close_button(_('Cancel')) %> + <% end %> + +<% end %> diff --git a/app/views/themes/add_image.rhtml b/app/views/themes/add_image.rhtml new file mode 100644 index 0000000..0a31980 --- /dev/null +++ b/app/views/themes/add_image.rhtml @@ -0,0 +1,6 @@ +<% form_tag({:action => 'add_image', :id => @theme.id}, :multipart => true) do %> + <%= labelled_form_field(_('Choose the image file'), file_field_tag(:image)) %> + <% button_bar do %> + <%= submit_button(:add, _('Add image')) %> + <% end %> +<% end %> diff --git a/app/views/themes/css_editor.rhtml b/app/views/themes/css_editor.rhtml new file mode 100644 index 0000000..0f84624 --- /dev/null +++ b/app/views/themes/css_editor.rhtml @@ -0,0 +1,13 @@ +

<%= _('CSS code: "%s"') % @css %>

+ +<% form_tag({:action => 'update_css', :id => @theme.id }, :name => 'csscode_form') do %> + <%= hidden_field_tag('css', @css) %> + <%= text_area_tag('csscode', @code, :id => "codepressWindow", :class => 'codepress css') %> + <% button_bar do %> + <%= submit_button(:save, _('Save')) %> + <% end %> +<% end %> + + + + diff --git a/app/views/themes/edit.rhtml b/app/views/themes/edit.rhtml index bd3c8e6..b1fdd2e 100644 --- a/app/views/themes/edit.rhtml +++ b/app/views/themes/edit.rhtml @@ -1,63 +1,41 @@
<%# FIXME %>

- <%= _('Editing theme %s') % text_field_tag('theme_name', 'my theme') %> - <%= icon_button(:save, _('Save'), '#') %> + <%= _('Editing theme "%s"') % @theme.name %> + <%= button(:search, _('Preview this theme'), :action => 'index') %> + <%= button(:back, _('Back'), :action => 'index') %>

+

<%= _('CSS files') %>

<% button_bar do %> - <%= button(:add, _('New CSS'), '') %> + <%= lightbox_button(:add, _('New CSS'), :action => 'add_css', :id => @theme.id) %> <% end %>

<%= _('Images') %>

<% button_bar do %> - <%= button(:add, _('Upload image'), '') %> + <%= lightbox_button(:add, _('Add image'), :action => 'add_image', :id => @theme.id) %> <% end %>
-
- <%# FIXME %> -

<%= _('CSS code: "%s"') % 'common.css' %>

- - <% button_bar do %> - <%= button(:save, _('Save'), '') %> - <% end %> -
+
+
+ <%= _('Select a CSS file to edit') %> +
+
-<%= render :file => 'shared/codepress' %> +<%# javascript_include_tag 'codepress/codepress' %> diff --git a/app/views/themes/index.rhtml b/app/views/themes/index.rhtml index a2028e4..f32c306 100644 --- a/app/views/themes/index.rhtml +++ b/app/views/themes/index.rhtml @@ -2,13 +2,53 @@

<%= _('Select theme') %>

-
- <% for theme in @themes %> - <%= link_to image_tag('/images/icons-app/design-editor.png', :alt => (_('Select the "%s" theme.') % theme.name)) + content_tag('span', theme.name), :action => 'set', :id => theme.id %> + + <% for themes in @themes.in_groups_of(3) %> + + <% for theme in themes %> + + + + <% end %> + + <% end %> - +
+ <%# FIXME add proper thumbnails %> + <%= image_tag('/images/icons-app/design-editor.png', :alt => (_('The "%s" theme.') % theme.name)) if theme %> + + <%= theme.name if theme%>
+ <%= link_to(_('Use this theme'), :action => 'set', :id => theme.id) if theme %> +
+      +
 
+

<%= _('My themes') %>

+ + + <% for themes in profile.themes.in_groups_of(3) %> + + <% for theme in themes %> + + + + <% end %> + + + <% end %> +
+ <%# FIXME add proper thumbnails %> + <%= image_tag('/images/icons-app/design-editor.png', :alt => (_('The "%s" theme.') % theme.name)) if theme %> + + <%= theme.name if theme%>
+ <%= link_to(_('Edit this theme'), :action => 'edit', :id => theme.id) if theme %> +
+ <%= link_to(_('Test this theme'), :action => 'start_test', :id => theme.id) if theme %> +
+      +
 
<% button_bar do %> + <%= lightbox_button(:add, _('New theme ...'), :action => 'new') %> <%= button(:back, _('Back'), :controller => 'profile_editor', :action => 'index') %> <% end %> diff --git a/app/views/themes/new.rhtml b/app/views/themes/new.rhtml new file mode 100644 index 0000000..fb77600 --- /dev/null +++ b/app/views/themes/new.rhtml @@ -0,0 +1,10 @@ +

<%= _('Create new theme') %>

+ +<% form_tag(:action => 'new') do %> + + <%= labelled_form_field(_('Name of the new theme:'), text_field_tag(:name)) %> + + <% button_bar do %> + <%= submit_button(:save, _('Create')) %> + <% end %> +<% end %> diff --git a/db/migrate/051_add_edit_appearance_permission.rb b/db/migrate/051_add_edit_appearance_permission.rb new file mode 100644 index 0000000..0b4eaa6 --- /dev/null +++ b/db/migrate/051_add_edit_appearance_permission.rb @@ -0,0 +1,16 @@ +class AddEditAppearancePermission < ActiveRecord::Migration + + def self.up + [ Profile::Roles.admin, Environment::Roles.admin].each do |item| + item.permissions += [ 'edit_appearance' ] + item.save! + end + end + + def self.down + [ Profile::Roles.admin, Environment::Roles.admin].each do |item| + item.permissions -= [ 'edit_appearance' ] + item.save! + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 027fdbe..4e62fd5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 50) do +ActiveRecord::Schema.define(:version => 51) do create_table "article_versions", :force => true do |t| t.integer "article_id" @@ -73,8 +73,8 @@ ActiveRecord::Schema.define(:version => 50) do t.boolean "virtual", :default => false end - add_index "articles_categories", ["article_id"], :name => "index_articles_categories_on_article_id" add_index "articles_categories", ["category_id"], :name => "index_articles_categories_on_category_id" + add_index "articles_categories", ["article_id"], :name => "index_articles_categories_on_article_id" create_table "blocks", :force => true do |t| t.string "title" @@ -114,8 +114,8 @@ ActiveRecord::Schema.define(:version => 50) do t.boolean "virtual", :default => false end - add_index "categories_profiles", ["profile_id"], :name => "index_categories_profiles_on_profile_id" add_index "categories_profiles", ["category_id"], :name => "index_categories_profiles_on_category_id" + add_index "categories_profiles", ["profile_id"], :name => "index_categories_profiles_on_profile_id" create_table "comments", :force => true do |t| t.string "title" @@ -182,8 +182,8 @@ ActiveRecord::Schema.define(:version => 50) do t.datetime "updated_at" end - add_index "product_categorizations", ["product_id"], :name => "index_product_categorizations_on_product_id" add_index "product_categorizations", ["category_id"], :name => "index_product_categorizations_on_category_id" + add_index "product_categorizations", ["product_id"], :name => "index_product_categorizations_on_product_id" create_table "products", :force => true do |t| t.integer "enterprise_id" @@ -254,8 +254,8 @@ ActiveRecord::Schema.define(:version => 50) do t.datetime "created_at" end - add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" add_index "taggings", ["taggable_id", "taggable_type"], :name => "index_taggings_on_taggable_id_and_taggable_type" + add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" create_table "tags", :force => true do |t| t.string "name" diff --git a/lib/tasks/populate.rake b/lib/tasks/populate.rake index 3745ace..db3a174 100644 --- a/lib/tasks/populate.rake +++ b/lib/tasks/populate.rake @@ -36,6 +36,7 @@ def create_roles 'post_content', 'edit_profile_design', 'manage_products', + 'edit_appearance', ]) Role.create!(:key => 'profile_admin', :name => N_('Profile Administrator'), :permissions => [ 'edit_profile', @@ -44,6 +45,7 @@ def create_roles 'post_content', 'edit_profile_design', 'manage_products', + 'edit_appearance', ]) # members for enterprises, communities etc Role.create!(:key => "profile_member", :name => N_('Member'), :permissions => [ diff --git a/public/stylesheets/common.css b/public/stylesheets/common.css index e61ab9d..b0d67a6 100644 --- a/public/stylesheets/common.css +++ b/public/stylesheets/common.css @@ -387,3 +387,23 @@ div.pending-tasks { max-width: 100%; overflow: hidden; } + +/* theme test panel */ +#theme-test-panel { + z-index: 1000; + position: absolute; + width: 300px; + height: 180; + right: 50px; + top: 50px; + background: white; + border: 2px solid black; +} + +#theme-test-panel { + text-align: center; + cursor: move; +} +#theme-test-panel .button-bar { + margin: 1em; +} diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb index e736010..a039492 100644 --- a/test/functional/application_controller_test.rb +++ b/test/functional/application_controller_test.rb @@ -193,4 +193,25 @@ class ApplicationControllerTest < Test::Unit::TestCase assert_tag :tag => 'base', :attributes => { :href => 'http://www.lala.net' } end + should 'display theme test panel when testing theme' do + @request.session[:theme] = 'my-test-theme' + theme = mock + profile = mock + theme.expects(:owner).returns(profile).at_least_once + profile.expects(:identifier).returns('testinguser').at_least_once + Theme.expects(:find).with('my-test-theme').returns(theme).at_least_once + get :index + + assert_tag :tag => 'div', :attributes => { :id => 'theme-test-panel' }, :descendant => { + :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/edit/my-test-theme'} + } + #{ :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/stop_test/my-test-theme'} } + end + + should 'not display theme test panel in general' do + @controller.stubs(:session).returns({}) + get :index + assert_no_tag :tag => 'div', :attributes => { :id => 'theme-test-panel' } + end + end diff --git a/test/functional/themes_controller_test.rb b/test/functional/themes_controller_test.rb index 573f5b5..ca609a9 100644 --- a/test/functional/themes_controller_test.rb +++ b/test/functional/themes_controller_test.rb @@ -1,43 +1,194 @@ require File.dirname(__FILE__) + '/../test_helper' +require 'themes_controller' -class ThemesControllerTest < ActionController::TestCase +class ThemesController; def rescue_action(e) raise e end; end + +class ThemesControllerTest < Test::Unit::TestCase + + def setup + @controller = ThemesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR) + + @profile = create_user('testinguser').person + login_as('testinguser') + end + attr_reader :profile + + def teardown + FileUtils.rm_rf(TMP_THEMES_DIR) + end + + TMP_THEMES_DIR = RAILS_ROOT + '/test/tmp/themes_controller' should 'display list of themes for selection' do - profile = create_user('testinguser').person Theme.expects(:system_themes).returns([Theme.new('first'), Theme.new('second')]) get :index, :profile => 'testinguser' %w[ first second ].each do |item| - assert_tag :tag => 'a', :attributes => { :href => "/myprofile/testinguser/themes/set/#{item}" }, :descendant => { :tag => 'img' } + assert_tag :tag => 'a', :attributes => { :href => "/myprofile/testinguser/themes/set/#{item}" } end end - should 'save selection of theme' do - profile = create_user('testinguser').person + should 'display list of my themes for edition' do + Theme.create('first', :owner => profile) + Theme.create('second', :owner => profile) + + get :index, :profile => 'testinguser' + + %w[ first second ].each do |item| + assert_tag :tag => 'a', :attributes => { :href => "/myprofile/testinguser/themes/edit/#{item}" } + end + end + should 'save selection of theme' do get :set, :profile => 'testinguser', :id => 'onetheme' profile.reload assert_equal 'onetheme', profile.theme end should 'point back to control panel' do - create_user('testinguser').person get :index, :profile => 'testinguser' assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testinguser' }, :content => 'Back' end - should 'check access control when choosing theme' + should 'display screen for creating new theme' do + @request.expects(:xhr?).returns(true).at_least_once + get :new, :profile => 'testinguser' + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/new', :method => /post/i }, :descendant => { :tag => 'input', :attributes => { :type => 'text', :name => 'name' } } + end + + should 'create a new theme' do + post :new, :profile => 'testinguser', :name => 'My theme' + + ok('theme should be created') do + profile.themes.first.id == 'my-theme' + end + end + + should 'edit a theme' do + theme = Theme.create('mytheme', :owner => profile) + get :edit, :profile => 'testinguser', :id => 'mytheme' + + assert_equal theme, assigns(:theme) + end + + should 'list CSS files in theme' do + theme = Theme.create('mytheme', :owner => profile) + theme.add_css('one.css') + theme.add_css('two.css') + + get :edit, :profile => 'testinguser', :id => 'mytheme' + + %w[ one.css two.css ].each do |item| + assert_includes assigns(:css_files), item + assert_tag :tag => 'li', :descendant => { :tag => 'a', :content => item} + end + end + + should 'display dialog for creating new CSS' do + theme = Theme.create('mytheme', :owner => profile) + @request.expects(:xhr?).returns(true) + get :add_css, :profile => 'testinguser', :id => 'mytheme' + + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/add_css/mytheme', :method => /post/i} + assert_tag :tag => 'input', :attributes => { :name => 'css', :type => 'text' } + assert_tag :tag => 'input', :attributes => { :type => 'submit' } + end + + should 'be able to add new CSS to theme' do + theme = Theme.create('mytheme', :owner => profile) + post :add_css, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css' + + assert_response :redirect + + reloaded_theme = Theme.find('mytheme') + assert_includes reloaded_theme.css_files, 'test.css' + end + + should 'load code from a given CSS file' do + theme = Theme.create('mytheme', :owner => profile); theme.update_css('test.css', '/* sample code */') + get :css_editor, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css' + + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/update_css/mytheme' }, :descendant => { :tag => 'textarea', :content => '/* sample code */' } + end + + should 'be able to save CSS code' do + theme = Theme.create('mytheme', :owner => profile); theme.update_css('test.css', '/* sample code */') + get :css_editor, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css' + + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/update_css/mytheme' }, :descendant => { :tag => 'input', :attributes => { :type => 'submit' } } + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/update_css/mytheme' }, :descendant => { :tag => 'input', :attributes => { :type => 'hidden', :name => 'css', :value => 'test.css' } } + end + + should 'update css code when saving' do + theme = Theme.create('mytheme', :owner => profile); theme.update_css('test.css', '/* sample code */') + post :update_css, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css', :csscode => 'body { background: white; }' + assert_equal 'body { background: white; }', theme.read_css('test.css') + end + + should 'list image files in theme' do + theme = Theme.create('mytheme', :owner => profile) + theme.add_image('one.png', 'FAKE IMAGE DATA 1') + theme.add_image('two.png', 'FAKE IMAGE DATA 2') + + get :edit, :profile => 'testinguser', :id => 'mytheme' + + assert_tag :tag => 'img', :attributes => { :src => '/user_themes/mytheme/images/one.png' } + assert_tag :tag => 'img', :attributes => { :src => '/user_themes/mytheme/images/two.png' } + end + + should 'display "add image" button' do + theme = Theme.create('mytheme', :owner => profile) + get :edit, :profile => 'testinguser', :id => 'mytheme' + + assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/add_image/mytheme' } + end - should 'check access control when editing themes' + should 'display the "add image" dialog' do + theme = Theme.create('mytheme', :owner => profile) + @request.expects(:xhr?).returns(true) - should 'only allow environment-approved themes to be selected' + get :add_image, :profile => 'testinguser', :id => 'mytheme' + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/add_image/mytheme', :method => /post/i, :enctype => 'multipart/form-data' }, :descendant => { :tag => 'input', :attributes => { :name => 'image', :type => 'file' } } + end - should 'list user-created themes with link for editing' + should 'be able to add new image to theme' do + theme = Theme.create('mytheme', :owner => profile) + @request.expects(:xhr?).returns(false) - should 'offer to create new theme' + post :add_image, :profile => 'testinguser', :id => 'mytheme', :image => fixture_file_upload('/files/rails.png', 'image/png', :binary) + assert_redirected_to :action => "edit", :id => 'mytheme' + assert theme.image_files.include?('rails.png') + assert(system('diff', RAILS_ROOT + '/test/fixtures/files/rails.png', TMP_THEMES_DIR + '/mytheme/images/rails.png'), 'should put the correct uploaded file in the right place') + end - should 'be able to save new theme' + should 'link to "test theme"' do + Theme.create('one', :owner => profile) + Theme.create('two', :owner => profile) + get :index, :profile => 'testinguser' - should 'be able to save existing theme' + %w[ one two ].each do |item| + assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/start_test/' + item } + end + end + + should 'start testing theme' do + theme = Theme.create('theme-under-test', :owner => profile) + post :start_test, :profile => 'testinguser', :id => 'theme-under-test' + + assert_equal 'theme-under-test', session[:theme] + assert_redirected_to :controller => 'content_viewer', :profile => 'testinguser' + end + + should 'stop testing theme' do + theme = Theme.create('theme-under-test', :owner => profile) + post :stop_test, :profile => 'testinguser', :id => 'theme-under-test' + + assert_nil session[:theme] + assert_redirected_to :action => 'index' + end end diff --git a/test/unit/application_helper_test.rb b/test/unit/application_helper_test.rb index 3469de2..727d054 100644 --- a/test/unit/application_helper_test.rb +++ b/test/unit/application_helper_test.rb @@ -4,6 +4,10 @@ class ApplicationHelperTest < Test::Unit::TestCase include ApplicationHelper + def setup + self.stubs(:session).returns({}) + end + should 'calculate correctly partial for object' do self.stubs(:params).returns({:controller => 'test'}) @@ -150,6 +154,52 @@ class ApplicationHelperTest < Test::Unit::TestCase assert_equal 'my-profile-theme', current_theme end + should 'override theme with testing theme from session' do + stubs(:session).returns(:theme => 'theme-under-test') + assert_equal 'theme-under-test', current_theme + end + + should 'point to system theme path by default' do + expects(:current_theme).returns('my-system-theme') + assert_equal '/designs/themes/my-system-theme', theme_path + end + + should 'point to user theme path when testing theme' do + stubs(:session).returns({:theme => 'theme-under-test'}) + assert_equal '/user_themes/theme-under-test', theme_path + end + + should 'render theme footer' do + stubs(:theme_path).returns('/user_themes/mytheme') + footer = '../../public/user_themes/mytheme/footer.rhtml' + + File.expects(:exists?).with(RAILS_ROOT + '/app/views/../../public/user_themes/mytheme/footer.rhtml').returns(true) + expects(:render).with(:file => footer).returns("BLI") + + assert_equal "BLI", theme_footer + end + + should 'ignore unexisting theme footer' do + stubs(:theme_path).returns('/user_themes/mytheme') + footer = '../../public/user_themes/mytheme/footer.rhtml' + + File.expects(:exists?).with(RAILS_ROOT + '/app/views/../../public/user_themes/mytheme/footer.rhtml').returns(false) + expects(:render).with(:file => footer).never + + assert_nil theme_footer + end + + should 'expose theme owner' do + theme = mock + profile = mock + Theme.expects(:find).with('theme-under-test').returns(theme) + theme.expects(:owner).returns(profile) + profile.expects(:identifier).returns('sampleuser') + + stubs(:current_theme).returns('theme-under-test') + + assert_equal 'sampleuser', theme_owner + end protected diff --git a/test/unit/profile_test.rb b/test/unit/profile_test.rb index 10d2235..eb3f365 100644 --- a/test/unit/profile_test.rb +++ b/test/unit/profile_test.rb @@ -854,6 +854,33 @@ class ProfileTest < Test::Unit::TestCase assert_equal 1, p.boxes[0].blocks.size end + TMP_THEMES_DIR = RAILS_ROOT + '/test/tmp/profile_themes' + should 'have themes' do + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR) + + begin + p1 = Profile.create!(:name => 'test profile 1', :identifier => 'test_profile1') + t = Theme.new('test_theme'); t.owner = p1; t.save + + assert_equal [t], p1.themes + ensure + FileUtils.rm_rf(TMP_THEMES_DIR) + end + end + + should 'find theme by id' do + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR) + + begin + p1 = Profile.create!(:name => 'test profile 1', :identifier => 'test_profile1') + t = Theme.new('test_theme'); t.owner = p1; t.save + + assert_equal t, p1.find_theme('test_theme') + ensure + FileUtils.rm_rf(TMP_THEMES_DIR) + end + end + private def assert_invalid_identifier(id) diff --git a/test/unit/theme_test.rb b/test/unit/theme_test.rb index 86af0a9..2a77671 100644 --- a/test/unit/theme_test.rb +++ b/test/unit/theme_test.rb @@ -1,6 +1,17 @@ require File.dirname(__FILE__) + '/../test_helper' class ThemeTest < ActiveSupport::TestCase + + TMP_THEMES_DIR = RAILS_ROOT + '/test/tmp/themes' + + def setup + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR) + end + + def teardown + FileUtils.rm_rf(TMP_THEMES_DIR) + end + should 'list system themes' do Dir.expects(:glob).with(RAILS_ROOT + '/public/designs/themes/*').returns( [ @@ -16,5 +27,127 @@ class ThemeTest < ActiveSupport::TestCase assert_equal 'the-id', Theme.new('the-id').name end + should 'create theme' do + t = Theme.create('mytheme') + assert_equal t, Theme.find('mytheme') + end + + should 'not be able to create two themes with the same identifier' do + Theme.create('themeid') + assert_raise Theme::DuplicatedIdentifier do + Theme.create('themeid') + end + end + + should 'not be able to create a theme named after a system theme' do + Theme.expects(:system_themes).returns([Theme.new('somesystemtheme')]) + assert_raise Theme::DuplicatedIdentifier do + Theme.create('somesystemtheme') + end + end + + should 'be able to add new CSS file to theme' do + t = Theme.create('mytheme') + t.add_css('common.css') + assert_equal '', File.read(TMP_THEMES_DIR + '/mytheme/stylesheets/common.css') + end + + should 'be able to update CSS file' do + t = Theme.create('mytheme') + t.add_css('common.css') + t.update_css('common.css', '/* only a comment */') + assert_equal '/* only a comment */', File.read(TMP_THEMES_DIR + '/mytheme/stylesheets/common.css') + end + + should 'be able to get content of CSS file' do + t = Theme.create('mytheme') + t.update_css('common.css', '/* only a comment */') + assert_equal '/* only a comment */', t.read_css('common.css') + end + + should 'force .css suffix for CSS files when adding' do + t = Theme.create('mytheme') + t.add_css('xyz') + assert_includes t.css_files, 'xyz.css' + end + + should 'list CSS files' do + t = Theme.create('mytheme') + t.add_css('one.css') + t.add_css('two.css') + assert_includes t.css_files, 'one.css' + assert_includes t.css_files, 'two.css' + end + + should 'add default stylesheets' do + theme = Theme.create('test') + %w[ common help menu article button search blocks forms login-box ].each do |item| + assert_includes theme.css_files, item + '.css' + end + end + + should 'be able to save twice' do + t = Theme.new('testtheme') + + assert_nothing_raised do + t.save + t.save + end + end + + should 'have an owner' do + profile = create_user('testinguser').person + t = Theme.new('mytheme') + t.owner = profile + t.save + + t = Theme.find('mytheme') + assert_equal profile, t.owner + end + + should 'have no owner by default' do + assert_nil Theme.new('test').owner + end + + should 'be able to find by owner' do + profile = create_user('testinguser').person + t = Theme.new('mytheme') + t.owner = profile + t.save + + assert_equal [t], Theme.find_by_owner(profile) + end + + should 'be able to set attributes in constructor' do + p = create_user('testuser').person + assert_equal p, Theme.new('test', :owner => p).owner + end + + should 'pass attributes to constructor' do + p = create_user('testuser').person + assert_equal p, Theme.create('test', :owner => p).owner + end + + should 'have a name' do + theme = Theme.new('mytheme', :name => 'My Theme') + assert_equal 'My Theme', theme.name + assert_equal 'My Theme', theme.config['name'] + end + + should 'insert image' do + theme = Theme.create('mytheme') + theme.add_image('test.png', 'FAKE IMAGE DATA') + + assert_equal 'FAKE IMAGE DATA', File.read(TMP_THEMES_DIR + '/mytheme/images/test.png') + end + + should 'list images' do + theme = Theme.create('mytheme') + theme.add_image('one.png', 'FAKE IMAGE DATA') + theme.add_image('two.png', 'FAKE IMAGE DATA') + + assert_equivalent [ 'one.png', 'two.png' ], theme.image_files + end + end -- libgit2 0.21.2