Commit f2258e83ec5bb7a0a8411b389b60618df0d9e227

Authored by AntonioTerceiro
1 parent 9f8b8883

ActionItem122: implementing theme editor

git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@2425 3f533792-8f58-4932-b0fe-aaf55b0a4547
app/controllers/my_profile/themes_controller.rb
1 class ThemesController < MyProfileController 1 class ThemesController < MyProfileController
2 2
  3 + protect 'edit_appearance', :profile
3 no_design_blocks 4 no_design_blocks
4 5
5 def set 6 def set
@@ -12,4 +13,66 @@ class ThemesController &lt; MyProfileController @@ -12,4 +13,66 @@ class ThemesController &lt; MyProfileController
12 @selected_theme = profile.theme 13 @selected_theme = profile.theme
13 end 14 end
14 15
  16 + def new
  17 + if !request.xhr?
  18 + id = params[:name].to_slug
  19 + t = Theme.new(id, :name => params[:name], :owner => profile)
  20 + t.save
  21 + redirect_to :action => 'index'
  22 + else
  23 + render :action => 'new', :layout => false
  24 + end
  25 + end
  26 +
  27 + def edit
  28 + @theme = profile.find_theme(params[:id])
  29 + @css_files = @theme.css_files
  30 + @image_files = @theme.image_files
  31 + end
  32 +
  33 + def add_css
  34 + @theme = profile.find_theme(params[:id])
  35 + if request.xhr?
  36 + render :action => 'add_css', :layout => false
  37 + else
  38 + @theme.add_css(params[:css])
  39 + redirect_to :action => 'edit', :id => @theme.id
  40 + end
  41 + end
  42 +
  43 + def css_editor
  44 + @theme = profile.find_theme(params[:id])
  45 + @css = params[:css]
  46 +
  47 + @code = @theme.read_css(@css)
  48 + render :action => 'css_editor', :layout => false
  49 + end
  50 +
  51 + post_only :update_css
  52 + def update_css
  53 + @theme = profile.find_theme(params[:id])
  54 + @theme.update_css(params[:css], params[:csscode])
  55 + redirect_to :action => 'edit', :id => @theme.id
  56 + end
  57 +
  58 + def add_image
  59 + @theme = profile.find_theme(params[:id])
  60 + if request.xhr?
  61 + render :action => 'add_image', :layout => false
  62 + else
  63 + @theme.add_image(params[:image].original_filename, params[:image].read)
  64 + redirect_to :action => 'edit', :id => @theme.id
  65 + end
  66 + end
  67 +
  68 + def start_test
  69 + session[:theme] = params[:id]
  70 + redirect_to :controller => 'content_viewer', :profile => profile.identifier, :action => 'view_page'
  71 + end
  72 +
  73 + def stop_test
  74 + session[:theme] = nil
  75 + redirect_to :action => 'index'
  76 + end
  77 +
15 end 78 end
app/helpers/application_helper.rb
@@ -220,24 +220,12 @@ module ApplicationHelper @@ -220,24 +220,12 @@ module ApplicationHelper
220 link_to(content_tag('span', label), url, html_options.merge(:class => the_class )) 220 link_to(content_tag('span', label), url, html_options.merge(:class => the_class ))
221 end 221 end
222 222
223 - def button_to_function(type, label, js_code, html_options = {})  
224 - html_options[:class] = "button with-text" unless html_options[:class]  
225 - html_options[:class] << " icon-#{type}"  
226 - link_to_function(label, js_code, html_options)  
227 - end  
228 -  
229 def button_to_function(type, label, js_code, html_options = {}, &block) 223 def button_to_function(type, label, js_code, html_options = {}, &block)
230 html_options[:class] = "button with-text" unless html_options[:class] 224 html_options[:class] = "button with-text" unless html_options[:class]
231 html_options[:class] << " icon-#{type}" 225 html_options[:class] << " icon-#{type}"
232 link_to_function(label, js_code, html_options, &block) 226 link_to_function(label, js_code, html_options, &block)
233 end 227 end
234 228
235 - def button_to_function_without_text(type, label, js_code, html_options = {})  
236 - html_options[:class] = "" unless html_options[:class]  
237 - html_options[:class] << " button icon-#{type}"  
238 - link_to_function(content_tag('span', label), js_code, html_options)  
239 - end  
240 -  
241 def button_to_function_without_text(type, label, js_code, html_options = {}, &block) 229 def button_to_function_without_text(type, label, js_code, html_options = {}, &block)
242 html_options[:class] = "" unless html_options[:class] 230 html_options[:class] = "" unless html_options[:class]
243 html_options[:class] << " button icon-#{type}" 231 html_options[:class] << " button icon-#{type}"
@@ -323,12 +311,21 @@ module ApplicationHelper @@ -323,12 +311,21 @@ module ApplicationHelper
323 def filename_for_stylesheet(name, in_theme) 311 def filename_for_stylesheet(name, in_theme)
324 result = '' 312 result = ''
325 if in_theme 313 if in_theme
326 - result << '/designs/themes/' + current_theme 314 + result << theme_path
327 end 315 end
328 result << '/stylesheets/' << name << '.css' 316 result << '/stylesheets/' << name << '.css'
329 end 317 end
330 318
  319 + def theme_path
  320 + if session[:theme]
  321 + '/user_themes/' + current_theme
  322 + else
  323 + '/designs/themes/' + current_theme
  324 + end
  325 + end
  326 +
331 def current_theme 327 def current_theme
  328 + return session[:theme] if (session[:theme])
332 p = profile 329 p = profile
333 if p 330 if p
334 p.theme 331 p.theme
@@ -337,6 +334,21 @@ module ApplicationHelper @@ -337,6 +334,21 @@ module ApplicationHelper
337 end 334 end
338 end 335 end
339 336
  337 + def theme_footer
  338 + footer = ('../../public' + theme_path + '/footer.rhtml')
  339 + if File.exists?(RAILS_ROOT + '/app/views/' + footer)
  340 + render :file => footer
  341 + end
  342 + end
  343 +
  344 + def is_testing_theme
  345 + !@controller.session[:theme].nil?
  346 + end
  347 +
  348 + def theme_owner
  349 + Theme.find(current_theme).owner.identifier
  350 + end
  351 +
340 # generates a image tag for the profile. 352 # generates a image tag for the profile.
341 # 353 #
342 # If the profile has no image set yet, then a default image is used. 354 # If the profile has no image set yet, then a default image is used.
@@ -492,8 +504,7 @@ module ApplicationHelper @@ -492,8 +504,7 @@ module ApplicationHelper
492 504
493 def theme_option(opt = nil) 505 def theme_option(opt = nil)
494 conf = RAILS_ROOT.to_s() + 506 conf = RAILS_ROOT.to_s() +
495 - '/public/designs/themes/' +  
496 - current_theme.to_s() + 507 + '/public' + theme_path +
497 '/theme.yml' 508 '/theme.yml'
498 if File.exists?(conf) 509 if File.exists?(conf)
499 opt ? YAML.load_file(conf)[opt.to_s()] : YAML.load_file(conf) 510 opt ? YAML.load_file(conf)[opt.to_s()] : YAML.load_file(conf)
@@ -529,7 +540,7 @@ module ApplicationHelper @@ -529,7 +540,7 @@ module ApplicationHelper
529 return if option.nil? 540 return if option.nil?
530 html = [] 541 html = []
531 option.each do |file| 542 option.each do |file|
532 - file = '/designs/themes/'+ current_theme.to_s() + 543 + file = theme_path +
533 '/javascript/'+ file +'.js' 544 '/javascript/'+ file +'.js'
534 if File.exists? RAILS_ROOT.to_s() +'/public'+ file 545 if File.exists? RAILS_ROOT.to_s() +'/public'+ file
535 html << javascript_src_tag( file, {} ) 546 html << javascript_src_tag( file, {} )
app/models/profile.rb
@@ -35,6 +35,7 @@ class Profile &lt; ActiveRecord::Base @@ -35,6 +35,7 @@ class Profile &lt; ActiveRecord::Base
35 'validate_enterprise' => N_('Validate enterprise'), 35 'validate_enterprise' => N_('Validate enterprise'),
36 'perform_task' => N_('Perform task'), 36 'perform_task' => N_('Perform task'),
37 'moderate_comments' => N_('Moderate comments'), 37 'moderate_comments' => N_('Moderate comments'),
  38 + 'edit_appearance' => N_('Edit appearance'),
38 } 39 }
39 40
40 acts_as_accessible 41 acts_as_accessible
@@ -450,4 +451,13 @@ class Profile &lt; ActiveRecord::Base @@ -450,4 +451,13 @@ class Profile &lt; ActiveRecord::Base
450 def public? 451 def public?
451 public_profile 452 public_profile
452 end 453 end
  454 +
  455 + def themes
  456 + Theme.find_by_owner(self)
  457 + end
  458 +
  459 + def find_theme(the_id)
  460 + themes.find { |item| item.id == the_id }
  461 + end
  462 +
453 end 463 end
app/models/theme.rb
1 class Theme 1 class Theme
2 2
  3 + class << self
  4 + def system_themes
  5 + Dir.glob(RAILS_ROOT + '/public/designs/themes/*').map do |item|
  6 + File.basename(item)
  7 + end.map do |item|
  8 + new(item)
  9 + end
  10 + end
  11 +
  12 + def user_themes_dir
  13 + File.join(RAILS_ROOT, 'public', 'user_themes')
  14 + end
  15 +
  16 + def create(id, attributes = {})
  17 + if find(id) || system_themes.map(&:id).include?(id)
  18 + raise DuplicatedIdentifier
  19 + end
  20 + Theme.new(id, attributes).save
  21 + end
  22 +
  23 + def find(the_id)
  24 + if File.directory?(File.join(user_themes_dir, the_id))
  25 + Theme.new(the_id)
  26 + else
  27 + nil
  28 + end
  29 + end
  30 +
  31 + def find_by_owner(owner)
  32 + Dir.glob(File.join(user_themes_dir, '*', 'theme.yml')).select do |desc|
  33 + config = YAML.load_file(desc)
  34 + (config['owner_type'] == owner.class.base_class.name) && (config['owner_id'] == owner.id)
  35 + end.map do |desc|
  36 + Theme.find(File.basename(File.dirname(desc)))
  37 + end
  38 + end
  39 +
  40 + end
  41 +
  42 + class DuplicatedIdentifier < Exception; end
  43 +
3 attr_reader :id 44 attr_reader :id
  45 + attr_reader :config
4 46
5 - def initialize(id) 47 + def initialize(id, attributes = {})
6 @id = id 48 @id = id
  49 + load_config
  50 + attributes.each do |k,v|
  51 + self.send("#{k}=", v)
  52 + end
7 end 53 end
8 54
9 def name 55 def name
10 - id 56 + config['name'] || id
11 end 57 end
12 58
13 - class << self  
14 - def system_themes  
15 - Dir.glob(RAILS_ROOT + '/public/designs/themes/*').map do |item|  
16 - File.basename(item)  
17 - end.map do |item|  
18 - new(item)  
19 - end 59 + def name=(value)
  60 + config['name'] = value
  61 + end
  62 +
  63 + def ==(other)
  64 + other.is_a?(self.class) && (other.id == self.id)
  65 + end
  66 +
  67 + def add_css(filename)
  68 + FileUtils.mkdir_p(stylesheets_directory)
  69 + FileUtils.touch(stylesheet_path(filename))
  70 + end
  71 +
  72 + def update_css(filename, content)
  73 + add_css(filename)
  74 + File.open(stylesheet_path(filename), 'w') do |f|
  75 + f.write(content)
  76 + end
  77 + end
  78 +
  79 + def read_css(filename)
  80 + File.read(stylesheet_path(filename))
  81 + end
  82 +
  83 + def css_files
  84 + Dir.glob(File.join(stylesheets_directory, '*.css')).map { |f| File.basename(f) }
  85 + end
  86 +
  87 + def add_image(filename, data)
  88 + FileUtils.mkdir_p(images_directory)
  89 + File.open(image_path(filename), 'w') do |f|
  90 + f.write(data)
20 end 91 end
21 end 92 end
  93 +
  94 + def image_files
  95 + Dir.glob(image_path('*')).map {|item| File.basename(item)}
  96 + end
  97 +
  98 + def stylesheet_path(filename)
  99 + suffix = ''
  100 + unless filename =~ /\.css$/
  101 + suffix = '.css'
  102 + end
  103 + File.join(stylesheets_directory, filename + suffix)
  104 + end
  105 +
  106 + def stylesheets_directory
  107 + File.join(Theme.user_themes_dir, self.id, 'stylesheets')
  108 + end
  109 +
  110 + def image_path(filename)
  111 + File.join(images_directory, filename)
  112 + end
  113 +
  114 + def images_directory
  115 + File.join(self.class.user_themes_dir, id, 'images')
  116 + end
  117 +
  118 + def save
  119 + FileUtils.mkdir_p(self.class.user_themes_dir)
  120 + FileUtils.mkdir_p(File.join(self.class.user_themes_dir, id))
  121 + %w[ common help menu article button search blocks forms login-box ].each do |item|
  122 + add_css(item)
  123 + end
  124 + write_config
  125 + self
  126 + end
  127 +
  128 + def owner
  129 + return nil unless config['owner_type'] && config['owner_id']
  130 + @owner ||= config['owner_type'].constantize.find(config['owner_id'])
  131 + end
  132 +
  133 + def owner=(model)
  134 + config['owner_type'] = model.class.base_class.name
  135 + config['owner_id'] = model.id
  136 + @owner = model
  137 + end
  138 +
  139 + protected
  140 +
  141 + def write_config
  142 + File.open(File.join(self.class.user_themes_dir, self.id, 'theme.yml'), 'w') do |f|
  143 + f.write(config.to_yaml)
  144 + end
  145 + end
  146 +
  147 + def load_config
  148 + if File.exists?(File.join(self.class.user_themes_dir, self.id, 'theme.yml'))
  149 + @config = YAML.load_file(File.join(self.class.user_themes_dir, self.id, 'theme.yml'))
  150 + else
  151 + @config = {}
  152 + end
  153 + end
  154 +
22 end 155 end
app/views/layouts/application.rhtml
@@ -154,7 +154,7 @@ @@ -154,7 +154,7 @@
154 </div><!-- id="wrap" --> 154 </div><!-- id="wrap" -->
155 155
156 <div id="footer"> 156 <div id="footer">
157 - <%= render :file => ('../../public/designs/themes/' + current_theme + '/footer.rhtml' ) %> 157 + <%= theme_footer %>
158 </div><!-- id="footer" --> 158 </div><!-- id="footer" -->
159 159
160 <div id="helpBox" style="display:none"> 160 <div id="helpBox" style="display:none">
@@ -177,5 +177,9 @@ @@ -177,5 +177,9 @@
177 </div> 177 </div>
178 <%= javascript_include_tag 'better-browser-promotion' %> 178 <%= javascript_include_tag 'better-browser-promotion' %>
179 179
  180 + <% if is_testing_theme %>
  181 + <%= render :file => 'shared/theme_test_panel' %>
  182 + <% end %>
  183 +
180 </body> 184 </body>
181 </html> 185 </html>
app/views/shared/codepress.rhtml
1 -<%= javascript_include_tag 'codepress/codepress.js' %> 1 +<%= javascript_include_tag 'codepress/codepress' %>
app/views/shared/theme_test_panel.rhtml 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +<div id='theme-test-panel'>
  2 + <h4>
  3 + <%= _('Testing theme "%s"') % current_theme %>
  4 + </h4>
  5 +
  6 + <p><small><em><%= _('You can move this window away to have a better visualization of specific parts of screen.') %></em></small></p>
  7 +
  8 + <% button_bar do %>
  9 + <%= button(:ok, _('Finished testing'), :controller => 'themes', :profile => theme_owner, :action => 'stop_test', :id => current_theme) %>
  10 + <%= button(:edit, _('Edit theme'), :controller => 'themes', :profile => theme_owner, :action => 'edit', :id => current_theme) %>
  11 + <% end %>
  12 +</div>
  13 +<%= draggable_element('theme-test-panel') %>
app/views/themes/add_css.rhtml 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +<h2><%= _('Add a CSS file') %></h2>
  2 +
  3 +<% form_tag do %>
  4 + <%= labelled_form_field(_('File name'), text_field_tag('css')) %>
  5 +
  6 + <% button_bar do %>
  7 + <%= submit_button(:add, _('Add')) %>
  8 + <%= lightbox_close_button(_('Cancel')) %>
  9 + <% end %>
  10 +
  11 +<% end %>
app/views/themes/add_image.rhtml 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +<% form_tag({:action => 'add_image', :id => @theme.id}, :multipart => true) do %>
  2 + <%= labelled_form_field(_('Choose the image file'), file_field_tag(:image)) %>
  3 + <% button_bar do %>
  4 + <%= submit_button(:add, _('Add image')) %>
  5 + <% end %>
  6 +<% end %>
app/views/themes/css_editor.rhtml 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +<h2><%= _('CSS code: "%s"') % @css %></h2>
  2 +
  3 +<% form_tag({:action => 'update_css', :id => @theme.id }, :name => 'csscode_form') do %>
  4 + <%= hidden_field_tag('css', @css) %>
  5 + <%= text_area_tag('csscode', @code, :id => "codepressWindow", :class => 'codepress css') %>
  6 + <% button_bar do %>
  7 + <%= submit_button(:save, _('Save')) %>
  8 + <% end %>
  9 +<% end %>
  10 +
  11 +<!--<script type='text/javascript'>-->
  12 + <!--CodePress.run();-->
  13 +<!--</script>-->
app/views/themes/edit.rhtml
1 <form> 1 <form>
2 <%# FIXME %> 2 <%# FIXME %>
3 <h1 id='theme-name'> 3 <h1 id='theme-name'>
4 - <%= _('Editing theme %s') % text_field_tag('theme_name', 'my theme') %>  
5 - <%= icon_button(:save, _('Save'), '#') %> 4 + <%= _('Editing theme "%s"') % @theme.name %>
  5 + <%= button(:search, _('Preview this theme'), :action => 'index') %>
  6 + <%= button(:back, _('Back'), :action => 'index') %>
6 </h1> 7 </h1>
7 </form> 8 </form>
8 9
  10 +
9 <div id='css-files-list'> 11 <div id='css-files-list'>
10 <h2><%= _('CSS files') %></h2> 12 <h2><%= _('CSS files') %></h2>
11 <ul> 13 <ul>
12 - <li><a href="">common.css</a></li>  
13 - <li><a href="">header.css</a></li>  
14 - <li><a href="">footer.css</a></li>  
15 - <li><a href="">sideboxes.css</a></li>  
16 - <li><a href="">otherstuff1.css</a></li>  
17 - <li><a href="">otherstuff1.css</a></li>  
18 - <li><a href="">otherstuff1.css</a></li>  
19 - <li><a href="">otherstuff2css</a></li>  
20 - <li><a href="">otherstuff3.css</a></li>  
21 - <li><a href="">otherstuff4.css</a></li>  
22 - <li><a href="">otherstuff2css</a></li>  
23 - <li><a href="">otherstuff3.css</a></li>  
24 - <li><a href="">otherstuff4.css</a></li>  
25 - <li><a href="">otherstuff2css</a></li>  
26 - <li><a href="">otherstuff3.css</a></li>  
27 - <li><a href="">otherstuff4.css</a></li> 14 + <% for css in @css_files %>
  15 + <li><%= link_to_remote(css, :url => { :action => 'css_editor', :id => @theme.id, :css => css }, :update => { :success => 'css-code' }) %></li>
  16 + <% end %>
28 </ul> 17 </ul>
29 <% button_bar do %> 18 <% button_bar do %>
30 - <%= button(:add, _('New CSS'), '') %> 19 + <%= lightbox_button(:add, _('New CSS'), :action => 'add_css', :id => @theme.id) %>
31 <% end %> 20 <% end %>
32 </div> 21 </div>
33 22
34 <div id='image-files-list'> 23 <div id='image-files-list'>
35 <h2><%= _('Images') %></h2> 24 <h2><%= _('Images') %></h2>
36 <ul> 25 <ul>
37 - <li><img src='/images/icons-app/gtk-cancel.png' /><span>Image 1</span></li>  
38 - <li><img src='/images/icons-app/cms.png' /><span>Image_2_a_very_large_filename</span></li>  
39 - <li><img src='http://www.ynternet.org/banner/' /><span>a wide image</span></li>  
40 - <li><img src='/images/icons-app/enable.png' /><span>Image 3</span></li>  
41 - <li><img src='/images/icons-app/gnome-globe.png' /><span>Image 4</span></li>  
42 - <li><img src='/images/icons-app/gtk-yes.png' /><span>Image 5</span></li>  
43 - <li><img src='/images/icons-app/mail.png' /><span>Image 6</span></li> 26 + <% for image in @image_files %>
  27 + <li><%= image_tag("/user_themes/#{@theme.id}/images/#{image}") %></li>
  28 + <% end %>
44 </ul> 29 </ul>
45 <% button_bar do %> 30 <% button_bar do %>
46 - <%= button(:add, _('Upload image'), '') %> 31 + <%= lightbox_button(:add, _('Add image'), :action => 'add_image', :id => @theme.id) %>
47 <% end %> 32 <% end %>
48 </div> 33 </div>
49 34
50 -<form id='css-code'>  
51 - <%# FIXME %>  
52 - <h2><%= _('CSS code: "%s"') % 'common.css' %></h2>  
53 - <textarea name='csscode' id="csscode" class='codepress css'>  
54 - body {  
55 - background: yellow;  
56 - }  
57 - </textarea>  
58 - <% button_bar do %>  
59 - <%= button(:save, _('Save'), '') %>  
60 - <% end %>  
61 -</form> 35 +<div id='css-code'>
  36 + <center style='padding-top: 5em;'>
  37 + <em><%= _('Select a CSS file to edit') %></em>
  38 + </center>
  39 +</div>
62 40
63 -<%= render :file => 'shared/codepress' %> 41 +<%# javascript_include_tag 'codepress/codepress' %>
app/views/themes/index.rhtml
@@ -2,13 +2,53 @@ @@ -2,13 +2,53 @@
2 2
3 <h2><%= _('Select theme') %></h2> 3 <h2><%= _('Select theme') %></h2>
4 4
5 -<div>  
6 - <% for theme in @themes %>  
7 - <%= 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 %> 5 +<table style='margin: auto'>
  6 + <% for themes in @themes.in_groups_of(3) %>
  7 + <tr>
  8 + <% for theme in themes %>
  9 + <td>
  10 + <%# FIXME add proper thumbnails %>
  11 + <%= image_tag('/images/icons-app/design-editor.png', :alt => (_('The "%s" theme.') % theme.name)) if theme %>
  12 + </td>
  13 + <td>
  14 + <strong><%= theme.name if theme%></strong><br/>
  15 + <%= link_to(_('Use this theme'), :action => 'set', :id => theme.id) if theme %>
  16 + </td>
  17 + <td>
  18 + &nbsp;&nbsp;&nbsp;&nbsp;
  19 + </td>
  20 + <% end %>
  21 + </tr>
  22 + <tr><td colspan='3'>&nbsp;</td></tr>
8 <% end %> 23 <% end %>
9 -</div> 24 +</table>
10 25
  26 +<h2><%= _('My themes') %></h2>
  27 +
  28 +<table style='margin: auto'>
  29 + <% for themes in profile.themes.in_groups_of(3) %>
  30 + <tr>
  31 + <% for theme in themes %>
  32 + <td>
  33 + <%# FIXME add proper thumbnails %>
  34 + <%= image_tag('/images/icons-app/design-editor.png', :alt => (_('The "%s" theme.') % theme.name)) if theme %>
  35 + </td>
  36 + <td>
  37 + <strong><%= theme.name if theme%></strong><br/>
  38 + <%= link_to(_('Edit this theme'), :action => 'edit', :id => theme.id) if theme %>
  39 + <br/>
  40 + <%= link_to(_('Test this theme'), :action => 'start_test', :id => theme.id) if theme %>
  41 + </td>
  42 + <td>
  43 + &nbsp;&nbsp;&nbsp;&nbsp;
  44 + </td>
  45 + <% end %>
  46 + </tr>
  47 + <tr><td colspan='3'>&nbsp;</td></tr>
  48 + <% end %>
  49 +</table>
11 50
12 <% button_bar do %> 51 <% button_bar do %>
  52 + <%= lightbox_button(:add, _('New theme ...'), :action => 'new') %>
13 <%= button(:back, _('Back'), :controller => 'profile_editor', :action => 'index') %> 53 <%= button(:back, _('Back'), :controller => 'profile_editor', :action => 'index') %>
14 <% end %> 54 <% end %>
app/views/themes/new.rhtml 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<h2><%= _('Create new theme') %></h2>
  2 +
  3 +<% form_tag(:action => 'new') do %>
  4 +
  5 + <%= labelled_form_field(_('Name of the new theme:'), text_field_tag(:name)) %>
  6 +
  7 + <% button_bar do %>
  8 + <%= submit_button(:save, _('Create')) %>
  9 + <% end %>
  10 +<% end %>
db/migrate/051_add_edit_appearance_permission.rb 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +class AddEditAppearancePermission < ActiveRecord::Migration
  2 +
  3 + def self.up
  4 + [ Profile::Roles.admin, Environment::Roles.admin].each do |item|
  5 + item.permissions += [ 'edit_appearance' ]
  6 + item.save!
  7 + end
  8 + end
  9 +
  10 + def self.down
  11 + [ Profile::Roles.admin, Environment::Roles.admin].each do |item|
  12 + item.permissions -= [ 'edit_appearance' ]
  13 + item.save!
  14 + end
  15 + end
  16 +end
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 # 9 #
10 # It's strongly recommended to check this file into your version control system. 10 # It's strongly recommended to check this file into your version control system.
11 11
12 -ActiveRecord::Schema.define(:version => 50) do 12 +ActiveRecord::Schema.define(:version => 51) do
13 13
14 create_table "article_versions", :force => true do |t| 14 create_table "article_versions", :force => true do |t|
15 t.integer "article_id" 15 t.integer "article_id"
@@ -73,8 +73,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do @@ -73,8 +73,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do
73 t.boolean "virtual", :default => false 73 t.boolean "virtual", :default => false
74 end 74 end
75 75
76 - add_index "articles_categories", ["article_id"], :name => "index_articles_categories_on_article_id"  
77 add_index "articles_categories", ["category_id"], :name => "index_articles_categories_on_category_id" 76 add_index "articles_categories", ["category_id"], :name => "index_articles_categories_on_category_id"
  77 + add_index "articles_categories", ["article_id"], :name => "index_articles_categories_on_article_id"
78 78
79 create_table "blocks", :force => true do |t| 79 create_table "blocks", :force => true do |t|
80 t.string "title" 80 t.string "title"
@@ -114,8 +114,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do @@ -114,8 +114,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do
114 t.boolean "virtual", :default => false 114 t.boolean "virtual", :default => false
115 end 115 end
116 116
117 - add_index "categories_profiles", ["profile_id"], :name => "index_categories_profiles_on_profile_id"  
118 add_index "categories_profiles", ["category_id"], :name => "index_categories_profiles_on_category_id" 117 add_index "categories_profiles", ["category_id"], :name => "index_categories_profiles_on_category_id"
  118 + add_index "categories_profiles", ["profile_id"], :name => "index_categories_profiles_on_profile_id"
119 119
120 create_table "comments", :force => true do |t| 120 create_table "comments", :force => true do |t|
121 t.string "title" 121 t.string "title"
@@ -182,8 +182,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do @@ -182,8 +182,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do
182 t.datetime "updated_at" 182 t.datetime "updated_at"
183 end 183 end
184 184
185 - add_index "product_categorizations", ["product_id"], :name => "index_product_categorizations_on_product_id"  
186 add_index "product_categorizations", ["category_id"], :name => "index_product_categorizations_on_category_id" 185 add_index "product_categorizations", ["category_id"], :name => "index_product_categorizations_on_category_id"
  186 + add_index "product_categorizations", ["product_id"], :name => "index_product_categorizations_on_product_id"
187 187
188 create_table "products", :force => true do |t| 188 create_table "products", :force => true do |t|
189 t.integer "enterprise_id" 189 t.integer "enterprise_id"
@@ -254,8 +254,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do @@ -254,8 +254,8 @@ ActiveRecord::Schema.define(:version =&gt; 50) do
254 t.datetime "created_at" 254 t.datetime "created_at"
255 end 255 end
256 256
257 - add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"  
258 add_index "taggings", ["taggable_id", "taggable_type"], :name => "index_taggings_on_taggable_id_and_taggable_type" 257 add_index "taggings", ["taggable_id", "taggable_type"], :name => "index_taggings_on_taggable_id_and_taggable_type"
  258 + add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"
259 259
260 create_table "tags", :force => true do |t| 260 create_table "tags", :force => true do |t|
261 t.string "name" 261 t.string "name"
lib/tasks/populate.rake
@@ -36,6 +36,7 @@ def create_roles @@ -36,6 +36,7 @@ def create_roles
36 'post_content', 36 'post_content',
37 'edit_profile_design', 37 'edit_profile_design',
38 'manage_products', 38 'manage_products',
  39 + 'edit_appearance',
39 ]) 40 ])
40 Role.create!(:key => 'profile_admin', :name => N_('Profile Administrator'), :permissions => [ 41 Role.create!(:key => 'profile_admin', :name => N_('Profile Administrator'), :permissions => [
41 'edit_profile', 42 'edit_profile',
@@ -44,6 +45,7 @@ def create_roles @@ -44,6 +45,7 @@ def create_roles
44 'post_content', 45 'post_content',
45 'edit_profile_design', 46 'edit_profile_design',
46 'manage_products', 47 'manage_products',
  48 + 'edit_appearance',
47 ]) 49 ])
48 # members for enterprises, communities etc 50 # members for enterprises, communities etc
49 Role.create!(:key => "profile_member", :name => N_('Member'), :permissions => [ 51 Role.create!(:key => "profile_member", :name => N_('Member'), :permissions => [
public/stylesheets/common.css
@@ -387,3 +387,23 @@ div.pending-tasks { @@ -387,3 +387,23 @@ div.pending-tasks {
387 max-width: 100%; 387 max-width: 100%;
388 overflow: hidden; 388 overflow: hidden;
389 } 389 }
  390 +
  391 +/* theme test panel */
  392 +#theme-test-panel {
  393 + z-index: 1000;
  394 + position: absolute;
  395 + width: 300px;
  396 + height: 180;
  397 + right: 50px;
  398 + top: 50px;
  399 + background: white;
  400 + border: 2px solid black;
  401 +}
  402 +
  403 +#theme-test-panel {
  404 + text-align: center;
  405 + cursor: move;
  406 +}
  407 +#theme-test-panel .button-bar {
  408 + margin: 1em;
  409 +}
test/functional/application_controller_test.rb
@@ -193,4 +193,25 @@ class ApplicationControllerTest &lt; Test::Unit::TestCase @@ -193,4 +193,25 @@ class ApplicationControllerTest &lt; Test::Unit::TestCase
193 assert_tag :tag => 'base', :attributes => { :href => 'http://www.lala.net' } 193 assert_tag :tag => 'base', :attributes => { :href => 'http://www.lala.net' }
194 end 194 end
195 195
  196 + should 'display theme test panel when testing theme' do
  197 + @request.session[:theme] = 'my-test-theme'
  198 + theme = mock
  199 + profile = mock
  200 + theme.expects(:owner).returns(profile).at_least_once
  201 + profile.expects(:identifier).returns('testinguser').at_least_once
  202 + Theme.expects(:find).with('my-test-theme').returns(theme).at_least_once
  203 + get :index
  204 +
  205 + assert_tag :tag => 'div', :attributes => { :id => 'theme-test-panel' }, :descendant => {
  206 + :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/edit/my-test-theme'}
  207 + }
  208 + #{ :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/stop_test/my-test-theme'} }
  209 + end
  210 +
  211 + should 'not display theme test panel in general' do
  212 + @controller.stubs(:session).returns({})
  213 + get :index
  214 + assert_no_tag :tag => 'div', :attributes => { :id => 'theme-test-panel' }
  215 + end
  216 +
196 end 217 end
test/functional/themes_controller_test.rb
1 require File.dirname(__FILE__) + '/../test_helper' 1 require File.dirname(__FILE__) + '/../test_helper'
  2 +require 'themes_controller'
2 3
3 -class ThemesControllerTest < ActionController::TestCase 4 +class ThemesController; def rescue_action(e) raise e end; end
  5 +
  6 +class ThemesControllerTest < Test::Unit::TestCase
  7 +
  8 + def setup
  9 + @controller = ThemesController.new
  10 + @request = ActionController::TestRequest.new
  11 + @response = ActionController::TestResponse.new
  12 +
  13 + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR)
  14 +
  15 + @profile = create_user('testinguser').person
  16 + login_as('testinguser')
  17 + end
  18 + attr_reader :profile
  19 +
  20 + def teardown
  21 + FileUtils.rm_rf(TMP_THEMES_DIR)
  22 + end
  23 +
  24 + TMP_THEMES_DIR = RAILS_ROOT + '/test/tmp/themes_controller'
4 25
5 should 'display list of themes for selection' do 26 should 'display list of themes for selection' do
6 - profile = create_user('testinguser').person  
7 Theme.expects(:system_themes).returns([Theme.new('first'), Theme.new('second')]) 27 Theme.expects(:system_themes).returns([Theme.new('first'), Theme.new('second')])
8 get :index, :profile => 'testinguser' 28 get :index, :profile => 'testinguser'
9 29
10 %w[ first second ].each do |item| 30 %w[ first second ].each do |item|
11 - assert_tag :tag => 'a', :attributes => { :href => "/myprofile/testinguser/themes/set/#{item}" }, :descendant => { :tag => 'img' } 31 + assert_tag :tag => 'a', :attributes => { :href => "/myprofile/testinguser/themes/set/#{item}" }
12 end 32 end
13 end 33 end
14 34
15 - should 'save selection of theme' do  
16 - profile = create_user('testinguser').person 35 + should 'display list of my themes for edition' do
  36 + Theme.create('first', :owner => profile)
  37 + Theme.create('second', :owner => profile)
  38 +
  39 + get :index, :profile => 'testinguser'
  40 +
  41 + %w[ first second ].each do |item|
  42 + assert_tag :tag => 'a', :attributes => { :href => "/myprofile/testinguser/themes/edit/#{item}" }
  43 + end
  44 + end
17 45
  46 + should 'save selection of theme' do
18 get :set, :profile => 'testinguser', :id => 'onetheme' 47 get :set, :profile => 'testinguser', :id => 'onetheme'
19 profile.reload 48 profile.reload
20 assert_equal 'onetheme', profile.theme 49 assert_equal 'onetheme', profile.theme
21 end 50 end
22 51
23 should 'point back to control panel' do 52 should 'point back to control panel' do
24 - create_user('testinguser').person  
25 get :index, :profile => 'testinguser' 53 get :index, :profile => 'testinguser'
26 assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testinguser' }, :content => 'Back' 54 assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testinguser' }, :content => 'Back'
27 end 55 end
28 56
29 - should 'check access control when choosing theme' 57 + should 'display screen for creating new theme' do
  58 + @request.expects(:xhr?).returns(true).at_least_once
  59 + get :new, :profile => 'testinguser'
  60 + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/new', :method => /post/i }, :descendant => { :tag => 'input', :attributes => { :type => 'text', :name => 'name' } }
  61 + end
  62 +
  63 + should 'create a new theme' do
  64 + post :new, :profile => 'testinguser', :name => 'My theme'
  65 +
  66 + ok('theme should be created') do
  67 + profile.themes.first.id == 'my-theme'
  68 + end
  69 + end
  70 +
  71 + should 'edit a theme' do
  72 + theme = Theme.create('mytheme', :owner => profile)
  73 + get :edit, :profile => 'testinguser', :id => 'mytheme'
  74 +
  75 + assert_equal theme, assigns(:theme)
  76 + end
  77 +
  78 + should 'list CSS files in theme' do
  79 + theme = Theme.create('mytheme', :owner => profile)
  80 + theme.add_css('one.css')
  81 + theme.add_css('two.css')
  82 +
  83 + get :edit, :profile => 'testinguser', :id => 'mytheme'
  84 +
  85 + %w[ one.css two.css ].each do |item|
  86 + assert_includes assigns(:css_files), item
  87 + assert_tag :tag => 'li', :descendant => { :tag => 'a', :content => item}
  88 + end
  89 + end
  90 +
  91 + should 'display dialog for creating new CSS' do
  92 + theme = Theme.create('mytheme', :owner => profile)
  93 + @request.expects(:xhr?).returns(true)
  94 + get :add_css, :profile => 'testinguser', :id => 'mytheme'
  95 +
  96 + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/add_css/mytheme', :method => /post/i}
  97 + assert_tag :tag => 'input', :attributes => { :name => 'css', :type => 'text' }
  98 + assert_tag :tag => 'input', :attributes => { :type => 'submit' }
  99 + end
  100 +
  101 + should 'be able to add new CSS to theme' do
  102 + theme = Theme.create('mytheme', :owner => profile)
  103 + post :add_css, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css'
  104 +
  105 + assert_response :redirect
  106 +
  107 + reloaded_theme = Theme.find('mytheme')
  108 + assert_includes reloaded_theme.css_files, 'test.css'
  109 + end
  110 +
  111 + should 'load code from a given CSS file' do
  112 + theme = Theme.create('mytheme', :owner => profile); theme.update_css('test.css', '/* sample code */')
  113 + get :css_editor, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css'
  114 +
  115 + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/update_css/mytheme' }, :descendant => { :tag => 'textarea', :content => '/* sample code */' }
  116 + end
  117 +
  118 + should 'be able to save CSS code' do
  119 + theme = Theme.create('mytheme', :owner => profile); theme.update_css('test.css', '/* sample code */')
  120 + get :css_editor, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css'
  121 +
  122 + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/update_css/mytheme' }, :descendant => { :tag => 'input', :attributes => { :type => 'submit' } }
  123 + assert_tag :tag => 'form', :attributes => { :action => '/myprofile/testinguser/themes/update_css/mytheme' }, :descendant => { :tag => 'input', :attributes => { :type => 'hidden', :name => 'css', :value => 'test.css' } }
  124 + end
  125 +
  126 + should 'update css code when saving' do
  127 + theme = Theme.create('mytheme', :owner => profile); theme.update_css('test.css', '/* sample code */')
  128 + post :update_css, :profile => 'testinguser', :id => 'mytheme', :css => 'test.css', :csscode => 'body { background: white; }'
  129 + assert_equal 'body { background: white; }', theme.read_css('test.css')
  130 + end
  131 +
  132 + should 'list image files in theme' do
  133 + theme = Theme.create('mytheme', :owner => profile)
  134 + theme.add_image('one.png', 'FAKE IMAGE DATA 1')
  135 + theme.add_image('two.png', 'FAKE IMAGE DATA 2')
  136 +
  137 + get :edit, :profile => 'testinguser', :id => 'mytheme'
  138 +
  139 + assert_tag :tag => 'img', :attributes => { :src => '/user_themes/mytheme/images/one.png' }
  140 + assert_tag :tag => 'img', :attributes => { :src => '/user_themes/mytheme/images/two.png' }
  141 + end
  142 +
  143 + should 'display "add image" button' do
  144 + theme = Theme.create('mytheme', :owner => profile)
  145 + get :edit, :profile => 'testinguser', :id => 'mytheme'
  146 +
  147 + assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/add_image/mytheme' }
  148 + end
30 149
31 - should 'check access control when editing themes' 150 + should 'display the "add image" dialog' do
  151 + theme = Theme.create('mytheme', :owner => profile)
  152 + @request.expects(:xhr?).returns(true)
32 153
33 - should 'only allow environment-approved themes to be selected' 154 + get :add_image, :profile => 'testinguser', :id => 'mytheme'
  155 + 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' } }
  156 + end
34 157
35 - should 'list user-created themes with link for editing' 158 + should 'be able to add new image to theme' do
  159 + theme = Theme.create('mytheme', :owner => profile)
  160 + @request.expects(:xhr?).returns(false)
36 161
37 - should 'offer to create new theme' 162 + post :add_image, :profile => 'testinguser', :id => 'mytheme', :image => fixture_file_upload('/files/rails.png', 'image/png', :binary)
  163 + assert_redirected_to :action => "edit", :id => 'mytheme'
  164 + assert theme.image_files.include?('rails.png')
  165 + 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')
  166 + end
38 167
39 - should 'be able to save new theme' 168 + should 'link to "test theme"' do
  169 + Theme.create('one', :owner => profile)
  170 + Theme.create('two', :owner => profile)
  171 + get :index, :profile => 'testinguser'
40 172
41 - should 'be able to save existing theme' 173 + %w[ one two ].each do |item|
  174 + assert_tag :tag => 'a', :attributes => { :href => '/myprofile/testinguser/themes/start_test/' + item }
  175 + end
  176 + end
  177 +
  178 + should 'start testing theme' do
  179 + theme = Theme.create('theme-under-test', :owner => profile)
  180 + post :start_test, :profile => 'testinguser', :id => 'theme-under-test'
  181 +
  182 + assert_equal 'theme-under-test', session[:theme]
  183 + assert_redirected_to :controller => 'content_viewer', :profile => 'testinguser'
  184 + end
  185 +
  186 + should 'stop testing theme' do
  187 + theme = Theme.create('theme-under-test', :owner => profile)
  188 + post :stop_test, :profile => 'testinguser', :id => 'theme-under-test'
  189 +
  190 + assert_nil session[:theme]
  191 + assert_redirected_to :action => 'index'
  192 + end
42 193
43 end 194 end
test/unit/application_helper_test.rb
@@ -4,6 +4,10 @@ class ApplicationHelperTest &lt; Test::Unit::TestCase @@ -4,6 +4,10 @@ class ApplicationHelperTest &lt; Test::Unit::TestCase
4 4
5 include ApplicationHelper 5 include ApplicationHelper
6 6
  7 + def setup
  8 + self.stubs(:session).returns({})
  9 + end
  10 +
7 should 'calculate correctly partial for object' do 11 should 'calculate correctly partial for object' do
8 self.stubs(:params).returns({:controller => 'test'}) 12 self.stubs(:params).returns({:controller => 'test'})
9 13
@@ -150,6 +154,52 @@ class ApplicationHelperTest &lt; Test::Unit::TestCase @@ -150,6 +154,52 @@ class ApplicationHelperTest &lt; Test::Unit::TestCase
150 assert_equal 'my-profile-theme', current_theme 154 assert_equal 'my-profile-theme', current_theme
151 end 155 end
152 156
  157 + should 'override theme with testing theme from session' do
  158 + stubs(:session).returns(:theme => 'theme-under-test')
  159 + assert_equal 'theme-under-test', current_theme
  160 + end
  161 +
  162 + should 'point to system theme path by default' do
  163 + expects(:current_theme).returns('my-system-theme')
  164 + assert_equal '/designs/themes/my-system-theme', theme_path
  165 + end
  166 +
  167 + should 'point to user theme path when testing theme' do
  168 + stubs(:session).returns({:theme => 'theme-under-test'})
  169 + assert_equal '/user_themes/theme-under-test', theme_path
  170 + end
  171 +
  172 + should 'render theme footer' do
  173 + stubs(:theme_path).returns('/user_themes/mytheme')
  174 + footer = '../../public/user_themes/mytheme/footer.rhtml'
  175 +
  176 + File.expects(:exists?).with(RAILS_ROOT + '/app/views/../../public/user_themes/mytheme/footer.rhtml').returns(true)
  177 + expects(:render).with(:file => footer).returns("BLI")
  178 +
  179 + assert_equal "BLI", theme_footer
  180 + end
  181 +
  182 + should 'ignore unexisting theme footer' do
  183 + stubs(:theme_path).returns('/user_themes/mytheme')
  184 + footer = '../../public/user_themes/mytheme/footer.rhtml'
  185 +
  186 + File.expects(:exists?).with(RAILS_ROOT + '/app/views/../../public/user_themes/mytheme/footer.rhtml').returns(false)
  187 + expects(:render).with(:file => footer).never
  188 +
  189 + assert_nil theme_footer
  190 + end
  191 +
  192 + should 'expose theme owner' do
  193 + theme = mock
  194 + profile = mock
  195 + Theme.expects(:find).with('theme-under-test').returns(theme)
  196 + theme.expects(:owner).returns(profile)
  197 + profile.expects(:identifier).returns('sampleuser')
  198 +
  199 + stubs(:current_theme).returns('theme-under-test')
  200 +
  201 + assert_equal 'sampleuser', theme_owner
  202 + end
153 203
154 protected 204 protected
155 205
test/unit/profile_test.rb
@@ -854,6 +854,33 @@ class ProfileTest &lt; Test::Unit::TestCase @@ -854,6 +854,33 @@ class ProfileTest &lt; Test::Unit::TestCase
854 assert_equal 1, p.boxes[0].blocks.size 854 assert_equal 1, p.boxes[0].blocks.size
855 end 855 end
856 856
  857 + TMP_THEMES_DIR = RAILS_ROOT + '/test/tmp/profile_themes'
  858 + should 'have themes' do
  859 + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR)
  860 +
  861 + begin
  862 + p1 = Profile.create!(:name => 'test profile 1', :identifier => 'test_profile1')
  863 + t = Theme.new('test_theme'); t.owner = p1; t.save
  864 +
  865 + assert_equal [t], p1.themes
  866 + ensure
  867 + FileUtils.rm_rf(TMP_THEMES_DIR)
  868 + end
  869 + end
  870 +
  871 + should 'find theme by id' do
  872 + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR)
  873 +
  874 + begin
  875 + p1 = Profile.create!(:name => 'test profile 1', :identifier => 'test_profile1')
  876 + t = Theme.new('test_theme'); t.owner = p1; t.save
  877 +
  878 + assert_equal t, p1.find_theme('test_theme')
  879 + ensure
  880 + FileUtils.rm_rf(TMP_THEMES_DIR)
  881 + end
  882 + end
  883 +
857 private 884 private
858 885
859 def assert_invalid_identifier(id) 886 def assert_invalid_identifier(id)
test/unit/theme_test.rb
1 require File.dirname(__FILE__) + '/../test_helper' 1 require File.dirname(__FILE__) + '/../test_helper'
2 2
3 class ThemeTest < ActiveSupport::TestCase 3 class ThemeTest < ActiveSupport::TestCase
  4 +
  5 + TMP_THEMES_DIR = RAILS_ROOT + '/test/tmp/themes'
  6 +
  7 + def setup
  8 + Theme.stubs(:user_themes_dir).returns(TMP_THEMES_DIR)
  9 + end
  10 +
  11 + def teardown
  12 + FileUtils.rm_rf(TMP_THEMES_DIR)
  13 + end
  14 +
4 should 'list system themes' do 15 should 'list system themes' do
5 Dir.expects(:glob).with(RAILS_ROOT + '/public/designs/themes/*').returns( 16 Dir.expects(:glob).with(RAILS_ROOT + '/public/designs/themes/*').returns(
6 [ 17 [
@@ -16,5 +27,127 @@ class ThemeTest &lt; ActiveSupport::TestCase @@ -16,5 +27,127 @@ class ThemeTest &lt; ActiveSupport::TestCase
16 assert_equal 'the-id', Theme.new('the-id').name 27 assert_equal 'the-id', Theme.new('the-id').name
17 end 28 end
18 29
  30 + should 'create theme' do
  31 + t = Theme.create('mytheme')
  32 + assert_equal t, Theme.find('mytheme')
  33 + end
  34 +
  35 + should 'not be able to create two themes with the same identifier' do
  36 + Theme.create('themeid')
  37 + assert_raise Theme::DuplicatedIdentifier do
  38 + Theme.create('themeid')
  39 + end
  40 + end
  41 +
  42 + should 'not be able to create a theme named after a system theme' do
  43 + Theme.expects(:system_themes).returns([Theme.new('somesystemtheme')])
  44 + assert_raise Theme::DuplicatedIdentifier do
  45 + Theme.create('somesystemtheme')
  46 + end
  47 + end
  48 +
  49 + should 'be able to add new CSS file to theme' do
  50 + t = Theme.create('mytheme')
  51 + t.add_css('common.css')
  52 + assert_equal '', File.read(TMP_THEMES_DIR + '/mytheme/stylesheets/common.css')
  53 + end
  54 +
  55 + should 'be able to update CSS file' do
  56 + t = Theme.create('mytheme')
  57 + t.add_css('common.css')
  58 + t.update_css('common.css', '/* only a comment */')
  59 + assert_equal '/* only a comment */', File.read(TMP_THEMES_DIR + '/mytheme/stylesheets/common.css')
  60 + end
  61 +
  62 + should 'be able to get content of CSS file' do
  63 + t = Theme.create('mytheme')
  64 + t.update_css('common.css', '/* only a comment */')
  65 + assert_equal '/* only a comment */', t.read_css('common.css')
  66 + end
  67 +
  68 + should 'force .css suffix for CSS files when adding' do
  69 + t = Theme.create('mytheme')
  70 + t.add_css('xyz')
  71 + assert_includes t.css_files, 'xyz.css'
  72 + end
  73 +
  74 + should 'list CSS files' do
  75 + t = Theme.create('mytheme')
  76 + t.add_css('one.css')
  77 + t.add_css('two.css')
  78 + assert_includes t.css_files, 'one.css'
  79 + assert_includes t.css_files, 'two.css'
  80 + end
  81 +
  82 + should 'add default stylesheets' do
  83 + theme = Theme.create('test')
  84 + %w[ common help menu article button search blocks forms login-box ].each do |item|
  85 + assert_includes theme.css_files, item + '.css'
  86 + end
  87 + end
  88 +
  89 + should 'be able to save twice' do
  90 + t = Theme.new('testtheme')
  91 +
  92 + assert_nothing_raised do
  93 + t.save
  94 + t.save
  95 + end
  96 + end
  97 +
  98 + should 'have an owner' do
  99 + profile = create_user('testinguser').person
  100 + t = Theme.new('mytheme')
  101 + t.owner = profile
  102 + t.save
  103 +
  104 + t = Theme.find('mytheme')
  105 + assert_equal profile, t.owner
  106 + end
  107 +
  108 + should 'have no owner by default' do
  109 + assert_nil Theme.new('test').owner
  110 + end
  111 +
  112 + should 'be able to find by owner' do
  113 + profile = create_user('testinguser').person
  114 + t = Theme.new('mytheme')
  115 + t.owner = profile
  116 + t.save
  117 +
  118 + assert_equal [t], Theme.find_by_owner(profile)
  119 + end
  120 +
  121 + should 'be able to set attributes in constructor' do
  122 + p = create_user('testuser').person
  123 + assert_equal p, Theme.new('test', :owner => p).owner
  124 + end
  125 +
  126 + should 'pass attributes to constructor' do
  127 + p = create_user('testuser').person
  128 + assert_equal p, Theme.create('test', :owner => p).owner
  129 + end
  130 +
  131 + should 'have a name' do
  132 + theme = Theme.new('mytheme', :name => 'My Theme')
  133 + assert_equal 'My Theme', theme.name
  134 + assert_equal 'My Theme', theme.config['name']
  135 + end
  136 +
  137 + should 'insert image' do
  138 + theme = Theme.create('mytheme')
  139 + theme.add_image('test.png', 'FAKE IMAGE DATA')
  140 +
  141 + assert_equal 'FAKE IMAGE DATA', File.read(TMP_THEMES_DIR + '/mytheme/images/test.png')
  142 + end
  143 +
  144 + should 'list images' do
  145 + theme = Theme.create('mytheme')
  146 + theme.add_image('one.png', 'FAKE IMAGE DATA')
  147 + theme.add_image('two.png', 'FAKE IMAGE DATA')
  148 +
  149 + assert_equivalent [ 'one.png', 'two.png' ], theme.image_files
  150 + end
  151 +
19 end 152 end
20 153