Commit f2258e83ec5bb7a0a8411b389b60618df0d9e227
1 parent
9f8b8883
Exists in
master
and in
29 other branches
ActionItem122: implementing theme editor
git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@2425 3f533792-8f58-4932-b0fe-aaf55b0a4547
Showing
22 changed files
with
801 additions
and
89 deletions
Show diff stats
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 < MyProfileController | @@ -12,4 +13,66 @@ class ThemesController < 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 < ActiveRecord::Base | @@ -35,6 +35,7 @@ class Profile < 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 < ActiveRecord::Base | @@ -450,4 +451,13 @@ class Profile < 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
@@ -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') %> |
@@ -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 %> |
@@ -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 | + | ||
19 | + </td> | ||
20 | + <% end %> | ||
21 | + </tr> | ||
22 | + <tr><td colspan='3'> </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 | + | ||
44 | + </td> | ||
45 | + <% end %> | ||
46 | + </tr> | ||
47 | + <tr><td colspan='3'> </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 %> |
@@ -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 %> |
@@ -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 |
db/schema.rb
@@ -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 => 50) do | @@ -73,8 +73,8 @@ ActiveRecord::Schema.define(:version => 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 => 50) do | @@ -114,8 +114,8 @@ ActiveRecord::Schema.define(:version => 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 => 50) do | @@ -182,8 +182,8 @@ ActiveRecord::Schema.define(:version => 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 => 50) do | @@ -254,8 +254,8 @@ ActiveRecord::Schema.define(:version => 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 < Test::Unit::TestCase | @@ -193,4 +193,25 @@ class ApplicationControllerTest < 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 < Test::Unit::TestCase | @@ -4,6 +4,10 @@ class ApplicationHelperTest < 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 < Test::Unit::TestCase | @@ -150,6 +154,52 @@ class ApplicationHelperTest < 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 < Test::Unit::TestCase | @@ -854,6 +854,33 @@ class ProfileTest < 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 < ActiveSupport::TestCase | @@ -16,5 +27,127 @@ class ThemeTest < 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 |